From e9801891092bd2760e52cffde18d2fe8bae2dc73 Mon Sep 17 00:00:00 2001 From: Rohan Kumar Date: Sat, 22 Jun 2024 15:23:40 +0530 Subject: [PATCH] fix : RegistryService should also consider buildArgs while resolving base images Signed-off-by: Rohan Kumar --- doc/changelog.md | 1 + it/dockerfile-push-build-arg-from/pom.xml | 56 +++++ .../src/main/docker/Dockerfile | 2 + it/pom.xml | 1 + pom.xml | 8 + .../docker/AbstractBuildSupportMojo.java | 38 ---- .../maven/docker/AbstractDockerMojo.java | 15 ++ .../io/fabric8/maven/docker/BuildMojo.java | 8 +- .../io/fabric8/maven/docker/PushMojo.java | 2 +- .../io/fabric8/maven/docker/SourceMojo.java | 2 +- .../io/fabric8/maven/docker/WatchMojo.java | 2 +- .../maven/docker/service/BuildService.java | 99 ++------- .../maven/docker/service/RegistryService.java | 46 ++-- .../service/helper/BuildArgResolver.java | 93 ++++++++ .../maven/docker/util/DockerFileUtil.java | 2 +- .../io/fabric8/maven/docker/util/EnvUtil.java | 37 +++- .../io/fabric8/maven/docker/PushMojoTest.java | 6 +- .../docker/service/BuildServiceTest.java | 58 ++--- .../RegistryServicePushImagesBuildXTest.java | 21 +- .../docker/service/RegistryServiceTest.java | 201 ++++++++++++------ .../service/helper/BuildArgResolverTest.java | 115 ++++++++++ 21 files changed, 550 insertions(+), 263 deletions(-) create mode 100644 it/dockerfile-push-build-arg-from/pom.xml create mode 100644 it/dockerfile-push-build-arg-from/src/main/docker/Dockerfile delete mode 100644 src/main/java/io/fabric8/maven/docker/AbstractBuildSupportMojo.java create mode 100644 src/main/java/io/fabric8/maven/docker/service/helper/BuildArgResolver.java create mode 100644 src/test/java/io/fabric8/maven/docker/service/helper/BuildArgResolverTest.java diff --git a/doc/changelog.md b/doc/changelog.md index 2996c8bb2..bb596d92c 100644 --- a/doc/changelog.md +++ b/doc/changelog.md @@ -2,6 +2,7 @@ * **0.45-SNAPSHOT**: - 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)) * **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/dockerfile-push-build-arg-from/pom.xml b/it/dockerfile-push-build-arg-from/pom.xml new file mode 100644 index 000000000..dafbe1902 --- /dev/null +++ b/it/dockerfile-push-build-arg-from/pom.xml @@ -0,0 +1,56 @@ + + + + 4.0.0 + + + io.fabric8.dmp.itests + dmp-it-parent + 0.45-SNAPSHOT + ../pom.xml + + + dockerfile-push-build-arg-from + 0.45-SNAPSHOT + dmp-it-dockerfile-build-arg-from + + + busybox + + + + + + io.fabric8 + docker-maven-plugin + + + + ttl.sh/%a:5m + dockerfile + + ${project.basedir}/src/main/docker + + + + + + + build + + build + + install + + + push + + push + + install + + + + + + diff --git a/it/dockerfile-push-build-arg-from/src/main/docker/Dockerfile b/it/dockerfile-push-build-arg-from/src/main/docker/Dockerfile new file mode 100644 index 000000000..576bb1089 --- /dev/null +++ b/it/dockerfile-push-build-arg-from/src/main/docker/Dockerfile @@ -0,0 +1,2 @@ +ARG baseImage +FROM ${baseImage} diff --git a/it/pom.xml b/it/pom.xml index 18a8397b7..966cddbeb 100644 --- a/it/pom.xml +++ b/it/pom.xml @@ -32,6 +32,7 @@ dockerfile dockerfile-base-as-arg dockerfile-base-as-arg-buildconfig + dockerfile-push-build-arg-from dockerignore graceful-container-removal healthcheck diff --git a/pom.xml b/pom.xml index 2e1e46ee3..16721f51b 100644 --- a/pom.xml +++ b/pom.xml @@ -82,6 +82,7 @@ UTF-8 3.0.0-M2 2.0.1 + 3.25.3 @@ -212,6 +213,13 @@ + + org.assertj + assertj-core + ${version.assertj-core} + test + + org.bouncycastle bcpkix-jdk15on diff --git a/src/main/java/io/fabric8/maven/docker/AbstractBuildSupportMojo.java b/src/main/java/io/fabric8/maven/docker/AbstractBuildSupportMojo.java deleted file mode 100644 index 75c251a6d..000000000 --- a/src/main/java/io/fabric8/maven/docker/AbstractBuildSupportMojo.java +++ /dev/null @@ -1,38 +0,0 @@ -package io.fabric8.maven.docker; - -import java.util.List; -import java.util.Map; - -import io.fabric8.maven.docker.service.BuildService; -import io.fabric8.maven.docker.util.MojoParameters; -import org.apache.maven.archiver.MavenArchiveConfiguration; -import org.apache.maven.plugin.MojoExecutionException; -import org.apache.maven.plugins.annotations.Component; -import org.apache.maven.plugins.annotations.Parameter; -import org.apache.maven.project.MavenProject; -import org.apache.maven.shared.filtering.MavenFileFilter; -import org.apache.maven.shared.filtering.MavenReaderFilter; - -/** - * @author roland - * @author balazsmaria - * @since 26/06/15 - */ -abstract public class AbstractBuildSupportMojo extends AbstractDockerMojo { - // ============================================================================================================== - // Parameters required from Maven when building an assembly. They cannot be injected directly - // into DockerAssemblyCreator. - // See also here: http://maven.40175.n5.nabble.com/Mojo-Java-1-5-Component-MavenProject-returns-null-vs-JavaDoc-parameter-expression-quot-project-quot-s-td5733805.html - @Parameter(property = "docker.pull.registry") - String pullRegistry; - - - protected BuildService.BuildContext getBuildContext() throws MojoExecutionException { - return new BuildService.BuildContext.Builder() - .buildArgs(buildArgs) - .mojoParameters(createMojoParameters()) - .registryConfig(getRegistryConfig(pullRegistry)) - .build(); - } - -} diff --git a/src/main/java/io/fabric8/maven/docker/AbstractDockerMojo.java b/src/main/java/io/fabric8/maven/docker/AbstractDockerMojo.java index 6ff31c1e4..3d3af1d6c 100644 --- a/src/main/java/io/fabric8/maven/docker/AbstractDockerMojo.java +++ b/src/main/java/io/fabric8/maven/docker/AbstractDockerMojo.java @@ -25,6 +25,7 @@ import io.fabric8.maven.docker.log.LogDispatcher; import io.fabric8.maven.docker.log.LogOutputSpecFactory; import io.fabric8.maven.docker.model.Container; +import io.fabric8.maven.docker.service.BuildService; import io.fabric8.maven.docker.service.DockerAccessFactory; import io.fabric8.maven.docker.service.ImagePullManager; import io.fabric8.maven.docker.service.QueryService; @@ -242,6 +243,12 @@ public abstract class AbstractDockerMojo extends AbstractMojo implements Context @Parameter protected Map buildArgs; + // ============================================================================================================== + // Parameters required from Maven when building an assembly. They cannot be injected directly + // into DockerAssemblyCreator. + // See also here: http://maven.40175.n5.nabble.com/Mojo-Java-1-5-Component-MavenProject-returns-null-vs-JavaDoc-parameter-expression-quot-project-quot-s-td5733805.html + @Parameter(property = "docker.pull.registry") + String pullRegistry; // Images resolved with external image resolvers and hooks for subclass to // mangle the image configurations. @@ -315,6 +322,14 @@ public void execute() throws MojoExecutionException, MojoFailureException { } } + protected BuildService.BuildContext getBuildContext() { + return new BuildService.BuildContext.Builder() + .buildArgs(buildArgs) + .mojoParameters(createMojoParameters()) + .registryConfig(getRegistryConfig(pullRegistry)) + .build(); + } + private void logException(Exception exp) { if (exp.getCause() != null) { log.error("%s [%s]", exp.getMessage(), exp.getCause().getMessage()); diff --git a/src/main/java/io/fabric8/maven/docker/BuildMojo.java b/src/main/java/io/fabric8/maven/docker/BuildMojo.java index f2e5f562c..c4386c24c 100644 --- a/src/main/java/io/fabric8/maven/docker/BuildMojo.java +++ b/src/main/java/io/fabric8/maven/docker/BuildMojo.java @@ -7,6 +7,7 @@ import io.fabric8.maven.docker.service.ImagePullManager; import io.fabric8.maven.docker.service.JibBuildService; import io.fabric8.maven.docker.service.ServiceHub; +import io.fabric8.maven.docker.service.helper.BuildArgResolver; import io.fabric8.maven.docker.util.EnvUtil; import io.fabric8.maven.docker.util.Logger; import org.apache.maven.plugin.MojoExecutionException; @@ -24,6 +25,7 @@ import java.net.URL; import java.util.Date; import java.util.Enumeration; +import java.util.Map; import static io.fabric8.maven.docker.service.RegistryService.createCompleteAuthConfigList; @@ -34,7 +36,7 @@ * @since 28.07.14 */ @Mojo(name = "build", defaultPhase = LifecyclePhase.INSTALL, requiresDependencyResolution = ResolutionScope.TEST) -public class BuildMojo extends AbstractBuildSupportMojo { +public class BuildMojo extends AbstractDockerMojo { public static final String DMP_PLUGIN_DESCRIPTOR = "META-INF/maven/io.fabric8/dmp-plugin"; public static final String DOCKER_EXTRA_DIR = "docker-extra"; @@ -101,7 +103,9 @@ private void proceedWithDockerBuild(ServiceHub hub, BuildService.BuildContext bu File buildArchiveFile = buildService.buildArchive(imageConfig, buildContext, resolveBuildArchiveParameter()); if (Boolean.FALSE.equals(shallBuildArchiveOnly())) { if (imageConfig.isBuildX()) { - hub.getBuildXService().build(createProjectPaths(), imageConfig, null, createCompleteAuthConfigList(false, imageConfig, getRegistryConfig(pullRegistry), createMojoParameters()), buildArchiveFile); + BuildArgResolver buildArgResolver = new BuildArgResolver(log); + Map buildArgsFromExternalSources = buildArgResolver.resolveBuildArgs(buildContext); + hub.getBuildXService().build(createProjectPaths(), imageConfig, null, createCompleteAuthConfigList(false, imageConfig, getRegistryConfig(pullRegistry), createMojoParameters(), buildArgsFromExternalSources), buildArchiveFile); } else { buildService.buildImage(imageConfig, pullManager, buildContext, buildArchiveFile); if (!skipTag && !imageConfig.getBuildConfiguration().skipTag()) { diff --git a/src/main/java/io/fabric8/maven/docker/PushMojo.java b/src/main/java/io/fabric8/maven/docker/PushMojo.java index 01acdab1a..545b13631 100644 --- a/src/main/java/io/fabric8/maven/docker/PushMojo.java +++ b/src/main/java/io/fabric8/maven/docker/PushMojo.java @@ -51,7 +51,7 @@ public void executeInternal(ServiceHub hub) throws DockerAccessException, MojoEx } private void executeDockerPush(ServiceHub hub) throws MojoExecutionException, DockerAccessException { - hub.getRegistryService().pushImages(createProjectPaths(), getResolvedImages(), retries, getRegistryConfig(pushRegistry), skipTag, createMojoParameters()); + hub.getRegistryService().pushImages(createProjectPaths(), getResolvedImages(), retries, getRegistryConfig(pushRegistry), skipTag, getBuildContext()); } private void executeJibPush(ServiceHub hub) throws MojoExecutionException { diff --git a/src/main/java/io/fabric8/maven/docker/SourceMojo.java b/src/main/java/io/fabric8/maven/docker/SourceMojo.java index 93c226a84..b899dd3db 100644 --- a/src/main/java/io/fabric8/maven/docker/SourceMojo.java +++ b/src/main/java/io/fabric8/maven/docker/SourceMojo.java @@ -45,7 +45,7 @@ * @since 25/10/15 */ @Mojo(name = "source", defaultPhase = LifecyclePhase.PACKAGE) -public class SourceMojo extends AbstractBuildSupportMojo { +public class SourceMojo extends AbstractDockerMojo { @Component private MavenProjectHelper projectHelper; diff --git a/src/main/java/io/fabric8/maven/docker/WatchMojo.java b/src/main/java/io/fabric8/maven/docker/WatchMojo.java index 04d4c0d64..82d982634 100644 --- a/src/main/java/io/fabric8/maven/docker/WatchMojo.java +++ b/src/main/java/io/fabric8/maven/docker/WatchMojo.java @@ -41,7 +41,7 @@ * @since 16/06/15 */ @Mojo(name = "watch") -public class WatchMojo extends AbstractBuildSupportMojo { +public class WatchMojo extends AbstractDockerMojo { /** * Watching mode for rebuilding images diff --git a/src/main/java/io/fabric8/maven/docker/service/BuildService.java b/src/main/java/io/fabric8/maven/docker/service/BuildService.java index a099b69fb..61c0c8566 100644 --- a/src/main/java/io/fabric8/maven/docker/service/BuildService.java +++ b/src/main/java/io/fabric8/maven/docker/service/BuildService.java @@ -1,7 +1,6 @@ package io.fabric8.maven.docker.service; import com.google.common.collect.ImmutableMap; -import com.google.gson.JsonObject; import com.google.gson.JsonParseException; import io.fabric8.maven.docker.access.BuildOptions; import io.fabric8.maven.docker.access.DockerAccess; @@ -14,6 +13,7 @@ import io.fabric8.maven.docker.config.ImageConfiguration; import io.fabric8.maven.docker.model.ImageArchiveManifest; import io.fabric8.maven.docker.model.ImageArchiveManifestEntry; +import io.fabric8.maven.docker.service.helper.BuildArgResolver; import io.fabric8.maven.docker.util.DockerFileUtil; import io.fabric8.maven.docker.util.EnvUtil; import io.fabric8.maven.docker.util.ImageArchiveUtil; @@ -28,17 +28,13 @@ import java.io.Serializable; import java.nio.file.Files; import java.util.Collections; -import java.util.HashMap; import java.util.LinkedList; import java.util.List; import java.util.Map; -import java.util.Properties; +import java.util.Optional; import java.util.regex.PatternSyntaxException; public class BuildService { - - private final String argPrefix = "docker.buildArg."; - private final DockerAccess docker; private final QueryService queryService; private final ArchiveService archiveService; @@ -64,13 +60,14 @@ public class BuildService { public void buildImage(ImageConfiguration imageConfig, ImagePullManager imagePullManager, BuildContext buildContext, File buildArchiveFile) throws DockerAccessException, MojoExecutionException { - Map buildArgs = addBuildArgs(buildContext); + BuildArgResolver buildArgResolver = new BuildArgResolver(log); + Map buildArgsFromExternalSources = buildArgResolver.resolveBuildArgs(buildContext); if (imagePullManager != null) { - autoPullBaseImage(imageConfig, imagePullManager, buildContext, prepareBuildArgs(buildArgs, imageConfig.getBuildConfiguration())); + autoPullBaseImage(imageConfig, imagePullManager, buildContext, prepareBuildArgs(buildArgsFromExternalSources, imageConfig.getBuildConfiguration())); autoPullCacheFromImage(imageConfig, imagePullManager, buildContext); } - buildImage(imageConfig, buildContext.getMojoParameters(), ConfigHelper.isNoCache(imageConfig), checkForSquash(imageConfig), buildArgs, buildArchiveFile); + buildImage(imageConfig, buildContext.getMojoParameters(), ConfigHelper.isNoCache(imageConfig), checkForSquash(imageConfig), buildArgsFromExternalSources, buildArchiveFile); } /** @@ -206,8 +203,8 @@ public void tagImage(String imageName, String tag, String repo, CleanupMode clea } } - private Map prepareBuildArgs(Map buildArgs, BuildImageConfiguration buildConfig) { - ImmutableMap.Builder builder = ImmutableMap.builder().putAll(buildArgs); + static Map prepareBuildArgs(Map buildArgs, BuildImageConfiguration buildConfig) { + ImmutableMap.Builder builder = ImmutableMap.builder().putAll(Optional.ofNullable(buildArgs).orElse(Collections.emptyMap())); if (buildConfig.getArgs() != null) { builder.putAll(buildConfig.getArgs()); } @@ -296,67 +293,7 @@ private String doBuildImage(String imageName, File dockerArchive, BuildOptions o return queryService.getImageId(imageName); } - private Map addBuildArgs(BuildContext buildContext) { - Map buildArgsFromProject = addBuildArgsFromProperties(buildContext.getMojoParameters().getProject().getProperties()); - Map buildArgsFromSystem = addBuildArgsFromProperties(System.getProperties()); - Map buildArgsFromDockerConfig = addBuildArgsFromDockerConfig(); - - //merge build args from all the sources into one map. Different sources maps are allowed to contain duplicate keys between them - Map mergedBuildArgs = new HashMap<>(); - mergedBuildArgs.putAll(buildArgsFromDockerConfig); - mergedBuildArgs.putAll(buildContext.getBuildArgs() != null ? buildContext.getBuildArgs() : Collections.emptyMap()); - mergedBuildArgs.putAll(buildArgsFromProject); - mergedBuildArgs.putAll(buildArgsFromSystem); - - return ImmutableMap.copyOf(mergedBuildArgs); - } - - private Map addBuildArgsFromProperties(Properties properties) { - Map buildArgs = new HashMap<>(); - for (Object keyObj : properties.keySet()) { - String key = (String) keyObj; - if (key.startsWith(argPrefix)) { - String argKey = key.replaceFirst(argPrefix, ""); - String value = properties.getProperty(key); - if (!isEmpty(value)) { - buildArgs.put(argKey, value); - } - } - } - log.debug("Build args set %s", buildArgs); - return buildArgs; - } - - private Map addBuildArgsFromDockerConfig() { - JsonObject dockerConfig = DockerFileUtil.readDockerConfig(); - if (dockerConfig == null) { - return Collections.emptyMap(); - } - - // add proxies - Map buildArgs = new HashMap<>(); - if (dockerConfig.has("proxies")) { - JsonObject proxies = dockerConfig.getAsJsonObject("proxies"); - if (proxies.has("default")) { - JsonObject defaultProxyObj = proxies.getAsJsonObject("default"); - String[] proxyMapping = new String[]{ - "httpProxy", "http_proxy", - "httpsProxy", "https_proxy", - "noProxy", "no_proxy", - "ftpProxy", "ftp_proxy" - }; - - for (int index = 0; index < proxyMapping.length; index += 2) { - if (defaultProxyObj.has(proxyMapping[index])) { - buildArgs.put(proxyMapping[index + 1], defaultProxyObj.get(proxyMapping[index]).getAsString()); - } - } - } - } - log.debug("Build args set %s", buildArgs); - return buildArgs; - } private void autoPullBaseImage(ImageConfiguration imageConfig, ImagePullManager imagePullManager, BuildContext buildContext, Map buildArgs) throws DockerAccessException, MojoExecutionException { @@ -370,7 +307,7 @@ private void autoPullBaseImage(ImageConfiguration imageConfig, ImagePullManager List fromImages; if (buildConfig.isDockerFileMode()) { - fromImages = extractBaseFromDockerfile(buildConfig, buildContext, buildArgs); + fromImages = extractBaseFromDockerfile(buildConfig, buildContext.getMojoParameters(), buildArgs); } else { fromImages = new LinkedList<>(); String baseImage = extractBaseFromConfiguration(buildConfig); @@ -432,13 +369,20 @@ private String extractBaseFromConfiguration(BuildImageConfiguration buildConfig) return fromImage; } - private List extractBaseFromDockerfile(BuildImageConfiguration buildConfig, BuildContext buildContext, Map buildArgs) { + static List extractBaseFromDockerfile(BuildImageConfiguration buildConfig, MojoParameters mojoParameters, Map buildArgs) { + if (buildConfig.getDockerFile() == null || !buildConfig.getDockerFile().exists()) { + if (buildConfig.getFrom() != null && !buildConfig.getFrom().isEmpty()) { + return Collections.singletonList(buildConfig.getFrom()); + } + return Collections.emptyList(); + } + List fromImage; try { - File fullDockerFilePath = buildConfig.getAbsoluteDockerFilePath(buildContext.getMojoParameters()); + File fullDockerFilePath = buildConfig.getAbsoluteDockerFilePath(mojoParameters); fromImage = DockerFileUtil.extractBaseImages( fullDockerFilePath, - DockerFileUtil.createInterpolator(buildContext.getMojoParameters(), buildConfig.getFilter()), + DockerFileUtil.createInterpolator(mojoParameters, buildConfig.getFilter()), buildArgs); } catch (IOException e) { // Cant extract base image, so we wont try an auto pull. An error will occur later anyway when @@ -491,11 +435,6 @@ private boolean checkForSquash(ImageConfiguration imageConfig) { } } - private boolean isEmpty(String str) { - return str == null || str.isEmpty(); - } - - // =========================================== diff --git a/src/main/java/io/fabric8/maven/docker/service/RegistryService.java b/src/main/java/io/fabric8/maven/docker/service/RegistryService.java index efef542bc..fe3f8d19d 100644 --- a/src/main/java/io/fabric8/maven/docker/service/RegistryService.java +++ b/src/main/java/io/fabric8/maven/docker/service/RegistryService.java @@ -1,7 +1,5 @@ package io.fabric8.maven.docker.service; -import java.io.File; -import java.io.IOException; import java.io.Serializable; import java.util.Collection; import java.util.Collections; @@ -18,8 +16,8 @@ import io.fabric8.maven.docker.config.BuildImageConfiguration; import io.fabric8.maven.docker.config.ImageConfiguration; import io.fabric8.maven.docker.config.ImagePullPolicy; +import io.fabric8.maven.docker.service.helper.BuildArgResolver; import io.fabric8.maven.docker.util.AuthConfigFactory; -import io.fabric8.maven.docker.util.DockerFileUtil; import io.fabric8.maven.docker.util.EnvUtil; import io.fabric8.maven.docker.util.ImageName; import io.fabric8.maven.docker.util.Logger; @@ -30,6 +28,9 @@ import org.apache.maven.plugin.MojoExecutionException; import org.apache.maven.settings.Settings; +import static io.fabric8.maven.docker.service.BuildService.extractBaseFromDockerfile; +import static io.fabric8.maven.docker.service.BuildService.prepareBuildArgs; + /** * Allows to interact with registries, eg. to push/pull images. */ @@ -58,7 +59,7 @@ public class RegistryService { * @throws MojoExecutionException */ public void pushImages(ProjectPaths projectPaths, Collection imageConfigs, - int retries, RegistryConfig registryConfig, boolean skipTag, MojoParameters mojoParameters) throws DockerAccessException, MojoExecutionException { + int retries, RegistryConfig registryConfig, boolean skipTag, BuildService.BuildContext buildContext) throws DockerAccessException, MojoExecutionException { for (ImageConfiguration imageConfig : imageConfigs) { BuildImageConfiguration buildConfig = imageConfig.getBuildConfiguration(); if (buildConfig == null || buildConfig.skipPush()) { @@ -74,8 +75,10 @@ public void pushImages(ProjectPaths projectPaths, Collection imageConfig.getRegistry(), registryConfig.getRegistry()); + BuildArgResolver buildArgResolver = new BuildArgResolver(log); + Map buildArgsFromExternalSources = buildArgResolver.resolveBuildArgs(buildContext); AuthConfig authConfigForLegacyPush = createAuthConfig(true, imageName.getUser(), configuredRegistry, registryConfig); - AuthConfigList authConfigListForBuildXPush = createCompleteAuthConfigList(true, imageConfig, registryConfig, mojoParameters); + AuthConfigList authConfigListForBuildXPush = createCompleteAuthConfigList(true, imageConfig, registryConfig, buildContext.getMojoParameters(), buildArgsFromExternalSources); if (imageConfig.isBuildX()) { buildXService.push(projectPaths, imageConfig, configuredRegistry, authConfigListForBuildXPush); @@ -146,7 +149,7 @@ public void pullImageWithPolicy(String image, ImagePullManager pullManager, Regi } - public static AuthConfigList createCompleteAuthConfigList(boolean isPush, ImageConfiguration imageConfig, RegistryConfig registryConfig, MojoParameters mojoParameters) throws MojoExecutionException { + public static AuthConfigList createCompleteAuthConfigList(boolean isPush, ImageConfiguration imageConfig, RegistryConfig registryConfig, MojoParameters mojoParameters, Map buildArgsFromExternalSources) throws MojoExecutionException { ImageName imageName = new ImageName(imageConfig.getName()); String configuredRegistry = EnvUtil.firstRegistryOf( imageName.getRegistry(), @@ -154,7 +157,7 @@ public static AuthConfigList createCompleteAuthConfigList(boolean isPush, ImageC registryConfig.getRegistry()); AuthConfig authConfig = registryConfig.createAuthConfig(isPush, imageName.getUser(), configuredRegistry); - AuthConfigList authConfigList = createAuthConfigListForBaseImages(imageConfig.getBuildConfiguration(), mojoParameters, configuredRegistry, registryConfig); + AuthConfigList authConfigList = createAuthConfigListForBaseImages(imageConfig.getBuildConfiguration(), mojoParameters, configuredRegistry, registryConfig, buildArgsFromExternalSources); if (authConfig != null) { authConfigList.addAuthConfig(authConfig); } @@ -162,9 +165,9 @@ public static AuthConfigList createCompleteAuthConfigList(boolean isPush, ImageC return authConfigList; } - public static AuthConfigList createAuthConfigListForBaseImages(BuildImageConfiguration buildConfig, MojoParameters mojoParameters, String configuredRegistry, RegistryConfig registryConfig) throws MojoExecutionException { + public static AuthConfigList createAuthConfigListForBaseImages(BuildImageConfiguration buildConfig, MojoParameters mojoParameters, String configuredRegistry, RegistryConfig registryConfig, Map buildArgsFromExternalSources) throws MojoExecutionException { AuthConfigList authConfigList = new AuthConfigList(); - Set fromRegistries = getRegistriesForPull(buildConfig, mojoParameters); + Set fromRegistries = getRegistriesForPull(buildConfig, mojoParameters, buildArgsFromExternalSources); for (String fromRegistry : fromRegistries) { if (StringUtils.isNotBlank(configuredRegistry) && configuredRegistry.equalsIgnoreCase(fromRegistry)) { continue; @@ -180,9 +183,9 @@ public static AuthConfigList createAuthConfigListForBaseImages(BuildImageConfigu // ============================================================================================================ - private static Set getRegistriesForPull(BuildImageConfiguration buildConfig, MojoParameters mojoParameters) { + private static Set getRegistriesForPull(BuildImageConfiguration buildConfig, MojoParameters mojoParameters, Map buildArgsFromExternalSources) { Set registries = new HashSet<>(); - List fromImages = extractBaseFromDockerfile(buildConfig, mojoParameters); + List fromImages = extractBaseFromDockerfile(buildConfig, mojoParameters, prepareBuildArgs(buildArgsFromExternalSources, buildConfig)); for (String fromImage : fromImages) { ImageName imageName = new ImageName(fromImage); @@ -193,27 +196,6 @@ private static Set getRegistriesForPull(BuildImageConfiguration buildCon return registries; } - private static List extractBaseFromDockerfile(BuildImageConfiguration buildConfig, MojoParameters mojoParameters) { - if (buildConfig.getDockerFile() == null || !buildConfig.getDockerFile().exists()) { - if (buildConfig.getFrom() != null && !buildConfig.getFrom().isEmpty()) { - return Collections.singletonList(buildConfig.getFrom()); - } - return Collections.emptyList(); - } - - List fromImage; - try { - File fullDockerFilePath = buildConfig.getAbsoluteDockerFilePath(mojoParameters); - fromImage = DockerFileUtil.extractBaseImages( - fullDockerFilePath, - DockerFileUtil.createInterpolator(mojoParameters, buildConfig.getFilter()), - buildConfig.getArgs()); - } catch (IOException e) { - return Collections.emptyList(); - } - return fromImage; - } - private boolean imageRequiresPull(boolean hasImage, ImagePullPolicy pullPolicy, String imageName) throws MojoExecutionException { diff --git a/src/main/java/io/fabric8/maven/docker/service/helper/BuildArgResolver.java b/src/main/java/io/fabric8/maven/docker/service/helper/BuildArgResolver.java new file mode 100644 index 000000000..fdfde8284 --- /dev/null +++ b/src/main/java/io/fabric8/maven/docker/service/helper/BuildArgResolver.java @@ -0,0 +1,93 @@ +package io.fabric8.maven.docker.service.helper; + +import com.google.gson.JsonObject; +import io.fabric8.maven.docker.service.BuildService; +import io.fabric8.maven.docker.util.DockerFileUtil; +import io.fabric8.maven.docker.util.Logger; +import org.apache.commons.lang3.StringUtils; + +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; +import java.util.Properties; +import java.util.stream.Stream; + +public class BuildArgResolver { + private static final String ARG_PREFIX = "docker.buildArg."; + private final Logger log; + + public BuildArgResolver(Logger log) { + this.log = log; + } + + public Map resolveBuildArgs(BuildService.BuildContext buildContext) { + Map buildArgsFromProject = addBuildArgsFromProperties(buildContext.getMojoParameters().getProject().getProperties()); + Map buildArgsFromSystem = addBuildArgsFromProperties(System.getProperties()); + Map buildArgsFromDockerConfig = addBuildArgsFromDockerConfig(); + + //merge build args from all the sources into one map. Different sources maps are allowed to contain duplicate keys between them + return mergeBuildArgsFrom(buildArgsFromDockerConfig, + buildContext.getBuildArgs() != null ? buildContext.getBuildArgs() : Collections.emptyMap(), + buildArgsFromProject, + buildArgsFromSystem); + } + + private Map addBuildArgsFromProperties(Properties properties) { + Map buildArgs = new HashMap<>(); + for (Object keyObj : properties.keySet()) { + String key = (String) keyObj; + if (key.startsWith(ARG_PREFIX)) { + String argKey = key.replaceFirst(ARG_PREFIX, ""); + String value = properties.getProperty(key); + + if (StringUtils.isNotEmpty(value)) { + buildArgs.put(argKey, value); + } + } + } + log.debug("Build args set %s", buildArgs); + return buildArgs; + } + + private Map addBuildArgsFromDockerConfig() { + JsonObject dockerConfig = DockerFileUtil.readDockerConfig(); + if (dockerConfig == null) { + return Collections.emptyMap(); + } + + // add proxies + Map buildArgs = new HashMap<>(); + if (dockerConfig.has("proxies")) { + JsonObject proxies = dockerConfig.getAsJsonObject("proxies"); + if (proxies.has("default")) { + JsonObject defaultProxyObj = proxies.getAsJsonObject("default"); + String[] proxyMapping = new String[]{ + "httpProxy", "http_proxy", + "httpsProxy", "https_proxy", + "noProxy", "no_proxy", + "ftpProxy", "ftp_proxy" + }; + + for (int index = 0; index < proxyMapping.length; index += 2) { + if (defaultProxyObj.has(proxyMapping[index])) { + buildArgs.put(proxyMapping[index + 1], defaultProxyObj.get(proxyMapping[index]).getAsString()); + } + } + } + } + log.debug("Build args set %s", buildArgs); + return buildArgs; + } + + @SafeVarargs + private static Map mergeBuildArgsFrom(Map... buildArgSources) { + final Map buildArgs = new HashMap<>(); + Stream.of(buildArgSources) + .filter(Objects::nonNull) + .flatMap(map -> map.entrySet().stream()) + .forEach(entry -> buildArgs.put(entry.getKey(), entry.getValue())); + return buildArgs; + } + +} \ No newline at end of file diff --git a/src/main/java/io/fabric8/maven/docker/util/DockerFileUtil.java b/src/main/java/io/fabric8/maven/docker/util/DockerFileUtil.java index 25dae0ba3..fef69193e 100644 --- a/src/main/java/io/fabric8/maven/docker/util/DockerFileUtil.java +++ b/src/main/java/io/fabric8/maven/docker/util/DockerFileUtil.java @@ -220,7 +220,7 @@ private static Reader getFileReaderFromDir(File file) { } public static JsonObject readDockerConfig() { - String dockerConfig = System.getenv("DOCKER_CONFIG"); + String dockerConfig = EnvUtil.getEnv("DOCKER_CONFIG"); Reader reader = dockerConfig == null ? getFileReaderFromDir(new File(getHomeDir(),".docker/config.json")) diff --git a/src/main/java/io/fabric8/maven/docker/util/EnvUtil.java b/src/main/java/io/fabric8/maven/docker/util/EnvUtil.java index 5bf1b0602..4861f39b8 100644 --- a/src/main/java/io/fabric8/maven/docker/util/EnvUtil.java +++ b/src/main/java/io/fabric8/maven/docker/util/EnvUtil.java @@ -6,6 +6,7 @@ import java.nio.file.Paths; import java.util.*; import java.util.concurrent.TimeUnit; +import java.util.function.UnaryOperator; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -37,9 +38,37 @@ public class EnvUtil { public static final String DOCKER_HTTP_PORT = "2375"; public static final String PROPERTY_COMBINE_POLICY_SUFFIX = "_combine"; + private static UnaryOperator envGetter = System::getenv; + private static UnaryOperator propertyGetter = System::getProperty; + private EnvUtil() {} + /** + * Don't use in production code. Only for testing purposes. + * @param getter + */ + public static void overrideEnvGetter(UnaryOperator getter) { + envGetter = getter; + } + + public static void overridePropertyGetter(UnaryOperator propsGetter) { + propertyGetter = propsGetter; + } + + /** + * Return the value of the given environment variable or null if it is not set. + * @param variableName name of the environment variable. + * @return the value of the environment variable or null if it is not set. + */ + public static String getEnv(String variableName) { + return envGetter.apply(variableName); + } + + public static String getProperty(String propertyName) { + return propertyGetter.apply(propertyName); + } + // Convert docker host URL to an HTTP(s) URL public static String convertTcpToHttpUrl(String connect) { String protocol = connect.contains(":" + DOCKER_HTTP_PORT) ? "http:" : "https:"; @@ -397,7 +426,7 @@ public static String firstRegistryOf(String ... checkFirst) { } } // Check environment as last resort - return System.getenv("DOCKER_REGISTRY"); + return getEnv("DOCKER_REGISTRY"); } // sometimes registries might be specified with https? schema, sometimes not @@ -459,7 +488,7 @@ public static Date loadTimestamp(File tsFile) throws IOException { } public static boolean isWindows() { - return System.getProperty("os.name").toLowerCase().contains("windows"); + return getProperty("os.name").toLowerCase().contains("windows"); } public static boolean isMaven350OrLater(MavenSession mavenSession) { @@ -474,9 +503,9 @@ public static boolean isMaven350OrLater(MavenSession mavenSession) { * @return a String value for user's home directory */ public static String getUserHome() { - String homeDir = System.getenv("HOME"); + String homeDir = getEnv("HOME"); if (homeDir == null) { - homeDir = System.getProperty("user.home"); + homeDir = getProperty("user.home"); } return homeDir; } diff --git a/src/test/java/io/fabric8/maven/docker/PushMojoTest.java b/src/test/java/io/fabric8/maven/docker/PushMojoTest.java index 2036aa2a7..8c5120b0a 100644 --- a/src/test/java/io/fabric8/maven/docker/PushMojoTest.java +++ b/src/test/java/io/fabric8/maven/docker/PushMojoTest.java @@ -1,8 +1,8 @@ package io.fabric8.maven.docker; import io.fabric8.maven.docker.access.DockerAccessException; +import io.fabric8.maven.docker.service.BuildService; import io.fabric8.maven.docker.service.RegistryService; -import io.fabric8.maven.docker.util.MojoParameters; import io.fabric8.maven.docker.util.ProjectPaths; import org.apache.maven.plugin.MojoExecutionException; import org.junit.jupiter.api.Test; @@ -13,6 +13,8 @@ import java.io.IOException; +import static org.mockito.ArgumentMatchers.any; + @ExtendWith(MockitoExtension.class) class PushMojoTest extends MojoTestBase { @InjectMocks @@ -82,7 +84,7 @@ private void thenImageNotPushed() throws DockerAccessException, MojoExecutionExc private void verifyPush(int wantedNumberOfInvocations) throws DockerAccessException, MojoExecutionException { Mockito.verify(registryService, Mockito.times(wantedNumberOfInvocations)) - .pushImages(Mockito.any(ProjectPaths.class), Mockito.anyCollection(), Mockito.anyInt(), Mockito.any(RegistryService.RegistryConfig.class), Mockito.anyBoolean(), Mockito.any(MojoParameters.class)); + .pushImages(any(ProjectPaths.class), Mockito.anyCollection(), Mockito.anyInt(), any(RegistryService.RegistryConfig.class), Mockito.anyBoolean(), any(BuildService.BuildContext.class)); } private void whenMojoExecutes() throws IOException, MojoExecutionException { diff --git a/src/test/java/io/fabric8/maven/docker/service/BuildServiceTest.java b/src/test/java/io/fabric8/maven/docker/service/BuildServiceTest.java index 8e1a5cf66..3088f765d 100644 --- a/src/test/java/io/fabric8/maven/docker/service/BuildServiceTest.java +++ b/src/test/java/io/fabric8/maven/docker/service/BuildServiceTest.java @@ -333,33 +333,37 @@ void tagImage_whenForceFalseAndNoDanglingTags_thenImageRemoved() throws DockerAc @Test void testBuildArgsFromDifferentSources() throws MojoExecutionException, DockerAccessException { - //takes precedence over others - System.setProperty("docker.buildArg.http_proxy", "http://system-props.com"); - - Properties mavenProjectProps = new Properties(); - mavenProjectProps.setProperty("docker.buildArg.http_proxy", "http://project-props.com"); - Mockito.doReturn(mavenProjectProps).when(mavenProject).getProperties(); - Mockito.doReturn(mavenProject).when(mojoParameters).getProject(); - - BuildImageConfiguration buildConfig = new BuildImageConfiguration.Builder().build(); - - imageConfig = new ImageConfiguration.Builder() - .name("build-image") - .alias("build-alias") - .buildConfig(buildConfig) - .build(); - - Map buildArgs = new HashMap<>(); - buildArgs.put("http_proxy", "http://build-context.com"); - - final BuildService.BuildContext buildContext = new BuildService.BuildContext.Builder() - .mojoParameters(mojoParameters).buildArgs(buildArgs) - .build(); - - File buildArchive = buildService.buildArchive(imageConfig, buildContext, ""); - buildService.buildImage(imageConfig, null, buildContext, buildArchive); - Mockito.verify(docker).buildImage(Mockito.any(), Mockito.any(), - Mockito.argThat((BuildOptions options) -> options.getOptions().get("buildargs").equals("{\"http_proxy\":\"http://system-props.com\"}"))); + try { + //takes precedence over others + System.setProperty("docker.buildArg.http_proxy", "http://system-props.com"); + + Properties mavenProjectProps = new Properties(); + mavenProjectProps.setProperty("docker.buildArg.http_proxy", "http://project-props.com"); + Mockito.doReturn(mavenProjectProps).when(mavenProject).getProperties(); + Mockito.doReturn(mavenProject).when(mojoParameters).getProject(); + + BuildImageConfiguration buildConfig = new BuildImageConfiguration.Builder().build(); + + imageConfig = new ImageConfiguration.Builder() + .name("build-image") + .alias("build-alias") + .buildConfig(buildConfig) + .build(); + + Map buildArgs = new HashMap<>(); + buildArgs.put("http_proxy", "http://build-context.com"); + + final BuildService.BuildContext buildContext = new BuildService.BuildContext.Builder() + .mojoParameters(mojoParameters).buildArgs(buildArgs) + .build(); + + File buildArchive = buildService.buildArchive(imageConfig, buildContext, ""); + buildService.buildImage(imageConfig, null, buildContext, buildArchive); + Mockito.verify(docker).buildImage(Mockito.any(), Mockito.any(), + Mockito.argThat((BuildOptions options) -> options.getOptions().get("buildargs").equals("{\"http_proxy\":\"http://system-props.com\"}"))); + } finally { + System.clearProperty("docker.buildArg.http_proxy"); + } } private void givenAnImageConfiguration(String cleanup) { diff --git a/src/test/java/io/fabric8/maven/docker/service/RegistryServicePushImagesBuildXTest.java b/src/test/java/io/fabric8/maven/docker/service/RegistryServicePushImagesBuildXTest.java index 3060e6bf1..7d18995db 100644 --- a/src/test/java/io/fabric8/maven/docker/service/RegistryServicePushImagesBuildXTest.java +++ b/src/test/java/io/fabric8/maven/docker/service/RegistryServicePushImagesBuildXTest.java @@ -9,8 +9,10 @@ import io.fabric8.maven.docker.config.ImageConfiguration; import io.fabric8.maven.docker.util.AuthConfigFactory; import io.fabric8.maven.docker.util.Logger; +import io.fabric8.maven.docker.util.MojoParameters; import io.fabric8.maven.docker.util.ProjectPaths; import org.apache.maven.plugin.MojoExecutionException; +import org.apache.maven.project.MavenProject; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.io.TempDir; @@ -23,6 +25,7 @@ import java.util.Arrays; import java.util.Collections; import java.util.List; +import java.util.Properties; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.mockito.ArgumentMatchers.any; @@ -40,6 +43,9 @@ class RegistryServicePushImagesBuildXTest { private ProjectPaths projectPaths; private RegistryService.RegistryConfig registryConfig; private AuthConfigFactory authConfigFactory; + private BuildService.BuildContext buildContext; + private MojoParameters mojoParameters; + private MavenProject mavenProject; @TempDir private File temporaryFolder; @@ -48,7 +54,10 @@ class RegistryServicePushImagesBuildXTest { void setUp() { buildXService = mock(BuildXService.class); Logger logger = mock(Logger.class); + buildContext = mock(BuildService.BuildContext.class); authConfigFactory = mock(AuthConfigFactory.class); + mojoParameters = mock(MojoParameters.class); + mavenProject = mock(MavenProject.class); DockerAccess dockerAccess = mock(DockerAccess.class); QueryService queryService = new QueryService(dockerAccess); imageConfigurationList = Collections.singletonList(createNewImageConfiguration("user1/sample-image:latest", "foo/base:latest", null)); @@ -56,6 +65,10 @@ void setUp() { .registry("registry1.org") .authConfigFactory(authConfigFactory) .build(); + when(buildContext.getBuildArgs()).thenReturn(Collections.emptyMap()); + when(buildContext.getMojoParameters()).thenReturn(mojoParameters); + when(mojoParameters.getProject()).thenReturn(mavenProject); + when(mavenProject.getProperties()).thenReturn(new Properties()); projectPaths = new ProjectPaths(temporaryFolder, "target/docker"); registryService = new RegistryService(dockerAccess, queryService, buildXService, logger); } @@ -63,7 +76,7 @@ void setUp() { @Test void whenNoRegistryConfigured_thenAuthConfigEmpty() throws MojoExecutionException, DockerAccessException { // When - registryService.pushImages(projectPaths, imageConfigurationList, 0, registryConfig, false, null); + registryService.pushImages(projectPaths, imageConfigurationList, 0, registryConfig, false, buildContext); // Then verifyBuildXServiceInvokedWithAuthConfigListSize(0); @@ -75,7 +88,7 @@ void whenOnlyPushRegistryConfigured_thenAuthConfigHasSingleEntry() throws MojoEx givenAuthConfigExistsForRegistry("registry1.org", "user1", "password1"); // When - registryService.pushImages(projectPaths, imageConfigurationList, 0, registryConfig, false, null); + registryService.pushImages(projectPaths, imageConfigurationList, 0, registryConfig, false, buildContext); // Then verifyBuildXServiceInvokedWithAuthConfigListSize(1); @@ -89,7 +102,7 @@ void whenFromRegistryAndPushRegistryProvided_thenAuthConfigListContainsEntriesFo givenAuthConfigExistsForRegistry("registry2.org", "user2", "password2"); // When - registryService.pushImages(projectPaths, imageConfigurationList, 0, registryConfig, false, null); + registryService.pushImages(projectPaths, imageConfigurationList, 0, registryConfig, false, buildContext); // Then verifyBuildXServiceInvokedWithAuthConfigListSize(2); @@ -107,7 +120,7 @@ void whenDockerfileContainsFromReferencingMultipleRegistries_thenAuthConfigListC givenAuthConfigExistsForRegistry("registry3.org", "user3", "password2"); // When - registryService.pushImages(projectPaths, imageConfigurationList, 0, registryConfig, false, null); + registryService.pushImages(projectPaths, imageConfigurationList, 0, registryConfig, false, buildContext); // Then verifyBuildXServiceInvokedWithAuthConfigListSize(3); diff --git a/src/test/java/io/fabric8/maven/docker/service/RegistryServiceTest.java b/src/test/java/io/fabric8/maven/docker/service/RegistryServiceTest.java index 86cf2e168..fcbbba89d 100644 --- a/src/test/java/io/fabric8/maven/docker/service/RegistryServiceTest.java +++ b/src/test/java/io/fabric8/maven/docker/service/RegistryServiceTest.java @@ -13,10 +13,15 @@ import io.fabric8.maven.docker.util.AutoPullMode; import io.fabric8.maven.docker.util.ImageName; import io.fabric8.maven.docker.util.Logger; +import io.fabric8.maven.docker.util.MojoParameters; import io.fabric8.maven.docker.util.ProjectPaths; +import org.apache.maven.execution.MavenSession; import org.apache.maven.plugin.MojoExecutionException; +import org.apache.maven.project.MavenProject; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.junit.jupiter.api.io.TempDir; @@ -28,6 +33,9 @@ import org.mockito.junit.jupiter.MockitoExtension; import java.io.File; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; import java.nio.file.Path; import java.util.ArrayList; import java.util.Arrays; @@ -36,7 +44,9 @@ import java.util.List; import java.util.Map; import java.util.Objects; +import java.util.Properties; +import static org.mockito.Mockito.when; /** * @author roland @@ -56,7 +66,7 @@ class RegistryServiceTest { private AutoPullMode autoPullMode; private RegistryService registryService; private Map authConfig; - + private Properties properties; @TempDir private File projectBaseDir; @@ -79,6 +89,14 @@ class RegistryServiceTest { @Mock private BuildXService.Exec exec; + private MojoParameters mojoParameters; + @Mock + private MavenSession mockedMavenSession; + @Mock + private MavenProject mockedMavenProject; + + private BuildService.BuildContext buildContext; + private static String getOsDependentBuild(Path buildPath, String docker) { return buildPath.resolve(docker).toString().replace('/', File.separatorChar); } @@ -89,12 +107,14 @@ void setup() { registryService = new RegistryService(docker, queryService, buildXService, logger); cacheStore = new TestCacheStore(); authConfig = new HashMap<>(); + properties = new Properties(); imageName = null; imagePullPolicy = null; autoPullMode = null; registry = null; imageConfiguration = null; + mojoParameters = new MojoParameters(mockedMavenSession, mockedMavenProject, null, null, null, null, null, null, Collections.emptyList()); } @ParameterizedTest @@ -246,6 +266,7 @@ void tagForCustomRegistry() throws DockerAccessException { @Test void pushImage() throws DockerAccessException { + givenBuildContext(); givenAnImageConfiguration("user/test:1.0.1"); whenPushImage(); @@ -259,6 +280,7 @@ void pushImageWithRegistry() throws DockerAccessException { String registry = "myregistry.com"; givenAnImageConfiguration("user/test:1.0.1"); givenRegistry(registry); + givenBuildContext(); whenPushImage(); @@ -271,6 +293,7 @@ void pushImageWithRegistry() throws DockerAccessException { void pushImageWithImageRegistry() throws DockerAccessException { String registry = "myregistry.com"; givenAnImageConfiguration(registry + "/" + "user/test:1.0.1"); + givenBuildContext(); whenPushImage(); @@ -284,6 +307,7 @@ void pushImageWithImageConfigRegistry() throws DockerAccessException { String registry = "myregistry.com"; givenAnImageConfiguration("user/test:1.0.1"); andImageConfigRegistry(registry); + givenBuildContext(); whenPushImage(); @@ -293,113 +317,142 @@ void pushImageWithImageConfigRegistry() throws DockerAccessException { } @Test - void pushBuildXImage() throws MojoExecutionException { - givenBuildxImageConfiguration("user/test:1.0.1", null, null, null); + void pushImageWithDockerfileAndBuildArgs() throws MojoExecutionException, IOException { + properties.put("docker.buildArg.baseImage", "scratch"); + when(mockedMavenProject.getProperties()).thenReturn(properties); + Path dockerFile = projectBaseDir.toPath().resolve("DockerfileWithBuildArgs"); + Files.write(dockerFile, ("ARG baseImage\n" + + "FROM ${baseImage}").getBytes(StandardCharsets.UTF_8)); + givenImageNameAndBuildX("user/test:1.0.1", null, dockerFile.toString(), null); givenCredentials("skroob", "12345"); givenRegistry(registry); + givenBuildContext(); whenPushImage(); - thenBuildxImageHasBeenPushed(null, null, false, null); + thenImageHasBeenPushed(); thenNoExceptionThrown(); } @Test - void pushBuildXImageWithDockerfile() throws MojoExecutionException { - String dockerFile = projectBaseDir.toPath().resolve("src/docker/Dockerfile").toString(); - givenBuildxImageConfiguration("user/test:1.0.1", null, dockerFile, null); - givenCredentials("skroob", "12345"); - givenRegistry(registry); + void pushImageWithoutBuildConfig() throws DockerAccessException { + givenAnImageConfigurationWithoutBuildConfig("user/test:1.0.1"); whenPushImage(); - thenBuildxImageHasBeenPushed(null, "Dockerfile", false, null); + thenImageHasNotBeenPushed(); thenNoExceptionThrown(); } @Test - void pushBuildXImageProvidedBuilder() throws MojoExecutionException { - givenBuildxImageConfiguration("user/test:1.0.1", "provided-builder", null, null); - givenCredentials("King_Roland_of_Druidia", "12345"); - givenRegistry(registry); + void pushImageSkipped() throws DockerAccessException { + givenAnImageConfiguration("user/test:1.0.1"); + givenPushSkipped(true); whenPushImage(); - thenBuildxImageHasBeenPushed("provided-builder", null, false, null); + thenImageHasNotBeenPushed(); thenNoExceptionThrown(); } - @Test - void pushBuildXImageTag() throws MojoExecutionException { - givenBuildxImageConfiguration("user/test:1.0.1", null, null, "perri-air"); - givenCredentials("King_Roland_of_Druidia", "12345"); - givenRegistry(registry); + @Nested + @DisplayName("buildx") + class BuildX { + @Test + void pushImage() throws MojoExecutionException { + givenBuildxImageConfiguration("user/test:1.0.1", null, null, null); + givenCredentials("skroob", "12345"); + givenRegistry(registry); + givenBuildContext(); - whenPushImage(); + whenPushImage(); - thenBuildxImageHasBeenPushed(null, null, true, null); - thenNoExceptionThrown(); - } + thenBuildxImageHasBeenPushed(null, null, false, null); + thenNoExceptionThrown(); + } - @Test - void pushBuildXImageWithRegistry() throws MojoExecutionException { - String registry = "myregistry.com"; - givenBuildxImageConfiguration("user/test:1.0.1", null, null, "perri-air"); - givenCredentials("King_Roland_of_Druidia", "12345"); - givenRegistry(registry); + @Test + void pushImageWithDockerfile() throws MojoExecutionException { + String dockerFile = projectBaseDir.toPath().resolve("src/docker/Dockerfile").toString(); + givenBuildxImageConfiguration("user/test:1.0.1", null, dockerFile, null); + givenCredentials("skroob", "12345"); + givenRegistry(registry); + givenBuildContext(); - whenPushImage(); + whenPushImage(); - thenBuildxImageHasBeenPushed(null, null, true, registry); - thenNoExceptionThrown(); - } + thenBuildxImageHasBeenPushed(null, "Dockerfile", false, null); + thenNoExceptionThrown(); + } - @Test - void pushBuildXImageWithImageRegistry() throws MojoExecutionException { - String registry = "myregistry.com"; - givenBuildxImageConfiguration(registry + "/" + "user/test:1.0.1", null, null, "perri-air"); - givenCredentials("King_Roland_of_Druidia", "12345"); - givenRegistry(registry); + @Test + void pushImageProvidedBuilder() throws MojoExecutionException { + givenBuildxImageConfiguration("user/test:1.0.1", "provided-builder", null, null); + givenCredentials("King_Roland_of_Druidia", "12345"); + givenRegistry(registry); + givenBuildContext(); - whenPushImage(); + whenPushImage(); - thenBuildxImageHasBeenPushed(null, null, true, registry); - thenNoExceptionThrown(); - } + thenBuildxImageHasBeenPushed("provided-builder", null, false, null); + thenNoExceptionThrown(); + } - @Test - void pushBuildXImageWithImageConfigRegistry() throws MojoExecutionException { - String registry = "myregistry.com"; - givenBuildxImageConfiguration("user/test:1.0.1", null, null, "perri-air"); - andImageConfigRegistry(registry); - givenCredentials("King_Roland_of_Druidia", "12345"); - givenRegistry(registry); + @Test + void pushImageTag() throws MojoExecutionException { + givenBuildxImageConfiguration("user/test:1.0.1", null, null, "perri-air"); + givenCredentials("King_Roland_of_Druidia", "12345"); + givenRegistry(registry); + givenBuildContext(); - whenPushImage(); + whenPushImage(); - thenBuildxImageHasBeenPushed(null, null, true, registry); - thenNoExceptionThrown(); - } + thenBuildxImageHasBeenPushed(null, null, true, null); + thenNoExceptionThrown(); + } - @Test - void pushImageWithoutBuildConfig() throws DockerAccessException { - givenAnImageConfigurationWithoutBuildConfig("user/test:1.0.1"); + @Test + void pushImageWithRegistry() throws MojoExecutionException { + String registry = "myregistry.com"; + givenBuildxImageConfiguration("user/test:1.0.1", null, null, "perri-air"); + givenCredentials("King_Roland_of_Druidia", "12345"); + givenRegistry(registry); + givenBuildContext(); - whenPushImage(); + whenPushImage(); - thenImageHasNotBeenPushed(); - thenNoExceptionThrown(); - } + thenBuildxImageHasBeenPushed(null, null, true, registry); + thenNoExceptionThrown(); + } - @Test - void pushImageSkipped() throws DockerAccessException { - givenAnImageConfiguration("user/test:1.0.1"); - givenPushSkipped(true); + @Test + void pushImageWithImageRegistry() throws MojoExecutionException { + String registry = "myregistry.com"; + givenBuildxImageConfiguration(registry + "/" + "user/test:1.0.1", null, null, "perri-air"); + givenCredentials("King_Roland_of_Druidia", "12345"); + givenRegistry(registry); + givenBuildContext(); - whenPushImage(); + whenPushImage(); - thenImageHasNotBeenPushed(); - thenNoExceptionThrown(); + thenBuildxImageHasBeenPushed(null, null, true, registry); + thenNoExceptionThrown(); + } + + @Test + void pushImageWithImageConfigRegistry() throws MojoExecutionException { + String registry = "myregistry.com"; + givenBuildxImageConfiguration("user/test:1.0.1", null, null, "perri-air"); + andImageConfigRegistry(registry); + givenCredentials("King_Roland_of_Druidia", "12345"); + givenRegistry(registry); + givenBuildContext(); + + whenPushImage(); + + thenBuildxImageHasBeenPushed(null, null, true, registry); + thenNoExceptionThrown(); + } } // ==================================================================================================== @@ -473,6 +526,14 @@ private void thenImageHasBeenPulledWithRegistry(final String registry) throws Do Assertions.assertNotNull(cacheStore.get(imageName)); } + private void givenBuildContext() { + when(mockedMavenProject.getProperties()).thenReturn(properties); + buildContext = new BuildService.BuildContext.Builder() + .mojoParameters(mojoParameters) + .buildArgs(Collections.emptyMap()) + .build(); + } + private void whenAutoPullImage() { try { @@ -503,7 +564,7 @@ private void whenPushImage() { .authConfig(authConfig) .registry(registry) .build(); - registryService.pushImages(projectPaths, Collections.singleton(imageConfiguration), 1, registryConfig, false, null); + registryService.pushImages(projectPaths, Collections.singleton(imageConfiguration), 1, registryConfig, false, buildContext); } catch (Exception e) { this.actualException = e; } diff --git a/src/test/java/io/fabric8/maven/docker/service/helper/BuildArgResolverTest.java b/src/test/java/io/fabric8/maven/docker/service/helper/BuildArgResolverTest.java new file mode 100644 index 000000000..618993e52 --- /dev/null +++ b/src/test/java/io/fabric8/maven/docker/service/helper/BuildArgResolverTest.java @@ -0,0 +1,115 @@ +package io.fabric8.maven.docker.service.helper; + +import io.fabric8.maven.docker.service.BuildService; +import io.fabric8.maven.docker.util.EnvUtil; +import io.fabric8.maven.docker.util.Logger; +import io.fabric8.maven.docker.util.MojoParameters; +import org.apache.maven.project.MavenProject; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Collections; +import java.util.Map; +import java.util.Properties; + +import static org.assertj.core.api.AssertionsForInterfaceTypes.assertThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +class BuildArgResolverTest { + private Properties projectProperties; + private BuildArgResolver buildArgResolver; + private BuildService.BuildContext buildContext; + + @BeforeEach + void setUp() { + projectProperties = new Properties(); + Logger log = mock(Logger.class); + MojoParameters mojoParameters = mock(MojoParameters.class); + MavenProject mavenProject = mock(MavenProject.class); + buildContext = new BuildService.BuildContext.Builder() + .mojoParameters(mojoParameters) + .build(); + when(mavenProject.getProperties()).thenReturn(projectProperties); + when(mojoParameters.getProject()).thenReturn(mavenProject); + buildArgResolver = new BuildArgResolver(log); + } + + @Test + @DisplayName("build args in project properties") + void whenBuildArgsFromProjectProperties_shouldMergeBuildArgs() { + // Given + projectProperties.setProperty("docker.buildArg.VERSION", "latest"); + projectProperties.setProperty("docker.buildArg.FULL_IMAGE", "busybox:latest"); + + // When + Map mergedBuildArgs = buildArgResolver.resolveBuildArgs(buildContext); + + // Then + assertThat(mergedBuildArgs) + .containsEntry("VERSION", "latest") + .containsEntry("FULL_IMAGE", "busybox:latest"); + } + + @Test + @DisplayName("build args in project properties, system properties") + void fromAllSourcesWithDifferentKeys_shouldMergeBuildArgs() { + // Given + System.setProperty("docker.buildArg.IMAGE-1", "openjdk"); + projectProperties.setProperty("docker.buildArg.REPO_1", "docker.io/library"); + + // When + Map mergedBuildArgs = buildArgResolver.resolveBuildArgs(buildContext); + + // Then + assertThat(mergedBuildArgs) + .containsEntry("REPO_1", "docker.io/library") + .containsEntry("IMAGE-1", "openjdk"); + } + + @Nested + @DisplayName("local ~/.docker/config.json contains proxy settings") + class LocalDockerConfigContainsProxySettings { + @BeforeEach + void setUp() throws IOException { + Path dockerConfig = Files.createTempDirectory("docker-config"); + dockerConfig.toFile().deleteOnExit(); + final Map env = Collections.singletonMap("DOCKER_CONFIG", dockerConfig.toFile().getAbsolutePath()); + EnvUtil.overrideEnvGetter(env::get); + Files.write(dockerConfig.resolve("config.json"), (String.format("{\"proxies\": {\"default\": {%n" + + " \"httpProxy\": \"http://proxy.example.com:3128\",%n" + + " \"httpsProxy\": \"https://proxy.example.com:3129\",%n" + + " \"noProxy\": \"*.test.example.com,.example.org,127.0.0.0/8\"%n" + + " }}}")).getBytes()); + } + + @Test + @DisplayName("mergeBuildArgsIncludingLocalDockerConfigProxySettings, should add proxy build args for docker build strategy") + void shouldAddBuildArgsFromDockerConfigInDockerBuild() { + // When + final Map mergedBuildArgs = buildArgResolver.resolveBuildArgs(buildContext); + // Then + assertThat(mergedBuildArgs) + .containsEntry("http_proxy", "http://proxy.example.com:3128") + .containsEntry("https_proxy", "https://proxy.example.com:3129") + .containsEntry("no_proxy", "*.test.example.com,.example.org,127.0.0.0/8"); + } + + @AfterEach + void tearDown() { + EnvUtil.overrideEnvGetter(System::getenv); + } + } + + @AfterEach + void clearSystemPropertiesUsedInTests() { + System.clearProperty("docker.buildArg.IMAGE-1"); + System.clearProperty("docker.buildArg.VERSION"); + } +}