diff --git a/jkube-kit/build/api/src/main/java/org/eclipse/jkube/kit/build/api/config/handler/property/PropertyConfigHandler.java b/jkube-kit/build/api/src/main/java/org/eclipse/jkube/kit/build/api/config/handler/property/PropertyConfigHandler.java deleted file mode 100644 index 9fad0b1dca..0000000000 --- a/jkube-kit/build/api/src/main/java/org/eclipse/jkube/kit/build/api/config/handler/property/PropertyConfigHandler.java +++ /dev/null @@ -1,291 +0,0 @@ -/* - * 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.config.handler.property; - -import org.eclipse.jkube.kit.config.image.ImageConfiguration; -import org.eclipse.jkube.kit.config.image.WatchImageConfiguration; -import org.eclipse.jkube.kit.build.api.helper.ExternalConfigHandler; -import org.eclipse.jkube.kit.common.JavaProject; -import org.eclipse.jkube.kit.common.util.EnvUtil; -import org.eclipse.jkube.kit.common.util.JKubeProjectUtil; -import org.eclipse.jkube.kit.common.util.MapUtil; -import org.eclipse.jkube.kit.common.Arguments; -import org.eclipse.jkube.kit.common.AssemblyConfiguration; -import org.eclipse.jkube.kit.config.image.build.BuildConfiguration; -import org.eclipse.jkube.kit.config.image.build.HealthCheckConfiguration; - -import java.io.File; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.List; -import java.util.Map; -import java.util.Optional; -import java.util.Properties; -import java.util.function.Function; -import java.util.function.Supplier; -import java.util.stream.Collectors; -import java.util.stream.Stream; - -import static org.eclipse.jkube.kit.build.api.config.handler.property.ConfigKey.ALIAS; -import static org.eclipse.jkube.kit.build.api.config.handler.property.ConfigKey.ARGS; -import static org.eclipse.jkube.kit.build.api.config.handler.property.ConfigKey.ASSEMBLY_BASEDIR; -import static org.eclipse.jkube.kit.build.api.config.handler.property.ConfigKey.ASSEMBLY_EXPORT_TARGET_DIR; -import static org.eclipse.jkube.kit.build.api.config.handler.property.ConfigKey.ASSEMBLY_MODE; -import static org.eclipse.jkube.kit.build.api.config.handler.property.ConfigKey.ASSEMBLY_PERMISSIONS; -import static org.eclipse.jkube.kit.build.api.config.handler.property.ConfigKey.ASSEMBLY_TARLONGFILEMODE; -import static org.eclipse.jkube.kit.build.api.config.handler.property.ConfigKey.ASSEMBLY_USER; -import static org.eclipse.jkube.kit.build.api.config.handler.property.ConfigKey.BUILD_OPTIONS; -import static org.eclipse.jkube.kit.build.api.config.handler.property.ConfigKey.CACHEFROM; -import static org.eclipse.jkube.kit.build.api.config.handler.property.ConfigKey.CLEANUP; -import static org.eclipse.jkube.kit.build.api.config.handler.property.ConfigKey.CMD; -import static org.eclipse.jkube.kit.build.api.config.handler.property.ConfigKey.CONTEXT_DIR; -import static org.eclipse.jkube.kit.build.api.config.handler.property.ConfigKey.DOCKER_ARCHIVE; -import static org.eclipse.jkube.kit.build.api.config.handler.property.ConfigKey.DOCKER_FILE; -import static org.eclipse.jkube.kit.build.api.config.handler.property.ConfigKey.ENTRYPOINT; -import static org.eclipse.jkube.kit.build.api.config.handler.property.ConfigKey.ENV; -import static org.eclipse.jkube.kit.build.api.config.handler.property.ConfigKey.ENV_BUILD; -import static org.eclipse.jkube.kit.build.api.config.handler.property.ConfigKey.FILTER; -import static org.eclipse.jkube.kit.build.api.config.handler.property.ConfigKey.FROM; -import static org.eclipse.jkube.kit.build.api.config.handler.property.ConfigKey.FROM_EXT; -import static org.eclipse.jkube.kit.build.api.config.handler.property.ConfigKey.HEALTHCHECK; -import static org.eclipse.jkube.kit.build.api.config.handler.property.ConfigKey.HEALTHCHECK_CMD; -import static org.eclipse.jkube.kit.build.api.config.handler.property.ConfigKey.HEALTHCHECK_INTERVAL; -import static org.eclipse.jkube.kit.build.api.config.handler.property.ConfigKey.HEALTHCHECK_MODE; -import static org.eclipse.jkube.kit.build.api.config.handler.property.ConfigKey.HEALTHCHECK_RETRIES; -import static org.eclipse.jkube.kit.build.api.config.handler.property.ConfigKey.HEALTHCHECK_START_PERIOD; -import static org.eclipse.jkube.kit.build.api.config.handler.property.ConfigKey.HEALTHCHECK_TIMEOUT; -import static org.eclipse.jkube.kit.build.api.config.handler.property.ConfigKey.IMAGE_PULL_POLICY_BUILD; -import static org.eclipse.jkube.kit.build.api.config.handler.property.ConfigKey.LABELS; -import static org.eclipse.jkube.kit.build.api.config.handler.property.ConfigKey.MAINTAINER; -import static org.eclipse.jkube.kit.build.api.config.handler.property.ConfigKey.NAME; -import static org.eclipse.jkube.kit.build.api.config.handler.property.ConfigKey.NOCACHE; -import static org.eclipse.jkube.kit.build.api.config.handler.property.ConfigKey.OPTIMISE; -import static org.eclipse.jkube.kit.build.api.config.handler.property.ConfigKey.PORTS; -import static org.eclipse.jkube.kit.build.api.config.handler.property.ConfigKey.RUN; -import static org.eclipse.jkube.kit.build.api.config.handler.property.ConfigKey.SHELL; -import static org.eclipse.jkube.kit.build.api.config.handler.property.ConfigKey.SKIP_BUILD; -import static org.eclipse.jkube.kit.build.api.config.handler.property.ConfigKey.TAGS; -import static org.eclipse.jkube.kit.build.api.config.handler.property.ConfigKey.USER; -import static org.eclipse.jkube.kit.build.api.config.handler.property.ConfigKey.VOLUMES; -import static org.eclipse.jkube.kit.build.api.config.handler.property.ConfigKey.WATCH_INTERVAL; -import static org.eclipse.jkube.kit.build.api.config.handler.property.ConfigKey.WATCH_POSTEXEC; -import static org.eclipse.jkube.kit.build.api.config.handler.property.ConfigKey.WATCH_POSTGOAL; -import static org.eclipse.jkube.kit.build.api.config.handler.property.ConfigKey.WORKDIR; - -/** - * @author roland - */ -public class PropertyConfigHandler implements ExternalConfigHandler { - - public static final String TYPE_NAME = "properties"; - public static final String DEFAULT_PREFIX = "docker"; - - @Override - public String getType() { - return TYPE_NAME; - } - - @Override - public List resolve(ImageConfiguration fromConfig, JavaProject project) { - Map externalConfig = fromConfig.getExternalConfig(); - String prefix = getPrefix(externalConfig); - Properties properties = JKubeProjectUtil.getPropertiesWithSystemOverrides(project); - PropertyMode propertyMode = getMode(externalConfig); - ValueProvider valueProvider = new ValueProvider(prefix, properties, propertyMode); - - BuildConfiguration build = extractBuildConfiguration(fromConfig, valueProvider, project); - WatchImageConfiguration watch = extractWatchConfig(fromConfig, valueProvider); - String name = valueProvider.getString(NAME, fromConfig.getName()); - String alias = valueProvider.getString(ALIAS, fromConfig.getAlias()); - - if (name == null) { - throw new IllegalArgumentException(String.format("Mandatory property [%s] is not defined", NAME)); - } - - return Collections.singletonList(ImageConfiguration.builder() - .name(name) - .alias(alias) - .build(build) - .watch(watch) - .build()); - } - - private static boolean isStringValueNull(ValueProvider valueProvider, BuildConfiguration config, ConfigKey key, Supplier supplier) { - return valueProvider.getString(key, config == null ? null : supplier.get()) != null; - } - - // Enable build config only when a `.from.`, or `.dockerFile.` is configured - private boolean buildConfigured(BuildConfiguration config, ValueProvider valueProvider, JavaProject project) { - - if (isStringValueNull(valueProvider, config, FROM, config::getFrom)) { - return true; - } - - if (valueProvider.getMap(FROM_EXT, config == null ? null : config.getFromExt()) != null) { - return true; - } - if (isStringValueNull(valueProvider, config, DOCKER_FILE, config::getDockerFileRaw)) { - return true; - } - if (isStringValueNull(valueProvider, config, DOCKER_ARCHIVE, config::getDockerArchiveRaw)) { - return true; - } - - if (isStringValueNull(valueProvider, config, CONTEXT_DIR, config::getContextDirRaw)) { - return true; - } - - // Simple Dockerfile mode - return new File(project.getBaseDirectory(),"Dockerfile").exists(); - } - - private static R valueOrNull(T input, Function function) { - return Optional.ofNullable(input).map(function).orElse(null); - } - - private BuildConfiguration extractBuildConfiguration(ImageConfiguration fromConfig, ValueProvider valueProvider, JavaProject project) { - BuildConfiguration config = fromConfig.getBuildConfiguration(); - if (!buildConfigured(config, valueProvider, project)) { - return null; - } - return BuildConfiguration.builder() - .cmd(extractArguments(valueProvider, CMD, valueOrNull(config, BuildConfiguration::getCmd))) - .cleanup(valueProvider.getString(CLEANUP, valueOrNull(config, BuildConfiguration::getCleanup))) - .nocache(valueProvider.getBoolean(NOCACHE, valueOrNull(config, BuildConfiguration::getNocache))) - .cacheFrom(extractCacheFrom(valueProvider.getString(CACHEFROM, config == null ? null : (config.getCacheFrom() == null ? null : config.getCacheFrom().toString())))) - .optimise(valueProvider.getBoolean(OPTIMISE, valueOrNull(config, BuildConfiguration::getOptimise))) - .entryPoint(extractArguments(valueProvider, ENTRYPOINT, valueOrNull(config, BuildConfiguration::getEntryPoint))) - .assembly(extractAssembly(valueOrNull(config, BuildConfiguration::getAssembly), valueProvider)) - .env(MapUtil.mergeMaps( - valueProvider.getMap(ENV_BUILD, valueOrNull(config, BuildConfiguration::getEnv)), - valueProvider.getMap(ENV, Collections.emptyMap()) - )) - .args(valueProvider.getMap(ARGS, valueOrNull(config, BuildConfiguration::getArgs))) - .labels(valueProvider.getMap(LABELS, valueOrNull(config, BuildConfiguration::getLabels))) - .ports(extractPortValues(valueOrNull(config, BuildConfiguration::getPorts), valueProvider)) - .shell(extractArguments(valueProvider, SHELL, valueOrNull(config, BuildConfiguration::getShell))) - .runCmds(valueProvider.getList(RUN, valueOrNull(config, BuildConfiguration::getRunCmds))) - .from(valueProvider.getString(FROM, valueOrNull(config, BuildConfiguration::getFrom))) - .fromExt(valueProvider.getMap(FROM_EXT, valueOrNull(config, BuildConfiguration::getFromExt))) - .volumes(valueProvider.getList(VOLUMES, valueOrNull(config, BuildConfiguration::getVolumes))) - .tags(valueProvider.getList(TAGS, valueOrNull(config, BuildConfiguration::getTags))) - .maintainer(valueProvider.getString(MAINTAINER, valueOrNull(config, BuildConfiguration::getMaintainer))) - .workdir(valueProvider.getString(WORKDIR, valueOrNull(config, BuildConfiguration::getWorkdir))) - .skip(valueProvider.getBoolean(SKIP_BUILD, valueOrNull(config, BuildConfiguration::getSkip))) - .imagePullPolicy(valueProvider.getString(IMAGE_PULL_POLICY_BUILD, valueOrNull(config, BuildConfiguration::getImagePullPolicy))) - .contextDir(valueProvider.getString(CONTEXT_DIR, valueOrNull(config, BuildConfiguration::getContextDirRaw))) - .dockerArchive(valueProvider.getString(DOCKER_ARCHIVE, valueOrNull(config, BuildConfiguration::getDockerArchiveRaw))) - .dockerFile(valueProvider.getString(DOCKER_FILE, valueOrNull(config, BuildConfiguration::getDockerFileRaw))) - .buildOptions(valueProvider.getMap(BUILD_OPTIONS, valueOrNull(config, BuildConfiguration::getBuildOptions))) - .filter(valueProvider.getString(FILTER, valueOrNull(config, BuildConfiguration::getFilter))) - .user(valueProvider.getString(USER, valueOrNull(config, BuildConfiguration::getUser))) - .healthCheck(extractHealthCheck(valueOrNull(config, BuildConfiguration::getHealthCheck), valueProvider)) - .build(); - } - - List extractCacheFrom(String cacheFrom, String ...more) { - if (more == null || more.length == 0) { - return Collections.singletonList(cacheFrom); - } - - return Stream.concat(Stream.of(cacheFrom), Arrays.stream(more)).collect(Collectors.toList()); - } - - private AssemblyConfiguration extractAssembly(AssemblyConfiguration config, ValueProvider valueProvider) { - return AssemblyConfiguration.builder() - .targetDir(valueProvider.getString(ASSEMBLY_BASEDIR, valueOrNull(config, AssemblyConfiguration::getTargetDir))) - .exportTargetDir(valueProvider.getBoolean(ASSEMBLY_EXPORT_TARGET_DIR, valueOrNull(config, AssemblyConfiguration::getExportTargetDir))) - .permissionsString(valueProvider.getString(ASSEMBLY_PERMISSIONS, valueOrNull(config, AssemblyConfiguration::getPermissionsRaw))) - .user(valueProvider.getString(ASSEMBLY_USER, valueOrNull(config, AssemblyConfiguration::getUser))) - .modeString(valueProvider.getString(ASSEMBLY_MODE, valueOrNull(config, AssemblyConfiguration::getModeRaw))) - .tarLongFileMode(valueProvider.getString(ASSEMBLY_TARLONGFILEMODE, valueOrNull(config, AssemblyConfiguration::getTarLongFileMode))) - .build(); - } - - private HealthCheckConfiguration extractHealthCheck(HealthCheckConfiguration config, ValueProvider valueProvider) { - Map healthCheckProperties = valueProvider.getMap(HEALTHCHECK, Collections.emptyMap()); - if (healthCheckProperties != null && healthCheckProperties.size() > 0) { - return HealthCheckConfiguration.builder() - .interval(valueProvider.getString(HEALTHCHECK_INTERVAL, valueOrNull(config, HealthCheckConfiguration::getInterval))) - .timeout(valueProvider.getString(HEALTHCHECK_TIMEOUT, valueOrNull(config, HealthCheckConfiguration::getTimeout))) - .startPeriod(valueProvider.getString(HEALTHCHECK_START_PERIOD, valueOrNull(config, HealthCheckConfiguration::getStartPeriod))) - .retries(valueProvider.getInteger(HEALTHCHECK_RETRIES, valueOrNull(config, HealthCheckConfiguration::getRetries))) - .modeString(valueProvider.getString(HEALTHCHECK_MODE, config == null || config.getMode() == null ? null : config.getMode().name())) - .cmd(extractArguments(valueProvider, HEALTHCHECK_CMD, valueOrNull(config, HealthCheckConfiguration::getCmd))) - .build(); - } else { - return config; - } - } - - // Extract only the values of the port mapping - - private List extractPortValues(List config, ValueProvider valueProvider) { - List ret = new ArrayList<>(); - List ports = valueProvider.getList(PORTS, config); - if (ports == null) { - return null; - } - List parsedPorts = EnvUtil.splitOnLastColon(ports); - for (String[] port : parsedPorts) { - ret.add(port[1]); - } - return ret; - } - - private Arguments extractArguments(ValueProvider valueProvider, ConfigKey configKey, Arguments alternative) { - return valueProvider.getObject(configKey, alternative, raw -> raw != null ? Arguments.builder().shell(raw).build() : null); - } - - private WatchImageConfiguration extractWatchConfig(ImageConfiguration fromConfig, ValueProvider valueProvider) { - WatchImageConfiguration config = fromConfig.getWatchConfiguration(); - - return WatchImageConfiguration.builder() - .interval(valueProvider.getInteger(WATCH_INTERVAL, config == null ? null : config.getIntervalRaw())) - .postGoal(valueProvider.getString(WATCH_POSTGOAL, config == null ? null : config.getPostGoal())) - .postExec(valueProvider.getString(WATCH_POSTEXEC, config == null ? null : config.getPostExec())) - .modeString(valueProvider.getString(WATCH_POSTGOAL, config == null || config.getMode() == null ? null : config.getMode().name())) - .build(); - } - - - private static String getPrefix(Map externalConfig) { - String prefix = externalConfig.get("prefix"); - if (prefix == null) { - prefix = DEFAULT_PREFIX; - } - return prefix; - } - - private static PropertyMode getMode(Map externalConfig) { - return PropertyMode.parse(externalConfig.get("mode")); - } - - public static boolean canCoexistWithOtherPropertyConfiguredImages(Map externalConfig) { - if(externalConfig == null || externalConfig.isEmpty()) { - return false; - } - - if(!TYPE_NAME.equals(externalConfig.get("type"))) - { - // This images loads config from something totally different - return true; - } - - // This image has a specified prefix. If multiple images have explicitly set docker. as prefix we - // assume user know what they are doing and allow it. - return externalConfig.get("prefix") != null; - } -} diff --git a/jkube-kit/build/api/src/main/java/org/eclipse/jkube/kit/build/api/config/handler/property/ConfigKey.java b/jkube-kit/build/api/src/main/java/org/eclipse/jkube/kit/build/api/config/property/ConfigKey.java similarity index 63% rename from jkube-kit/build/api/src/main/java/org/eclipse/jkube/kit/build/api/config/handler/property/ConfigKey.java rename to jkube-kit/build/api/src/main/java/org/eclipse/jkube/kit/build/api/config/property/ConfigKey.java index e32f65725e..f276e332d5 100644 --- a/jkube-kit/build/api/src/main/java/org/eclipse/jkube/kit/build/api/config/handler/property/ConfigKey.java +++ b/jkube-kit/build/api/src/main/java/org/eclipse/jkube/kit/build/api/config/property/ConfigKey.java @@ -11,7 +11,7 @@ * Contributors: * Red Hat, Inc. - initial API and implementation */ -package org.eclipse.jkube.kit.build.api.config.handler.property; +package org.eclipse.jkube.kit.build.api.config.property; /** @@ -29,34 +29,19 @@ public enum ConfigKey { ASSEMBLY_USER("assembly.user"), ASSEMBLY_MODE("assembly.mode"), ASSEMBLY_TARLONGFILEMODE("assembly.tarLongFileMode"), - AUTO_REMOVE, - BIND, BUILD_OPTIONS, - CAP_ADD, - CAP_DROP, CLEANUP, - CONTAINER_NAME_PATTERN, - CPUSHARES, - CPUS, - CPUSET, NOCACHE, CACHEFROM, OPTIMISE, CMD, CONTEXT_DIR, - DEPENDS_ON, - DOMAINNAME, - DNS, - DNS_SEARCH, DOCKER_ARCHIVE, DOCKER_FILE, ENTRYPOINT, ENV, - ENV_PROPERTY_FILE, ENV_BUILD("envBuild", ValueCombinePolicy.Merge), ENV_RUN("envRun", ValueCombinePolicy.Merge), - EXPOSED_PROPERTY_KEY, - EXTRA_HOSTS, FILTER, FROM, FROM_EXT, @@ -67,67 +52,23 @@ public enum ConfigKey { HEALTHCHECK_START_PERIOD("healthcheck.startPeriod"), HEALTHCHECK_RETRIES("healthcheck.retries"), HEALTHCHECK_CMD("healthcheck.cmd"), - HOSTNAME, - IMAGE_PULL_POLICY_BUILD("imagePullPolicy.build"), - IMAGE_PULL_POLICY_RUN("imagePullPolicy.run"), + IMAGE_PULL_POLICY, LABELS(ValueCombinePolicy.Merge), - LINKS, - LOG_ENABLED("log.enabled"), - LOG_PREFIX("log.prefix"), - LOG_DATE("log.date"), - LOG_FILE("log.file"), - LOG_COLOR("log.color"), - LOG_DRIVER_NAME("log.driver.name"), - LOG_DRIVER_OPTS("log.driver.opts"), MAINTAINER, - MEMORY, - MEMORY_SWAP, NAME, - NET, - NETWORK_MODE("network.mode"), - NETWORK_NAME("network.name"), - NETWORK_ALIAS("network.alias"), - PORT_PROPERTY_FILE, PORTS(ValueCombinePolicy.Merge), - PRIVILEGED, - READ_ONLY, REGISTRY, - RESTART_POLICY_NAME("restartPolicy.name"), - RESTART_POLICY_RETRY("restartPolicy.retry"), SHELL, RUN, - SECURITY_OPTS, - SHMSIZE, - SKIP_BUILD("skip.build"), - SKIP_RUN("skip.run"), + SKIP, TAGS(ValueCombinePolicy.Merge), - TMPFS, - ULIMITS, USER, VOLUMES, - VOLUMES_FROM, - WAIT_LOG("wait.log"), - WAIT_TIME("wait.time"), - WAIT_HEALTHY("wait.healthy"), - WAIT_URL("wait.url"), - WAIT_HTTP_URL("wait.http.url"), - WAIT_HTTP_METHOD("wait.http.method"), - WAIT_HTTP_STATUS("wait.http.status"), - WAIT_KILL("wait.kill"), - WAIT_EXEC_POST_START("wait.exec.postStart"), - WAIT_EXEC_PRE_STOP("wait.exec.preStop"), - WAIT_EXEC_BREAK_ON_ERROR("wait.exec.breakOnError"), - WAIT_EXIT("wait.exit"), - WAIT_SHUTDOWN("wait.shutdown"), - WAIT_TCP_MODE("wait.tcp.mode"), - WAIT_TCP_HOST("wait.tcp.host"), - WAIT_TCP_PORT("wait.tcp.port"), WATCH_INTERVAL("watch.interval"), WATCH_MODE("watch.mode"), WATCH_POSTGOAL("watch.postGoal"), WATCH_POSTEXEC("watch.postExec"), - WORKDIR, - WORKING_DIR; + WORKDIR; ConfigKey() { this(ValueCombinePolicy.Replace); diff --git a/jkube-kit/build/api/src/main/java/org/eclipse/jkube/kit/build/api/config/property/PropertyConfigResolver.java b/jkube-kit/build/api/src/main/java/org/eclipse/jkube/kit/build/api/config/property/PropertyConfigResolver.java new file mode 100644 index 0000000000..b33f40067d --- /dev/null +++ b/jkube-kit/build/api/src/main/java/org/eclipse/jkube/kit/build/api/config/property/PropertyConfigResolver.java @@ -0,0 +1,162 @@ +/* + * 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.config.property; + +import org.apache.commons.lang3.StringUtils; +import org.eclipse.jkube.kit.config.image.ImageConfiguration; +import org.eclipse.jkube.kit.config.image.WatchImageConfiguration; +import org.eclipse.jkube.kit.common.JavaProject; +import org.eclipse.jkube.kit.common.util.EnvUtil; +import org.eclipse.jkube.kit.common.util.JKubeProjectUtil; +import org.eclipse.jkube.kit.common.util.MapUtil; +import org.eclipse.jkube.kit.common.Arguments; +import org.eclipse.jkube.kit.common.AssemblyConfiguration; +import org.eclipse.jkube.kit.config.image.build.BuildConfiguration; +import org.eclipse.jkube.kit.config.image.build.HealthCheckConfiguration; + +import java.util.Collections; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.Properties; +import java.util.Set; +import java.util.function.Function; + +public class PropertyConfigResolver { + + private static final String DEFAULT_PREFIX = "jkube.container-image"; + // The ValueProvider class has more functionality than the one used by the PropertyConfigResolver class. + // Let's add some default values so that we can conserve the ValueProvider as is and still leverage its functionality. + private static final PropertyMode DEFAULT_MODE = PropertyMode.OVERRIDE; + + public final ImageConfiguration resolve(ImageConfiguration fromConfig, JavaProject project) { + final Properties properties = JKubeProjectUtil.getPropertiesWithSystemOverrides(project); + final String prefix = StringUtils.isBlank(fromConfig.getPropertyResolverPrefix()) ? + DEFAULT_PREFIX : fromConfig.getPropertyResolverPrefix(); + final ValueProvider valueProvider = new ValueProvider(prefix, properties, DEFAULT_MODE); + return ImageConfiguration.builder() + .name(valueProvider.getString(ConfigKey.NAME, fromConfig.getName())) + .alias(valueProvider.getString(ConfigKey.ALIAS, fromConfig.getAlias())) + .build(extractBuildConfiguration(fromConfig, valueProvider)) + .watch(extractWatchConfig(fromConfig, valueProvider)) + .build(); + } + + private static R valueOr(T input, Function function, R defaultValue) { + return Optional.ofNullable(input).map(function).orElse(defaultValue); + } + + private static R valueOrNull(T input, Function function) { + return valueOr(input, function, null); + } + + private BuildConfiguration extractBuildConfiguration(ImageConfiguration fromConfig, ValueProvider valueProvider) { + final BuildConfiguration config = fromConfig.getBuild() != null ? + fromConfig.getBuild() : new BuildConfiguration(); + return config.toBuilder() + .cmd(extractArguments(valueProvider, ConfigKey.CMD, valueOrNull(config, BuildConfiguration::getCmd))) + .cleanup(valueProvider.getString(ConfigKey.CLEANUP, valueOrNull(config, BuildConfiguration::getCleanup))) + .nocache(valueProvider.getBoolean(ConfigKey.NOCACHE, valueOrNull(config, BuildConfiguration::getNocache))) + .clearCacheFrom().cacheFrom(valueProvider.getList(ConfigKey.CACHEFROM, valueOr(config, BuildConfiguration::getCacheFrom, Collections.emptyList()))) + .optimise(valueProvider.getBoolean(ConfigKey.OPTIMISE, valueOrNull(config, BuildConfiguration::getOptimise))) + .entryPoint(extractArguments(valueProvider, ConfigKey.ENTRYPOINT, valueOrNull(config, BuildConfiguration::getEntryPoint))) + .assembly(extractAssembly(valueOrNull(config, BuildConfiguration::getAssembly), valueProvider)) + .env(MapUtil.mergeMaps( + valueProvider.getMap(ConfigKey.ENV_BUILD, Collections.emptyMap()), + valueProvider.getMap(ConfigKey.ENV, valueOrNull(config, BuildConfiguration::getEnv)) + )) + .args(valueProvider.getMap(ConfigKey.ARGS, valueOr(config, BuildConfiguration::getArgs, Collections.emptyMap()))) + .labels(valueProvider.getMap(ConfigKey.LABELS, valueOr(config, BuildConfiguration::getLabels, Collections.emptyMap()))) + .clearPorts().ports(extractPortValues(valueOr(config, BuildConfiguration::getPorts, Collections.emptyList()), valueProvider)) + .shell(extractArguments(valueProvider, ConfigKey.SHELL, valueOrNull(config, BuildConfiguration::getShell))) + .clearRunCmds().runCmds(valueProvider.getList(ConfigKey.RUN, valueOr(config, BuildConfiguration::getRunCmds, Collections.emptyList()))) + .from(valueProvider.getString(ConfigKey.FROM, valueOrNull(config, BuildConfiguration::getFrom))) + .fromExt(valueProvider.getMap(ConfigKey.FROM_EXT, valueOrNull(config, BuildConfiguration::getFromExt))) + .clearVolumes().volumes(valueProvider.getList(ConfigKey.VOLUMES, valueOr(config, BuildConfiguration::getVolumes, Collections.emptyList()))) + .clearTags().tags(valueProvider.getList(ConfigKey.TAGS, valueOr(config, BuildConfiguration::getTags, Collections.emptyList()))) + .maintainer(valueProvider.getString(ConfigKey.MAINTAINER, valueOrNull(config, BuildConfiguration::getMaintainer))) + .workdir(valueProvider.getString(ConfigKey.WORKDIR, valueOrNull(config, BuildConfiguration::getWorkdir))) + .skip(valueProvider.getBoolean(ConfigKey.SKIP, valueOrNull(config, BuildConfiguration::getSkip))) + .imagePullPolicy(valueProvider.getString(ConfigKey.IMAGE_PULL_POLICY, valueOrNull(config, BuildConfiguration::getImagePullPolicy))) + .contextDir(valueProvider.getString(ConfigKey.CONTEXT_DIR, valueOrNull(config, BuildConfiguration::getContextDirRaw))) + .dockerArchive(valueProvider.getString(ConfigKey.DOCKER_ARCHIVE, valueOrNull(config, BuildConfiguration::getDockerArchiveRaw))) + .dockerFile(valueProvider.getString(ConfigKey.DOCKER_FILE, valueOrNull(config, BuildConfiguration::getDockerFileRaw))) + .buildOptions(valueProvider.getMap(ConfigKey.BUILD_OPTIONS, valueOrNull(config, BuildConfiguration::getBuildOptions))) + .filter(valueProvider.getString(ConfigKey.FILTER, valueOrNull(config, BuildConfiguration::getFilter))) + .user(valueProvider.getString(ConfigKey.USER, valueOrNull(config, BuildConfiguration::getUser))) + .healthCheck(extractHealthCheck(valueOrNull(config, BuildConfiguration::getHealthCheck), valueProvider)) + .build(); + } + + private AssemblyConfiguration extractAssembly(AssemblyConfiguration originalConfig, ValueProvider valueProvider) { + final AssemblyConfiguration config = originalConfig != null ? originalConfig : new AssemblyConfiguration(); + return config.toBuilder() + .targetDir(valueProvider.getString(ConfigKey.ASSEMBLY_BASEDIR, valueOrNull(originalConfig, AssemblyConfiguration::getTargetDir))) + .exportTargetDir(valueProvider.getBoolean(ConfigKey.ASSEMBLY_EXPORT_TARGET_DIR, valueOrNull(originalConfig, AssemblyConfiguration::getExportTargetDir))) + .permissionsString(valueProvider.getString(ConfigKey.ASSEMBLY_PERMISSIONS, valueOrNull(originalConfig, AssemblyConfiguration::getPermissionsRaw))) + .user(valueProvider.getString(ConfigKey.ASSEMBLY_USER, valueOrNull(originalConfig, AssemblyConfiguration::getUser))) + .modeString(valueProvider.getString(ConfigKey.ASSEMBLY_MODE, valueOrNull(originalConfig, AssemblyConfiguration::getModeRaw))) + .tarLongFileMode(valueProvider.getString(ConfigKey.ASSEMBLY_TARLONGFILEMODE, valueOrNull(originalConfig, AssemblyConfiguration::getTarLongFileMode))) + .build(); + } + + private HealthCheckConfiguration extractHealthCheck(HealthCheckConfiguration originalConfig, ValueProvider valueProvider) { + final Map healthCheckProperties = valueProvider.getMap(ConfigKey.HEALTHCHECK, Collections.emptyMap()); + if (healthCheckProperties != null && !healthCheckProperties.isEmpty()) { + final HealthCheckConfiguration config = originalConfig != null ? originalConfig : new HealthCheckConfiguration(); + return config.toBuilder() + .interval(valueProvider.getString(ConfigKey.HEALTHCHECK_INTERVAL, valueOrNull(originalConfig, HealthCheckConfiguration::getInterval))) + .timeout(valueProvider.getString(ConfigKey.HEALTHCHECK_TIMEOUT, valueOrNull(originalConfig, HealthCheckConfiguration::getTimeout))) + .startPeriod(valueProvider.getString(ConfigKey.HEALTHCHECK_START_PERIOD, valueOrNull(originalConfig, HealthCheckConfiguration::getStartPeriod))) + .retries(valueProvider.getInteger(ConfigKey.HEALTHCHECK_RETRIES, valueOrNull(originalConfig, HealthCheckConfiguration::getRetries))) + .modeString(valueProvider.getString(ConfigKey.HEALTHCHECK_MODE, originalConfig == null || originalConfig.getMode() == null ? null : originalConfig.getMode().name())) + .cmd(extractArguments(valueProvider, ConfigKey.HEALTHCHECK_CMD, valueOrNull(originalConfig, HealthCheckConfiguration::getCmd))) + .build(); + } else { + return originalConfig; + } + } + + // Extract only the values of the port mapping + + private Set extractPortValues(List config, ValueProvider valueProvider) { + final Set ret = new LinkedHashSet<>(); + final List ports = valueProvider.getList(ConfigKey.PORTS, config); + if (ports == null) { + return null; + } + final List parsedPorts = EnvUtil.splitOnLastColon(ports); + for (String[] port : parsedPorts) { + ret.add(port[1]); + } + return ret; + } + + private Arguments extractArguments(ValueProvider valueProvider, ConfigKey configKey, Arguments alternative) { + return valueProvider.getObject(configKey, alternative, raw -> raw != null ? Arguments.builder().shell(raw).build() : null); + } + + private WatchImageConfiguration extractWatchConfig(ImageConfiguration fromConfig, ValueProvider valueProvider) { + final WatchImageConfiguration config = fromConfig.getWatchConfiguration() != null ? + fromConfig.getWatchConfiguration() : new WatchImageConfiguration(); + return config.toBuilder() + .interval(valueProvider.getInteger(ConfigKey.WATCH_INTERVAL, config.getIntervalRaw())) + .postGoal(valueProvider.getString(ConfigKey.WATCH_POSTGOAL, config.getPostGoal())) + .postExec(valueProvider.getString(ConfigKey.WATCH_POSTEXEC, config.getPostExec())) + .modeString(valueProvider.getString(ConfigKey.WATCH_POSTGOAL, config.getMode() == null ? null : config.getMode().name())) + .build(); + } + +} diff --git a/jkube-kit/build/api/src/main/java/org/eclipse/jkube/kit/build/api/config/handler/property/PropertyMode.java b/jkube-kit/build/api/src/main/java/org/eclipse/jkube/kit/build/api/config/property/PropertyMode.java similarity index 78% rename from jkube-kit/build/api/src/main/java/org/eclipse/jkube/kit/build/api/config/handler/property/PropertyMode.java rename to jkube-kit/build/api/src/main/java/org/eclipse/jkube/kit/build/api/config/property/PropertyMode.java index cabaa7be6a..4cfb8f5ec8 100644 --- a/jkube-kit/build/api/src/main/java/org/eclipse/jkube/kit/build/api/config/handler/property/PropertyMode.java +++ b/jkube-kit/build/api/src/main/java/org/eclipse/jkube/kit/build/api/config/property/PropertyMode.java @@ -11,20 +11,20 @@ * Contributors: * Red Hat, Inc. - initial API and implementation */ -package org.eclipse.jkube.kit.build.api.config.handler.property; +package org.eclipse.jkube.kit.build.api.config.property; /** - * Identifies how the {@link PropertyConfigHandler} should treat properties vs configuration + * Identifies how the {@link PropertyConfigResolver} should treat properties vs configuration * from POM file in the {@link ValueProvider}. * * @author Johan Ström */ -public enum PropertyMode { - Only, - Override, - Fallback, - Skip; +enum PropertyMode { + ONLY, + OVERRIDE, + FALLBACK, + SKIP; /** * Given String, parse to a valid property mode. @@ -36,7 +36,7 @@ public enum PropertyMode { */ static PropertyMode parse(String name) { if(name == null) { - return PropertyMode.Only; + return PropertyMode.ONLY; } name = name.toLowerCase(); diff --git a/jkube-kit/build/api/src/main/java/org/eclipse/jkube/kit/build/api/config/handler/property/ValueCombinePolicy.java b/jkube-kit/build/api/src/main/java/org/eclipse/jkube/kit/build/api/config/property/ValueCombinePolicy.java similarity index 89% rename from jkube-kit/build/api/src/main/java/org/eclipse/jkube/kit/build/api/config/handler/property/ValueCombinePolicy.java rename to jkube-kit/build/api/src/main/java/org/eclipse/jkube/kit/build/api/config/property/ValueCombinePolicy.java index f1586fa898..a50bf609a3 100644 --- a/jkube-kit/build/api/src/main/java/org/eclipse/jkube/kit/build/api/config/handler/property/ValueCombinePolicy.java +++ b/jkube-kit/build/api/src/main/java/org/eclipse/jkube/kit/build/api/config/property/ValueCombinePolicy.java @@ -11,14 +11,14 @@ * Contributors: * Red Hat, Inc. - initial API and implementation */ -package org.eclipse.jkube.kit.build.api.config.handler.property; +package org.eclipse.jkube.kit.build.api.config.property; import org.apache.commons.lang3.StringUtils; /** - * Dictates how to combine values from different sources. See {@link PropertyConfigHandler} for details. + * Dictates how to combine values from different sources. See {@link PropertyConfigResolver} for details. */ -public enum ValueCombinePolicy { +enum ValueCombinePolicy { /** * The prioritized value fully replaces any other values. */ diff --git a/jkube-kit/build/api/src/main/java/org/eclipse/jkube/kit/build/api/config/handler/property/ValueProvider.java b/jkube-kit/build/api/src/main/java/org/eclipse/jkube/kit/build/api/config/property/ValueProvider.java similarity index 89% rename from jkube-kit/build/api/src/main/java/org/eclipse/jkube/kit/build/api/config/handler/property/ValueProvider.java rename to jkube-kit/build/api/src/main/java/org/eclipse/jkube/kit/build/api/config/property/ValueProvider.java index 3a88e85c1b..9004db0469 100644 --- a/jkube-kit/build/api/src/main/java/org/eclipse/jkube/kit/build/api/config/handler/property/ValueProvider.java +++ b/jkube-kit/build/api/src/main/java/org/eclipse/jkube/kit/build/api/config/property/ValueProvider.java @@ -11,12 +11,13 @@ * Contributors: * Red Hat, Inc. - initial API and implementation */ -package org.eclipse.jkube.kit.build.api.config.handler.property; +package org.eclipse.jkube.kit.build.api.config.property; import org.eclipse.jkube.kit.common.util.EnvUtil; import java.util.ArrayList; +import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Properties; @@ -32,10 +33,10 @@ * Obtaining a value is done via data-type specific methods (such as {@link #getString}). The ConfigKey parameter * tells us which property to look for, and how to handle combination of multiple values. * - * For {@link PropertyMode#Only} we only look at the properties, ignoring any config value. - * For {@link PropertyMode#Skip} we only look at the config, ignoring any properties value. - * For {@link PropertyMode#Override} we use the property value if it is non-null, else the config value. - * For {@link PropertyMode#Fallback} we use the config value if it is non-null, else the property value. + * For {@link PropertyMode#ONLY} we only look at the properties, ignoring any config value. + * For {@link PropertyMode#SKIP} we only look at the config, ignoring any properties value. + * For {@link PropertyMode#OVERRIDE} we use the property value if it is non-null, else the config value. + * For {@link PropertyMode#FALLBACK} we use the config value if it is non-null, else the property value. * * For Override and Fallback mode, merging may take place as dictated by the {@link ValueCombinePolicy} * defined in the {@link ConfigKey}, or as overriden by the property <prefix.someproperty>._combine @@ -49,19 +50,19 @@ * * @author Johan Ström */ -public class ValueProvider { - private String prefix; - private Properties properties; - private PropertyMode propertyMode; - - private StringListValueExtractor stringListValueExtractor; - private IntListValueExtractor intListValueExtractor; - private MapValueExtractor mapValueExtractor; - private StringValueExtractor stringValueExtractor; - private IntValueExtractor intValueExtractor; - private LongValueExtractor longValueExtractor; - private BooleanValueExtractor booleanValueExtractor; - private DoubleValueExtractor doubleValueExtractor; +class ValueProvider { + private final String prefix; + private final Properties properties; + private final PropertyMode propertyMode; + + private final StringListValueExtractor stringListValueExtractor; + private final IntListValueExtractor intListValueExtractor; + private final MapValueExtractor mapValueExtractor; + private final StringValueExtractor stringValueExtractor; + private final IntValueExtractor intValueExtractor; + private final LongValueExtractor longValueExtractor; + private final BooleanValueExtractor booleanValueExtractor; + private final DoubleValueExtractor doubleValueExtractor; /** * Initiates ValueProvider which is to work with data from the given properties. @@ -139,14 +140,14 @@ protected T withPrefix(String prefix, ConfigKey key, Properties properties) { } /** - * Helper base class for picking values out of the the Properties class and/or config value. + * Helper base class for picking values out of the Properties class and/or config value. * * If there is only one source defined, we only use that. If multiple source are defined, the first one get's priority. * If more than one value is specified, a merge policy as specified for the ConfigKey */ private abstract class ValueExtractor { T getFromPreferredSource(String prefix, ConfigKey key, T fromConfig) { - if(propertyMode == PropertyMode.Skip) { + if(propertyMode == PropertyMode.SKIP) { return fromConfig; } @@ -161,9 +162,9 @@ T getFromPreferredSource(String prefix, ConfigKey key, T fromConfig) { } switch (propertyMode) { - case Only: + case ONLY: return fromProperty; - case Override: + case OVERRIDE: if(fromProperty != null) { values.add(fromProperty); } @@ -171,7 +172,7 @@ T getFromPreferredSource(String prefix, ConfigKey key, T fromConfig) { values.add(fromConfig); } break; - case Fallback: + case FALLBACK: if(fromConfig != null) { values.add(fromConfig); } @@ -312,16 +313,12 @@ protected Map withPrefix(String prefix, ConfigKey key, Propertie @Override protected Map merge(ConfigKey key, List> values) { - Map merged = null; + final Map merged = new LinkedHashMap<>(); // Iterate in reverse, the first entry in values has highest priority for(int i = values.size() - 1; i >= 0; i--) { Map value = values.get(i); - if(merged == null) { - merged = value; - } else { - merged.putAll(value); - } + merged.putAll(value); } return merged; } diff --git a/jkube-kit/build/api/src/main/java/org/eclipse/jkube/kit/build/api/helper/ConfigHelper.java b/jkube-kit/build/api/src/main/java/org/eclipse/jkube/kit/build/api/helper/ConfigHelper.java deleted file mode 100644 index 63cea69f00..0000000000 --- a/jkube-kit/build/api/src/main/java/org/eclipse/jkube/kit/build/api/helper/ConfigHelper.java +++ /dev/null @@ -1,88 +0,0 @@ -/* - * 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.build.api.config.handler.property.PropertyConfigHandler; -import org.eclipse.jkube.kit.build.api.config.handler.property.PropertyMode; -import org.eclipse.jkube.kit.config.image.ImageConfiguration; -import org.eclipse.jkube.kit.common.JavaProject; -import org.eclipse.jkube.kit.common.util.JKubeProjectUtil; - -import java.util.List; -import java.util.Properties; - -/** - * Utility class which helps in resolving, customizing, initializing and validating - * image configuration. - * - * @author roland - */ -public class ConfigHelper { - // Property which can be set to activate externalConfiguration through properties. - // Only works for single image project. - public static final String EXTERNALCONFIG_ACTIVATION_PROPERTY = "docker.imagePropertyConfiguration"; - - private ConfigHelper() {} - - public static void validateExternalPropertyActivation(JavaProject project, List images) { - String prop = getExternalConfigActivationProperty(project); - if(prop == null) { - return; - } - - if(images.size() == 1) { - return; - } - - // With more than one image, externally activating propertyConfig get's tricky. We can only allow it to affect - // one single image. Go through each image and check if they will be controlled by default properties. - // If more than one image matches, fail. - int imagesWithoutExternalConfig = 0; - for (ImageConfiguration image : images) { - if(PropertyConfigHandler.canCoexistWithOtherPropertyConfiguredImages(image.getExternalConfig())) { - continue; - } - - // else, it will be affected by the external property. - imagesWithoutExternalConfig++; - } - - if(imagesWithoutExternalConfig > 1) { - throw new IllegalStateException("Configuration error: Cannot use property "+EXTERNALCONFIG_ACTIVATION_PROPERTY+" on projects with multiple images without explicit image external configuration."); - } - } - - public static String getExternalConfigActivationProperty(JavaProject project) { - Properties properties = JKubeProjectUtil.getPropertiesWithSystemOverrides(project); - String value = properties.getProperty(EXTERNALCONFIG_ACTIVATION_PROPERTY); - - // This can be used to disable in a more "local" context, if set globally - if(PropertyMode.Skip.name().equalsIgnoreCase(value)) { - return null; - } - - return value; - } - - // =========================================================================================================== - - /** - * Format an image name by replacing certain placeholders - */ - public interface NameFormatter { - String format(String name); - } - - -} diff --git a/jkube-kit/build/api/src/main/java/org/eclipse/jkube/kit/build/api/helper/ExternalConfigHandler.java b/jkube-kit/build/api/src/main/java/org/eclipse/jkube/kit/build/api/helper/ExternalConfigHandler.java deleted file mode 100644 index 23f7ba0329..0000000000 --- a/jkube-kit/build/api/src/main/java/org/eclipse/jkube/kit/build/api/helper/ExternalConfigHandler.java +++ /dev/null @@ -1,47 +0,0 @@ -/* - * 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 java.util.List; - -import org.eclipse.jkube.kit.config.image.ImageConfiguration; -import org.eclipse.jkube.kit.common.JavaProject; - -/** - * Interface which needs to be implemented to create - * image configurations from external sources - * - * @author roland - * @since 18/11/14 - */ -public interface ExternalConfigHandler { - - /** - * Get the unique type of this plugin as referenced with the <type> tag within a - * <reference> configuration section - * - * @return plugin type - */ - String getType(); - - /** - * For the given plugin configuration (which also contains the type) extract one or more - * {@link ImageConfiguration} objects describing the image to manage - * - * @param unresolvedConfig the original, unresolved config - * @param project project - * @return list of image configuration. Must not be null but can be empty. - */ - List resolve(ImageConfiguration unresolvedConfig, JavaProject project); -} diff --git a/jkube-kit/build/api/src/main/java/org/eclipse/jkube/kit/build/api/helper/ImageConfigResolver.java b/jkube-kit/build/api/src/main/java/org/eclipse/jkube/kit/build/api/helper/ImageConfigResolver.java deleted file mode 100644 index 21906be1a8..0000000000 --- a/jkube-kit/build/api/src/main/java/org/eclipse/jkube/kit/build/api/helper/ImageConfigResolver.java +++ /dev/null @@ -1,110 +0,0 @@ -/* - * 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 java.util.*; - -import org.eclipse.jkube.kit.config.image.ImageConfiguration; -import org.eclipse.jkube.kit.common.JavaProject; -import org.eclipse.jkube.kit.common.KitLogger; - -/** - * Manager holding all config handlers for external configuration - * - * @author roland - * @since 18/11/14 - */ - -public class ImageConfigResolver { - // Map type to handler - private Map registry; - - // No List injection possible currently with Plexus. - // Strangely, only the first element is injected in the list. - // So the elements are injected via scalar field injection and collected later. - // Very ugly, but I dont see any other solution until Plexus is fixed. - - private ExternalConfigHandler propertyConfigHandler; - - private ExternalConfigHandler dockerComposeConfigHandler; - - private KitLogger log; - - public void initialize() { - this.registry = new HashMap<>(); - for (ExternalConfigHandler handler : new ExternalConfigHandler[] { propertyConfigHandler, dockerComposeConfigHandler }) { - if (handler != null) { - registry.put(handler.getType(), handler); - } - } - } - - public void setLog(KitLogger log) { - this.log = log; - } - - /** - * Resolve an image configuration. If it contains a reference to an external configuration - * the corresponding resolver is called and the resolved image configurations are returned (can - * be multiple). - * - * If no reference to an external configuration is found, the original configuration - * is returned directly. - * - * @param unresolvedConfig the configuration to resolve - * @param project project used for resolving - * @return list of resolved image configurations - * or when the type is not known (i.e. no handler is registered for this type). - */ - public List resolve(ImageConfiguration unresolvedConfig, JavaProject project) { - injectExternalConfigActivation(unresolvedConfig, project); - Map externalConfig = unresolvedConfig.getExternalConfig(); - if (externalConfig != null) { - String type = externalConfig.get("type"); - if (type == null) { - throw new IllegalArgumentException(unresolvedConfig.getDescription() + ": No config type given"); - } - ExternalConfigHandler handler = registry.get(type); - if (handler == null) { - throw new IllegalArgumentException(unresolvedConfig.getDescription() + ": No handler for type " + type + " given"); - } - return handler.resolve(unresolvedConfig, project); - } else { - return Collections.singletonList(unresolvedConfig); - } - } - - private void injectExternalConfigActivation(ImageConfiguration unresolvedConfig, JavaProject project) { - // Allow external activation of property configuration - String mode = ConfigHelper.getExternalConfigActivationProperty(project); - - if(mode == null) { - return; - } - - Map externalConfig = unresolvedConfig.getExternalConfig(); - if(externalConfig == null) { - externalConfig = new HashMap<>(); - externalConfig.put("type", propertyConfigHandler.getType()); - externalConfig.put("mode", mode); - unresolvedConfig.setExternalConfiguration(externalConfig); - - log.verbose("Global %s=%s property activates property configuration for image", ConfigHelper.EXTERNALCONFIG_ACTIVATION_PROPERTY, mode); - } - else - { - log.verbose("Ignoring %s=%s property, image has in POM which takes precedence", ConfigHelper.EXTERNALCONFIG_ACTIVATION_PROPERTY, mode); - } - } -} diff --git a/jkube-kit/build/api/src/main/java/org/eclipse/jkube/kit/build/api/helper/ImageNameFormatter.java b/jkube-kit/build/api/src/main/java/org/eclipse/jkube/kit/build/api/helper/ImageNameFormatter.java index 0ee2134d9a..d050cce163 100644 --- a/jkube-kit/build/api/src/main/java/org/eclipse/jkube/kit/build/api/helper/ImageNameFormatter.java +++ b/jkube-kit/build/api/src/main/java/org/eclipse/jkube/kit/build/api/helper/ImageNameFormatter.java @@ -31,7 +31,7 @@ * @author roland * @since 07/06/16 */ -public class ImageNameFormatter implements ConfigHelper.NameFormatter { +public class ImageNameFormatter implements NameFormatter { /** * Property to lookup for image user which overwrites the calculated default (group). diff --git a/jkube-kit/build/api/src/main/java/org/eclipse/jkube/kit/build/api/helper/NameFormatter.java b/jkube-kit/build/api/src/main/java/org/eclipse/jkube/kit/build/api/helper/NameFormatter.java new file mode 100644 index 0000000000..f6d21a1319 --- /dev/null +++ b/jkube-kit/build/api/src/main/java/org/eclipse/jkube/kit/build/api/helper/NameFormatter.java @@ -0,0 +1,21 @@ +/* + * 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; + +/** + * Format an image name by replacing certain placeholders + */ +public interface NameFormatter { + String format(String name); +} diff --git a/jkube-kit/build/api/src/test/java/org/eclipse/jkube/kit/build/api/config/handler/property/PropertyConfigHandlerTest.java b/jkube-kit/build/api/src/test/java/org/eclipse/jkube/kit/build/api/config/handler/property/PropertyConfigHandlerTest.java deleted file mode 100644 index 8046a29352..0000000000 --- a/jkube-kit/build/api/src/test/java/org/eclipse/jkube/kit/build/api/config/handler/property/PropertyConfigHandlerTest.java +++ /dev/null @@ -1,597 +0,0 @@ -/* - * 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.config.handler.property; - -import java.io.File; -import java.nio.file.Path; -import java.nio.file.Paths; -import java.util.Arrays; -import java.util.Collections; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Properties; -import org.assertj.core.api.InstanceOfAssertFactories; -import org.eclipse.jkube.kit.common.JavaProject; -import org.eclipse.jkube.kit.config.image.ImageConfiguration; -import org.eclipse.jkube.kit.config.image.build.BuildConfiguration; -import org.eclipse.jkube.kit.config.image.build.CleanupMode; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; -import static org.assertj.core.api.Assertions.entry; -import static org.eclipse.jkube.kit.config.image.build.BuildConfiguration.DEFAULT_CLEANUP; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; - -/** - * @author roland - * @since 05/12/14 - */ - -class PropertyConfigHandlerTest { - private JavaProject javaProject; - - private PropertyConfigHandler configHandler; - - private ImageConfiguration imageConfiguration; - - @BeforeEach - void setUp() { - javaProject = mock(JavaProject.class); - configHandler = new PropertyConfigHandler(); - imageConfiguration = buildAnUnresolvedImage(); - } - - @Test - void testSkipBuild() { - assertThat(resolveExternalImageConfig(getSkipTestData(ConfigKey.SKIP_BUILD, false)).getBuildConfiguration().getSkip()).isFalse(); - assertThat(resolveExternalImageConfig(getSkipTestData(ConfigKey.SKIP_BUILD, true)).getBuildConfiguration().getSkip()).isTrue(); - - assertThat(resolveExternalImageConfig(mergeArrays(getBaseTestData(), new String[] {k(ConfigKey.NAME), "image", k(ConfigKey.FROM), "busybox"})).getBuildConfiguration().getSkip()).isFalse(); - } - - @Test - void testType() { - assertThat(configHandler.getType()).isNotNull(); - } - - @Test - void testPortsFromConfigAndProperties() { - imageConfiguration = ImageConfiguration.builder() - .external(new HashMap<>()) - .build(BuildConfiguration.builder() - .ports(Collections.singletonList("1234")) - .addCacheFrom("foo/bar:latest") - .build() - ) - .build(); - - makeExternalConfigUse(); - - List configs = resolveImage( - imageConfiguration,props( - "docker.name","demo", - "docker.ports.1", "9090", - "docker.ports.2", "0.0.0.0:80:80", - "docker.from", "busybox" - )); - assertThat(configs).singleElement() - .satisfies(config -> assertThat(config) - .extracting(ImageConfiguration::getBuildConfiguration) - .extracting(BuildConfiguration::getPorts) - .asList() - .containsExactly("9090", "80", "1234")); - } - - @Test - void testInvalidPropertyMode() { - makeExternalConfigUse(); - imageConfiguration.getExternalConfig().put("mode", "invalid"); - assertThatIllegalArgumentException() - .isThrownBy(() -> resolveImage(imageConfiguration, props())); - } - - @Test - void testRunCommands() { - List configs = resolveImage( - imageConfiguration,props(mergeArrays(getBaseTestData(), new String[] { - "docker.from", "base", - "docker.name","demo", - "docker.run.1", "foo", - "docker.run.2", "bar", - "docker.run.3", "wibble"}) - )); - - assertThat(configs).singleElement() - .extracting(ImageConfiguration::getBuildConfiguration) - .extracting(BuildConfiguration::getRunCmds) - .asList() - .containsExactly("xyz", "foo", "bar", "wibble"); - } - - @Test - void testShell() { - List configs = resolveImage( - imageConfiguration,props(mergeArrays(getBaseTestData(), new String[] { - "docker.from", "base", - "docker.name","demo", - "docker.shell", "/bin/sh -c"})) - ); - - assertThat(configs).singleElement() - .extracting(ImageConfiguration::getBuildConfiguration) - .returns(new String[]{"/bin/sh", "-c"}, c -> c.getShell().asStrings().toArray()); - } - - @Test - void testRunCommandsFromPropertiesAndConfig() { - imageConfiguration = ImageConfiguration.builder() - .external(new HashMap<>()) - .build(BuildConfiguration.builder() - .runCmds(Arrays.asList("some","ignored","value")) - .addCacheFrom("foo/bar:latest") - .build() - ) - .build(); - - makeExternalConfigUse(); - - List configs = resolveImage( - imageConfiguration,props( - "docker.from", "base", - "docker.name","demo", - "docker.run.1", "propconf", - "docker.run.2", "withrun", - "docker.run.3", "used") - ); - - assertThat(configs).singleElement() - .extracting(ImageConfiguration::getBuildConfiguration) - .extracting(BuildConfiguration::getRunCmds) - .asList() - .containsExactly("propconf", "withrun", "used"); - } - - @Test - void testRunCommandsFromConfigAndProperties() { - imageConfiguration = ImageConfiguration.builder() - .external(externalMode(PropertyMode.Fallback)) - .build(BuildConfiguration.builder() - .runCmds(Arrays.asList("some","configured","value")) - .addCacheFrom("foo/bar:latest") - .build() - ) - .build(); - - List configs = resolveImage( - imageConfiguration,props( - "docker.from", "base", - "docker.name","demo", - "docker.run.1", "this", - "docker.run.2", "is", - "docker.run.3", "ignored") - ); - - assertThat(configs).singleElement() - .extracting(ImageConfiguration::getBuildConfiguration) - .extracting(BuildConfiguration::getRunCmds) - .asList() - .containsExactly("some", "configured", "value"); - } - - @Test - void testEntrypoint() { - List configs = resolveImage( - imageConfiguration,props(mergeArrays(getBaseTestData(), new String[] { - "docker.from", "base", - "docker.name","demo", - "docker.entrypoint", "/entrypoint.sh --from-property"})) - ); - - assertThat(configs).singleElement() - .extracting(ImageConfiguration::getBuildConfiguration) - .returns(new String[]{"/entrypoint.sh", "--from-property"}, c -> c.getEntryPoint().asStrings().toArray()); - } - - @Test - void testBuildFromDockerFileMerged() { - - imageConfiguration = ImageConfiguration.builder() - .name("myimage") - .external(externalMode(PropertyMode.Override)) - .build(BuildConfiguration.builder() - .dockerFile("/some/path") - .addCacheFrom("foo/bar:latest") - .build() - ) - .build(); - - List configs = resolveImage( - imageConfiguration, props() - ); - - assertThat(configs).hasSize(1); - - BuildConfiguration buildConfiguration = configs.get(0).getBuildConfiguration(); - assertThat(buildConfiguration).isNotNull(); - buildConfiguration.initAndValidate(); - - Path absolutePath = Paths.get(".").toAbsolutePath(); - String expectedPath = absolutePath.getRoot() + "some" + File.separator + "path"; - assertThat(buildConfiguration.getDockerFile().getAbsolutePath()).isEqualTo(expectedPath); - } - - @Test - void testEnvAndLabels() { - List configs = resolveImage( - imageConfiguration,props(mergeArrays(getBaseTestData(), new String[]{ - "docker.from", "baase", - "docker.name", "demo", - "docker.env.HOME", "/tmp", - "docker.env.root.dir", "/bla", - "docker.labels.version", "1.0.0", - "docker.labels.blub.bla.foobar", "yep" - }))); - - assertThat(configs).hasSize(1); - ImageConfiguration calcConfig = configs.get(0); - assertThat(calcConfig.getBuildConfiguration().getEnv()).hasSize(2) - .contains( - entry("HOME", "/tmp"), - entry("root.dir", "/bla")); - assertThat(calcConfig.getBuildConfiguration().getLabels()).hasSize(3) - .contains( - entry("version", "1.0.0"), - entry("blub.bla.foobar", "yep")); - } - - - @Test - void testSpecificEnv() { - List configs = resolveImage( - imageConfiguration,props(mergeArrays(getBaseTestData(), new String[] { - "docker.from", "baase", - "docker.name","demo", - "docker.envBuild.HOME", "/tmp", - "docker.envRun.root.dir", "/bla" - }))); - - assertThat(configs).singleElement() - .extracting(ImageConfiguration::getBuildConfiguration) - .extracting(BuildConfiguration::getEnv) - .asInstanceOf(InstanceOfAssertFactories.MAP) - .hasSize(1) - .containsEntry("HOME", "/tmp"); - } - - @Test - void testNoCleanup() { - String[] testData = new String[] {k(ConfigKey.NAME), "image", k(ConfigKey.CLEANUP), "none", k(ConfigKey.FROM), "base" }; - - ImageConfiguration config = resolveExternalImageConfig(mergeArrays(getBaseTestData(), testData)); - assertThat(config.getBuildConfiguration().cleanupMode()).isEqualTo(CleanupMode.NONE); - } - - @Test - void testNoBuildConfig() { - String[] testData = new String[] {k(ConfigKey.NAME), "image" }; - - ImageConfiguration config = resolveExternalImageConfig(testData); - assertThat(config.getBuildConfiguration()).isNull(); - } - - @Test - void testNoCacheDisabled() { - String[] testData = new String[] {k(ConfigKey.NAME), "image", k(ConfigKey.NOCACHE), "false", k(ConfigKey.FROM), "base" }; - - ImageConfiguration config = resolveExternalImageConfig(mergeArrays(getBaseTestData(), testData)); - assertThat(config.getBuildConfiguration().getNocache()).isFalse(); - } - - @Test - void testNoCacheEnabled() { - String[] testData = new String[] {k(ConfigKey.NAME), "image", k(ConfigKey.NOCACHE), "true", k(ConfigKey.FROM), "base" }; - - ImageConfiguration config = resolveExternalImageConfig(mergeArrays(getBaseTestData(), testData)); - assertThat(config.getBuildConfiguration().getNocache()).isTrue(); - } - - @Test - void testCacheFrom() { - String[] testData = new String[] {k(ConfigKey.NAME), "image", k(ConfigKey.CACHEFROM), "foo/bar:latest", k(ConfigKey.FROM), "base"}; - - ImageConfiguration config = resolveExternalImageConfig(mergeArrays(getBaseTestData(), testData)); - assertThat(config.getBuildConfiguration().getCacheFrom()).isEqualTo(Collections.singletonList("foo/bar:latest")); - } - - @Test - void testExtractCacheFrom() { - // Given - String cacheFrom1 = "foo/bar:latest"; - String cacheFrom2 = "foo/bar1:0.1.0"; - PropertyConfigHandler propertyConfigHandler = new PropertyConfigHandler(); - - // When - List result = propertyConfigHandler.extractCacheFrom(cacheFrom1, cacheFrom2); - - // Then - assertThat(result).hasSize(2) - .containsExactly(cacheFrom1, cacheFrom2); - } - - @Test - void testNoOptimise() { - String[] testData = new String[] {k(ConfigKey.NAME), "image", k(ConfigKey.OPTIMISE), "false", k(ConfigKey.FROM), "base" }; - - ImageConfiguration config = resolveExternalImageConfig(mergeArrays(getBaseTestData(), testData)); - assertThat(config.getBuildConfiguration().optimise()).isFalse(); - } - - @Test - void testDockerFile() { - String[] testData = new String[] {k(ConfigKey.NAME), "image", k(ConfigKey.DOCKER_FILE), "file", "docker.args.foo", "bar" }; - - ImageConfiguration config = resolveExternalImageConfig(mergeArrays(getBaseTestData(), testData)); - assertThat(config.getBuildConfiguration()).isNotNull(); - } - - @Test - void testContextDir() { - String[] testData = new String[] {k(ConfigKey.NAME), "image", k(ConfigKey.CONTEXT_DIR), "dir" }; - - ImageConfiguration config = resolveExternalImageConfig(mergeArrays(getBaseTestData(), testData)); - assertThat(config.getBuildConfiguration()).isNotNull(); - } - - @Test - void testFilter() { - String filter = "@"; - String[] testData = new String[] {k(ConfigKey.NAME), "image", k(ConfigKey.FROM), "base", k(ConfigKey.FILTER), filter }; - - ImageConfiguration config = resolveExternalImageConfig(mergeArrays(getBaseTestData(), testData)); - assertThat(config.getBuildConfiguration().getFilter()).isEqualTo(filter); - } - - @Test - void testCleanupDefault() { - String[] testData = new String[] {k(ConfigKey.NAME), "image", k(ConfigKey.FROM), "base" }; - - ImageConfiguration config = resolveExternalImageConfig(mergeArrays(getBaseTestData(), testData)); - assertThat(config.getBuildConfiguration().cleanupMode().toParameter()).isEqualTo(DEFAULT_CLEANUP); - } - - @Test - void testCleanup() { - CleanupMode mode = CleanupMode.REMOVE; - String[] testData = new String[] {k(ConfigKey.NAME), "image", k(ConfigKey.FROM), "base", k(ConfigKey.CLEANUP), mode.toParameter() }; - - ImageConfiguration config = resolveExternalImageConfig(mergeArrays(getBaseTestData(), testData)); - assertThat(config.getBuildConfiguration().cleanupMode()).isEqualTo(mode); - } - - @Test - void testResolve() { - ImageConfiguration resolved = resolveExternalImageConfig(mergeArrays(getBaseTestData(), getTestData())); - - validateBuildConfiguration(resolved.getBuildConfiguration()); - //validateWaitConfiguraion(resolved.getRunConfiguration().getWaitConfiguration()); - } - - protected void validateEnv(Map env) { - assertThat(env).containsEntry("HOME", "/Users/roland"); - } - - private ImageConfiguration buildAnUnresolvedImage() { - return ImageConfiguration.builder() - .build(BuildConfiguration.builder().build()) - .external(new HashMap<>()) - .build(); - } - - private Map externalMode(PropertyMode mode) { - Map external = new HashMap<>(); - if(mode != null) { - external.put("type", "properties"); - external.put("mode", mode.name()); - } - return external; - } - - private void makeExternalConfigUse() { - Map external = imageConfiguration.getExternalConfig(); - external.put("type", "properties"); - external.put("mode", PropertyMode.Override.name()); - } - - private List resolveImage(ImageConfiguration image, final Properties properties) { - when(javaProject.getProperties()).thenReturn(properties); - when(javaProject.getBaseDirectory()).thenReturn(new File("./")); - return configHandler.resolve(image, javaProject); - } - - private ImageConfiguration resolveExternalImageConfig(String[] testData) { - Map external = new HashMap<>(); - external.put("type", "props"); - - ImageConfiguration config = ImageConfiguration.builder().name("image") - .alias("alias") - .external(external) - .build(BuildConfiguration.builder().build()) - .build(); - - List resolvedImageConfigs = resolveImage(config, props(testData)); - assertThat(resolvedImageConfigs).hasSize(1); - - return resolvedImageConfigs.get(0); - } - - private void validateBuildConfiguration(BuildConfiguration buildConfig) { - assertThat(buildConfig) - .returns(CleanupMode.TRY_TO_REMOVE, BuildConfiguration::cleanupMode) - .returns("command.sh", c -> c.getCmd().getShell()) - .returns("image", BuildConfiguration::getFrom) - .returns("image-ext", c -> c.getFromExt().get("name")) - .returns(a("8080", "8080"), BuildConfiguration::getPorts) - .returns(a("/vol1", "/foo"), BuildConfiguration::getVolumes) - .returns("fabric8io@redhat.com", BuildConfiguration::getMaintainer) - .returns(null, BuildConfiguration::getNocache) - .returns("Always", BuildConfiguration::getImagePullPolicy) - .returns(null, c -> c.getAssembly().getUser()) - .returns(null, c -> c.getAssembly().getExportTargetDir()); - /* - * validate only the descriptor is required and defaults are all used, 'testAssembly' validates - * all options can be set - */ - validateEnv(buildConfig.getEnv()); - validateLabels(buildConfig.getLabels()); - validateArgs(buildConfig.getArgs()); - validateBuildOptions(buildConfig.getBuildOptions()); - } - - private void validateArgs(Map args) { - assertThat(args).containsEntry("PROXY", "http://proxy"); - } - - private void validateLabels(Map labels) { - assertThat(labels).containsEntry("com.acme.label", "Hello\"World"); - } - - private void validateBuildOptions(Map buildOptions) { - assertThat(buildOptions).containsEntry("shmsize", "2147483648"); - } - - private Properties props(String ... args) { - Properties ret = new Properties(); - for (int i = 0; i < args.length; i += 2) { - ret.setProperty(args[i], args[i + 1]); - } - return ret; - } - - private String[] getTestData() { - return new String[] { - k(ConfigKey.ALIAS), "alias", - k(ConfigKey.BIND) + ".1", "/foo", - k(ConfigKey.BIND) + ".2", "/tmp:/tmp", - k(ConfigKey.CAP_ADD) + ".1", "CAP", - k(ConfigKey.CAP_DROP) + ".1", "CAP", - k(ConfigKey.SECURITY_OPTS) + ".1", "seccomp=unconfined", - k(ConfigKey.CPUS), "1000000000", - k(ConfigKey.CPUSET), "0,1", - k(ConfigKey.CPUSHARES), "1", - k(ConfigKey.CMD), "command.sh", - k(ConfigKey.DNS) + ".1", "8.8.8.8", - k(ConfigKey.NET), "host", - k(ConfigKey.DNS_SEARCH) + ".1", "example.com", - k(ConfigKey.DOMAINNAME), "domain.com", - k(ConfigKey.ENTRYPOINT), "entrypoint.sh", - k(ConfigKey.ENV) + ".HOME", "/Users/roland", - k(ConfigKey.ARGS) + ".PROXY", "http://proxy", - k(ConfigKey.LABELS) + ".com.acme.label", "Hello\"World", - k(ConfigKey.BUILD_OPTIONS) + ".shmsize", "2147483648", - k(ConfigKey.ENV_PROPERTY_FILE), "/tmp/envProps.txt", - k(ConfigKey.EXTRA_HOSTS) + ".1", "localhost:127.0.0.1", - k(ConfigKey.FROM), "image", - k(ConfigKey.FROM_EXT) + ".name", "image-ext", - k(ConfigKey.FROM_EXT) + ".kind", "kind", - k(ConfigKey.HOSTNAME), "subdomain", - k(ConfigKey.LINKS) + ".1", "redis", - k(ConfigKey.MAINTAINER), "fabric8io@redhat.com", - k(ConfigKey.MEMORY), "1", - k(ConfigKey.MEMORY_SWAP), "1", - k(ConfigKey.NAME), "image", - k(ConfigKey.PORT_PROPERTY_FILE), "/tmp/props.txt", - k(ConfigKey.PORTS) + ".1", "8081:8080", - k(ConfigKey.PRIVILEGED), "true", - k(ConfigKey.REGISTRY), "registry", - k(ConfigKey.RESTART_POLICY_NAME), "on-failure", - k(ConfigKey.RESTART_POLICY_RETRY), "1", - k(ConfigKey.USER), "tomcat", - k(ConfigKey.ULIMITS)+".1", "memlock=10:10", - k(ConfigKey.ULIMITS)+".2", "memlock=:-1", - k(ConfigKey.ULIMITS)+".3", "memlock=1024:", - k(ConfigKey.ULIMITS)+".4", "memlock=2048", - k(ConfigKey.VOLUMES) + ".1", "/foo", - k(ConfigKey.VOLUMES_FROM) + ".1", "from", - k(ConfigKey.WAIT_EXEC_PRE_STOP), "pre_stop_command", - k(ConfigKey.WAIT_EXEC_POST_START), "post_start_command", - k(ConfigKey.WAIT_EXEC_BREAK_ON_ERROR), "true", - k(ConfigKey.WAIT_LOG), "pattern", - k(ConfigKey.WAIT_HEALTHY), "true", - k(ConfigKey.WAIT_TIME), "5", - k(ConfigKey.WAIT_EXIT), "0", - k(ConfigKey.WAIT_URL), "http://foo.com", - k(ConfigKey.LOG_PREFIX), "SRV", - k(ConfigKey.LOG_COLOR), "green", - k(ConfigKey.LOG_ENABLED), "true", - k(ConfigKey.LOG_DATE), "iso8601", - k(ConfigKey.LOG_DRIVER_NAME), "json", - k(ConfigKey.LOG_DRIVER_OPTS) + ".max-size", "1024", - k(ConfigKey.LOG_DRIVER_OPTS) + ".max-file", "10", - k(ConfigKey.WORKING_DIR), "foo", - k(ConfigKey.TMPFS) + ".1", "/var/lib/mysql:10m", - k(ConfigKey.IMAGE_PULL_POLICY_BUILD), "Always", - k(ConfigKey.IMAGE_PULL_POLICY_RUN), "Never", - k(ConfigKey.READ_ONLY), "true", - k(ConfigKey.AUTO_REMOVE), "true", - }; - } - - private String[] getSkipTestData(ConfigKey key, boolean value) { - String[] baseData = getBaseTestData(); - String[] data = new String[baseData.length + 6]; - System.arraycopy(baseData, 0, data, 0, baseData.length); - - data[baseData.length] = k(ConfigKey.NAME); - data[baseData.length + 1] = "image"; - data[baseData.length + 2] = k(key); - data[baseData.length + 3] = String.valueOf(value); - data[baseData.length + 4] = k(ConfigKey.FROM); - data[baseData.length + 5] = "busybox"; - return data; - } - - private String k(ConfigKey from) { - return from.asPropertyKey(); - } - - private List a(String ... args) { - return Arrays.asList(args); - } - - private String[] getBaseTestData() { - return new String[] { - "docker.name", "docker-project", - "docker.from", "docker-from:ladocker", - "docker.cachefrom", "docker-image:ladocker", - "docker.args.foo", "bar", - "docker.labels.foo", "l1", - "docker.ports.p1", "8080", - "docker.run.0", "xyz", - "docker.volumes.0", "/vol1", - "docker.tags.0", "0.1.0" - }; - } - - private String[] mergeArrays(String[] a, String[] b) { - String[] mergedArr = new String[a.length + b.length]; - int mergedIndex = 0; - for (String s : a) mergedArr[mergedIndex++] = s; - for (String s : b) mergedArr[mergedIndex++] = s; - return mergedArr; - } -} diff --git a/jkube-kit/build/api/src/test/java/org/eclipse/jkube/kit/build/api/config/property/PropertyConfigResolverTest.java b/jkube-kit/build/api/src/test/java/org/eclipse/jkube/kit/build/api/config/property/PropertyConfigResolverTest.java new file mode 100644 index 0000000000..ba26be8c9c --- /dev/null +++ b/jkube-kit/build/api/src/test/java/org/eclipse/jkube/kit/build/api/config/property/PropertyConfigResolverTest.java @@ -0,0 +1,301 @@ +/* + * 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.config.property; + +import org.eclipse.jkube.kit.common.Arguments; +import org.eclipse.jkube.kit.common.JavaProject; +import org.eclipse.jkube.kit.config.image.ImageConfiguration; +import org.eclipse.jkube.kit.config.image.build.BuildConfiguration; +import org.eclipse.jkube.kit.config.image.build.HealthCheckConfiguration; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; + +import java.util.Properties; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.entry; + +class PropertyConfigResolverTest { + + private PropertyConfigResolver propertyConfigResolver; + private ImageConfiguration initialImageConfiguration; + private JavaProject javaProject; + + @BeforeEach + void setUp() { + propertyConfigResolver = new PropertyConfigResolver(); + initialImageConfiguration = ImageConfiguration.builder() + .name("initial-name") + .alias("initial-alias") + .build(BuildConfiguration.builder() + .cmd(Arguments.builder().shell("./initial-cmd").build()) + .cleanup("none") + .imagePullPolicy("Never") + .from("initial-from") + .addCacheFrom("initial-cache-from-image:0.0.0") + .putEnv("VAR_1", "INITIAL_VALUE_1") + .label("label-1", "value-1") + .port("8082") + .port("9082") + .tag("initial-tag-1") + .tag("initial-tag-2") + .healthCheck(HealthCheckConfiguration.builder() + .interval("30s") + .build()) + .build()) + .build(); + javaProject = JavaProject.builder() + .properties(new Properties()) + .build(); + javaProject.getProperties().put("jkube.container-image.name", "image-name"); + javaProject.getProperties().put("jkube.container-image.alias", "image-alias"); + javaProject.getProperties().put("jkube.container-image.cmd", "./cmd"); + javaProject.getProperties().put("jkube.container-image.cleanup", "try"); + javaProject.getProperties().put("jkube.container-image.imagePullPolicy", "Always"); + javaProject.getProperties().put("jkube.container-image.from", "busybox"); + javaProject.getProperties().put("jkube.container-image.cachefrom.1", "cache-from-image:0.0.1"); + javaProject.getProperties().put("jkube.container-image.env.VAR_2", "VALUE_2"); + javaProject.getProperties().put("jkube.container-image.envBuild.VAR_3", "VALUE_3"); + javaProject.getProperties().put("jkube.container-image.labels.label-from-property", "value-2"); + javaProject.getProperties().put("jkube.container-image.ports.1", "8080"); + javaProject.getProperties().put("jkube.container-image.ports.2", "9080"); + javaProject.getProperties().put("jkube.container-image.tags.1", "tag-1"); + javaProject.getProperties().put("jkube.container-image.tags.2", "tag-2"); + javaProject.getProperties().put("jkube.container-image.healthcheck.interval", "10s"); + } + + @Test + void usesPrefixForPropertyResolution() { + javaProject.getProperties().put("app.images.image-1.name", "prefixed-image-name"); + initialImageConfiguration = initialImageConfiguration.toBuilder().propertyResolverPrefix("app.images.image-1").build(); + final ImageConfiguration resolved = propertyConfigResolver.resolve(initialImageConfiguration, javaProject); + assertThat(resolved.getName()).isEqualTo("prefixed-image-name"); + } + + @Nested + class SetsIfNotInConfig { + + private ImageConfiguration resolved; + + @BeforeEach + void setUp() { + final ImageConfiguration empty = new ImageConfiguration(); + resolved = propertyConfigResolver.resolve(empty, javaProject); + } + + @Test + void setsName() { + assertThat(resolved.getName()).isEqualTo("image-name"); + } + + @Test + void setsAlias() { + assertThat(resolved.getAlias()).isEqualTo("image-alias"); + } + + @Test + void setsCmd() { + assertThat(resolved.getBuild().getCmd().asStrings()).singleElement().isEqualTo("./cmd"); + } + + @Test + void setsCleanup() { + assertThat(resolved.getBuild().getCleanup()).isEqualTo("try"); + } + + @Test + void setsImagePullPolicy() { + assertThat(resolved.getBuild().getImagePullPolicy()).isEqualTo("Always"); + } + + @Test + void setsFrom() { + assertThat(resolved.getBuild().getFrom()).isEqualTo("busybox"); + } + + @Test + void setsCacheFrom() { + assertThat(resolved.getBuild().getCacheFrom()).containsExactly("cache-from-image:0.0.1"); + } + + @Test + void setsEnv() { + assertThat(resolved.getBuild().getEnv()) + .containsOnly( + entry("VAR_2", "VALUE_2"), + entry("VAR_3", "VALUE_3") + ); + } + + @Test + void setsLabels() { + assertThat(resolved.getBuild().getLabels()).containsOnly(entry("label-from-property", "value-2")); + } + + @Test + void setsPorts() { + assertThat(resolved.getBuild().getPorts()).containsExactlyInAnyOrder("8080", "9080"); + } + + @Test + void setsTags() { + assertThat(resolved.getBuild().getTags()).containsExactlyInAnyOrder("tag-1", "tag-2"); + } + + @Test + void setsHealthCheckInterval() { + assertThat(resolved.getBuild().getHealthCheck().getInterval()).isEqualTo("10s"); + } + } + + @Nested + class OverridesIfInConfig { + + private ImageConfiguration resolved; + + @BeforeEach + void setUp() { + resolved = propertyConfigResolver.resolve(initialImageConfiguration, javaProject); + } + + @Test + void overridesName() { + assertThat(resolved.getName()).isEqualTo("image-name"); + } + + @Test + void overridesAlias() { + assertThat(resolved.getAlias()).isEqualTo("image-alias"); + } + + @Test + void overridesCmd() { + assertThat(resolved.getBuild().getCmd().asStrings()).singleElement().isEqualTo("./cmd"); + } + + @Test + void overridesCleanup() { + assertThat(resolved.getBuild().getCleanup()).isEqualTo("try"); + } + + @Test + void overridesImagePullPolicy() { + assertThat(resolved.getBuild().getImagePullPolicy()).isEqualTo("Always"); + } + + @Test + void overridesFrom() { + assertThat(resolved.getBuild().getFrom()).isEqualTo("busybox"); + } + + @Test // Replace combine policy + void overridesCacheFrom() { + assertThat(resolved.getBuild().getCacheFrom()).containsExactlyInAnyOrder("cache-from-image:0.0.1"); + } + + @Test + void mergesEnv() { + assertThat(resolved.getBuild().getEnv()) + .containsOnly( + entry("VAR_1", "INITIAL_VALUE_1"), + entry("VAR_2", "VALUE_2"), + entry("VAR_3", "VALUE_3") + ); + } + + @Test // Merge combine policy + void appendsPorts() { + assertThat(resolved.getBuild().getPorts()).containsExactlyInAnyOrder("8080", "9080", "8082", "9082"); + } + + @Test + void appendsTags() { + assertThat(resolved.getBuild().getTags()).containsExactlyInAnyOrder("tag-1", "tag-2", "initial-tag-1", "initial-tag-2"); + } + + @Test + void overridesHealthCheckInterval() { + assertThat(resolved.getBuild().getHealthCheck().getInterval()).isEqualTo("10s"); + } + } + + @Nested + class PreservesInConfig { + + private ImageConfiguration resolved; + + @BeforeEach + void setUp() { + javaProject.getProperties().clear(); + resolved = propertyConfigResolver.resolve(initialImageConfiguration, javaProject); + } + + @Test + void preservesName() { + assertThat(resolved.getName()).isEqualTo("initial-name"); + } + + @Test + void preservesAlias() { + assertThat(resolved.getAlias()).isEqualTo("initial-alias"); + } + + @Test + void preservesCmd() { + assertThat(resolved.getBuild().getCmd().asStrings()).singleElement().isEqualTo("./initial-cmd"); + } + + @Test + void preservesCleanup() { + assertThat(resolved.getBuild().getCleanup()).isEqualTo("none"); + } + + @Test + void preservesImagePullPolicy() { + assertThat(resolved.getBuild().getImagePullPolicy()).isEqualTo("Never"); + } + + @Test + void preservesFrom() { + assertThat(resolved.getBuild().getFrom()).isEqualTo("initial-from"); + } + + @Test + void preservesCacheFrom() { + assertThat(resolved.getBuild().getCacheFrom()).containsExactly("initial-cache-from-image:0.0.0"); + } + + @Test + void preservesEnv() { + assertThat(resolved.getBuild().getEnv()).containsOnly(entry("VAR_1", "INITIAL_VALUE_1")); + } + + @Test + void preservesPorts() { + assertThat(resolved.getBuild().getPorts()).containsExactlyInAnyOrder("8082", "9082"); + } + + @Test + void preservesTags() { + assertThat(resolved.getBuild().getTags()).containsExactlyInAnyOrder("initial-tag-1", "initial-tag-2"); + } + + @Test + void preservesHealthCheckInterval() { + assertThat(resolved.getBuild().getHealthCheck().getInterval()).isEqualTo("30s"); + } + } + +} diff --git a/jkube-kit/build/api/src/test/java/org/eclipse/jkube/kit/build/api/helper/ConfigHelperTest.java b/jkube-kit/build/api/src/test/java/org/eclipse/jkube/kit/build/api/helper/ConfigHelperTest.java deleted file mode 100644 index e819f474f9..0000000000 --- a/jkube-kit/build/api/src/test/java/org/eclipse/jkube/kit/build/api/helper/ConfigHelperTest.java +++ /dev/null @@ -1,75 +0,0 @@ -/* - * 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 java.io.File; -import java.util.Arrays; -import java.util.Collections; -import java.util.List; -import java.util.Properties; - -import org.eclipse.jkube.kit.common.JavaProject; -import org.eclipse.jkube.kit.config.image.ImageConfiguration; -import org.eclipse.jkube.kit.config.image.build.BuildConfiguration; - -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - -import static org.assertj.core.api.Assertions.assertThatIllegalStateException; - -class ConfigHelperTest { - private JavaProject javaProject; - - @BeforeEach - void setUp() { - javaProject = JavaProject.builder() - .groupId("org.eclipse.jkube") - .artifactId("test-java-project") - .version("0.0.1-SNAPSHOT") - .properties(new Properties()) - .baseDirectory(new File("dummy-dir")) - .build(); - } - - @Test - void validateExternalPropertyActivation_withMultipleImagesWithoutExplicitExternalConfig_shouldThrowException() { - // Given - javaProject.getProperties().put("docker.imagePropertyConfiguration", "Override"); - ImageConfiguration i1 = createNewDummyImageConfiguration(); - ImageConfiguration i2 = createNewDummyImageConfiguration().toBuilder() - .name("imageconfig2") - .external(Collections.singletonMap("type", "compose")) - .build(); - ImageConfiguration i3 = createNewDummyImageConfiguration() - .toBuilder() - .name("external") - .external(Collections.singletonMap("type", "properties")) - .build(); - List images = Arrays.asList(i1, i2, i3); - - // When + Then - assertThatIllegalStateException() - .isThrownBy(() -> ConfigHelper.validateExternalPropertyActivation(javaProject, images)) - .withMessage("Configuration error: Cannot use property docker.imagePropertyConfiguration on projects with multiple images without explicit image external configuration."); - } - - private ImageConfiguration createNewDummyImageConfiguration() { - return ImageConfiguration.builder() - .name("foo/bar:latest") - .build(BuildConfiguration.builder() - .from("foobase:latest") - .build()) - .build(); - } -} diff --git a/jkube-kit/config/image/src/main/java/org/eclipse/jkube/kit/config/image/ImageConfiguration.java b/jkube-kit/config/image/src/main/java/org/eclipse/jkube/kit/config/image/ImageConfiguration.java index b87e00928d..efcb771241 100644 --- a/jkube-kit/config/image/src/main/java/org/eclipse/jkube/kit/config/image/ImageConfiguration.java +++ b/jkube-kit/config/image/src/main/java/org/eclipse/jkube/kit/config/image/ImageConfiguration.java @@ -14,7 +14,6 @@ package org.eclipse.jkube.kit.config.image; import java.io.Serializable; -import java.util.Map; import org.eclipse.jkube.kit.config.image.build.BuildConfiguration; @@ -34,7 +33,7 @@ @EqualsAndHashCode public class ImageConfiguration implements Serializable { /** - * Change the name which can be useful in long running runs e.g. for updating + * Change the name which can be useful in long-running runs e.g. for updating * images when doing updates. Use with caution and only for those circumstances. * * @param name image name to set. @@ -43,22 +42,18 @@ public class ImageConfiguration implements Serializable { private String alias; private BuildConfiguration build; private WatchImageConfiguration watch; - /** - * Override externalConfiguration when defined via special property. - * - * @param external Map with alternative config - */ - private Map external; private String registry; - /** - * Override externalConfiguration when defined via special property. + * Prefix to use for property resolution. + * + *

If not set, properties are resolved from jkube.container-image.xxx. + * The image name property would be resolved from jkube.container-image.name. * - * @param externalConfiguration Map with alternative config + *

Use this to narrow down the properties to use for image configuration resolution. + * For example, if set with app.images.image-1, properties are resolved from app.images.image-1.xxx. + * The image name property is then resolved from app.images.image-1.name. */ - public void setExternalConfiguration(Map externalConfiguration) { - this.external = externalConfiguration; - } + private String propertyResolverPrefix; public BuildConfiguration getBuildConfiguration() { return build; @@ -68,13 +63,8 @@ public WatchImageConfiguration getWatchConfiguration() { return watch; } - public Map getExternalConfig() { - return external; - } - public String getDescription() { return String.format("[%s] %s", new ImageName(name).getFullName(), (alias != null ? "\"" + alias + "\"" : "")).trim(); } - } diff --git a/jkube-kit/config/image/src/main/java/org/eclipse/jkube/kit/config/image/build/HealthCheckConfiguration.java b/jkube-kit/config/image/src/main/java/org/eclipse/jkube/kit/config/image/build/HealthCheckConfiguration.java index 7407e2c78c..7aeda4fdbf 100644 --- a/jkube-kit/config/image/src/main/java/org/eclipse/jkube/kit/config/image/build/HealthCheckConfiguration.java +++ b/jkube-kit/config/image/src/main/java/org/eclipse/jkube/kit/config/image/build/HealthCheckConfiguration.java @@ -26,7 +26,7 @@ /** * Build configuration for health checks. */ -@Builder +@Builder(toBuilder = true) @AllArgsConstructor @NoArgsConstructor @Getter diff --git a/jkube-kit/doc/src/main/asciidoc/inc/build/_assembly.adoc b/jkube-kit/doc/src/main/asciidoc/inc/build/_assembly.adoc index 75277315e6..6887dc7b26 100644 --- a/jkube-kit/doc/src/main/asciidoc/inc/build/_assembly.adoc +++ b/jkube-kit/doc/src/main/asciidoc/inc/build/_assembly.adoc @@ -6,35 +6,41 @@ logic (see <> for more details). [[config-image-build-assembly]] .Assembly Configuration (<> : <>) -[cols="1,5"] +[cols="1,5,1"] |=== -| Element | Description +| Element | Description | Property | *name* | Assembly name, which is `maven` by default. This name is used for the archives and directories created during the build. This directory holds the files specified by the assembly. If an <> is used then this name is also the relative directory which contains the assembly files. +| | *targetDir* | Directory under which the files and artifacts contained in the assembly will be copied within the container. The default value for this is `${assembly.name}`, so `/maven` if *name* is not set to a different value. +| | <> | _Deprecated: Use layers instead_ Inlined assembly descriptor as described in <> below. +| | <> | Each of the layers that the assembly will contain as described in <> below. +| | *exportTargetDir* | Specification whether the `targetDir` should be exported as a volume. This value is `true` by default except in the case the `targetDir` is set to the container root (`/`). It is also `false` by default when a base image is used with `from` since exporting makes no sense in this case and will waste disk space unnecessarily. +| `jkube.container-image.assembly.exportTargetDir` | *excludeFinalOutputArtifact* | By default, the project's final artifact will be included in the assembly, set this flag to true in case the artifact should be excluded from the assembly. +| | *mode* a| Mode how the assembled files should be collected: @@ -45,6 +51,7 @@ a| Mode how the assembled files should be collected: * `zip` : Transfer via ZIP archive The archive formats have the advantage that file permission can be preserved better (since the copying is independent from the underlying files systems) +| `jkube.container-image.assembly.mode` | *permissions* a| Permission of the files to add: @@ -56,9 +63,11 @@ assembly configuration * `auto` to let the plugin select `exec` on Windows and `keep` on others. `keep` is the default value. +| `jkube.container-image.assembly.permissions` | *tarLongFileMode* | Sets the TarArchiver behaviour on file paths with more than 100 characters length. Valid values are: "warn"(default), "fail", "truncate", "gnu", "posix", "posix_warn" or "omit" +| `jkube.container-image.assembly.tarLongFileMode` | *user* a| User and/or group under which the files should be added. The user must already exist in the base image. @@ -69,6 +78,7 @@ If a third part is given, then the build changes to user `root` before changing For example, the image `jboss/wildfly` use a "jboss" user under which all commands are executed. Adding files in Docker always happens under the UID root. These files can only be changed to "jboss" is the `chown` command is executed as root. For the following commands to be run again as "jboss" (like the final `standalone.sh`), the plugin switches back to user `jboss` (this is this "run-user") after changing the file ownership. For this example a specification of `jboss:jboss:jboss` would be required. +| `jkube.container-image.assembly.user` |=== In the event you do not need to include any artifacts with the image, you may safely omit this element from the configuration. diff --git a/jkube-kit/doc/src/main/asciidoc/inc/build/_configuration.adoc b/jkube-kit/doc/src/main/asciidoc/inc/build/_configuration.adoc index 385295d3d9..4918e6bb63 100644 --- a/jkube-kit/doc/src/main/asciidoc/inc/build/_configuration.adoc +++ b/jkube-kit/doc/src/main/asciidoc/inc/build/_configuration.adoc @@ -3,23 +3,26 @@ of an image configuration. The following configuration options are supported: [[config-image-build]] .Build configuration (<>) -[cols="1,5"] +[cols="1,5,1"] |=== -| Element | Description +| Element | Description | Property | <> | Specifies the assembly configuration as described in <> +| `jkube.container-image.assembly.xxx` | <> | Map specifying the value of https://docs.docker.com/engine/reference/commandline/build/#set-build-time-variables-build-arg[Docker build args] which should be used when building the image with an external Dockerfile which uses build arguments. The key-value syntax is the same as when defining {plugin-type} properties (or `labels` or `env`). This argument is ignored when no external Dockerfile is used. Build args can also be specified as properties as described in <> +| `jkube.container-image.args` | *buildOptions* | Map specifying the build options to provide to the docker daemon when building the image. These options map to the ones listed as query parameters in the https://docs.docker.com/engine/reference/api/docker_remote_api_v1.24/#build-image-from-a-dockerfile[Docker Remote API] and are restricted to simple options (e.g.: memory, shmsize). If you use the respective configuration options for build options natively supported by the build configuration (i.e. `noCache`, `cleanup=remove` for buildoption `forcerm=1` and `args` for build args) then these will override any corresponding options given here. The key-value syntax is the same as when defining environment variables or labels as described in <>. +| `jkube.container-image.buildOptions` | *createImageOptions* | Map specifying the create image options to provide to the docker daemon when pulling or @@ -27,15 +30,19 @@ https://docs.docker.com/engine/reference/api/docker_remote_api_v1.24/#build-imag These options map to the ones listed as query parameters in the https://docs.docker.com/engine/api/v1.41/#operation/ImageCreate[Docker Remote API] and are restricted to simple options (e.g.: fromImage, fromSrc, platform). +| | *cleanup* | Cleanup dangling (untagged) images after each build (including any containers created from them). Default is `try` which tries to remove the old image, but doesn't fail the build if this is not possible because e.g. the image is still used by a running container. Use `remove` if you want to fail the build and `none` if no cleanup is requested. +| `jkube.container-image.cleanup` | [[context-dir]]*contextDir* | Path to a directory used for the build's context. You can specify the `Dockerfile` to use with *dockerFile*, which by default is the Dockerfile found in the `contextDir`. The Dockerfile can be also located outside of the `contextDir`, if provided with an absolute file path. See <> for details. +| `jkube.container-image.contextDir` | <> | A command to execute by default (i.e. if no command is provided when a container for this image is started). See <> for details. +| `jkube.container-image.cmd` | *compression* | @@ -45,21 +52,29 @@ endif::[] ifeval::["{plugin-type}" == "maven"] The compression mode how the build archive is transmitted to the docker daemon (`{goal-prefix}:build`) and how docker build archives are attached to this build as sources. The value can be `none` (default), `gzip` or `bzip2`. endif::[] +| | *dockerFile* | Path to a `Dockerfile` which also triggers _Dockerfile mode_. See <> for details. +| `jkube.container-image.dockerFile` | *dockerArchive* | Path to a saved image archive which is then imported. See <> for details. +| `jkube.container-image.dockerArchive` | <> | An entrypoint allows you to configure a container that will run as an executable. See <> for details. +| `jkube.container-image.entrypoint` | <> | The environments as described in <>. +| `jkube.container-image.env` + + e.g. `jkube.container-image.env.FOO=bar` | *filter* | Enable and set the delimiters for property replacements. By default, properties in the format `${..}` are replaced with {plugin-type} properties. You can switch off property replacement by setting this property to `false`. When using a single char like `@` then this is used as a delimiter (e.g `@...@`). See <> for more details. +| `jkube.container-image.filter` | [[build-config-from]]*from* | The base image which should be used for this image. If not given this default to `busybox:latest` and is suitable for a pure data image. @@ -69,11 +84,13 @@ endif::[] ifeval::["{goal-prefix}" == "oc"] In case of an <> this parameter specifies the S2I Builder Image to use, which by default is `fabric8/s2i-java:latest`. See also <> how to add additional properties for the base image. endif::[] +| `jkube.container-image.from` | *buildpacksBuilderImage* | Configure https://buildpacks.io/docs/for-platform-operators/concepts/builder/[BuildPack builder] OCI image for BuildPack Build. This field is applicable only in `buildpacks` build strategy. This overrides builder image specified in local `~/.pack/config.toml`. If not specified this defaults to `null`. -This field is only applicable for `buildpacks` build strategy. + This field is only applicable for `buildpacks` build strategy. +| | [[build-config-from-ext]]**fromExt** a| Extended definition for a base image. This field holds a map of defined in `key = "value"` format. The known keys are: @@ -88,53 +105,81 @@ ifeval::["{goal-prefix}" == "oc"] * `namespace` : Namespace where this builder image lives. endif::[] -A provided `from` takes precedence over the name given here. This tag is useful for extensions of this plugin. + A provided `from` takes precedence over the name given here. This tag is useful for extensions of this plugin. +| `jkube.container-image.fromExt` + +| <> +| Specifies the health check configuration as described in <> +| `jkube.container-image.healthcheck.xxx` | *imagePullPolicy* | Specific pull policy for the base image. This overwrites any global pull policy. See the global configuration option <> for the possible values and the default. +| `jkube.container-image.imagePullPolicy` | <> | Labels as described in <>. +| `jkube.container-image.labels` + + e.g. `jkube.container-image.label.foo=bar` | *maintainer* | The author (`MAINTAINER`) field for the generated image +| `jkube.container-image.maintainer` | *noCache* | Don't use Docker's build cache. This can be overwritten by setting a system property `docker.noCache` when running {plugin-type}. +| `jkube.container-image.nocache` | *cacheFrom* | A list of `image` elements specifying image names to use as cache sources. +| `jkube.container-image.cachefrom` + + e.g. `jkube.container-image.cachefrom.1=my-cache-image:0.0.1` | *optimise* | if set to true then it will compress all the `runCmds` into a single `RUN` directive so that only one image layer is created. +| `jkube.container-image.optimise` | *ports* | The exposed ports which is a list of `port` elements, one for each port to expose. Whitespace is trimmed from each element and empty elements are ignored. The format can be either pure numerical ("8080") or with the protocol attached ("8080/tcp"). +| `jkube.container-image.ports` + + e.g. `jkube.container-image.ports.1=8080` | *shell* | Shell to be used for the *runCmds*. It contains *arg* elements which are defining the executable and its params. +| `jkube.container-image.shell` | *runCmds* | Commands to be run during the build process. It contains *run* elements which are passed to the shell. Whitespace is trimmed from each element and empty elements are ignored. The run commands are inserted right after the assembly and after *workdir* into the Dockerfile. +| `jkube.container-image.runCmds` + + e.g. `jkube.container-image.runCmds.1=groupadd -r appUser` | *skip* | if set to true disables building of the image. This config option is best used together with a {plugin-type} property - -| *skipTag* -| If set to `true` this plugin won't add any tags to images. +| `jkube.container-image.skip` | *tags* | List of additional `tag` elements with which an image is to be tagged after the build. Whitespace is trimmed from each element and empty elements are ignored. +| `jkube.container-image.tags` + + e.g. `jkube.container-image.tags.1=latest` | *user* | User to which the Dockerfile should switch to the end (corresponds to the `USER` Dockerfile directive). +| `jkube.container-image.user` | *volumes* | List of `volume` elements to create a container volume. Whitespace is trimmed from each element and empty elements are ignored. +| `jkube.container-image.volumes` + + e.g. `jkube.container-image.volumes.1=/path/to/expose` | *workdir* | Directory to change to when starting the container. +| `jkube.container-image.workdir` |=== diff --git a/jkube-kit/doc/src/main/asciidoc/inc/build/_healthcheck.adoc b/jkube-kit/doc/src/main/asciidoc/inc/build/_healthcheck.adoc index b58a5105c3..842812df38 100644 --- a/jkube-kit/doc/src/main/asciidoc/inc/build/_healthcheck.adoc +++ b/jkube-kit/doc/src/main/asciidoc/inc/build/_healthcheck.adoc @@ -3,29 +3,36 @@ Healthchecks has been introduced since Docker 1.12 and are a way to tell Docker The healtcheck configuration can have the following options -.Healthcheck Configuration -[cols="1,5"] +[[config-image-build-healthcheck]] +.Healthcheck Configuration (<> : <>) +[cols="1,5,1"] |=== -| Element | Description +| Element | Description | Property | *cmd* | Command to execute, which can be given in an shell or exec format as described in <>. +| `jkube.container-image.healthcheck.cmd` | *interval* | Interval for how often to run the healthcheck. The time is specified in seconds, but a time unit can be appended to change this. +| `jkube.container-image.healthcheck.interval` | *mode* | Mode of the healthcheck. This can be `cmd` which is the default and specifies that the health check should be executed. Or `none` to disable a health check from the base image. Only use this option with `none` for disabling some healthcheck from the base image. +| `jkube.container-image.healthcheck.mode` | *retries* | How many retries should be performed before the container is to be considered unhealthy. +| `jkube.container-image.healthcheck.retries` | *startPeriod* | Initialization time for containers that need time to bootstrap. Probe failure during that period will not be counted towards the maximum number of retries. However, if a health check succeeds during the start period, the container is considered started and all consecutive failures will be counted towards the maximum number of retries. Given in seconds, but another time unit can be appended. +| `jkube.container-image.healthcheck.startPeriod` | *timeout* | Timeout after which healthckeck should be stopped and considered to have failed. Given in seconds, but another time unit can be appended. +| `jkube.container-image.healthcheck.timeout` |=== The following example queries an URL every 10s as an healthcheck: diff --git a/jkube-kit/doc/src/main/asciidoc/inc/image/_configuration.adoc b/jkube-kit/doc/src/main/asciidoc/inc/image/_configuration.adoc index c45b767849..d4eb24e6af 100644 --- a/jkube-kit/doc/src/main/asciidoc/inc/image/_configuration.adoc +++ b/jkube-kit/doc/src/main/asciidoc/inc/image/_configuration.adoc @@ -1,20 +1,23 @@ [[config-image]] .Image Configuration -[cols="1,5"] +[cols="1,5,1"] |=== -| Element | Description +| Element | Description | Property | <> -| Each `image` configuration has a mandatory, unique docker -repository _name_. This can include registry and tag parts, but also placeholder parameters. See below for a detailed explanation. +| Each `image` configuration has a mandatory, unique docker repository _name_. + This can include registry and tag parts, but also placeholder parameters. + See below for a detailed explanation. +| `jkube.container-image.name` | *alias* -| Shortcut name for an image which can be used for -identifying the image within this configuration. This is used when -linking images together or for specifying it with the global *image* configuration element. +| Shortcut name for an image which can be used for identifying the image within this configuration. + This is used when linking images together or for specifying it with the global *image* configuration element. +| `jkube.container-image.alias` | <> | Registry to use for this image. If the `name` already contains a registry this takes precedence. See <> for more details. +| | <> | @@ -25,6 +28,13 @@ ifeval::["{plugin-type}" == "gradle"] Element which contains all the configuration aspects when doing a <>. endif::[] -This element can be omitted if the image is only pulled from a registry e.g. as support for integration tests like database images. + This element can be omitted if the image is only pulled from a registry. + e.g. as support for integration tests like database images. +| + +| *propertyResolverPrefix* +| Prefix for property resolution. This is used to resolve properties in the configuration. + If not set, the default prefix is `jkube.container-image`. +| |=== diff --git a/jkube-kit/generator/api/src/main/java/org/eclipse/jkube/generator/api/DefaultGeneratorManager.java b/jkube-kit/generator/api/src/main/java/org/eclipse/jkube/generator/api/DefaultGeneratorManager.java index 66b5af8ebc..5fd598c33e 100644 --- a/jkube-kit/generator/api/src/main/java/org/eclipse/jkube/generator/api/DefaultGeneratorManager.java +++ b/jkube-kit/generator/api/src/main/java/org/eclipse/jkube/generator/api/DefaultGeneratorManager.java @@ -21,7 +21,7 @@ import java.util.stream.Collectors; import org.apache.commons.lang3.StringUtils; -import org.eclipse.jkube.kit.build.api.helper.ImageConfigResolver; +import org.eclipse.jkube.kit.build.api.config.property.PropertyConfigResolver; import org.eclipse.jkube.kit.build.api.helper.ImageNameFormatter; import org.eclipse.jkube.kit.common.JKubeException; import org.eclipse.jkube.kit.common.KitLogger; @@ -43,11 +43,11 @@ public class DefaultGeneratorManager implements GeneratorManager { "META-INF/jkube-generator" }; private final GeneratorContext genCtx; - private final ImageConfigResolver imageConfigResolver; + private final PropertyConfigResolver propertyConfigResolver; public DefaultGeneratorManager(GeneratorContext context) { this.genCtx = context; - imageConfigResolver = new ImageConfigResolver(); + propertyConfigResolver = new PropertyConfigResolver(); } @Override @@ -73,16 +73,17 @@ public List generateAndMerge(List unreso return filteredImages; } + // TODO: + // - IT (Gradle) for image config from properties only private List resolveImages(List unresolvedImages) { final List resolvedImages = new ArrayList<>(); if (unresolvedImages != null) { - for (ImageConfiguration image : unresolvedImages) { - resolvedImages.addAll(imageConfigResolver.resolve(image, genCtx.getProject())); - } - for (ImageConfiguration config : resolvedImages) { - if (config.getName() == null) { + for (ImageConfiguration unresolvedImage : unresolvedImages) { + final ImageConfiguration resolvedImage = propertyConfigResolver.resolve(unresolvedImage, genCtx.getProject()); + if (resolvedImage.getName() == null) { throw new JKubeException("Configuration error: must have a non-null "); } + resolvedImages.add(resolvedImage); } } return resolvedImages; diff --git a/jkube-kit/generator/api/src/test/java/org/eclipse/jkube/generator/api/DefaultGeneratorManagerTest.java b/jkube-kit/generator/api/src/test/java/org/eclipse/jkube/generator/api/DefaultGeneratorManagerTest.java index c426840e9d..a939048fca 100644 --- a/jkube-kit/generator/api/src/test/java/org/eclipse/jkube/generator/api/DefaultGeneratorManagerTest.java +++ b/jkube-kit/generator/api/src/test/java/org/eclipse/jkube/generator/api/DefaultGeneratorManagerTest.java @@ -15,12 +15,12 @@ import java.io.File; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.Properties; import java.util.stream.Collectors; -import org.assertj.core.api.AssertionsForInterfaceTypes; import org.eclipse.jkube.kit.common.BuildRecreateMode; import org.eclipse.jkube.kit.common.JKubeException; import org.eclipse.jkube.kit.common.JavaProject; @@ -48,8 +48,8 @@ class DefaultGeneratorManagerTest { private KitLogger logger; + private ImageConfiguration baseImageConfig; private GeneratorManager generatorManager; - private ImageConfiguration imageConfig; private GeneratorContext generatorContext; @BeforeEach @@ -64,242 +64,252 @@ void setUp() { .properties(new Properties()) .baseDirectory(new File("dummy-dir")) .build(); + baseImageConfig = ImageConfiguration.builder() + .name("foo/bar:latest") + .build(BuildConfiguration.builder() + .from("foobase:latest") + .build()) + .build(); generatorContext = GeneratorContext.builder() .config(processorConfig) .logger(logger) .project(javaProject) .build(); - imageConfig = ImageConfiguration.builder() - .name("foo/bar:latest") - .build(BuildConfiguration.builder() - .from("foobase:latest") - .build()) - .build(); generatorManager = new DefaultGeneratorManager(generatorContext); } + @Test + void withEmptyImageConfigurations_shouldCreteImageConfigViaGenerator() { + // Given + final List images = Collections.emptyList(); + // When + final List result = generatorManager.generateAndMerge(images); + // Then + assertThat(result) + .isNotSameAs(images) + .singleElement() + .hasFieldOrPropertyWithValue("alias", "processed-by-test") + .hasFieldOrPropertyWithValue("name", "generated-by-test"); + verify(logger, times(1)).info("Running generator %s", "fake-generator"); + } + + @Test + void withImageConfigurationNameBlank_throwsException() { + // Given + final List images = Collections.singletonList(ImageConfiguration.builder().build()); + // When + Then + assertThatThrownBy(() -> generatorManager.generateAndMerge(images)) + .isInstanceOf(JKubeException.class) + .hasMessage("Configuration error: must have a non-null "); + } + + @Test + void withSimpleImageConfiguration_shouldMergeWithImageConfigGeneratedViaGenerator() { + // Given + final List images = Collections.singletonList(baseImageConfig); + // When + final List result = generatorManager.generateAndMerge(images); + // Then + assertThat(result) + .isNotSameAs(images) + .hasSize(1) + .extracting(ImageConfiguration::getAlias) + .contains("processed-by-test"); + verify(logger, times(1)).info("Running generator %s", "fake-generator"); + } + + @Test + void whenNoMatchForImageFilter_thenLogWarning() { + // Given + generatorContext = generatorContext.toBuilder().filter("i-dont-exist").build(); + // When + new DefaultGeneratorManager(generatorContext).generateAndMerge(Collections.singletonList(baseImageConfig)); + // Then + verify(logger).warn("None of the resolved images [%s] match the configured filter '%s'", "foo/bar:latest", "i-dont-exist"); + } + + @Test + void whenNoMatchForMultipleImageNameFilters_thenLogWarning() { + // Given + generatorContext = generatorContext.toBuilder().filter("filter1,filter2").build(); + // When + new DefaultGeneratorManager(generatorContext).generateAndMerge(Collections.singletonList(baseImageConfig)); + // Then + verify(logger).warn("None of the resolved images [%s] match the configured filter '%s'", "foo/bar:latest", "filter1,filter2"); + } + + @Test + void whenDockerfileUsed_thenLogDockerfilePathAndContextDir(@TempDir File temporaryFolder) { + File dockerFile = temporaryFolder.toPath().resolve("Dockerfile").toFile(); + ImageConfiguration dummyImageConfiguration = ImageConfiguration.builder() + .name("imageconfiguration-no-build:latest") + .build(BuildConfiguration.builder() + .dockerFile(dockerFile.getAbsolutePath()) + .build()) + .build(); + List images = new ArrayList<>(); + images.add(dummyImageConfiguration); + // When + generatorManager.generateAndMerge(images); + // Then + verify(logger).info(eq("Using Dockerfile: %s"), anyString()); + verify(logger).info(eq("Using Docker Context Directory: %s"), any(File.class)); + } @Nested - @DisplayName("generateAndMerge") - class GenerateAndMerge { + @DisplayName("merge configuration parameters for OpenShift S2I build into Image Build configuration") + class MergeOpenShiftS2IImageConfigParams { @Test - void withEmptyImageConfiguration_shouldMergeWithImageConfigGeneratedViaGenerator() { - // Given - ImageConfiguration imageConfiguration = new ImageConfiguration(); - imageConfiguration.setName("foo/bar:latest"); - final List images = Collections.singletonList(imageConfiguration); + @DisplayName("zero configuration, do not set anything in image build configuration") + void whenNoConfigurationProvided_thenDoNotSetInBuildConfig() { // When - final List result = generatorManager.generateAndMerge(images); + final List result = generatorManager.generateAndMerge(Collections.singletonList(baseImageConfig)); // Then assertThat(result) - .isNotSameAs(images) - .hasSize(1) - .extracting(ImageConfiguration::getAlias) - .contains("processed-by-test"); - verify(logger, times(1)).info("Running generator %s", "fake-generator"); + .singleElement() + .extracting(ImageConfiguration::getBuild) + .hasFieldOrPropertyWithValue("openshiftForcePull", false) + .hasFieldOrPropertyWithValue("openshiftS2iBuildNameSuffix", null) + .hasFieldOrPropertyWithValue("openshiftS2iImageStreamLookupPolicyLocal", false) + .hasFieldOrPropertyWithValue("openshiftPullSecret", null) + .hasFieldOrPropertyWithValue("openshiftPushSecret", null) + .hasFieldOrPropertyWithValue("openshiftBuildOutputKind", null) + .hasFieldOrPropertyWithValue("openshiftBuildRecreateMode", null); } @Test - void withSimpleImageConfiguration_shouldReturnImageConfiguration() { + @DisplayName("image Configuration, then fields retained in Image Configuration") + void whenProvidedInImageConfiguration_thenDoNotUpdateBuildConfig() { // Given - List images = new ArrayList<>(); - images.add(imageConfig); - + baseImageConfig = baseImageConfig.toBuilder() + .build(BuildConfiguration.builder() + .openshiftForcePull(true) + .openshiftS2iBuildNameSuffix("-custom") + .openshiftS2iImageStreamLookupPolicyLocal(true) + .openshiftPushSecret("push-secret") + .openshiftPullSecret("pull-secret") + .openshiftBuildOutputKind("ImageStreamTag") + .openshiftBuildRecreateMode(BuildRecreateMode.buildConfig) + .build()) + .build(); // When - List resolvedImages = generatorManager.generateAndMerge(images); + List result = generatorManager.generateAndMerge(Collections.singletonList(baseImageConfig)); // Then - AssertionsForInterfaceTypes.assertThat(resolvedImages).isNotNull() - .singleElement() - .isEqualTo(imageConfig); + assertThat(result) + .singleElement() + .extracting(ImageConfiguration::getBuild) + .hasFieldOrPropertyWithValue("openshiftForcePull", true) + .hasFieldOrPropertyWithValue("openshiftS2iBuildNameSuffix", "-custom") + .hasFieldOrPropertyWithValue("openshiftS2iImageStreamLookupPolicyLocal", true) + .hasFieldOrPropertyWithValue("openshiftPullSecret", "pull-secret") + .hasFieldOrPropertyWithValue("openshiftPushSecret", "push-secret") + .hasFieldOrPropertyWithValue("openshiftBuildOutputKind", "ImageStreamTag") + .hasFieldOrPropertyWithValue("openshiftBuildRecreateMode", BuildRecreateMode.buildConfig); } @Test - void whenImageConfigurationNameBlank_thenThrowException() { + @DisplayName("plugin configuration, then fields merged in final Image Configuration") + void whenProvidedViaPluginConfiguration_thenSetInBuildConfig() { // Given - ImageConfiguration imageConfiguration = ImageConfiguration.builder().build(); - List images = Collections.singletonList(imageConfiguration); - - // When + Then - assertThatThrownBy(() -> generatorManager.generateAndMerge(images)) - .isInstanceOf(JKubeException.class) - .hasMessage("Configuration error: must have a non-null "); + generatorContext = generatorContext.toBuilder() + .openshiftForcePull(true) + .openshiftS2iBuildNameSuffix("-custom") + .openshiftS2iImageStreamLookupPolicyLocal(true) + .openshiftPullSecret("pull-secret") + .openshiftPushSecret("push-secret") + .openshiftBuildOutputKind("ImageStreamTag") + .openshiftBuildRecreate(BuildRecreateMode.buildConfig) + .build(); + // When + final List result = new DefaultGeneratorManager(generatorContext) + .generateAndMerge(Collections.singletonList(baseImageConfig)); + // Then + assertThat(result) + .singleElement() + .extracting(ImageConfiguration::getBuild) + .hasFieldOrPropertyWithValue("openshiftForcePull", true) + .hasFieldOrPropertyWithValue("openshiftS2iBuildNameSuffix", "-custom") + .hasFieldOrPropertyWithValue("openshiftS2iImageStreamLookupPolicyLocal", true) + .hasFieldOrPropertyWithValue("openshiftPullSecret", "pull-secret") + .hasFieldOrPropertyWithValue("openshiftPushSecret", "push-secret") + .hasFieldOrPropertyWithValue("openshiftBuildOutputKind", "ImageStreamTag") + .hasFieldOrPropertyWithValue("openshiftBuildRecreateMode", BuildRecreateMode.buildConfig); } @Test - void whenNoMatchForImageFilter_thenLogWarning() { + @DisplayName("plugin configuration and image configuration, then fields retained in Image Configuration") + void whenProvidedViaBothPluginAndImageConfiguration_thenDoNotModifyConfigurationSetInBuildConfig() { // Given - generatorContext = generatorContext.toBuilder().filter("i-dont-exist").build(); - List images = Collections.singletonList(imageConfig); - + baseImageConfig = baseImageConfig.toBuilder() + .build(BuildConfiguration.builder() + .openshiftForcePull(true) + .openshiftS2iBuildNameSuffix("-custom-via-image-config") + .openshiftS2iImageStreamLookupPolicyLocal(true) + .openshiftPushSecret("push-secret-via-image-config") + .openshiftPullSecret("pull-secret-via-image-config") + .openshiftBuildOutputKind("ImageStreamTag-via-image-config") + .openshiftBuildRecreateMode(BuildRecreateMode.buildConfig) + .build()) + .build(); + generatorContext = generatorContext.toBuilder() + .openshiftForcePull(true) + .openshiftS2iBuildNameSuffix("-custom") + .openshiftS2iImageStreamLookupPolicyLocal(true) + .openshiftPullSecret("pull-secret") + .openshiftPushSecret("push-secret") + .openshiftBuildOutputKind("ImageStreamTag") + .openshiftBuildRecreate(BuildRecreateMode.buildConfig) + .build(); // When - new DefaultGeneratorManager(generatorContext).generateAndMerge(images); - + final List result = new DefaultGeneratorManager(generatorContext) + .generateAndMerge(Collections.singletonList(baseImageConfig)); // Then - verify(logger).warn("None of the resolved images [%s] match the configured filter '%s'", "foo/bar:latest", "i-dont-exist"); + assertThat(result) + .singleElement() + .extracting(ImageConfiguration::getBuild) + .hasFieldOrPropertyWithValue("openshiftForcePull", true) + .hasFieldOrPropertyWithValue("openshiftS2iBuildNameSuffix", "-custom-via-image-config") + .hasFieldOrPropertyWithValue("openshiftS2iImageStreamLookupPolicyLocal", true) + .hasFieldOrPropertyWithValue("openshiftPullSecret", "pull-secret-via-image-config") + .hasFieldOrPropertyWithValue("openshiftPushSecret", "push-secret-via-image-config") + .hasFieldOrPropertyWithValue("openshiftBuildOutputKind", "ImageStreamTag-via-image-config") + .hasFieldOrPropertyWithValue("openshiftBuildRecreateMode", BuildRecreateMode.buildConfig); } + } + + @Nested + @DisplayName("Resolve ImageConfiguration from properties") + class ImageConfigurationFromProperties { @Test - void whenNoMatchForMultipleImageNameFilters_thenLogWarning() { + void withGlobalPrefix() { // Given - generatorContext = generatorContext.toBuilder().filter("filter1,filter2").build(); - List images = Collections.singletonList(imageConfig); - + generatorContext.getProject().getProperties().put("jkube.container-image.name", "image-name"); + generatorContext.getProject().getProperties().put("jkube.container-image.alias", "image-alias"); + generatorContext.getProject().getProperties().put("jkube.container-image.from", "scratch"); // When - new DefaultGeneratorManager(generatorContext).generateAndMerge(images); - + final List result = generatorManager.generateAndMerge(Collections.singletonList(new ImageConfiguration())); // Then - verify(logger).warn("None of the resolved images [%s] match the configured filter '%s'", "foo/bar:latest", "filter1,filter2"); + assertThat(result).singleElement() + .hasFieldOrPropertyWithValue("name", "image-name") + .extracting(ImageConfiguration::getBuild) + .hasFieldOrPropertyWithValue("from", "scratch"); } @Test - void whenDockerfileUsed_thenLogDockerfilePathAndContextDir(@TempDir File temporaryFolder) { - File dockerFile = temporaryFolder.toPath().resolve("Dockerfile").toFile(); - ImageConfiguration dummyImageConfiguration = ImageConfiguration.builder() - .name("imageconfiguration-no-build:latest") - .build(BuildConfiguration.builder() - .dockerFile(dockerFile.getAbsolutePath()) - .build()) - .build(); - List images = new ArrayList<>(); - images.add(dummyImageConfiguration); - + void withPrefix() { + // Given + final ImageConfiguration prefixed = ImageConfiguration.builder() + .propertyResolverPrefix("app.images.image-1") + .build(); + final ImageConfiguration standard = ImageConfiguration.builder().build(); + generatorContext.getProject().getProperties().put("app.images.image-1.name", "prefixed-image-name"); + generatorContext.getProject().getProperties().put("jkube.container-image.name", "image-name"); // When - generatorManager.generateAndMerge(images); - + final List result = generatorManager.generateAndMerge(Arrays.asList(prefixed, standard)); // Then - verify(logger).info(eq("Using Dockerfile: %s"), anyString()); - verify(logger).info(eq("Using Docker Context Directory: %s"), any(File.class)); - } - - - @Nested - @DisplayName("merge configuration parameters for OpenShift S2I build into Image Build configuration") - class MergeOpenShiftS2IImageConfigParams { - @Test - @DisplayName("zero configuration, do not set anything in image build configuration") - void whenNoConfigurationProvided_thenDoNotSetInBuildConfig() { - // When - List result = generatorManager.generateAndMerge(Collections.singletonList(imageConfig)); - - // Then - assertThat(result) - .singleElement() - .extracting(ImageConfiguration::getBuild) - .hasFieldOrPropertyWithValue("openshiftForcePull", false) - .hasFieldOrPropertyWithValue("openshiftS2iBuildNameSuffix", null) - .hasFieldOrPropertyWithValue("openshiftS2iImageStreamLookupPolicyLocal", false) - .hasFieldOrPropertyWithValue("openshiftPullSecret", null) - .hasFieldOrPropertyWithValue("openshiftPushSecret", null) - .hasFieldOrPropertyWithValue("openshiftBuildOutputKind", null) - .hasFieldOrPropertyWithValue("openshiftBuildRecreateMode", null); - } - - @Test - @DisplayName("image Configuration, then fields retained in Image Configuration") - void whenProvidedInImageConfiguration_thenDoNotUpdateBuildConfig() { - // Given - imageConfig = imageConfig.toBuilder() - .build(BuildConfiguration.builder() - .openshiftForcePull(true) - .openshiftS2iBuildNameSuffix("-custom") - .openshiftS2iImageStreamLookupPolicyLocal(true) - .openshiftPushSecret("push-secret") - .openshiftPullSecret("pull-secret") - .openshiftBuildOutputKind("ImageStreamTag") - .openshiftBuildRecreateMode(BuildRecreateMode.buildConfig) - .build()) - .build(); - List images = Collections.singletonList(imageConfig); - - // When - List result = generatorManager.generateAndMerge(images); - - // Then - assertThat(result) - .singleElement() - .extracting(ImageConfiguration::getBuild) - .hasFieldOrPropertyWithValue("openshiftForcePull", true) - .hasFieldOrPropertyWithValue("openshiftS2iBuildNameSuffix", "-custom") - .hasFieldOrPropertyWithValue("openshiftS2iImageStreamLookupPolicyLocal", true) - .hasFieldOrPropertyWithValue("openshiftPullSecret", "pull-secret") - .hasFieldOrPropertyWithValue("openshiftPushSecret", "push-secret") - .hasFieldOrPropertyWithValue("openshiftBuildOutputKind", "ImageStreamTag") - .hasFieldOrPropertyWithValue("openshiftBuildRecreateMode", BuildRecreateMode.buildConfig); - } - - @Test - @DisplayName("plugin configuration, then fields merged in final Image Configuration") - void whenProvidedViaPluginConfiguration_thenSetInBuildConfig() { - // Given - generatorContext = generatorContext.toBuilder() - .openshiftForcePull(true) - .openshiftS2iBuildNameSuffix("-custom") - .openshiftS2iImageStreamLookupPolicyLocal(true) - .openshiftPullSecret("pull-secret") - .openshiftPushSecret("push-secret") - .openshiftBuildOutputKind("ImageStreamTag") - .openshiftBuildRecreate(BuildRecreateMode.buildConfig) - .build(); - List images = Collections.singletonList(imageConfig); - - // When - List result = new DefaultGeneratorManager(generatorContext).generateAndMerge(images); - - // Then - assertThat(result) - .singleElement() - .extracting(ImageConfiguration::getBuild) - .hasFieldOrPropertyWithValue("openshiftForcePull", true) - .hasFieldOrPropertyWithValue("openshiftS2iBuildNameSuffix", "-custom") - .hasFieldOrPropertyWithValue("openshiftS2iImageStreamLookupPolicyLocal", true) - .hasFieldOrPropertyWithValue("openshiftPullSecret", "pull-secret") - .hasFieldOrPropertyWithValue("openshiftPushSecret", "push-secret") - .hasFieldOrPropertyWithValue("openshiftBuildOutputKind", "ImageStreamTag") - .hasFieldOrPropertyWithValue("openshiftBuildRecreateMode", BuildRecreateMode.buildConfig); - } - - @Test - @DisplayName("plugin configuration and image configuration, then fields retained in Image Configuration") - void whenProvidedViaBothPluginAndImageConfiguration_thenDoNotModifyConfigurationSetInBuildConfig() { - // Given - imageConfig = imageConfig.toBuilder() - .build(BuildConfiguration.builder() - .openshiftForcePull(true) - .openshiftS2iBuildNameSuffix("-custom-via-image-config") - .openshiftS2iImageStreamLookupPolicyLocal(true) - .openshiftPushSecret("push-secret-via-image-config") - .openshiftPullSecret("pull-secret-via-image-config") - .openshiftBuildOutputKind("ImageStreamTag-via-image-config") - .openshiftBuildRecreateMode(BuildRecreateMode.buildConfig) - .build()) - .build(); - generatorContext = generatorContext.toBuilder() - .openshiftForcePull(true) - .openshiftS2iBuildNameSuffix("-custom") - .openshiftS2iImageStreamLookupPolicyLocal(true) - .openshiftPullSecret("pull-secret") - .openshiftPushSecret("push-secret") - .openshiftBuildOutputKind("ImageStreamTag") - .openshiftBuildRecreate(BuildRecreateMode.buildConfig) - .build(); - List images = Collections.singletonList(imageConfig); - - // When - List result = new DefaultGeneratorManager(generatorContext).generateAndMerge(images); - - // Then - assertThat(result) - .singleElement() - .extracting(ImageConfiguration::getBuild) - .hasFieldOrPropertyWithValue("openshiftForcePull", true) - .hasFieldOrPropertyWithValue("openshiftS2iBuildNameSuffix", "-custom-via-image-config") - .hasFieldOrPropertyWithValue("openshiftS2iImageStreamLookupPolicyLocal", true) - .hasFieldOrPropertyWithValue("openshiftPullSecret", "pull-secret-via-image-config") - .hasFieldOrPropertyWithValue("openshiftPushSecret", "push-secret-via-image-config") - .hasFieldOrPropertyWithValue("openshiftBuildOutputKind", "ImageStreamTag-via-image-config") - .hasFieldOrPropertyWithValue("openshiftBuildRecreateMode", BuildRecreateMode.buildConfig); - } + assertThat(result).extracting("name").containsExactly("prefixed-image-name", "image-name"); } } @@ -321,6 +331,11 @@ public boolean isApplicable(List configs) { @Override public List customize(List existingConfigs, boolean prePackagePhase) { + if (existingConfigs == null || existingConfigs.isEmpty()) { + existingConfigs = Collections.singletonList(ImageConfiguration.builder() + .name("generated-by-test") + .build()); + } return existingConfigs.stream() .peek(ic -> ic.setAlias("processed-by-test")) .collect(Collectors.toList());