Support multi-stage builds in Dockerfile task
MODIFY
src/functTest/groovy/com/bmuschko/gradle/docker/tasks/image/DockerfileFunctionalTest.groovy
src/functTest/groovy/com/bmuschko/gradle/docker/tasks/image/DockerfileFunctionalTest.groovy
@@ -387,4 +387,64 @@ task ${DOCKERFILE_TASK_NAME}(type: Dockerfile) {
and:
buildAndFail('dockerFile2')
}
+
+ def "supports multi-stage builds"() {
+ buildFile << """
+import com.bmuschko.gradle.docker.tasks.image.Dockerfile
+
+task ${DOCKERFILE_TASK_NAME}(type: Dockerfile) {
+ from '$TEST_IMAGE_WITH_TAG', 'builder'
+ maintainer 'Benjamin Muschko "benjamin.muschko@gmail.com"'
+ copyFile 'http://hsql.sourceforge.net/m2-repo/com/h2database/h2/1.4.184/h2-1.4.184.jar', '/opt/h2.jar'
+ from({'$TEST_IMAGE_WITH_TAG'}, {'prod'})
+ copyFile '/opt/h2.jar', '/opt/h2.jar', 'builder'
+}
+"""
+ when:
+ build(DOCKERFILE_TASK_NAME)
+
+ then:
+ File dockerfile = new File(projectDir, 'build/docker/Dockerfile')
+ dockerfile.exists()
+ dockerfile.text ==
+ """FROM $TEST_IMAGE_WITH_TAG AS builder
+MAINTAINER Benjamin Muschko "benjamin.muschko@gmail.com"
+COPY http://hsql.sourceforge.net/m2-repo/com/h2database/h2/1.4.184/h2-1.4.184.jar /opt/h2.jar
+FROM alpine:3.4 AS prod
+COPY --from=builder /opt/h2.jar /opt/h2.jar
+"""
+ }
+
+
+ def "supports multi-stage builds (lazy evaluation)"() {
+ buildFile << """
+import com.bmuschko.gradle.docker.tasks.image.Dockerfile
+
+ext.buildStageName = ''
+
+task ${DOCKERFILE_TASK_NAME}(type: Dockerfile) {
+ from({'$TEST_IMAGE_WITH_TAG'}, {buildStageName})
+ maintainer 'Benjamin Muschko "benjamin.muschko@gmail.com"'
+ copyFile 'http://hsql.sourceforge.net/m2-repo/com/h2database/h2/1.4.184/h2-1.4.184.jar', '/opt/h2.jar'
+ from({'$TEST_IMAGE_WITH_TAG'}, {'prod'})
+ copyFile({'/opt/h2.jar'}, {'/opt/h2.jar'}, {buildStageName})
+ doFirst {
+ buildStageName = 'builder'
+ }
+}
+"""
+ when:
+ build(DOCKERFILE_TASK_NAME)
+
+ then:
+ File dockerfile = new File(projectDir, 'build/docker/Dockerfile')
+ dockerfile.exists()
+ dockerfile.text ==
+ """FROM $TEST_IMAGE_WITH_TAG AS builder
+MAINTAINER Benjamin Muschko "benjamin.muschko@gmail.com"
+COPY http://hsql.sourceforge.net/m2-repo/com/h2database/h2/1.4.184/h2-1.4.184.jar /opt/h2.jar
+FROM alpine:3.4 AS prod
+COPY --from=builder /opt/h2.jar /opt/h2.jar
+"""
+ }
}

