dwickern / sbt-swagger-play

sbt plugin for swagger-play
MIT License
12 stars 2 forks source link

Make it possible to Disable Swagger easily in Production Builds #12

Closed funkrusher closed 12 months ago

funkrusher commented 1 year ago

I like the plugin as it works pretty good for me, and i also like the Annotations Approach as its close to the code.

What i find bothersome, is that it's pretty involved to disable the plugin in specific builds (production vs. local/dev). Swagger-Ui should not be exposed in the production environment of the app. I have acchieved this in the following way for my app:

I made following checks in my build-files to make that sure, because sbt-swagger is an AutoPlugin and can not be disabled manually if it is set.

plugins.sbt

addSbtPlugin("org.scalameta"     % "sbt-scalafmt" % "2.4.6")
val includeSwaggerPlugin: Boolean = sys.props.getOrElse("includeSwaggerPlugin", "false") == "true"
if (includeSwaggerPlugin) {
  addSbtPlugin("com.github.dwickern" % "sbt-swagger-play" % "0.5.0")
} else {
  // sadly we need to add some plugin here, as otherwise sbt is not happy, so let's add one we already use (no-op)
  addSbtPlugin("org.scalameta" % "sbt-scalafmt" % "2.4.6")
}

i have put the swagger-ui stuff in a separate module "fk_swagger_ui" (website of swagger / index.html).

build.sbt


lazy val fk_swagger_ui = (project in file("modules/fk_swagger_ui"))
  .enablePlugins(PlayScala)
  .settings(
    settings
  )
def addSwaggerUiIfEnabled(prj: Project): Project = {
  val includeSwaggerPlugin: Boolean = sys.props.getOrElse("includeSwaggerPlugin", "false") == "true"
  if (includeSwaggerPlugin) {
    prj.dependsOn(fk_swagger_ui)
  } else {
    prj
  }
}
lazy val fk_server = addSwaggerUiIfEnabled(project in file("fk_server"))
  .enablePlugins(PlayScala)
  .dependsOn(fk_core, fk_library, fk_store)
  .settings(
    settings,
    libraryDependencies ++= dependencies,
    PlayKeys.playDefaultPort := 9000,
  )

In my "fk_server" project i also added a comment to the "routes" file to inform the developer, that some routes are just unimportant for specific environments. i have not found a documentation, that indicates that i can make "IF/ELSE" statements within the routes-file, so the only other way would be to write the Routes in the Code instead, but i don't want to go that path for now.

# swagger-ui
# those routes can stay in production, as they will just find nothing,
# as we don't deploy swagger.json and swagger-ui module in prod.
GET /swagger.json                 controllers.Assets.at(path="/public", file="swagger.json")
GET /docs/*file                   controllers.Assets.at(path="/swagger-ui", file)

see my test-project for reference:


as you can see, this is pretty involved. It's ok but i guess it would be a good idea to make it easy for the developers to only enable the plugin in a safe environment.

dwickern commented 12 months ago

Play doesn't normally differentiate dev vs prod at build-time, it's only based on the runtime environment flag. But it's easy enough to read that flag and disable the routes in production.

Instead of

GET     /swagger.json        controllers.Assets.at(path="/public", file="swagger.json")
GET     /docs/*file          controllers.Assets.at(path="/swagger-ui", file)

You can do

GET     /swagger.json        controllers.ApiController.spec
GET     /docs/*file          controllers.ApiController.swaggerUi(file)
// controllers/ApiController.scala
class ApiController @Inject()(val controllerComponents: ControllerComponents, assets: Assets, env: Environment) extends BaseController {
  def swaggerSpec = env.mode match {
    case Mode.Dev | Mode.Test => assets.at(path="/public", file="swagger.json")
    case Mode.Prod => Action(NotFound)
  }

  def swaggerUi(file: String) = env.mode match {
    case Mode.Dev | Mode.Test => assets.at("/swagger-ui", file)
    case Mode.Prod => Action(NotFound)
  }
}

Or you could create a filter and only apply it to those routes.


Now if you really want to disable this plugin at build-time, I suppose you could change swaggerPlayResourceGenerator:

lazy val fk_server = project in file("fk_server")
  .enablePlugins(PlayScala)
  .dependsOn(fk_core, fk_library, fk_store)
  .settings(
    settings,
    libraryDependencies ++= dependencies,
    PlayKeys.playDefaultPort := 9000,
    swaggerPlayResourceGenerator := {
      if (sys.props.getOrElse("includeSwaggerPlugin", "false") == "true")
        swaggerPlayResourceGenerator.value
      else
        Seq.empty
    }
  )

Would either of those options work for you?