Skip to content

Commit

Permalink
refactor (jkube-kit/build) : Move buildArgs related logic to a new cl…
Browse files Browse the repository at this point in the history
…ass BuildArgResolverUtil

Related to #2904

It's better to move code for resolving build args from different sources to a dedicated class.
It doesn't look like BuildService is the right place for this. This way
we can also use it from other BuildServices of other build strategies.

Signed-off-by: Rohan Kumar <rohaan@redhat.com>
  • Loading branch information
rohanKanojia committed Apr 30, 2024
1 parent dbd1067 commit 06017bf
Show file tree
Hide file tree
Showing 4 changed files with 299 additions and 197 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
/*
* Copyright (c) 2019 Red Hat, Inc.
* This program and the accompanying materials are made
* available under the terms of the Eclipse Public License 2.0
* which is available at:
*
* https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* Red Hat, Inc. - initial API and implementation
*/
package org.eclipse.jkube.kit.build.api.helper;

import com.google.common.collect.ImmutableMap;
import org.apache.commons.lang3.StringUtils;
import org.eclipse.jkube.kit.common.JKubeConfiguration;
import org.eclipse.jkube.kit.config.image.ImageConfiguration;

import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
import java.util.Properties;

public class BuildArgResolverUtil {
private static final String ARG_PREFIX = "docker.buildArg.";

private BuildArgResolverUtil() { }

/**
* Merges Docker Build Args in the following order (in decreasing order of precedence):
* <ul>
* <li>Build Args specified directly in ImageConfiguration</li>
* <li>Build Args specified via System Properties</li>
* <li>Build Args specified via Project Properties</li>
* <li>Build Args specified via Plugin configuration</li>
* <li>Docker Proxy Build Args detected from ~/.docker/config.json</li>
* </ul>
* @param imageConfig ImageConfiguration for which Build Args would be resolved
* @param configuration {@link JKubeConfiguration} JKubeConfiguration
* @return a Map containing merged Build Args from all sources.
*/
public static Map<String, String> mergeBuildArgs(ImageConfiguration imageConfig, JKubeConfiguration configuration) {
Map<String, String> buildArgsFromProjectProperties = addBuildArgsFromProperties(configuration.getProject().getProperties());
Map<String, String> buildArgsFromSystemProperties = addBuildArgsFromProperties(System.getProperties());
Map<String, String> buildArgsFromDockerConfig = addBuildArgsFromDockerConfig();

Map<String, String> resolvedBuildArgs = ImmutableMap.<String, String>builder()
.putAll(buildArgsFromDockerConfig)
.putAll(Optional.ofNullable(configuration.getBuildArgs()).orElse(Collections.emptyMap()))
.putAll(buildArgsFromProjectProperties)
.putAll(buildArgsFromSystemProperties)
.build();

if (imageConfig.getBuild().getArgs() != null) {
return ImmutableMap.<String, String>builder()
.putAll(resolvedBuildArgs)
.putAll(imageConfig.getBuild().getArgs())
.build();
}
return resolvedBuildArgs;
}

private static Map<String, String> addBuildArgsFromProperties(Properties properties) {
Map<String, String> 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.isNotBlank(value)) {
buildArgs.put(argKey, value);
}
}
}
return buildArgs;
}

private static Map<String, String> addBuildArgsFromDockerConfig() {
final Map<String, Object> dockerConfig = DockerFileUtil.readDockerConfig();
if (dockerConfig == null) {
return Collections.emptyMap();
}

// add proxies
Map<String, String> buildArgs = new HashMap<>();
if (dockerConfig.containsKey("proxies")) {
final Map<String, Object> proxies = (Map<String, Object>) dockerConfig.get("proxies");
if (proxies.containsKey("default")) {
final Map<String, String> defaultProxyObj = (Map<String, String>) proxies.get("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.containsKey(proxyMapping[index])) {
buildArgs.put(ARG_PREFIX + proxyMapping[index+1], defaultProxyObj.get(proxyMapping[index]));
}
}
}
}
return buildArgs;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,188 @@
/*
* Copyright (c) 2019 Red Hat, Inc.
* This program and the accompanying materials are made
* available under the terms of the Eclipse Public License 2.0
* which is available at:
*
* https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* Red Hat, Inc. - initial API and implementation
*/
package org.eclipse.jkube.kit.build.api.helper;

import org.eclipse.jkube.kit.common.JKubeConfiguration;
import org.eclipse.jkube.kit.common.JavaProject;
import org.eclipse.jkube.kit.common.util.EnvUtil;
import org.eclipse.jkube.kit.config.image.ImageConfiguration;
import org.eclipse.jkube.kit.config.image.build.BuildConfiguration;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir;

import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;

import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;