MODIFY
src/main/groovy/com/bmuschko/gradle/docker/tasks/image/Dockerfile.groovy
src/main/groovy/com/bmuschko/gradle/docker/tasks/image/Dockerfile.groovy
@@ -112,9 +112,10 @@ class Dockerfile extends DefaultTask {
* subsequent instructions.
*
* @param image Base image name
+ * @param stageName stage name in case of multi-stage builds (default null)
*/
- void from(String image) {
- instructions << new FromInstruction(image)
+ void from(String image, String stageName = null) {
+ instructions << new FromInstruction(image, stageName)
}
/**
@@ -132,9 +133,10 @@ class Dockerfile extends DefaultTask {
* subsequent instructions.
*
* @param image Base image name
+ * @param stageName closure returning stage name in case of multi-stage builds (default null)
*/
- void from(Closure image) {
- instructions << new FromInstruction(image)
+ void from(Closure image, Closure stageName = null) {
+ instructions << new FromInstruction(image, stageName)
}
/**
@@ -276,9 +278,10 @@ class Dockerfile extends DefaultTask {
*
* @param src Source file
* @param dest Destination path
+ * @param stageName stage name in case of multi stage build
*/
- void copyFile(String src, String dest) {
- instructions << new CopyFileInstruction(src, dest)
+ void copyFile(String src, String dest, String stageName = null) {
+ instructions << new CopyFileInstruction(src, dest, stageName)
}
/**
@@ -287,9 +290,10 @@ class Dockerfile extends DefaultTask {
*
* @param src Source file
* @param dest Destination path
+ * @param stageName closure returning stage name in case of multi stage build
*/
- void copyFile(Closure src, Closure dest) {
- instructions << new CopyFileInstruction(src, dest)
+ void copyFile(Closure src, Closure dest, Closure stageName) {
+ instructions << new CopyFileInstruction(src, dest, stageName)
}
/**
@@ -430,8 +434,7 @@ class Dockerfile extends DefaultTask {
String getKeyword() {
if (instruction instanceof String) {
parseKeyword(instruction)
- }
- else if(instruction instanceof Closure) {
+ } else if (instruction instanceof Closure) {
parseKeyword(instruction())
}
}
@@ -444,8 +447,7 @@ class Dockerfile extends DefaultTask {
String build() {
if (instruction instanceof String) {
instruction
- }
- else if(instruction instanceof Closure) {
+ } else if (instruction instanceof Closure) {
instruction()
}
}
@@ -466,8 +468,7 @@ class Dockerfile extends DefaultTask {
String build() {
if (command instanceof String) {
"$keyword $command"
- }
- else if(command instanceof Closure) {
+ } else if (command instanceof Closure) {
"$keyword ${command()}"
}
}
@@ -488,14 +489,12 @@ class Dockerfile extends DefaultTask {
String build() {
if (command instanceof String[]) {
keyword + ' ["' + command.join('", "') + '"]'
- }
- else if(command instanceof Closure) {
+ } else if (command instanceof Closure) {
def evaluatedCommand = command()
if (evaluatedCommand instanceof String) {
keyword + ' ["' + evaluatedCommand + '"]'
- }
- else {
+ } else {
keyword + ' ["' + command().join('", "') + '"]'
}
}
@@ -590,41 +589,70 @@ class Dockerfile extends DefaultTask {
static abstract class FileInstruction implements Instruction {
final Object src
final Object dest
+ final Object flags
- FileInstruction(String src, String dest) {
+ FileInstruction(String src, String dest, String flags = null) {
this.src = src
this.dest = dest
+ this.flags = flags
}
- FileInstruction(Closure src, Closure dest) {
+ FileInstruction(Closure src, Closure dest, Closure flags = null) {
this.src = src
this.dest = dest
+ this.flags = flags
}
@Override
String build() {
+ String keyword = getKeyword()
+ if (flags) {
+ if (flags instanceof String) {
+ keyword += " $flags"
+ } else if (flags instanceof Closure) {
+ keyword += " ${flags()}"
+ }
+ }
if (src instanceof String && dest instanceof String) {
"$keyword $src $dest"
- }
- else if(src instanceof Closure && dest instanceof Closure) {
+ } else if (src instanceof Closure && dest instanceof Closure) {
"$keyword ${src()} ${dest()}"
}
}
}
static class FromInstruction extends StringCommandInstruction {
- FromInstruction(String image) {
+ final Object stageName
+
+ FromInstruction(String image, String stageName = null) {
super(image)
+ this.stageName = stageName
}
- FromInstruction(Closure image) {
+ FromInstruction(Closure image, Closure stageName = null) {
super(image)
+ this.stageName = stageName
}
@Override
String getKeyword() {
"FROM"
}
+
+ @Override
+ String build() {
+ String result = super.build()
+
+ if (stageName) {
+ if (stageName instanceof String) {
+ result += " AS $stageName"
+ } else if (stageName instanceof Closure) {
+ result += " AS ${stageName()}"
+ }
+ }
+
+ result
+ }
}
static class ArgInstruction extends StringCommandInstruction {
@@ -707,14 +735,12 @@ class Dockerfile extends DefaultTask {
String build() {
if (ports instanceof Integer[]) {
"$keyword ${ports.join(' ')}"
- }
- else if(ports instanceof Closure) {
+ } else if (ports instanceof Closure) {
def evaluatedPorts = ports()
if (evaluatedPorts instanceof String || evaluatedPorts instanceof Integer) {
"$keyword ${evaluatedPorts}"
- }
- else {
+ } else {
"$keyword ${evaluatedPorts.join(' ')}"
}
}
@@ -756,12 +782,12 @@ class Dockerfile extends DefaultTask {
}
static class CopyFileInstruction extends FileInstruction {
- CopyFileInstruction(String src, String dest) {
- super(src, dest)
+ CopyFileInstruction(String src, String dest, String stageName = null) {
+ super(src, dest, stageName ? "--from=$stageName" : null)
}
- CopyFileInstruction(Closure src, Closure dest) {
- super(src, dest)
+ CopyFileInstruction(Closure src, Closure dest, Closure stageName = null) {
+ super(src, dest, stageName ? { "--from=${stageName()}" } : null)
}
@Override
@@ -887,6 +913,7 @@ class Dockerfile extends DefaultTask {
void defaultCommand(String... command) {
instructions << new DefaultCommandInstruction(command)
}
+
void defaultCommand(Closure command) {
instructions << new DefaultCommandInstruction(command)
}
@@ -894,6 +921,7 @@ class Dockerfile extends DefaultTask {
void entryPoint(String... entryPoint) {
instructions << new EntryPointInstruction(entryPoint)
}
+
void entryPoint(Closure entryPoint) {
instructions << new EntryPointInstruction(entryPoint)
}

Support multi-stage builds in Dockerfile task