Skip to content

Instantly share code, notes, and snippets.

@jonas
Last active November 29, 2016 02:49
Show Gist options
  • Save jonas/a631626d4554ae656a4673cc90b97072 to your computer and use it in GitHub Desktop.
Save jonas/a631626d4554ae656a4673cc90b97072 to your computer and use it in GitHub Desktop.
JSON templating with Typesafe Config
lazy val deployProd = taskKey[Unit]("Deploy a job to prod")
lazy val deployQA = taskKey[Unit]("Deploy a job to qa")
lazy val jobConfig = settingKey[Config]("Job configuration")
// Environments endpoints are defined via Credentials where realm contains the env name.
val qa = Credentials(...)
val prod = Credentials(...)
// Task to fetch an existing JSON document, generate a new configuration
// using per-job overrides and then use the result to deploy a job.
def deployJobTo(credentials: Credentials) =
Def.task {
val server = Credentials.toDirect(credentials)
val _ = (publish in Docker).value
// HTTP client based on "org.scalaj" %% "scalaj-http" % "2.3.0"
def http(path: String, transform: HttpRequest => HttpRequest = identity): String = {
val request = Http(server.host + path)
.auth(server.userName, server.passwd)
.header("Content-Type", "application/json")
.header("Accept", "application/json")
transform(request).asString.throwError.body
}
// Render Typesafe config as JSON
def toJson(config: ConfigObject): String = {
val opts = ConfigRenderOptions.defaults.setJson(true).setOriginComments(false).setComments(false)
config.render(opts)
}
// The job configuration with some defaults
val buildConf = jobConfig.value.withFallback(ConfigFactory.parseString(s"""
name = "${name.value}"
docker.image = "${(dockerTarget in Docker).value}"
docker.command = "/opt/docker/bin/${(packageName in Docker).value}"
deploy.environment = "${credentials.realm}"
"""))
// Fetch and parse the existing configuration
val currentJson = http(s"/scheduler/jobs/search?name=${buildConf.getString("name")}")
val currentConf = ConfigFactory.parseString(s"{ current = $currentJson }").getObjectList("current") match {
case list if list.isEmpty => ConfigFactory.empty.root
case list => list.get(0)
}
// Use a Typesafe Config as a template and resolve variables using our build config + defaults
val nextConf = ConfigFactory.parseFile((baseDirectory in ThisBuild).value / "jobs/default.conf")
.resolveWith(buildConf)
.withFallback(currentConf)
val json = toJson(nextConf.root)
// Only deploy if something changed
if (json == toJson(currentConf)) {
streams.value.log.info(s"Job ${name.value} is up-to-date in (${server.realm}) ${server.host} ...")
streams.value.log.info(json)
} else {
streams.value.log.info(s"Deploying ${name.value} to (${server.realm}) ${server.host} ...")
streams.value.log.info(json)
http("/scheduler/iso8601", _.postData(json))
streams.value.log.info("Job deployed")
}
}
// Example use
val someJob = project
.settings(
jobConfig := ConfigFactory.parseFile(baseDirectory.value / "src/main/resources/application.conf").getConfig("job"),
deployProd := deployDockerJobTo(prod).value,
deployQA := deployDockerJobTo(qa).value
)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment