From 64c9e453becfca181bc5057bc474b2df5bb77926 Mon Sep 17 00:00:00 2001 From: Ryan Hartkopf Date: Thu, 7 Apr 2022 04:41:56 -0500 Subject: [PATCH] Added support for BuildX (#528) --- .circleci/config.yml | 14 +++++++- changelog/@unreleased/pr-528.v1.yaml | 6 ++++ readme.md | 5 +++ .../gradle/docker/DockerExtension.groovy | 36 +++++++++++++++++++ .../gradle/docker/PalantirDockerPlugin.groovy | 16 ++++++++- .../docker/DockerComposePluginTests.groovy | 9 +++-- .../gradle/docker/DockerRunPluginTests.groovy | 2 +- .../docker/PalantirDockerPluginTests.groovy | 35 +++++++++++++++++- 8 files changed, 116 insertions(+), 7 deletions(-) create mode 100644 changelog/@unreleased/pr-528.v1.yaml diff --git a/.circleci/config.yml b/.circleci/config.yml index 1ac42832..937e950a 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -5,13 +5,25 @@ version: 2.1 jobs: build: - machine: { docker_layer_caching: true } + machine: + docker_layer_caching: true + image: ubuntu-2004:202104-01 environment: CIRCLE_TEST_REPORTS: /home/circleci/junit CIRCLE_ARTIFACTS: /home/circleci/artifacts _JAVA_OPTIONS: -Dorg.gradle.internal.launcher.welcomeMessageEnabled=false -Xmx8192m + DOCKER_BUILDKIT: 1 + BUILDX_PLATFORMS: linux/amd64,linux/arm64,linux/ppc64le,linux/s390x,linux/386,linux/arm/v7,linux/arm/v6 steps: - checkout + - run: + name: Install buildx + command: | + export DOCKER_BUILDKIT=1 + docker build --platform=local -o . "https://github.com/docker/buildx.git" + mkdir -p ~/.docker/cli-plugins + mv buildx ~/.docker/cli-plugins/docker-buildx + docker buildx create --name mybuilder --use - run: name: delete_unrelated_tags command: | diff --git a/changelog/@unreleased/pr-528.v1.yaml b/changelog/@unreleased/pr-528.v1.yaml new file mode 100644 index 00000000..e20344f6 --- /dev/null +++ b/changelog/@unreleased/pr-528.v1.yaml @@ -0,0 +1,6 @@ +type: improvement +improvement: + description: Add ability to do multi platform docker builds using buildx + links: + - https://github.com/palantir/gradle-docker/pull/528 + - https://github.com/palantir/gradle-docker/issues/353 diff --git a/readme.md b/readme.md index c03f53a9..91f0077d 100644 --- a/readme.md +++ b/readme.md @@ -63,6 +63,11 @@ build/ a newer version of the base image before building; defaults to `false` - `noCache` (optional) a boolean argument which defines whether Docker build should add the option --no-cache, so that it rebuilds the whole image from scratch; defaults to `false` +- `buildx` (optional) a boolean argument which defines whether Docker build should use buildx for cross platform builds; defaults to `false` +- `platform` (optional) a list of strings argument which defines which platforms buildx should target; defaults to empty +- `builder` (optional) a string argument which defines which builder buildx should use; defaults to `null` +- `load` (optional) a boolean argument which defines whether Docker buildx builder should add --load flag, + loading the image into the local repository; defaults to `false` To build a docker container, run the `docker` task. To push that container to a docker repository, run the `dockerPush` task. diff --git a/src/main/groovy/com/palantir/gradle/docker/DockerExtension.groovy b/src/main/groovy/com/palantir/gradle/docker/DockerExtension.groovy index f5a363bc..e344f522 100644 --- a/src/main/groovy/com/palantir/gradle/docker/DockerExtension.groovy +++ b/src/main/groovy/com/palantir/gradle/docker/DockerExtension.groovy @@ -42,6 +42,10 @@ class DockerExtension { private boolean pull = false private boolean noCache = false private String network = null + private boolean buildx = false + private Set platform = ImmutableSet.of() + private boolean load = false + private String builder = null private File resolvedDockerfile = null private File resolvedDockerComposeTemplate = null @@ -175,4 +179,36 @@ class DockerExtension { public void noCache(boolean noCache) { this.noCache = noCache } + + public boolean getLoad() { + return pull + } + + public void load(boolean pull) { + this.pull = pull + } + + boolean getBuildx() { + return buildx + } + + public void buildx(boolean buildx) { + this.buildx = buildx + } + + public Set getPlatform() { + return platform + } + + public void platform(String... args) { + this.platform = ImmutableSet.copyOf(args) + } + + String getBuilder() { + return builder + } + + public void builder(String builder) { + this.builder = builder + } } diff --git a/src/main/groovy/com/palantir/gradle/docker/PalantirDockerPlugin.groovy b/src/main/groovy/com/palantir/gradle/docker/PalantirDockerPlugin.groovy index ba63eca8..f0303cc5 100644 --- a/src/main/groovy/com/palantir/gradle/docker/PalantirDockerPlugin.groovy +++ b/src/main/groovy/com/palantir/gradle/docker/PalantirDockerPlugin.groovy @@ -172,7 +172,21 @@ class PalantirDockerPlugin implements Plugin { } private List buildCommandLine(DockerExtension ext) { - List buildCommandLine = ['docker', 'build'] + List buildCommandLine = ['docker'] + if (ext.buildx) { + buildCommandLine.addAll(['buildx', 'build']) + if (!ext.platform.isEmpty()) { + buildCommandLine.addAll('--platform', String.join(',', ext.platform)) + } + if (ext.load) { + buildCommandLine.add '--load' + } + if (ext.builder != null) { + buildCommandLine.addAll('--builder', ext.builder) + } + } else { + buildCommandLine.add 'build' + } if (ext.noCache) { buildCommandLine.add '--no-cache' } diff --git a/src/test/groovy/com/palantir/gradle/docker/DockerComposePluginTests.groovy b/src/test/groovy/com/palantir/gradle/docker/DockerComposePluginTests.groovy index 349dd240..1b0edec7 100644 --- a/src/test/groovy/com/palantir/gradle/docker/DockerComposePluginTests.groovy +++ b/src/test/groovy/com/palantir/gradle/docker/DockerComposePluginTests.groovy @@ -173,6 +173,8 @@ class DockerComposePluginTests extends AbstractPluginTest { with('dockerComposeUp').build() then: file("foobarbaz").exists() + execCond("docker stop helloworld") + execCond("docker rm helloworld") } def 'docker-compose successfully creates docker image from custom file'() { @@ -200,6 +202,8 @@ class DockerComposePluginTests extends AbstractPluginTest { with('dockerComposeUp').build() then: file("qux").exists() + execCond("docker stop helloworld2") + execCond("docker rm helloworld2") } def 'can set custom properties on generateDockerCompose.ext'() { @@ -236,10 +240,9 @@ class DockerComposePluginTests extends AbstractPluginTest { id 'com.palantir.docker-compose' } '''.stripIndent() - with('dockerComposeUp').build() when: - with('dockerComposeDown').build() + BuildResult buildResult = with('dockerComposeUp', 'dockerComposeDown').build() then: - processCount() == 0 + buildResult.task(':dockerComposeDown').outcome == TaskOutcome.SUCCESS } } diff --git a/src/test/groovy/com/palantir/gradle/docker/DockerRunPluginTests.groovy b/src/test/groovy/com/palantir/gradle/docker/DockerRunPluginTests.groovy index 84624e0f..9afaad24 100644 --- a/src/test/groovy/com/palantir/gradle/docker/DockerRunPluginTests.groovy +++ b/src/test/groovy/com/palantir/gradle/docker/DockerRunPluginTests.groovy @@ -212,7 +212,7 @@ class DockerRunPluginTests extends AbstractPluginTest { buildResult.output =~ /(?m)\/test/ buildResult.task(':dockerRunStatus').outcome == TaskOutcome.SUCCESS - buildResult.output =~ /(?m):dockerRunStatus\nDocker container 'foo' is STOPPED./ + buildResult.output =~ /(?m):dockerRunStatus\s+Docker container 'foo' is STOPPED./ } def 'can mount volumes'() { diff --git a/src/test/groovy/com/palantir/gradle/docker/PalantirDockerPluginTests.groovy b/src/test/groovy/com/palantir/gradle/docker/PalantirDockerPluginTests.groovy index e03928ba..4a639983 100644 --- a/src/test/groovy/com/palantir/gradle/docker/PalantirDockerPluginTests.groovy +++ b/src/test/groovy/com/palantir/gradle/docker/PalantirDockerPluginTests.groovy @@ -142,6 +142,37 @@ class PalantirDockerPluginTests extends AbstractPluginTest { execCond("docker rmi -f ${id}") } + def 'check multiarch'() { + given: + String id = 'id4' + String filename = "foo.txt" + file('Dockerfile') << """ + FROM alpine + MAINTAINER ${id} + ADD ${filename} /tmp/ + """.stripIndent() + buildFile << """ + plugins { + id 'com.palantir.docker' + } + docker { + name '${id}' + files "${filename}" + buildx true + load true + platform 'linux/arm64' + } + """.stripIndent() + new File(projectDir, filename).createNewFile() + when: + BuildResult buildResult = with('docker').build() + then: + buildResult.task(':dockerPrepare').outcome == TaskOutcome.SUCCESS + buildResult.task(':docker').outcome == TaskOutcome.SUCCESS + exec("docker inspect --format '{{.Architecture}}' ${id}") == "'arm64'\n" + execCond("docker rmi -f ${id}") + } + // Gradle explicitly disallows the test case, fails with the following: //Could not determine the dependencies of task ':publishDockerPublicationPublicationToMavenLocal'. //> Publishing is not able to resolve a dependency on a project with multiple publications that have different coordinates. @@ -457,7 +488,7 @@ class PalantirDockerPluginTests extends AbstractPluginTest { then: buildResult.task(':docker').outcome == TaskOutcome.SUCCESS - buildResult.output.contains 'Pulling from library/alpine' + buildResult.output.contains 'load metadata for docker.io/library/alpine' execCond("docker rmi -f ${id}") } @@ -488,6 +519,8 @@ class PalantirDockerPluginTests extends AbstractPluginTest { buildResult.task(':docker').outcome == TaskOutcome.FAILED buildResult.output.contains('network foobar not found') or( buildResult.output.contains('No such network: foobar') + ) or( + buildResult.output.contains('network mode "foobar" not supported by buildkit') ) execCond("docker rmi -f ${id}") }