diff --git a/README.md b/README.md index e9d7d15..4cd45c1 100644 --- a/README.md +++ b/README.md @@ -115,6 +115,23 @@ docker / dockerfile := NativeDockerfile(file("subdirectory") / "Dockerfile") Have a look at [DockerfileExamples](examples/DockerfileExamples.scala) for different ways of defining a Dockerfile. +#### Missing Dockerfile instructions + +Dockerfile instructions that are missing from the sbt-docker DSL can still be used by calling the `.customInstruction(instructionName, arguments)` method. +Example: +```scala +new Dockerfile { + customInstruction("FROM", "openjdk AS stage1") + run("build") + + customInstruction("FROM", "openjdk AS stage2") + customInstruction("COPY", "--from=stage1 /path/to/file /path/to/file") + customInstruction("STOPSIGNAL", "SIGQUIT") + + entryPoint("application") +} +``` + ### Building an image To build an image use the `docker` task. diff --git a/src/main/scala/sbtdocker/DockerfileCommands.scala b/src/main/scala/sbtdocker/DockerfileCommands.scala index 8a7d812..0d4cf85 100644 --- a/src/main/scala/sbtdocker/DockerfileCommands.scala +++ b/src/main/scala/sbtdocker/DockerfileCommands.scala @@ -311,4 +311,20 @@ trait DockerfileCommands { def healthCheckShell(commands: String*): T = healthCheckShell(commands) def healthCheckNone(): T = addInstruction(HealthCheckNone) + + /** + * Adds a custom Dockerfile instruction that is not currently supported by this DSL. + * + * @example + * {{{ + * // Copy in a multi-stage Dockerfile + * // Equivalent to: "COPY --from=stage1 /path/to/file /path/to/file" + * customInstruction("COPY", "--from=stage1 /path/to/file /path/to/file") + * + * // Onbuild instruction can be represented as: + * // ONBUILD RUN /usr/local/bin/python-build --dir /app/src + * customInstruction("ONBUILD", "RUN /usr/local/bin/python-build --dir /app/src") + * }}} + */ + def customInstruction(instructionName: String, arguments: String): T = addInstruction(Raw(instructionName, arguments)) } diff --git a/src/test/scala/sbtdocker/DockerfileLikeSuite.scala b/src/test/scala/sbtdocker/DockerfileLikeSuite.scala index c7fb975..ad06e26 100644 --- a/src/test/scala/sbtdocker/DockerfileLikeSuite.scala +++ b/src/test/scala/sbtdocker/DockerfileLikeSuite.scala @@ -34,7 +34,8 @@ class DockerfileLikeSuite extends AnyFunSuite with Matchers { .exec(Seq("cmd", "arg"), interval = Some(20.seconds), timeout = Some(10.seconds), startPeriod = Some(1.second), retries = Some(3)), HealthCheck .shell(Seq("cmd", "arg"), interval = Some(20.seconds), timeout = Some(10.seconds), startPeriod = Some(1.second), retries = Some(3)), - HealthCheck.none + HealthCheck.none, + Raw("COPY", "--from=stage1 /path/to/file /path/to/file") ) test("Instructions string is in correct order and matches instructions") { @@ -62,7 +63,8 @@ class DockerfileLikeSuite extends AnyFunSuite with Matchers { |ONBUILD RUN ["echo", "123"] |HEALTHCHECK --interval=20s --timeout=10s --start-period=1s --retries=3 CMD ["cmd", "arg"] |HEALTHCHECK --interval=20s --timeout=10s --start-period=1s --retries=3 CMD cmd arg - |HEALTHCHECK NONE""".stripMargin + |HEALTHCHECK NONE + |COPY --from=stage1 /path/to/file /path/to/file""".stripMargin } def staged(dockerfile: immutable.Dockerfile): StagedDockerfile = { @@ -118,6 +120,7 @@ class DockerfileLikeSuite extends AnyFunSuite with Matchers { retries = Some(3) ) .healthCheckNone() + .customInstruction("COPY", "--from=stage1 /path/to/file /path/to/file") withMethods shouldEqual predefined } diff --git a/src/test/scala/sbtdocker/InstructionsSpec.scala b/src/test/scala/sbtdocker/InstructionsSpec.scala index eeee582..b6cf688 100644 --- a/src/test/scala/sbtdocker/InstructionsSpec.scala +++ b/src/test/scala/sbtdocker/InstructionsSpec.scala @@ -113,4 +113,12 @@ class InstructionsSpec extends AnyFlatSpec with Matchers { """LABEL com.example.bar="foo" com.example.bor="boz"""" Label("foo=bar").toString shouldEqual "LABEL foo=bar" } + + "Raw" should "create a correct string" in { + Raw( + "ONBUILD", + "RUN /usr/local/bin/python-build --dir /app/src" + ).toString shouldEqual "ONBUILD RUN /usr/local/bin/python-build --dir /app/src" + Raw("COPY", "--from=stage1 /path/to/file /path/to/file").toString shouldEqual "COPY --from=stage1 /path/to/file /path/to/file" + } } diff --git a/src/test/scala/sbtdocker/immutable/ImmutableDockerfileSpec.scala b/src/test/scala/sbtdocker/immutable/ImmutableDockerfileSpec.scala index d3a2467..8db76b0 100644 --- a/src/test/scala/sbtdocker/immutable/ImmutableDockerfileSpec.scala +++ b/src/test/scala/sbtdocker/immutable/ImmutableDockerfileSpec.scala @@ -67,6 +67,7 @@ class ImmutableDockerfileSpec extends AnyFlatSpec with Matchers { retries = Some(3) ) .healthCheckNone() + .customInstruction("COPY", "--from=stage1 /path/to/file /path/to/file") val instructions = Seq( From("image"), @@ -108,7 +109,8 @@ class ImmutableDockerfileSpec extends AnyFlatSpec with Matchers { startPeriod = Some(1.second), retries = Some(3) ), - HealthCheckNone + HealthCheckNone, + Raw("COPY", "--from=stage1 /path/to/file /path/to/file") ) dockerfile.instructions should contain theSameElementsInOrderAs instructions diff --git a/src/test/scala/sbtdocker/mutable/MutableDockerfileSpec.scala b/src/test/scala/sbtdocker/mutable/MutableDockerfileSpec.scala index 2ce520a..7a1ee7f 100644 --- a/src/test/scala/sbtdocker/mutable/MutableDockerfileSpec.scala +++ b/src/test/scala/sbtdocker/mutable/MutableDockerfileSpec.scala @@ -66,6 +66,7 @@ class MutableDockerfileSpec extends AnyFlatSpec with Matchers { retries = Some(3) ) healthCheckNone() + customInstruction("COPY", "--from=stage1 /path/to/file /path/to/file") } val instructions = Seq( @@ -107,7 +108,8 @@ class MutableDockerfileSpec extends AnyFlatSpec with Matchers { startPeriod = Some(1.second), retries = Some(3) ), - HealthCheckNone + HealthCheckNone, + Raw("COPY", "--from=stage1 /path/to/file /path/to/file") ) dockerfile.instructions should contain theSameElementsInOrderAs instructions