Skip to content
This repository has been archived by the owner on Apr 29, 2024. It is now read-only.

Commit

Permalink
Merge pull request #17 from seglo/master
Browse files Browse the repository at this point in the history
Support docker-compose variable substitution
  • Loading branch information
kurtkopchik committed Jun 14, 2016
2 parents 255df97 + 811fbc0 commit 48b3f5c
Show file tree
Hide file tree
Showing 12 changed files with 91 additions and 8 deletions.
19 changes: 19 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ plugin will attempt to locate it in one of three places with the precedence orde
testTagsToExecute =: // Set of ScalaTest Tags to execute when dockerComposeTest is run. Separate multiple tags by a comma. It defaults to executing all tests.
testDependenciesClasspath =: // The path to all managed and unmanaged Test and Compile dependencies. This path needs to include the ScalaTest Jar for the tests to execute. This defaults to all managedClasspath and unmanagedClasspath in the Test and fullClasspath in the Compile Scope.
testCasesJar =: // The path to the Jar file containing the tests to execute. This defaults to the Jar file with the tests from the current sbt project.
variablesForSubstitution =: // A Map[String,String] of variables to substitute in your docker-compose file. These are substituted substituted by the plugin and not using environment variables.

There are several sample projects showing how to configure sbt-docker-compose that can be found in the [**examples**] (examples) folder.

Expand Down Expand Up @@ -274,6 +275,24 @@ launch a running instance that consists of both images:
Note how the docker-compose.yml file for the root project tags each image with "\<localBuild\>". This allows dockerComposeUp
to know that these images should not be updated from the Docker Registry.

5) [**basic-variable-substitution**] (examples/basic-variable-substitution): This project demonstrates how you can re-use your
existing docker-compose.yml with [variable substitution](https://docs.docker.com/compose/compose-file/#variable-substitution)
using sbt-docker-compose. Instead of passing your variables as environment variables you can define them in your build.sbt
programmatically.

build.sbt:

variablesForSubstitution := Map("SOURCE_PORT" -> "5555")

docker-compose.yml:

basic:
image: basic:1.0.0
environment:
JAVA_TOOL_OPTIONS: -agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=5005
ports:
- "${SOURCE_PORT}:5005"

Currently Unsupported Docker Compose Fields
-------------------------------------------
1) "build:" - All docker compose services need to specify an "image:" field.
Expand Down
11 changes: 11 additions & 0 deletions examples/basic-variable-substitution/build.sbt
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
name := "basic"

version := "1.0.0"

scalaVersion := "2.10.6"

enablePlugins(JavaAppPackaging, DockerComposePlugin)

dockerImageCreationTask := (publishLocal in Docker).value

variablesForSubstitution := Map("SOURCE_PORT" -> "5555")
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
basic:
image: basic:1.0.0
environment:
JAVA_TOOL_OPTIONS: -agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=5005
ports:
- "${SOURCE_PORT}:5005"
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
sbt.version=0.13.11
3 changes: 3 additions & 0 deletions examples/basic-variable-substitution/project/plugins.sbt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
addSbtPlugin("com.typesafe.sbt" %% "sbt-native-packager" % "1.1.0")

addSbtPlugin("com.tapad" % "sbt-docker-compose" % "1.0.8")
14 changes: 14 additions & 0 deletions examples/basic-variable-substitution/src/main/scala/BasicApp.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import scala.Console._
import scala.concurrent.duration._

object BasicApp extends App {
println("Application started....")

val deadline = 1.hour.fromNow
do {
println(s"Running application. Seconds left until showdown: ${deadline.timeLeft.toSeconds}")
Thread.sleep(1000)
} while (deadline.hasTimeLeft())

println("Application shutting down....")
}
24 changes: 17 additions & 7 deletions src/main/scala/com/tapad/docker/ComposeFile.scala
Original file line number Diff line number Diff line change
Expand Up @@ -227,15 +227,25 @@ trait ComposeFile extends SettingsHelper with ComposeCustomTagHelpers with Print
}
}

