diff --git a/doc/changelog.md b/doc/changelog.md index bb596d92c..95be01fa4 100644 --- a/doc/changelog.md +++ b/doc/changelog.md @@ -3,6 +3,7 @@ - Automatically create parent directories of portPropertyFile path - Added support for `platform` attribute of a container in the docker-compose configuration. - `docker:push` failed with build `ARG` in `FROM` ([1778](https://github.com/fabric8io/docker-maven-plugin/issues/1778)) + - Add the possibility to give secret to buildx build ([1798](https://github.com/fabric8io/docker-maven-plugin/issues/1798) * **0.44.0** (2024-02-17): - Add new option "useDefaultExclusion" for build configuration to handle exclusion of hidden files ([1708](https://github.com/fabric8io/docker-maven-plugin/issues/1708)) diff --git a/it/buildx-dockerfile-secret/pom.xml b/it/buildx-dockerfile-secret/pom.xml new file mode 100644 index 000000000..6585692ee --- /dev/null +++ b/it/buildx-dockerfile-secret/pom.xml @@ -0,0 +1,51 @@ + + + 4.0.0 + + + io.fabric8.dmp.itests + dmp-it-parent + 0.45-SNAPSHOT + ../pom.xml + + + dmp-it-buildx-dockerfile-secret + + + + + io.fabric8 + docker-maven-plugin + + + + dmp/alpine:${project.version} + + ${project.basedir}/src/main/docker/Dockerfile + + + + something + + + ${project.basedir}/../README.md + + + + + + + + + + default + + build + + package + + + + + + diff --git a/it/buildx-dockerfile-secret/src/main/docker/Dockerfile b/it/buildx-dockerfile-secret/src/main/docker/Dockerfile new file mode 100644 index 000000000..be731eb17 --- /dev/null +++ b/it/buildx-dockerfile-secret/src/main/docker/Dockerfile @@ -0,0 +1,4 @@ +FROM alpine + +RUN --mount=type=secret,id=myEnvVar cat /run/secrets/myEnvVar +RUN --mount=type=secret,id=myFile cat /run/secrets/myFile diff --git a/it/pom.xml b/it/pom.xml index 966cddbeb..56659ca53 100644 --- a/it/pom.xml +++ b/it/pom.xml @@ -25,6 +25,7 @@ buildx-contextdir buildx-dependencyset buildx-dockerfile + buildx-dockerfile-secret buildx-dockerfile_and_contextdir buildx-push docker-compose diff --git a/src/main/asciidoc/inc/build/_buildx.adoc b/src/main/asciidoc/inc/build/_buildx.adoc index c465bb481..4c05c134c 100644 --- a/src/main/asciidoc/inc/build/_buildx.adoc +++ b/src/main/asciidoc/inc/build/_buildx.adoc @@ -60,6 +60,8 @@ element defaults to `min` and the `` element defaults to `false`. | A value to be passed through to the `--cache-from` option of `docker buildx build`. See https://docs.docker.com/engine/reference/commandline/buildx_build/#cache-from[docker buildx reference docs]. | *cacheTo* | A value to be passed through to the `--cache-to` option of `docker buildx build`. See https://docs.docker.com/engine/reference/commandline/buildx_build/#cache-to[docker buildx reference docs]. +| *secret* +| Two Maps, under `envs` and `files` of `VALUE` elements specifying the values of https://docs.docker.com/reference/cli/docker/buildx/build/#secret[Docker Buildx secret] to give to the build as `--secret id=ID[,[env\|src]=VALUE]`. |=== .Examples diff --git a/src/main/java/io/fabric8/maven/docker/config/BuildXConfiguration.java b/src/main/java/io/fabric8/maven/docker/config/BuildXConfiguration.java index c28d44a79..67e458928 100644 --- a/src/main/java/io/fabric8/maven/docker/config/BuildXConfiguration.java +++ b/src/main/java/io/fabric8/maven/docker/config/BuildXConfiguration.java @@ -61,6 +61,12 @@ public class BuildXConfiguration implements Serializable { @Parameter private Map driverOpts; + /** + * Secret to expose to the build + */ + @Parameter + private SecretConfiguration secret; + public String getBuilderName() { return builderName; } @@ -86,7 +92,7 @@ public String getCacheTo() { } public boolean isBuildX() { - return !getPlatforms().isEmpty(); + return !getPlatforms().isEmpty() || hasSecret(); } @Nonnull @@ -102,6 +108,14 @@ public Map getDriverOpts() { return driverOpts; } + public boolean hasSecret() { + return secret != null && (!secret.getEnvs().isEmpty() || !secret.getFiles().isEmpty()); + } + + public SecretConfiguration getSecret() { + return secret; + } + public static class Builder { private final BuildXConfiguration config = new BuildXConfiguration(); @@ -159,7 +173,6 @@ public Builder attestations(AttestationConfiguration attestations) { return this; } - public Builder cacheFrom(String cacheFrom) { config.cacheFrom = cacheFrom; if (cacheFrom != null) { @@ -183,5 +196,13 @@ public Builder cacheTo(String cacheTo) { } return this; } + + public Builder secret(SecretConfiguration secret) { + config.secret = secret; + if (secret != null) { + isEmpty = false; + } + return this; + } } } diff --git a/src/main/java/io/fabric8/maven/docker/config/SecretConfiguration.java b/src/main/java/io/fabric8/maven/docker/config/SecretConfiguration.java new file mode 100644 index 000000000..b5e9a364a --- /dev/null +++ b/src/main/java/io/fabric8/maven/docker/config/SecretConfiguration.java @@ -0,0 +1,52 @@ +package io.fabric8.maven.docker.config; + +import org.apache.maven.plugins.annotations.Parameter; + +import java.io.Serializable; +import java.util.Map; + +/** + * @since 15/07/24 + */ +public class SecretConfiguration implements Serializable { + + @Parameter + private Map envs; + + @Parameter + private Map files; + + public Map getEnvs() { + return envs; + } + + public Map getFiles() { + return files; + } + + public static class Builder { + + private final SecretConfiguration config = new SecretConfiguration(); + private boolean isEmpty = true; + + public SecretConfiguration build() { + return isEmpty ? null : config; + } + + public SecretConfiguration.Builder envs(Map envs) { + config.envs = envs; + if (envs != null) { + isEmpty = false; + } + return this; + } + + public SecretConfiguration.Builder files(Map files) { + config.files = files; + if (files != null) { + isEmpty = false; + } + return this; + } + } +} diff --git a/src/main/java/io/fabric8/maven/docker/config/handler/property/ConfigKey.java b/src/main/java/io/fabric8/maven/docker/config/handler/property/ConfigKey.java index cc734c292..bc82e0ba1 100644 --- a/src/main/java/io/fabric8/maven/docker/config/handler/property/ConfigKey.java +++ b/src/main/java/io/fabric8/maven/docker/config/handler/property/ConfigKey.java @@ -50,6 +50,8 @@ public enum ConfigKey { BUILDX_ATTESTATION_SBOM("buildx.attestations.sbom"), BUILDX_CACHE_FROM("buildx.cacheFrom"), BUILDX_CACHE_TO("buildx.cacheTo"), + BUILDX_SECRET_ENVS("buildx.secret.envs", ValueCombinePolicy.Merge), + BUILDX_SECRET_FILES("buildx.secret.files", ValueCombinePolicy.Merge), CAP_ADD, CAP_DROP, SYSCTLS, diff --git a/src/main/java/io/fabric8/maven/docker/config/handler/property/PropertyConfigHandler.java b/src/main/java/io/fabric8/maven/docker/config/handler/property/PropertyConfigHandler.java index 82a0d7cd1..3d1cd495b 100644 --- a/src/main/java/io/fabric8/maven/docker/config/handler/property/PropertyConfigHandler.java +++ b/src/main/java/io/fabric8/maven/docker/config/handler/property/PropertyConfigHandler.java @@ -36,6 +36,7 @@ import io.fabric8.maven.docker.config.RestartPolicy; import io.fabric8.maven.docker.config.RunImageConfiguration; import io.fabric8.maven.docker.config.RunVolumeConfiguration; +import io.fabric8.maven.docker.config.SecretConfiguration; import io.fabric8.maven.docker.config.UlimitConfig; import io.fabric8.maven.docker.config.WaitConfiguration; import io.fabric8.maven.docker.config.WatchImageConfiguration; @@ -342,6 +343,7 @@ private BuildXConfiguration extractBuildx(BuildXConfiguration config, ValueProvi .attestations(extractAttestations(config.getAttestations(), valueProvider)) .cacheFrom(valueProvider.getString(BUILDX_CACHE_FROM, config.getCacheFrom())) .cacheTo(valueProvider.getString(BUILDX_CACHE_TO, config.getCacheTo())) + .secret(extractSecret(config.getSecret(), valueProvider)) .build(); } @@ -356,6 +358,17 @@ private AttestationConfiguration extractAttestations(AttestationConfiguration co .build(); } + private SecretConfiguration extractSecret(SecretConfiguration config, ValueProvider valueProvider) { + if (config == null) { + config = new SecretConfiguration(); + } + + return new SecretConfiguration.Builder() + .envs(valueProvider.getMap(BUILDX_SECRET_ENVS, config.getEnvs())) + .files(valueProvider.getMap(BUILDX_SECRET_FILES, config.getFiles())) + .build(); + } + // Extract only the values of the port mapping private List extractPortValues(List config, ValueProvider valueProvider) { diff --git a/src/main/java/io/fabric8/maven/docker/service/BuildXService.java b/src/main/java/io/fabric8/maven/docker/service/BuildXService.java index d262f0f19..1f63cd3e9 100644 --- a/src/main/java/io/fabric8/maven/docker/service/BuildXService.java +++ b/src/main/java/io/fabric8/maven/docker/service/BuildXService.java @@ -10,6 +10,7 @@ import io.fabric8.maven.docker.config.BuildXConfiguration; import io.fabric8.maven.docker.config.ConfigHelper; import io.fabric8.maven.docker.config.ImageConfiguration; +import io.fabric8.maven.docker.config.SecretConfiguration; import io.fabric8.maven.docker.util.EnvUtil; import io.fabric8.maven.docker.util.ImageName; import io.fabric8.maven.docker.util.Logger; @@ -35,6 +36,8 @@ import java.util.Map; import java.util.Optional; import java.util.concurrent.CompletableFuture; +import java.util.function.BiConsumer; +import java.util.function.Consumer; public class BuildXService { private static final String DOCKER = "docker"; @@ -121,7 +124,7 @@ protected void buildAndLoadSinglePlatform(List buildX, String builderNam String nativePlatform = dockerAccess.getNativePlatform(); if (platforms.size() == 1) { buildX(buildX, builderName, buildDirs, imageConfig, configuredRegistry, platforms, buildArchive, "--load"); - } else if (platforms.contains(nativePlatform)) { + } else if (platforms.isEmpty() || platforms.contains(nativePlatform)) { buildX(buildX, builderName, buildDirs, imageConfig, configuredRegistry, Collections.singletonList(nativePlatform), buildArchive, "--load"); } else { logger.info("More than one platform specified not including native %s, no image built", nativePlatform); @@ -130,7 +133,11 @@ protected void buildAndLoadSinglePlatform(List buildX, String builderNam protected void pushMultiPlatform(List buildX, String builderName, BuildDirs buildDirs, ImageConfiguration imageConfig, String configuredRegistry, File buildArchive) throws MojoExecutionException { // build and push all images. The native platform may be re-built, image should be cached and build should be quick - buildX(buildX, builderName, buildDirs, imageConfig, configuredRegistry, imageConfig.getBuildConfiguration().getBuildX().getPlatforms(), buildArchive, "--push"); + List platforms = new ArrayList<>(imageConfig.getBuildConfiguration().getBuildX().getPlatforms()); + if (platforms.isEmpty()) { + platforms.add(dockerAccess.getNativePlatform()); + } + buildX(buildX, builderName, buildDirs, imageConfig, configuredRegistry, platforms, buildArchive, "--push"); } protected void buildX(List buildX, String builderName, BuildDirs buildDirs, ImageConfiguration imageConfig, String configuredRegistry, List platforms, File buildArchive, String extraParam) @@ -190,6 +197,15 @@ protected void buildX(List buildX, String builderName, BuildDirs buildDi if (buildXConfiguration.getCacheTo() != null) { cmdLine.add("--cache-to=" + buildXConfiguration.getCacheTo()); } + SecretConfiguration secret = buildXConfiguration.getSecret(); + if (secret != null) { + if (secret.getEnvs() != null) { + secret.getEnvs().forEach(buildXSecretConsumerFor("env", cmdLine::add)); + } + if (secret.getFiles() != null) { + secret.getFiles().forEach(buildXSecretConsumerFor("src", cmdLine::add)); + } + } if (buildConfiguration.squash()) { cmdLine.add("--squash"); @@ -213,6 +229,17 @@ protected void buildX(List buildX, String builderName, BuildDirs buildDi } } + protected BiConsumer buildXSecretConsumerFor(String attribute, Consumer cmdLineConsumer) { + return (arg0, arg1) -> { + cmdLineConsumer.accept("--secret"); + String secretParameter = "id=" + arg0; + if (arg1 != null) { + secretParameter += "," + attribute + "=" + arg1; + } + cmdLineConsumer.accept(secretParameter); + }; + } + protected Path getContextPath(File buildArchive) throws MojoExecutionException { String archiveName = buildArchive.getName(); String fileName = archiveName.substring(0, archiveName.indexOf('.')); diff --git a/src/test/java/io/fabric8/maven/docker/BuildMojoTest.java b/src/test/java/io/fabric8/maven/docker/BuildMojoTest.java index 46dc7969a..0f21ca944 100644 --- a/src/test/java/io/fabric8/maven/docker/BuildMojoTest.java +++ b/src/test/java/io/fabric8/maven/docker/BuildMojoTest.java @@ -8,6 +8,7 @@ import io.fabric8.maven.docker.config.BuildImageConfiguration; import io.fabric8.maven.docker.config.BuildXConfiguration; import io.fabric8.maven.docker.config.ImageConfiguration; +import io.fabric8.maven.docker.config.SecretConfiguration; import io.fabric8.maven.docker.service.BuildService; import io.fabric8.maven.docker.service.BuildXService; import io.fabric8.maven.docker.service.ImagePullManager; @@ -95,7 +96,7 @@ void buildUsingBuildx() throws IOException, MojoExecutionException { whenMojoExecutes(); - thenBuildxRun(null, null, true, null ); + thenBuildxRun(null, null, true, Collections.emptyList()); } @Test @@ -108,7 +109,7 @@ void buildUsingConfiguredBuildx() throws IOException, MojoExecutionException { whenMojoExecutes(); - thenBuildxRun("src/docker/builder.toml", null, true, null ); + thenBuildxRun("src/docker/builder.toml", null, true, Collections.emptyList()); } @Test @@ -126,7 +127,7 @@ void buildUsingConfiguredBuildxWithContext() throws IOException, MojoExecutionEx whenMojoExecutes(); - thenBuildxRun(null, "src/main/docker", true,null ); + thenBuildxRun(null, "src/main/docker", true, Collections.emptyList()); } @Test @@ -156,7 +157,7 @@ void buildUsingBuildxWithSquash() throws IOException, MojoExecutionException { whenMojoExecutes(); - thenBuildxRun(null, null, true, "--squash"); + thenBuildxRun(null, null, true, Collections.singletonList("--squash")); } @Test @@ -169,7 +170,7 @@ void buildUsingBuildxWithSbom() throws IOException, MojoExecutionException { whenMojoExecutes(); - thenBuildxRun(null, null, true, "--sbom=true"); + thenBuildxRun(null, null, true, Collections.singletonList("--sbom=true")); } @Test @@ -182,7 +183,7 @@ void buildUsingBuildxWithMaxProvenance() throws IOException, MojoExecutionExcept whenMojoExecutes(); - thenBuildxRun(null, null, true, "--provenance=mode=max"); + thenBuildxRun(null, null, true, Collections.singletonList("--provenance=mode=max")); } @Test @@ -195,7 +196,7 @@ void buildUsingBuildxWithNoProvenance() throws IOException, MojoExecutionExcepti whenMojoExecutes(); - thenBuildxRun(null, null, true, "--provenance=false"); + thenBuildxRun(null, null, true, Collections.singletonList("--provenance=false")); } @Test @@ -208,7 +209,26 @@ void buildUsingBuildxWithIncorrectProvenanceMode() throws IOException, MojoExecu whenMojoExecutes(); - thenBuildxRun(null, null, true, null); + thenBuildxRun(null, null, true, Collections.emptyList()); + } + + @Test + void buildUsingBuildxWithSecretEnvsFiles() throws IOException, MojoExecutionException { + givenBuildXService(); + + givenMavenProject(buildMojo); + givenResolvedImages(buildMojo, Collections.singletonList(singleBuildXImageWithSecret( + Collections.singletonMap("githubToken", "GITHUB_TOKEN"), Collections.singletonMap("privateRepo", "../private.repo")))); + givenPackaging("jar"); + + whenMojoExecutes(); + + List secrets = new ArrayList<>(); + secrets.add("--secret"); + secrets.add("id=githubToken,env=GITHUB_TOKEN"); + secrets.add("--secret"); + secrets.add("id=privateRepo,src=../private.repo"); + thenBuildxRun(null, null, true, secrets); } @Test @@ -269,7 +289,7 @@ void buildWithTagByBuildx(boolean skipTag) throws IOException, MojoExecutionExce List fullTags = skipTag ? Collections.emptyList() : tags.stream() .map(tag -> new ImageName(imageConfiguration.getName(), tag).getFullName()) .collect(Collectors.toList()); - thenBuildxRun(null, null, true, null, fullTags); + thenBuildxRun(null, null, true, Collections.emptyList(), fullTags); } @ParameterizedTest @@ -347,12 +367,13 @@ private void verifyBuild(int wantedNumberOfInvocations) throws DockerAccessExcep } private void thenBuildxRun(String relativeConfigFile, String contextDir, boolean nativePlatformIncluded, - String attestation) throws MojoExecutionException { - thenBuildxRun(relativeConfigFile, contextDir, nativePlatformIncluded, attestation, Collections.emptyList()); + List additionalParameters) throws MojoExecutionException { + thenBuildxRun(relativeConfigFile, contextDir, nativePlatformIncluded, additionalParameters, + Collections.emptyList()); } - private void thenBuildxRun(String relativeConfigFile, String contextDir, - boolean nativePlatformIncluded, String attestation, List tags) + private void thenBuildxRun(String relativeConfigFile, String contextDir, boolean nativePlatformIncluded, + List additionalParameters, List tags) throws MojoExecutionException { Path buildPath = projectBaseDirectory.toPath().resolve("target/docker/example/latest"); String config = getOsDependentBuild(buildPath, "docker"); @@ -380,9 +401,7 @@ private void thenBuildxRun(String relativeConfigFile, String contextDir, buildXLine.add("--build-arg"); buildXLine.add("foo=bar"); - if (attestation != null) { - buildXLine.add(attestation); - } + buildXLine.addAll(additionalParameters); if (contextDir == null) { buildXLine.add(getOsDependentBuild(buildPath, "build")); @@ -446,6 +465,12 @@ private ImageConfiguration singleBuildXImageWithAttestations(Boolean sbom, Strin .build(), null); } + private ImageConfiguration singleBuildXImageWithSecret(Map envs, Map files) { + return singleImageConfiguration(getBuildXPlatforms(NATIVE_PLATFORM).secret( + new SecretConfiguration.Builder().envs(envs).files(files).build()) + .build(), null); + } + protected ImageConfiguration singleImageWithAuthRegistry(String dockerFile) { BuildImageConfiguration buildImageConfiguration = new BuildImageConfiguration.Builder() .dockerFile(dockerFile)