class BuildArgResolverUtilMergeBuildArgsTest {
private ImageConfiguration imageConfiguration;
private JKubeConfiguration jKubeConfiguration;
private Properties projectProperties;
private Map<String, String> buildArgFromPluginConfiguration;

@BeforeEach
void setUp() {
projectProperties = new Properties();
buildArgFromPluginConfiguration = new HashMap<>();
jKubeConfiguration = JKubeConfiguration.builder()
.project(JavaProject.builder()
.properties(projectProperties)
.build())
.buildArgs(buildArgFromPluginConfiguration)
.build();
imageConfiguration = ImageConfiguration.builder()
.name("image-name")
.build(BuildConfiguration.builder()
.build())
.build();
}

@Test
@DisplayName("build args in image config and project properties")
void whenBuildArgsFromImageConfigAndFromProjectProperties_shouldMergeBuildArgs() {
// Given
projectProperties.setProperty("docker.buildArg.VERSION", "latest");
projectProperties.setProperty("docker.buildArg.FULL_IMAGE", "busybox:latest");
Map<String, String> buildArgImageConfiguration = new HashMap<>();
buildArgImageConfiguration.put("REPO_1", "docker.io/library");
buildArgImageConfiguration.put("IMAGE-1", "openjdk");
imageConfiguration = imageConfiguration.toBuilder()
.build(imageConfiguration.getBuild().toBuilder().args(buildArgImageConfiguration).build())
.build();

// When
Map<String, String> mergedBuildArgs = BuildArgResolverUtil.mergeBuildArgs(imageConfiguration, jKubeConfiguration);

// Then
assertThat(mergedBuildArgs)
.containsEntry("VERSION", "latest")
.containsEntry("FULL_IMAGE", "busybox:latest")
.containsEntry("REPO_1", "docker.io/library")
.containsEntry("IMAGE-1", "openjdk");
}

@Test
@DisplayName("build args in image config, project properties, system properties, plugin configuration")
void fromAllSourcesWithDifferentKeys_shouldMergeBuildArgs() {
// Given
givenBuildArgsFromImageConfiguration("VERSION", "latest");
System.setProperty("docker.buildArg.IMAGE-1", "openjdk");
projectProperties.setProperty("docker.buildArg.REPO_1", "docker.io/library");
givenBuildArgsFromJKubeConfiguration("FULL_IMAGE", "busybox:latest");

// When
Map<String, String> mergedBuildArgs = BuildArgResolverUtil.mergeBuildArgs(imageConfiguration, jKubeConfiguration);

// Then
assertThat(mergedBuildArgs)
.containsEntry("VERSION", "latest")
.containsEntry("FULL_IMAGE", "busybox:latest")
.containsEntry("REPO_1", "docker.io/library")
.containsEntry("IMAGE-1", "openjdk");
}

@Test
@DisplayName("build args in image config and system properties with same key, should throw exception")
void fromBuildConfigurationAndSystemPropertiesWithSameKey_shouldNotMergeBuildArgs() {
// Given
givenBuildArgsFromImageConfiguration("VERSION", "latest");
System.setProperty("docker.buildArg.VERSION", "1.0.0");

// When & Then
assertThatIllegalArgumentException()
.isThrownBy(() -> BuildArgResolverUtil.mergeBuildArgs(imageConfiguration, jKubeConfiguration))
.withMessage("Multiple entries with same key: VERSION=latest and VERSION=1.0.0");
}

@Test
@DisplayName("build args in image config and project properties with same key, should throw exception")
void fromBuildConfigurationAndProjectPropertiesWithSameKey_shouldNotMergeBuildArgs() {
// Given
givenBuildArgsFromImageConfiguration("VERSION", "latest");
projectProperties.setProperty("docker.buildArg.VERSION", "1.0.0");

// When & Then
assertThatIllegalArgumentException()
.isThrownBy(() -> BuildArgResolverUtil.mergeBuildArgs(imageConfiguration, jKubeConfiguration))
.withMessage("Multiple entries with same key: VERSION=latest and VERSION=1.0.0");
}

@Test
@DisplayName("build args in image config and plugin config with same key, should throw exception")
void fromBuildConfigurationAndJKubeConfigurationWithSameKey_shouldNotMergeBuildArgs() {
// Given
givenBuildArgsFromImageConfiguration("VERSION", "latest");
givenBuildArgsFromJKubeConfiguration("VERSION", "1.0.0");

// When & Then
assertThatIllegalArgumentException()
.isThrownBy(() -> BuildArgResolverUtil.mergeBuildArgs(imageConfiguration, jKubeConfiguration))
.withMessage("Multiple entries with same key: VERSION=latest and VERSION=1.0.0");
}

@Test
@DisplayName("should add proxy build args from ~/.docker/config.json")
void shouldAddBuildArgsFromDockerConfig(@TempDir File temporaryFolder) throws IOException {
try {
// Given
Path dockerConfig = temporaryFolder.toPath();
final Map<String, String> env = Collections.singletonMap("DOCKER_CONFIG", dockerConfig.toFile().getAbsolutePath());
EnvUtil.overrideEnvGetter(env::get);
Files.write(dockerConfig.resolve("config.json"), ("{\"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());
// When
final Map<String, String> mergedBuildArgs = BuildArgResolverUtil.mergeBuildArgs(imageConfiguration, jKubeConfiguration);
// Then
assertThat(mergedBuildArgs)
.containsEntry("docker.buildArg.http_proxy", "http://proxy.example.com:3128")
.containsEntry("docker.buildArg.https_proxy", "https://proxy.example.com:3129")
.containsEntry("docker.buildArg.no_proxy", "*.test.example.com,.example.org,127.0.0.0/8");
} finally {
EnvUtil.overrideEnvGetter(System::getenv);
}
}

private void givenBuildArgsFromImageConfiguration(String key, String value) {
imageConfiguration = imageConfiguration.toBuilder()
.build(BuildConfiguration.builder()
.args(
Collections.singletonMap(key, value))
.build())
.build();
}

private void givenBuildArgsFromJKubeConfiguration(String key, String value) {
buildArgFromPluginConfiguration.put(key, value);
}

@AfterEach
void clearSystemPropertiesUsedInTests() {
System.clearProperty("docker.buildArg.IMAGE-1");
System.clearProperty("docker.buildArg.VERSION");
}
}
Loading

0 comments on commit 06017bf

Please sign in to comment.