def readComposeFile(composePath: String): yamlData = {
val fileReader = fromFile(composePath).reader()
try {
new Yaml().load(fileReader).asInstanceOf[java.util.Map[String, java.util.LinkedHashMap[String, Any]]].asScala.toMap
} finally {
fileReader.close()
}
def readComposeFile(composePath: String, variables: Vector[(String, String)] = Vector.empty): yamlData = {
val yamlString = fromFile(composePath).getLines().mkString("\n")
val yamlUpdated = processVariableSubstitution(yamlString, variables)

new Yaml().load(yamlUpdated).asInstanceOf[java.util.Map[String, java.util.LinkedHashMap[String, Any]]].asScala.toMap
}

/**
* Substitute all docker-compose variables in the YAML file. This is traditionally done by docker-compose itself,
* but is being performed by the plugin to support other functionality.
* @param yamlString Stringified docker-compose file.
* @param variables Substitution variables.
* @return An updated stringified docker-compile file.
*/
def processVariableSubstitution(yamlString: String, variables: Vector[(String, String)]) =
variables.foldLeft(yamlString) {
case (y, (key, value)) => y.replaceAll("\\$\\{" + key + "\\}", value)
}

def deleteComposeFile(composePath: String): Boolean = {
Try(new File(composePath).delete()) match {
case Success(i) => true
Expand Down
1 change: 1 addition & 0 deletions src/main/scala/com/tapad/docker/DockerComposeKeys.scala
Original file line number Diff line number Diff line change
Expand Up @@ -18,4 +18,5 @@ trait DockerComposeKeysLocal {
val testCasesJar = settingKey[String]("The path to the Jar file containing the tests to execute. This defaults to the Jar file with the tests from the current sbt project.")
val testDependenciesClasspath = taskKey[String]("The path to all managed and unmanaged Test and Compile dependencies. This path needs to include the ScalaTest Jar for the tests to execute. This defaults to all managedClasspath and unmanagedClasspath in the Test and fullClasspath in the Compile Scope.")
val runningInstances = AttributeKey[List[RunningInstanceInfo]]("For Internal Use: Contains information on the set of running Docker Compose instances.")
val variablesForSubstitution = settingKey[Map[String, String]]("Specify a Map of String to String that will be passed as environment variables to docker-compose and can be used for variable substitution.")
}
4 changes: 3 additions & 1 deletion src/main/scala/com/tapad/docker/DockerComposePlugin.scala
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ object DockerComposePlugin extends DockerComposePluginLocal {
val testTagsToExecute = DockerComposeKeys.testTagsToExecute
val testCasesJar = DockerComposeKeys.testCasesJar
val scalaTestJar = DockerComposeKeys.testDependenciesClasspath
val variablesForSubstitution = DockerComposeKeys.variablesForSubstitution
}
}

Expand Down Expand Up @@ -166,11 +167,12 @@ class DockerComposePluginLocal extends AutoPlugin with ComposeFile with DockerCo
*/
def startDockerCompose(implicit state: State, args: Seq[String]): (State, String) = {
val composeFilePath = getSetting(composeFile)
val variables = getSetting(variablesForSubstitution).toVector

printBold(s"Creating Local Docker Compose Environment.")
printBold(s"Reading Compose File: $composeFilePath")

val composeYaml = readComposeFile(composeFilePath)
val composeYaml = readComposeFile(composeFilePath, variables)
val servicesInfo = processCustomTags(state, args, composeYaml)
val updatedComposePath = saveComposeFile(composeYaml)
println(s"Created Compose File with Processed Custom Tags: $updatedComposePath")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ trait DockerComposeSettingsLocal extends PrintFormatting {
(fullClasspathCompile.files ++ classpathTestManaged.files ++ classpathTestUnmanaged.files).map(_.getAbsoluteFile).mkString(":")
},
testCasesJar := artifactPath.in(Test, packageBin).value.getAbsolutePath,
variablesForSubstitution := Map[String, String](),
commands ++= Seq(dockerComposeUpCommand, dockerComposeStopCommand, dockerComposeInstancesCommand, dockerComposeTest)
)
}
4 changes: 4 additions & 0 deletions src/test/resources/variable_substitution.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
testservice:
image: testservice:0.0.1
ports:
- "${SOURCE_PORT}:5005"
11 changes: 11 additions & 0 deletions src/test/scala/ComposeFileProcessingSpec.scala
Original file line number Diff line number Diff line change
Expand Up @@ -244,6 +244,17 @@ class ComposeFileProcessingSpec extends FunSuite with BeforeAndAfter with OneIns
}
}

test("Validate that docker-compose variables are substituted") {
val composeMock = getComposeFileMock()
val composeFilePath = getClass.getResource("variable_substitution.yml").getPath
doReturn(composeFilePath).when(composeMock).getSetting(composeFile)(null)

val composeYaml = composeMock.readComposeFile(composeFilePath, Vector(("SOURCE_PORT", "5555")))

val ports = composeYaml.get("testservice").get.get("ports").asInstanceOf[util.ArrayList[String]].get(0)
assert(ports == "5555:5005")
}

def getComposeFileMock(serviceName: String = "testservice", versionNumber: String = "1.0.0", noBuild: Boolean = false): ComposeFile = {
val composeMock = spy(new DockerComposePluginLocal)

Expand Down

0 comments on commit 48b3f5c

Please sign in to comment.