diff --git a/bom/application/pom.xml b/bom/application/pom.xml index f1a9b5fb188f5..9d73e081b9c17 100644 --- a/bom/application/pom.xml +++ b/bom/application/pom.xml @@ -147,7 +147,7 @@ 2.35.1 1.3.0 1.3.72 - 0.12.11 + 0.13.2 0.10.0 2.14.6 3.0.1 diff --git a/extensions/container-image/spi/src/main/java/io/quarkus/container/spi/ContainerImageInfoBuildItem.java b/extensions/container-image/spi/src/main/java/io/quarkus/container/spi/ContainerImageInfoBuildItem.java index cf49ccf9c618f..fd67ab86f1d7c 100644 --- a/extensions/container-image/spi/src/main/java/io/quarkus/container/spi/ContainerImageInfoBuildItem.java +++ b/extensions/container-image/spi/src/main/java/io/quarkus/container/spi/ContainerImageInfoBuildItem.java @@ -75,4 +75,8 @@ public Set getAdditionalTags() { public String getRepository() { return repository; } + + public String getGroup() { + return repository == null ? null : repository.split("/")[0]; + } } diff --git a/extensions/kubernetes/minikube/deployment/src/main/java/io/quarkus/minikube/deployment/MinikubeProcessor.java b/extensions/kubernetes/minikube/deployment/src/main/java/io/quarkus/minikube/deployment/MinikubeProcessor.java index 708663014f7e2..a7bee339301bd 100644 --- a/extensions/kubernetes/minikube/deployment/src/main/java/io/quarkus/minikube/deployment/MinikubeProcessor.java +++ b/extensions/kubernetes/minikube/deployment/src/main/java/io/quarkus/minikube/deployment/MinikubeProcessor.java @@ -1,17 +1,170 @@ package io.quarkus.minikube.deployment; +import static io.quarkus.kubernetes.deployment.Constants.DEFAULT_HTTP_PORT; import static io.quarkus.kubernetes.deployment.Constants.DEPLOYMENT; +import static io.quarkus.kubernetes.deployment.Constants.HTTP_PORT; +import static io.quarkus.kubernetes.deployment.Constants.KUBERNETES; +import static io.quarkus.kubernetes.deployment.Constants.MAX_NODE_PORT_VALUE; +import static io.quarkus.kubernetes.deployment.Constants.MAX_PORT_NUMBER; import static io.quarkus.kubernetes.deployment.Constants.MINIKUBE; +import static io.quarkus.kubernetes.deployment.Constants.MIN_NODE_PORT_VALUE; +import static io.quarkus.kubernetes.deployment.Constants.MIN_PORT_NUMBER; +import static io.quarkus.kubernetes.spi.KubernetesDeploymentTargetBuildItem.DEFAULT_PRIORITY; +import java.math.BigInteger; +import java.nio.charset.StandardCharsets; +import java.security.MessageDigest; +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; +import java.util.stream.Stream; + +import io.dekorate.kubernetes.annotation.ServiceType; +import io.dekorate.kubernetes.config.EnvBuilder; +import io.dekorate.kubernetes.decorator.AddEnvVarDecorator; +import io.dekorate.kubernetes.decorator.ApplicationContainerDecorator; +import io.dekorate.kubernetes.decorator.ApplyImagePullPolicyDecorator; +import io.dekorate.project.Project; +import io.quarkus.container.spi.BaseImageInfoBuildItem; +import io.quarkus.container.spi.ContainerImageInfoBuildItem; +import io.quarkus.container.spi.ContainerImageLabelBuildItem; import io.quarkus.deployment.annotations.BuildProducer; import io.quarkus.deployment.annotations.BuildStep; +import io.quarkus.deployment.builditem.ApplicationInfoBuildItem; +import io.quarkus.deployment.metrics.MetricsCapabilityBuildItem; +import io.quarkus.deployment.pkg.PackageConfig; +import io.quarkus.deployment.pkg.builditem.OutputTargetBuildItem; +import io.quarkus.kubernetes.deployment.AddNodePortDecorator; +import io.quarkus.kubernetes.deployment.ApplyContainerImageDecorator; +import io.quarkus.kubernetes.deployment.ApplyHttpGetActionPortDecorator; +import io.quarkus.kubernetes.deployment.ApplyServiceTypeDecorator; +import io.quarkus.kubernetes.deployment.EnvConverter; +import io.quarkus.kubernetes.deployment.KubernetesCommonHelper; +import io.quarkus.kubernetes.deployment.KubernetesConfig; +import io.quarkus.kubernetes.deployment.ResourceNameUtil; +import io.quarkus.kubernetes.spi.ConfiguratorBuildItem; +import io.quarkus.kubernetes.spi.DecoratorBuildItem; +import io.quarkus.kubernetes.spi.KubernetesAnnotationBuildItem; +import io.quarkus.kubernetes.spi.KubernetesCommandBuildItem; import io.quarkus.kubernetes.spi.KubernetesDeploymentTargetBuildItem; +import io.quarkus.kubernetes.spi.KubernetesEnvBuildItem; +import io.quarkus.kubernetes.spi.KubernetesHealthLivenessPathBuildItem; +import io.quarkus.kubernetes.spi.KubernetesHealthReadinessPathBuildItem; +import io.quarkus.kubernetes.spi.KubernetesLabelBuildItem; +import io.quarkus.kubernetes.spi.KubernetesPortBuildItem; +import io.quarkus.kubernetes.spi.KubernetesRoleBindingBuildItem; +import io.quarkus.kubernetes.spi.KubernetesRoleBuildItem; public class MinikubeProcessor { + public static final String DEFAULT_HASH_ALGORITHM = "SHA-256"; + private static final int MINIKUBE_PRIORITY = DEFAULT_PRIORITY + 20; + + @BuildStep + public void checkMinikube(BuildProducer deploymentTargets) { + deploymentTargets.produce(new KubernetesDeploymentTargetBuildItem(MINIKUBE, DEPLOYMENT, MINIKUBE_PRIORITY, true)); + } + + @BuildStep + public void createAnnotations(KubernetesConfig config, BuildProducer annotations) { + config.getAnnotations().forEach((k, v) -> { + annotations.produce(new KubernetesAnnotationBuildItem(k, v, MINIKUBE)); + }); + } + @BuildStep - public void checkOpenshift(BuildProducer deploymentTargets) { - deploymentTargets - .produce(new KubernetesDeploymentTargetBuildItem(MINIKUBE, DEPLOYMENT, true)); + public void createLabels(KubernetesConfig config, BuildProducer labels, + BuildProducer imageLabels) { + config.getLabels().forEach((k, v) -> { + labels.produce(new KubernetesLabelBuildItem(k, v, MINIKUBE)); + imageLabels.produce(new ContainerImageLabelBuildItem(k, v)); + }); } + + @BuildStep + public List createConfigurators(KubernetesConfig config, List ports) { + List result = new ArrayList<>(); + result.addAll(KubernetesCommonHelper.createPlatformConfigurators(config)); + result.addAll(KubernetesCommonHelper.createGlobalConfigurators(ports)); + return result; + + } + + @BuildStep + public List createDecorators(ApplicationInfoBuildItem applicationInfo, + OutputTargetBuildItem outputTarget, + KubernetesConfig config, + PackageConfig packageConfig, + Optional metricsConfiguration, + List annotations, + List labels, + List envs, + Optional baseImage, + Optional image, + Optional command, + List ports, + Optional livenessPath, + Optional readinessPath, + List roles, + List roleBindings) { + + List result = new ArrayList<>(); + String name = ResourceNameUtil.getResourceName(config, applicationInfo); + + Project project = KubernetesCommonHelper.createProject(applicationInfo, outputTarget, packageConfig); + result.addAll(KubernetesCommonHelper.createDecorators(project, MINIKUBE, name, config, metricsConfiguration, + annotations, labels, command, + ports, livenessPath, readinessPath, roles, roleBindings)); + + image.ifPresent(i -> { + result.add(new DecoratorBuildItem(MINIKUBE, new ApplyContainerImageDecorator(name, i.getImage()))); + }); + + Stream.concat(config.convertToBuildItems().stream(), + envs.stream().filter(e -> e.getTarget() == null || KUBERNETES.equals(e.getTarget()))).forEach(e -> { + result.add(new DecoratorBuildItem(MINIKUBE, + new AddEnvVarDecorator(ApplicationContainerDecorator.ANY, name, new EnvBuilder() + .withName(EnvConverter.convertName(e.getName())) + .withValue(e.getValue()) + .withSecret(e.getSecret()) + .withConfigmap(e.getConfigMap()) + .withField(e.getField()) + .build()))); + }); + + result.add(new DecoratorBuildItem(MINIKUBE, new ApplyImagePullPolicyDecorator(name, "IfNotPresent"))); + + //Service handling + result.add(new DecoratorBuildItem(MINIKUBE, new ApplyServiceTypeDecorator(name, ServiceType.NodePort.name()))); + result.add(new DecoratorBuildItem(MINIKUBE, new AddNodePortDecorator(name, config.getNodePort() + .orElseGet(() -> getStablePortNumberInRange(name, MIN_NODE_PORT_VALUE, MAX_NODE_PORT_VALUE))))); + + //Probe port handling + Integer port = ports.stream().filter(p -> HTTP_PORT.equals(p.getName())).map(KubernetesPortBuildItem::getPort) + .findFirst().orElse(DEFAULT_HTTP_PORT); + result.add(new DecoratorBuildItem(MINIKUBE, new ApplyHttpGetActionPortDecorator(port))); + + return result; + } + + /** + * Given a string, generate a port number within the supplied range + * The output is always the same (between {@code min} and {@code max}) + * given the same input and it's useful when we need to generate a port number + * which needs to stay the same but we don't care about the exact value + */ + private int getStablePortNumberInRange(String input, int min, int max) { + if (min < MIN_PORT_NUMBER || max > MAX_PORT_NUMBER) { + throw new IllegalArgumentException( + String.format("Port number range must be within [%d-%d]", MIN_PORT_NUMBER, MAX_PORT_NUMBER)); + } + + try { + byte[] hash = MessageDigest.getInstance(DEFAULT_HASH_ALGORITHM).digest(input.getBytes(StandardCharsets.UTF_8)); + return min + new BigInteger(hash).mod(BigInteger.valueOf(max - min)).intValue(); + } catch (Exception e) { + throw new RuntimeException("Unable to generate stable port number from input string: '" + input + "'", e); + } + } + } diff --git a/extensions/kubernetes/spi/src/main/java/io/quarkus/kubernetes/spi/ConfiguratorBuildItem.java b/extensions/kubernetes/spi/src/main/java/io/quarkus/kubernetes/spi/ConfiguratorBuildItem.java new file mode 100644 index 0000000000000..8eb01d10b9645 --- /dev/null +++ b/extensions/kubernetes/spi/src/main/java/io/quarkus/kubernetes/spi/ConfiguratorBuildItem.java @@ -0,0 +1,38 @@ +package io.quarkus.kubernetes.spi; + +import java.util.Optional; + +import io.quarkus.builder.item.MultiBuildItem; + +/** + * A build item that wraps around Configurator objects. + * The purpose of those build items is influence the configuration that will be feed to the generator process. + * Configurators are similar to decorators, but are applied to configuration instead of generated resources. + */ +public final class ConfiguratorBuildItem extends MultiBuildItem { + + /** + * The configurator + */ + private final Object configurator; + + public ConfiguratorBuildItem(Object configurator) { + this.configurator = configurator; + } + + public Object getConfigurator() { + return this.configurator; + } + + public boolean matches(Class type) { + return type.isInstance(configurator); + + } + + public Optional getConfigurator(Class type) { + if (matches(type)) { + return Optional. of((C) configurator); + } + return Optional.empty(); + } +} diff --git a/extensions/kubernetes/spi/src/main/java/io/quarkus/kubernetes/spi/DecoratorBuildItem.java b/extensions/kubernetes/spi/src/main/java/io/quarkus/kubernetes/spi/DecoratorBuildItem.java index f037636e35035..c76bf15b64122 100644 --- a/extensions/kubernetes/spi/src/main/java/io/quarkus/kubernetes/spi/DecoratorBuildItem.java +++ b/extensions/kubernetes/spi/src/main/java/io/quarkus/kubernetes/spi/DecoratorBuildItem.java @@ -5,6 +5,10 @@ import io.quarkus.builder.item.MultiBuildItem; +/** + * A build item that wraps around Decorator objects. + * The purpose of those build items is to perform modification on the generated resources. + */ public final class DecoratorBuildItem extends MultiBuildItem { /** diff --git a/extensions/kubernetes/vanilla/deployment/src/main/java/io/quarkus/kubernetes/deployment/ApplyImageGroupConfigurator.java b/extensions/kubernetes/vanilla/deployment/src/main/java/io/quarkus/kubernetes/deployment/ApplyImageGroupConfigurator.java new file mode 100644 index 0000000000000..155961cf6c2cb --- /dev/null +++ b/extensions/kubernetes/vanilla/deployment/src/main/java/io/quarkus/kubernetes/deployment/ApplyImageGroupConfigurator.java @@ -0,0 +1,20 @@ + +package io.quarkus.kubernetes.deployment; + +import io.dekorate.kubernetes.config.Configurator; +import io.dekorate.kubernetes.config.ImageConfigurationFluent; + +public class ApplyImageGroupConfigurator extends Configurator> { + + private final String group; + + public ApplyImageGroupConfigurator(String group) { + this.group = group; + } + + @Override + public void visit(ImageConfigurationFluent config) { + config.withGroup(group); + } + +} diff --git a/extensions/kubernetes/vanilla/deployment/src/main/java/io/quarkus/kubernetes/deployment/ApplyImageRegistryConfigurator.java b/extensions/kubernetes/vanilla/deployment/src/main/java/io/quarkus/kubernetes/deployment/ApplyImageRegistryConfigurator.java new file mode 100644 index 0000000000000..1d4106eaa3a0d --- /dev/null +++ b/extensions/kubernetes/vanilla/deployment/src/main/java/io/quarkus/kubernetes/deployment/ApplyImageRegistryConfigurator.java @@ -0,0 +1,19 @@ + +package io.quarkus.kubernetes.deployment; + +import io.dekorate.kubernetes.config.Configurator; +import io.dekorate.kubernetes.config.ImageConfigurationFluent; + +public class ApplyImageRegistryConfigurator extends Configurator> { + + private final String registry; + + public ApplyImageRegistryConfigurator(String registry) { + this.registry = registry; + } + + @Override + public void visit(ImageConfigurationFluent config) { + config.withRegistry(registry); + } +} diff --git a/extensions/kubernetes/vanilla/deployment/src/main/java/io/quarkus/kubernetes/deployment/Constants.java b/extensions/kubernetes/vanilla/deployment/src/main/java/io/quarkus/kubernetes/deployment/Constants.java index 393b63ef9c1db..db619fd12f565 100644 --- a/extensions/kubernetes/vanilla/deployment/src/main/java/io/quarkus/kubernetes/deployment/Constants.java +++ b/extensions/kubernetes/vanilla/deployment/src/main/java/io/quarkus/kubernetes/deployment/Constants.java @@ -2,7 +2,7 @@ public final class Constants { - static final String KUBERNETES = "kubernetes"; + public static final String KUBERNETES = "kubernetes"; public static final String MINIKUBE = "minikube"; public static final String DEPLOYMENT = "Deployment"; static final String DOCKER = "docker"; @@ -28,13 +28,13 @@ public final class Constants { static final String QUARKUS_ANNOTATIONS_VCS_URL = "app.quarkus.io/vcs-url"; static final String QUARKUS_ANNOTATIONS_BUILD_TIMESTAMP = "app.quarkus.io/build-timestamp"; - static final String HTTP_PORT = "http"; - static final int DEFAULT_HTTP_PORT = 8080; + public static final String HTTP_PORT = "http"; + public static final int DEFAULT_HTTP_PORT = 8080; - static final int MIN_PORT_NUMBER = 1; - static final int MAX_PORT_NUMBER = 65535; - static final int MIN_NODE_PORT_VALUE = 30000; - static final int MAX_NODE_PORT_VALUE = 31999; + public static final int MIN_PORT_NUMBER = 1; + public static final int MAX_PORT_NUMBER = 65535; + public static final int MIN_NODE_PORT_VALUE = 30000; + public static final int MAX_NODE_PORT_VALUE = 31999; private Constants() { } diff --git a/extensions/kubernetes/vanilla/deployment/src/main/java/io/quarkus/kubernetes/deployment/ContainerAdapter.java b/extensions/kubernetes/vanilla/deployment/src/main/java/io/quarkus/kubernetes/deployment/ContainerAdapter.java index a89b22e855883..70b254d16881c 100644 --- a/extensions/kubernetes/vanilla/deployment/src/main/java/io/quarkus/kubernetes/deployment/ContainerAdapter.java +++ b/extensions/kubernetes/vanilla/deployment/src/main/java/io/quarkus/kubernetes/deployment/ContainerAdapter.java @@ -44,8 +44,7 @@ public static Container adapt(io.dekorate.kubernetes.config.Container container) builder.accept(new AddMountDecorator(mount)); } - builder.accept(new ApplyImagePullPolicyDecorator(container.getImagePullPolicy())); - + builder.accept(new ApplyImagePullPolicyDecorator(name, container.getImagePullPolicy())); builder.accept(new AddLivenessProbeDecorator(name, container.getLivenessProbe())); builder.accept(new AddReadinessProbeDecorator(name, container.getReadinessProbe())); return builder.build(); diff --git a/extensions/kubernetes/vanilla/deployment/src/main/java/io/quarkus/kubernetes/deployment/DisableS2iConfigurator.java b/extensions/kubernetes/vanilla/deployment/src/main/java/io/quarkus/kubernetes/deployment/DisableS2iConfigurator.java new file mode 100644 index 0000000000000..a3d0c1e048387 --- /dev/null +++ b/extensions/kubernetes/vanilla/deployment/src/main/java/io/quarkus/kubernetes/deployment/DisableS2iConfigurator.java @@ -0,0 +1,13 @@ + +package io.quarkus.kubernetes.deployment; + +import io.dekorate.kubernetes.config.Configurator; +import io.dekorate.s2i.config.S2iBuildConfigFluent; + +public class DisableS2iConfigurator extends Configurator> { + + @Override + public void visit(S2iBuildConfigFluent s2i) { + s2i.withEnabled(false); + } +} diff --git a/extensions/kubernetes/vanilla/deployment/src/main/java/io/quarkus/kubernetes/deployment/KnativeProcessor.java b/extensions/kubernetes/vanilla/deployment/src/main/java/io/quarkus/kubernetes/deployment/KnativeProcessor.java new file mode 100644 index 0000000000000..bfd523b95131c --- /dev/null +++ b/extensions/kubernetes/vanilla/deployment/src/main/java/io/quarkus/kubernetes/deployment/KnativeProcessor.java @@ -0,0 +1,196 @@ +package io.quarkus.kubernetes.deployment; + +import static io.quarkus.kubernetes.deployment.Constants.DEPLOYMENT; +import static io.quarkus.kubernetes.deployment.Constants.KNATIVE; +import static io.quarkus.kubernetes.spi.KubernetesDeploymentTargetBuildItem.DEFAULT_PRIORITY; + +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; +import java.util.stream.Stream; + +import io.dekorate.knative.decorator.ApplyGlobalAutoscalingClassDecorator; +import io.dekorate.knative.decorator.ApplyGlobalContainerConcurrencyDecorator; +import io.dekorate.knative.decorator.ApplyGlobalRequestsPerSecondTargetDecorator; +import io.dekorate.knative.decorator.ApplyGlobalTargetUtilizationDecorator; +import io.dekorate.knative.decorator.ApplyLocalAutoscalingClassDecorator; +import io.dekorate.knative.decorator.ApplyLocalAutoscalingMetricDecorator; +import io.dekorate.knative.decorator.ApplyLocalAutoscalingTargetDecorator; +import io.dekorate.knative.decorator.ApplyLocalContainerConcurrencyDecorator; +import io.dekorate.knative.decorator.ApplyLocalTargetUtilizationPercentageDecorator; +import io.dekorate.knative.decorator.ApplyMaxScaleDecorator; +import io.dekorate.knative.decorator.ApplyMinScaleDecorator; +import io.dekorate.kubernetes.config.EnvBuilder; +import io.dekorate.kubernetes.decorator.AddConfigMapDataDecorator; +import io.dekorate.kubernetes.decorator.AddConfigMapResourceProvidingDecorator; +import io.dekorate.kubernetes.decorator.AddEnvVarDecorator; +import io.dekorate.kubernetes.decorator.AddLabelDecorator; +import io.dekorate.kubernetes.decorator.ApplicationContainerDecorator; +import io.dekorate.project.Project; +import io.quarkus.container.spi.BaseImageInfoBuildItem; +import io.quarkus.container.spi.ContainerImageInfoBuildItem; +import io.quarkus.container.spi.ContainerImageLabelBuildItem; +import io.quarkus.deployment.annotations.BuildProducer; +import io.quarkus.deployment.annotations.BuildStep; +import io.quarkus.deployment.builditem.ApplicationInfoBuildItem; +import io.quarkus.deployment.metrics.MetricsCapabilityBuildItem; +import io.quarkus.deployment.pkg.PackageConfig; +import io.quarkus.deployment.pkg.builditem.OutputTargetBuildItem; +import io.quarkus.kubernetes.spi.ConfiguratorBuildItem; +import io.quarkus.kubernetes.spi.DecoratorBuildItem; +import io.quarkus.kubernetes.spi.KubernetesAnnotationBuildItem; +import io.quarkus.kubernetes.spi.KubernetesCommandBuildItem; +import io.quarkus.kubernetes.spi.KubernetesDeploymentTargetBuildItem; +import io.quarkus.kubernetes.spi.KubernetesEnvBuildItem; +import io.quarkus.kubernetes.spi.KubernetesHealthLivenessPathBuildItem; +import io.quarkus.kubernetes.spi.KubernetesHealthReadinessPathBuildItem; +import io.quarkus.kubernetes.spi.KubernetesLabelBuildItem; +import io.quarkus.kubernetes.spi.KubernetesPortBuildItem; +import io.quarkus.kubernetes.spi.KubernetesRoleBindingBuildItem; +import io.quarkus.kubernetes.spi.KubernetesRoleBuildItem; + +public class KnativeProcessor { + + private static final int KNATIVE_PRIORITY = DEFAULT_PRIORITY; + + @BuildStep + public void checkKnative(BuildProducer deploymentTargets) { + List targets = KubernetesConfigUtil.getUserSpecifiedDeploymentTargets(); + deploymentTargets.produce( + new KubernetesDeploymentTargetBuildItem(KNATIVE, DEPLOYMENT, KNATIVE_PRIORITY, targets.contains(KNATIVE))); + } + + @BuildStep + public void createAnnotations(KnativeConfig config, BuildProducer annotations) { + config.getAnnotations().forEach((k, v) -> { + annotations.produce(new KubernetesAnnotationBuildItem(k, v, KNATIVE)); + }); + } + + @BuildStep + public void createLabels(KnativeConfig config, BuildProducer labels, + BuildProducer imageLabels) { + config.getLabels().forEach((k, v) -> { + labels.produce(new KubernetesLabelBuildItem(k, v, KNATIVE)); + imageLabels.produce(new ContainerImageLabelBuildItem(k, v)); + }); + } + + @BuildStep + public List createConfigurators(KnativeConfig config, List ports) { + List result = new ArrayList<>(); + result.addAll(KubernetesCommonHelper.createPlatformConfigurators(config)); + result.addAll(KubernetesCommonHelper.createGlobalConfigurators(ports)); + return result; + + } + + @BuildStep + public List createDecorators(ApplicationInfoBuildItem applicationInfo, + OutputTargetBuildItem outputTarget, + KnativeConfig config, + PackageConfig packageConfig, + Optional metricsConfiguration, + List annotations, + List labels, + List envs, + Optional baseImage, + Optional image, + Optional command, + List ports, + Optional livenessPath, + Optional readinessPath, + List roles, + List roleBindings) { + + List result = new ArrayList<>(); + String name = ResourceNameUtil.getResourceName(config, applicationInfo); + + Project project = KubernetesCommonHelper.createProject(applicationInfo, outputTarget, packageConfig); + result.addAll(KubernetesCommonHelper.createDecorators(project, KNATIVE, name, config, metricsConfiguration, annotations, + labels, command, + ports, livenessPath, readinessPath, roles, roleBindings)); + + image.ifPresent(i -> { + result.add(new DecoratorBuildItem(KNATIVE, new ApplyContainerImageDecorator(name, i.getImage()))); + }); + + Stream.concat(config.convertToBuildItems().stream(), + envs.stream().filter(e -> e.getTarget() == null || KNATIVE.equals(e.getTarget()))).forEach(e -> { + result.add(new DecoratorBuildItem(KNATIVE, + new AddEnvVarDecorator(ApplicationContainerDecorator.ANY, name, new EnvBuilder() + .withName(EnvConverter.convertName(e.getName())) + .withValue(e.getValue()) + .withSecret(e.getSecret()) + .withConfigmap(e.getConfigMap()) + .withField(e.getField()) + .build()))); + }); + + if (config.clusterLocal) { + result.add(new DecoratorBuildItem(KNATIVE, + new AddLabelDecorator(name, "serving.knative.dev/visibility", "cluster-local"))); + } + + config.minScale.ifPresent(min -> result.add(new DecoratorBuildItem(KNATIVE, new ApplyMinScaleDecorator(name, min)))); + + config.maxScale.ifPresent(max -> result.add(new DecoratorBuildItem(KNATIVE, new ApplyMaxScaleDecorator(name, max)))); + + config.revisionAutoScaling.autoScalerClass.map(AutoScalerClassConverter::convert) + .ifPresent(a -> result.add(new DecoratorBuildItem(KNATIVE, new ApplyLocalAutoscalingClassDecorator(name, a)))); + + config.revisionAutoScaling.metric.map(AutoScalingMetricConverter::convert) + .ifPresent(m -> result.add(new DecoratorBuildItem(KNATIVE, new ApplyLocalAutoscalingMetricDecorator(name, m)))); + + config.revisionAutoScaling.containerConcurrency + .ifPresent( + c -> result.add(new DecoratorBuildItem(KNATIVE, new ApplyLocalContainerConcurrencyDecorator(name, c)))); + + config.revisionAutoScaling.targetUtilizationPercentage + .ifPresent(t -> result + .add(new DecoratorBuildItem(KNATIVE, new ApplyLocalTargetUtilizationPercentageDecorator(name, t)))); + config.revisionAutoScaling.target + .ifPresent(t -> result.add(new DecoratorBuildItem(KNATIVE, new ApplyLocalAutoscalingTargetDecorator(name, t)))); + + config.globalAutoScaling.autoScalerClass + .map(AutoScalerClassConverter::convert) + .ifPresent(a -> { + result.add( + new DecoratorBuildItem(KNATIVE, new AddConfigMapResourceProvidingDecorator("config-autoscaler"))); + result.add(new DecoratorBuildItem(KNATIVE, new ApplyGlobalAutoscalingClassDecorator(a))); + }); + + config.globalAutoScaling.containerConcurrency + .ifPresent(c -> { + result.add(new DecoratorBuildItem(KNATIVE, new AddConfigMapResourceProvidingDecorator("config-defaults"))); + result.add(new DecoratorBuildItem(KNATIVE, new ApplyGlobalContainerConcurrencyDecorator(c))); + }); + + config.globalAutoScaling.requestsPerSecond + .ifPresent(r -> { + result.add( + new DecoratorBuildItem(KNATIVE, new AddConfigMapResourceProvidingDecorator("config-autoscaler"))); + result.add(new DecoratorBuildItem(KNATIVE, new ApplyGlobalRequestsPerSecondTargetDecorator(r))); + }); + + config.globalAutoScaling.targetUtilizationPercentage + .ifPresent(t -> { + result.add( + new DecoratorBuildItem(KNATIVE, new AddConfigMapResourceProvidingDecorator("config-autoscaler"))); + result.add(new DecoratorBuildItem(KNATIVE, new ApplyGlobalTargetUtilizationDecorator(t))); + }); + + if (!config.scaleToZeroEnabled) { + result.add(new DecoratorBuildItem(KNATIVE, new AddConfigMapResourceProvidingDecorator("config-autoscaler"))); + result.add( + new DecoratorBuildItem(KNATIVE, new AddConfigMapDataDecorator("config-autoscaler", "enable-scale-to-zero", + String.valueOf(config.scaleToZeroEnabled)))); + } + + result.add(new DecoratorBuildItem(KNATIVE, new ApplyServiceTypeDecorator(name, config.getServiceType().name()))); + + //In Knative its expected that all http ports in probe are ommitted (so we set them to null). + result.add(new DecoratorBuildItem(KNATIVE, new ApplyHttpGetActionPortDecorator(null))); + return result; + } +} diff --git a/extensions/kubernetes/vanilla/deployment/src/main/java/io/quarkus/kubernetes/deployment/KubernetesCommonHelper.java b/extensions/kubernetes/vanilla/deployment/src/main/java/io/quarkus/kubernetes/deployment/KubernetesCommonHelper.java new file mode 100644 index 0000000000000..859dcb2d39e89 --- /dev/null +++ b/extensions/kubernetes/vanilla/deployment/src/main/java/io/quarkus/kubernetes/deployment/KubernetesCommonHelper.java @@ -0,0 +1,364 @@ + +package io.quarkus.kubernetes.deployment; + +import static io.quarkus.kubernetes.deployment.Constants.QUARKUS_ANNOTATIONS_BUILD_TIMESTAMP; +import static io.quarkus.kubernetes.deployment.Constants.QUARKUS_ANNOTATIONS_COMMIT_ID; +import static io.quarkus.kubernetes.deployment.Constants.QUARKUS_ANNOTATIONS_VCS_URL; + +import java.nio.file.Path; +import java.time.ZoneOffset; +import java.time.ZonedDateTime; +import java.time.format.DateTimeFormatter; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.Set; + +import io.dekorate.kubernetes.config.Annotation; +import io.dekorate.kubernetes.config.PortBuilder; +import io.dekorate.kubernetes.configurator.AddPort; +import io.dekorate.kubernetes.decorator.AddAnnotationDecorator; +import io.dekorate.kubernetes.decorator.AddAwsElasticBlockStoreVolumeDecorator; +import io.dekorate.kubernetes.decorator.AddAzureDiskVolumeDecorator; +import io.dekorate.kubernetes.decorator.AddAzureFileVolumeDecorator; +import io.dekorate.kubernetes.decorator.AddConfigMapVolumeDecorator; +import io.dekorate.kubernetes.decorator.AddHostAliasesDecorator; +import io.dekorate.kubernetes.decorator.AddImagePullSecretDecorator; +import io.dekorate.kubernetes.decorator.AddInitContainerDecorator; +import io.dekorate.kubernetes.decorator.AddLabelDecorator; +import io.dekorate.kubernetes.decorator.AddLivenessProbeDecorator; +import io.dekorate.kubernetes.decorator.AddMountDecorator; +import io.dekorate.kubernetes.decorator.AddPvcVolumeDecorator; +import io.dekorate.kubernetes.decorator.AddReadinessProbeDecorator; +import io.dekorate.kubernetes.decorator.AddRoleBindingResourceDecorator; +import io.dekorate.kubernetes.decorator.AddSecretVolumeDecorator; +import io.dekorate.kubernetes.decorator.AddServiceAccountResourceDecorator; +import io.dekorate.kubernetes.decorator.ApplyArgsDecorator; +import io.dekorate.kubernetes.decorator.ApplyCommandDecorator; +import io.dekorate.kubernetes.decorator.ApplyServiceAccountNamedDecorator; +import io.dekorate.kubernetes.decorator.ApplyWorkingDirDecorator; +import io.dekorate.kubernetes.decorator.RemoveAnnotationDecorator; +import io.dekorate.project.BuildInfo; +import io.dekorate.project.FileProjectFactory; +import io.dekorate.project.Project; +import io.dekorate.project.ScmInfo; +import io.dekorate.utils.Annotations; +import io.quarkus.deployment.builditem.ApplicationInfoBuildItem; +import io.quarkus.deployment.metrics.MetricsCapabilityBuildItem; +import io.quarkus.deployment.pkg.PackageConfig; +import io.quarkus.deployment.pkg.builditem.OutputTargetBuildItem; +import io.quarkus.kubernetes.deployment.Annotations.Prometheus; +import io.quarkus.kubernetes.spi.ConfiguratorBuildItem; +import io.quarkus.kubernetes.spi.DecoratorBuildItem; +import io.quarkus.kubernetes.spi.KubernetesAnnotationBuildItem; +import io.quarkus.kubernetes.spi.KubernetesCommandBuildItem; +import io.quarkus.kubernetes.spi.KubernetesHealthLivenessPathBuildItem; +import io.quarkus.kubernetes.spi.KubernetesHealthReadinessPathBuildItem; +import io.quarkus.kubernetes.spi.KubernetesLabelBuildItem; +import io.quarkus.kubernetes.spi.KubernetesPortBuildItem; +import io.quarkus.kubernetes.spi.KubernetesRoleBindingBuildItem; +import io.quarkus.kubernetes.spi.KubernetesRoleBuildItem; + +public class KubernetesCommonHelper { + + private static final String OUTPUT_ARTIFACT_FORMAT = "%s%s.jar"; + + public static Project createProject(ApplicationInfoBuildItem app, OutputTargetBuildItem outputTarget, + PackageConfig packageConfig) { + return createProject(app, outputTarget.getOutputDirectory() + .resolve(String.format(OUTPUT_ARTIFACT_FORMAT, outputTarget.getBaseName(), packageConfig.runnerSuffix))); + } + + public static Project createProject(ApplicationInfoBuildItem app, Path artifactPath) { + //Let dekorate create a Project instance and then override with what is found in ApplicationInfoBuildItem. + Project project = FileProjectFactory.create(artifactPath.toFile()); + BuildInfo buildInfo = new BuildInfo(app.getName(), app.getVersion(), + "jar", project.getBuildInfo().getBuildTool(), + project.getBuildInfo().getBuildToolVersion(), + artifactPath.toAbsolutePath(), + project.getBuildInfo().getClassOutputDir(), + project.getBuildInfo().getResourceDir()); + + return new Project(project.getRoot(), buildInfo, project.getScmInfo()); + } + + /** + * Creates the common configurator build items. + */ + public static List createGlobalConfigurators(List ports) { + List result = new ArrayList<>(); + verifyPorts(ports).entrySet().stream() + .map(e -> new PortBuilder().withName(e.getKey()).withContainerPort(e.getValue()).build()) + .forEach(p -> result.add(new ConfiguratorBuildItem(new AddPort(p)))); + return result; + } + + /** + * Creates the common configurator build items. + */ + public static List createPlatformConfigurators(PlatformConfiguration config) { + List result = new ArrayList<>(); + config.getPorts().entrySet().forEach(e -> result.add(new ConfiguratorBuildItem(new AddPort(PortConverter.convert(e))))); + return result; + } + + /** + * Creates the common decorator build items. + */ + public static List createDecorators(Project project, String target, String name, + PlatformConfiguration config, + Optional metricsConfiguration, + List annotations, + List labels, + Optional command, + List ports, + Optional livenessProbePath, + Optional readinessProbePath, + List roles, + List roleBindings) { + List result = new ArrayList<>(); + + annotations.forEach(a -> { + result.add(new DecoratorBuildItem(a.getTarget(), + new AddAnnotationDecorator(name, a.getKey(), a.getValue()))); + }); + + labels.forEach(l -> { + result.add(new DecoratorBuildItem(l.getTarget(), + new AddLabelDecorator(name, l.getKey(), l.getValue()))); + }); + + result.addAll(createAnnotationDecorators(project, target, name, config, metricsConfiguration, ports)); + result.addAll(createPodDecorators(project, target, name, config)); + result.addAll(createContainerDecorators(project, target, name, config)); + result.addAll(createMountAndVolumeDecorators(project, target, name, config)); + + //Handle Command and arguments + command.ifPresent(c -> { + result.add(new DecoratorBuildItem(new ApplyCommandDecorator(name, new String[] { c.getCommand() }))); + result.add(new DecoratorBuildItem(new ApplyArgsDecorator(name, c.getArgs()))); + }); + + //Handle Probes + result.addAll(createProbeDecorators(name, target, config.getLivenessProbe(), config.getReadinessProbe(), + livenessProbePath, readinessProbePath)); + + //Handle RBAC + if (!roleBindings.isEmpty()) { + result.add(new DecoratorBuildItem(new ApplyServiceAccountNamedDecorator())); + result.add(new DecoratorBuildItem(new AddServiceAccountResourceDecorator())); + roles.forEach(r -> result.add(new DecoratorBuildItem(new AddRoleResourceDecorator(r)))); + roleBindings.forEach(rb -> result.add(new DecoratorBuildItem( + new AddRoleBindingResourceDecorator(rb.getName(), null, rb.getRole(), rb.isClusterWide() + ? AddRoleBindingResourceDecorator.RoleKind.ClusterRole + : AddRoleBindingResourceDecorator.RoleKind.Role)))); + } + + // The presence of optional is causing issues in OCP 3.11, so we better remove them. + // The following 4 decorator will set the optional property to null, so that it won't make it into the file. + result.add(new DecoratorBuildItem(target, new RemoveOptionalFromSecretEnvSourceDecorator())); + result.add(new DecoratorBuildItem(target, new RemoveOptionalFromConfigMapEnvSourceDecorator())); + result.add(new DecoratorBuildItem(target, new RemoveOptionalFromSecretKeySelectorDecorator())); + result.add(new DecoratorBuildItem(target, new RemoveOptionalFromConfigMapKeySelectorDecorator())); + return result; + } + + /** + * Creates container decorator build items. + * + * @param target The deployment target (e.g. kubernetes, openshift, knative) + * @param name The name of the resource to accept the configuration + * @param config The {@link PlatformConfiguration} instance + */ + private static List createContainerDecorators(Project project, String target, String name, + PlatformConfiguration config) { + List result = new ArrayList<>(); + if (config.getNamespace().isPresent()) { + result.add(new DecoratorBuildItem(target, new AddNamespaceDecorator(config.getNamespace().get()))); + } + + config.getWorkingDir().ifPresent(w -> { + result.add(new DecoratorBuildItem(target, new ApplyWorkingDirDecorator(name, w))); + }); + + config.getCommand().ifPresent(c -> { + result.add(new DecoratorBuildItem(target, new ApplyCommandDecorator(name, c.toArray(new String[0])))); + }); + + config.getArguments().ifPresent(a -> { + result.add(new DecoratorBuildItem(target, new ApplyArgsDecorator(name, a.toArray(new String[0])))); + }); + + return result; + } + + /** + * Creates pod decorator build items. + * + * @param target The deployment target (e.g. kubernetes, openshift, knative) + * @param name The name of the resource to accept the configuration + * @param config The {@link PlatformConfiguration} instance + */ + private static List createPodDecorators(Project project, String target, String name, + PlatformConfiguration config) { + List result = new ArrayList<>(); + config.getImagePullSecrets().ifPresent(l -> { + l.forEach(s -> result.add(new DecoratorBuildItem(target, new AddImagePullSecretDecorator(name, s)))); + }); + + config.getHostAliases().entrySet().forEach(e -> { + result.add(new DecoratorBuildItem(target, new AddHostAliasesDecorator(name, HostAliasConverter.convert(e)))); + }); + + config.getServiceAccount().ifPresent(s -> { + result.add(new DecoratorBuildItem(target, new ApplyServiceAccountNamedDecorator(name, s))); + }); + + config.getInitContainers().entrySet().forEach(e -> { + result.add(new DecoratorBuildItem(target, new AddInitContainerDecorator(name, ContainerConverter.convert(e)))); + }); + + config.getSidecars().entrySet().forEach(e -> { + result.add(new DecoratorBuildItem(target, new AddSidecarDecorator(name, ContainerConverter.convert(e)))); + }); + + return result; + } + + private static List createMountAndVolumeDecorators(Project project, String target, String name, + PlatformConfiguration config) { + List result = new ArrayList<>(); + config.getMounts().entrySet().forEach(e -> { + result.add(new DecoratorBuildItem(target, new AddMountDecorator(MountConverter.convert(e)))); + }); + + config.getSecretVolumes().entrySet().forEach(e -> { + result.add(new DecoratorBuildItem(target, new AddSecretVolumeDecorator(SecretVolumeConverter.convert(e)))); + }); + + config.getConfigMapVolumes().entrySet().forEach(e -> { + result.add(new DecoratorBuildItem(target, new AddConfigMapVolumeDecorator(ConfigMapVolumeConverter.convert(e)))); + }); + + config.getPvcVolumes().entrySet().forEach(e -> { + result.add(new DecoratorBuildItem(target, new AddPvcVolumeDecorator(PvcVolumeConverter.convert(e)))); + }); + + config.getAwsElasticBlockStoreVolumes().entrySet().forEach(e -> { + result.add(new DecoratorBuildItem(target, + new AddAwsElasticBlockStoreVolumeDecorator(AwsElasticBlockStoreVolumeConverter.convert(e)))); + }); + + config.getAzureFileVolumes().entrySet().forEach(e -> { + result.add(new DecoratorBuildItem(target, new AddAzureFileVolumeDecorator(AzureFileVolumeConverter.convert(e)))); + }); + + config.getAzureDiskVolumes().entrySet().forEach(e -> { + result.add(new DecoratorBuildItem(target, new AddAzureDiskVolumeDecorator(AzureDiskVolumeConverter.convert(e)))); + }); + return result; + } + + private static List createAnnotationDecorators(Project project, String target, String name, + PlatformConfiguration config, + Optional metricsConfiguration, + List ports) { + List result = new ArrayList<>(); + ZonedDateTime now = ZonedDateTime.now(ZoneOffset.UTC); + + ScmInfo scm = project.getScmInfo(); + String vcsUrl = scm != null ? scm.getUrl() : null; + String commitId = scm != null ? scm.getCommit() : null; + + //Dekorate uses its own annotations. Let's replace them with the quarkus ones. + result.add(new DecoratorBuildItem(target, new RemoveAnnotationDecorator(Annotations.VCS_URL))); + result.add(new DecoratorBuildItem(target, new RemoveAnnotationDecorator(Annotations.COMMIT_ID))); + + //Add quarkus vcs annotations + if (commitId != null) { + result.add(new DecoratorBuildItem(target, + new AddAnnotationDecorator(name, new Annotation(QUARKUS_ANNOTATIONS_COMMIT_ID, commitId, new String[0])))); + } + if (vcsUrl != null) { + result.add(new DecoratorBuildItem(target, + new AddAnnotationDecorator(name, new Annotation(QUARKUS_ANNOTATIONS_VCS_URL, vcsUrl, new String[0])))); + } + + if (config.isAddBuildTimestamp()) { + result.add(new DecoratorBuildItem(target, + new AddAnnotationDecorator(name, new Annotation(QUARKUS_ANNOTATIONS_BUILD_TIMESTAMP, + now.format(DateTimeFormatter.ofPattern("yyyy-MM-dd - HH:mm:ss Z")), new String[0])))); + } + + metricsConfiguration.ifPresent(m -> { + String path = m.metricsEndpoint(); + if (!ports.isEmpty() && path != null) { + result.add(new DecoratorBuildItem(target, new AddAnnotationDecorator(name, Prometheus.SCRAPE, "true"))); + result.add(new DecoratorBuildItem(target, new AddAnnotationDecorator(name, Prometheus.PATH, path))); + result.add(new DecoratorBuildItem(target, + new AddAnnotationDecorator(name, Prometheus.PORT, "" + ports.get(0).getPort()))); + } + }); + + //Add metrics annotations + return result; + } + + private static List createProbeDecorators(String name, String target, ProbeConfig livenessProbe, + ProbeConfig readinessProbe, + Optional livenessPath, + Optional readinessPath) { + List result = new ArrayList<>(); + createLivenessProbe(name, target, livenessProbe, livenessPath).ifPresent(d -> result.add(d)); + createReadinessProbe(name, target, readinessProbe, readinessPath).ifPresent(d -> result.add(d)); + return result; + } + + private static Optional createLivenessProbe(String name, String target, ProbeConfig livenessProbe, + Optional livenessPath) { + if (livenessProbe.hasUserSuppliedAction()) { + return Optional.of( + new DecoratorBuildItem(target, new AddLivenessProbeDecorator(name, ProbeConverter.convert(livenessProbe)))); + } else if (livenessPath.isPresent()) { + return Optional.of(new DecoratorBuildItem(target, new AddLivenessProbeDecorator(name, + ProbeConverter.builder(livenessProbe).withHttpActionPath(livenessPath.get().getPath()).build()))); + } + return Optional.empty(); + } + + private static Optional createReadinessProbe(String name, String target, ProbeConfig readinessProbe, + Optional readinessPath) { + if (readinessProbe.hasUserSuppliedAction()) { + return Optional.of(new DecoratorBuildItem(target, + new AddReadinessProbeDecorator(name, ProbeConverter.convert(readinessProbe)))); + } else if (readinessPath.isPresent()) { + return Optional.of(new DecoratorBuildItem(target, new AddReadinessProbeDecorator(name, + ProbeConverter.builder(readinessProbe).withHttpActionPath(readinessPath.get().getPath()).build()))); + } + return Optional.empty(); + } + + private static Map verifyPorts(List kubernetesPortBuildItems) { + final Map result = new HashMap<>(); + final Set usedPorts = new HashSet<>(); + for (KubernetesPortBuildItem entry : kubernetesPortBuildItems) { + final String name = entry.getName(); + if (result.containsKey(name)) { + throw new IllegalArgumentException( + "All Kubernetes ports must have unique names - " + name + "has been used multiple times"); + } + final Integer port = entry.getPort(); + if (usedPorts.contains(port)) { + throw new IllegalArgumentException( + "All Kubernetes ports must be unique - " + port + "has been used multiple times"); + } + result.put(name, port); + usedPorts.add(port); + } + return result; + } + +} diff --git a/extensions/kubernetes/vanilla/deployment/src/main/java/io/quarkus/kubernetes/deployment/KubernetesConfig.java b/extensions/kubernetes/vanilla/deployment/src/main/java/io/quarkus/kubernetes/deployment/KubernetesConfig.java index b356ff1cf7706..6aea94037205a 100644 --- a/extensions/kubernetes/vanilla/deployment/src/main/java/io/quarkus/kubernetes/deployment/KubernetesConfig.java +++ b/extensions/kubernetes/vanilla/deployment/src/main/java/io/quarkus/kubernetes/deployment/KubernetesConfig.java @@ -331,6 +331,10 @@ public ServiceType getServiceType() { return serviceType; } + public OptionalInt getNodePort() { + return this.nodePort; + } + public ImagePullPolicy getImagePullPolicy() { return imagePullPolicy; } diff --git a/extensions/kubernetes/vanilla/deployment/src/main/java/io/quarkus/kubernetes/deployment/KubernetesProcessor.java b/extensions/kubernetes/vanilla/deployment/src/main/java/io/quarkus/kubernetes/deployment/KubernetesProcessor.java index 4807c4705dea9..b766ef65f8ced 100644 --- a/extensions/kubernetes/vanilla/deployment/src/main/java/io/quarkus/kubernetes/deployment/KubernetesProcessor.java +++ b/extensions/kubernetes/vanilla/deployment/src/main/java/io/quarkus/kubernetes/deployment/KubernetesProcessor.java @@ -1,48 +1,18 @@ package io.quarkus.kubernetes.deployment; -import static io.quarkus.kubernetes.deployment.Constants.DEFAULT_HTTP_PORT; -import static io.quarkus.kubernetes.deployment.Constants.DEFAULT_S2I_IMAGE_NAME; -import static io.quarkus.kubernetes.deployment.Constants.DEPLOYMENT; -import static io.quarkus.kubernetes.deployment.Constants.DEPLOYMENT_CONFIG; -import static io.quarkus.kubernetes.deployment.Constants.HTTP_PORT; -import static io.quarkus.kubernetes.deployment.Constants.KNATIVE; import static io.quarkus.kubernetes.deployment.Constants.KUBERNETES; -import static io.quarkus.kubernetes.deployment.Constants.MAX_NODE_PORT_VALUE; -import static io.quarkus.kubernetes.deployment.Constants.MAX_PORT_NUMBER; -import static io.quarkus.kubernetes.deployment.Constants.MINIKUBE; -import static io.quarkus.kubernetes.deployment.Constants.MIN_NODE_PORT_VALUE; -import static io.quarkus.kubernetes.deployment.Constants.MIN_PORT_NUMBER; -import static io.quarkus.kubernetes.deployment.Constants.OPENSHIFT; -import static io.quarkus.kubernetes.deployment.Constants.OPENSHIFT_APP_RUNTIME; -import static io.quarkus.kubernetes.deployment.Constants.QUARKUS; -import static io.quarkus.kubernetes.deployment.Constants.QUARKUS_ANNOTATIONS_BUILD_TIMESTAMP; -import static io.quarkus.kubernetes.deployment.Constants.QUARKUS_ANNOTATIONS_COMMIT_ID; -import static io.quarkus.kubernetes.deployment.Constants.QUARKUS_ANNOTATIONS_VCS_URL; -import static io.quarkus.kubernetes.deployment.Constants.SERVICE; -import static io.quarkus.kubernetes.deployment.ResourceNameUtil.getResourceName; -import static io.quarkus.kubernetes.spi.KubernetesDeploymentTargetBuildItem.DEFAULT_PRIORITY; -import static io.quarkus.kubernetes.spi.KubernetesDeploymentTargetBuildItem.VANILLA_KUBERNETES_PRIORITY; import static io.quarkus.kubernetes.spi.KubernetesDeploymentTargetBuildItem.mergeList; import java.io.File; import java.io.IOException; -import java.math.BigInteger; import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; -import java.security.MessageDigest; -import java.time.ZoneOffset; -import java.time.ZonedDateTime; -import java.time.format.DateTimeFormatter; import java.util.ArrayList; -import java.util.Collection; import java.util.Collections; -import java.util.HashMap; -import java.util.HashSet; import java.util.List; import java.util.Map; -import java.util.Optional; import java.util.Set; import java.util.stream.Collectors; @@ -51,76 +21,14 @@ import io.dekorate.Session; import io.dekorate.SessionReader; import io.dekorate.SessionWriter; -import io.dekorate.knative.decorator.ApplyGlobalAutoscalingClassDecorator; -import io.dekorate.knative.decorator.ApplyGlobalContainerConcurrencyDecorator; -import io.dekorate.knative.decorator.ApplyGlobalRequestsPerSecondTargetDecorator; -import io.dekorate.knative.decorator.ApplyGlobalTargetUtilizationDecorator; -import io.dekorate.knative.decorator.ApplyLocalAutoscalingClassDecorator; -import io.dekorate.knative.decorator.ApplyLocalAutoscalingMetricDecorator; -import io.dekorate.knative.decorator.ApplyLocalAutoscalingTargetDecorator; -import io.dekorate.knative.decorator.ApplyLocalContainerConcurrencyDecorator; -import io.dekorate.knative.decorator.ApplyLocalTargetUtilizationPercentageDecorator; -import io.dekorate.knative.decorator.ApplyMaxScaleDecorator; -import io.dekorate.knative.decorator.ApplyMinScaleDecorator; -import io.dekorate.kubernetes.annotation.ImagePullPolicy; -import io.dekorate.kubernetes.annotation.ServiceType; -import io.dekorate.kubernetes.config.Annotation; import io.dekorate.kubernetes.config.Configurator; -import io.dekorate.kubernetes.config.EnvBuilder; -import io.dekorate.kubernetes.config.ImageConfigurationFluent; -import io.dekorate.kubernetes.config.Label; -import io.dekorate.kubernetes.config.LabelBuilder; -import io.dekorate.kubernetes.config.PortBuilder; -import io.dekorate.kubernetes.configurator.AddPort; -import io.dekorate.kubernetes.decorator.AddAnnotationDecorator; -import io.dekorate.kubernetes.decorator.AddAwsElasticBlockStoreVolumeDecorator; -import io.dekorate.kubernetes.decorator.AddAzureDiskVolumeDecorator; -import io.dekorate.kubernetes.decorator.AddAzureFileVolumeDecorator; -import io.dekorate.kubernetes.decorator.AddConfigMapDataDecorator; -import io.dekorate.kubernetes.decorator.AddConfigMapResourceProvidingDecorator; -import io.dekorate.kubernetes.decorator.AddConfigMapVolumeDecorator; -import io.dekorate.kubernetes.decorator.AddEnvVarDecorator; -import io.dekorate.kubernetes.decorator.AddHostAliasesDecorator; -import io.dekorate.kubernetes.decorator.AddImagePullSecretDecorator; -import io.dekorate.kubernetes.decorator.AddInitContainerDecorator; -import io.dekorate.kubernetes.decorator.AddLabelDecorator; -import io.dekorate.kubernetes.decorator.AddLivenessProbeDecorator; -import io.dekorate.kubernetes.decorator.AddMountDecorator; -import io.dekorate.kubernetes.decorator.AddPvcVolumeDecorator; -import io.dekorate.kubernetes.decorator.AddReadinessProbeDecorator; -import io.dekorate.kubernetes.decorator.AddRoleBindingResourceDecorator; -import io.dekorate.kubernetes.decorator.AddSecretVolumeDecorator; -import io.dekorate.kubernetes.decorator.AddServiceAccountResourceDecorator; -import io.dekorate.kubernetes.decorator.ApplicationContainerDecorator; -import io.dekorate.kubernetes.decorator.ApplyArgsDecorator; -import io.dekorate.kubernetes.decorator.ApplyCommandDecorator; -import io.dekorate.kubernetes.decorator.ApplyImagePullPolicyDecorator; -import io.dekorate.kubernetes.decorator.ApplyServiceAccountNamedDecorator; -import io.dekorate.kubernetes.decorator.ApplyWorkingDirDecorator; import io.dekorate.kubernetes.decorator.Decorator; -import io.dekorate.kubernetes.decorator.RemoveAnnotationDecorator; -import io.dekorate.kubernetes.decorator.RemoveFromMatchingLabelsDecorator; -import io.dekorate.kubernetes.decorator.RemoveFromSelectorDecorator; -import io.dekorate.kubernetes.decorator.RemoveLabelDecorator; import io.dekorate.logger.NoopLogger; import io.dekorate.processor.SimpleFileReader; import io.dekorate.processor.SimpleFileWriter; -import io.dekorate.project.BuildInfo; -import io.dekorate.project.FileProjectFactory; import io.dekorate.project.Project; -import io.dekorate.project.ScmInfo; -import io.dekorate.s2i.config.S2iBuildConfig; -import io.dekorate.s2i.config.S2iBuildConfigBuilder; -import io.dekorate.s2i.config.S2iBuildConfigFluent; -import io.dekorate.s2i.decorator.AddBuilderImageStreamResourceDecorator; -import io.dekorate.utils.Annotations; -import io.dekorate.utils.Labels; import io.dekorate.utils.Maps; import io.dekorate.utils.Strings; -import io.quarkus.container.image.deployment.util.ImageUtil; -import io.quarkus.container.spi.BaseImageInfoBuildItem; -import io.quarkus.container.spi.ContainerImageInfoBuildItem; -import io.quarkus.container.spi.ContainerImageLabelBuildItem; import io.quarkus.deployment.Capabilities; import io.quarkus.deployment.Feature; import io.quarkus.deployment.annotations.BuildProducer; @@ -129,22 +37,13 @@ import io.quarkus.deployment.builditem.FeatureBuildItem; import io.quarkus.deployment.builditem.GeneratedFileSystemResourceBuildItem; import io.quarkus.deployment.builditem.LaunchModeBuildItem; -import io.quarkus.deployment.metrics.MetricsCapabilityBuildItem; import io.quarkus.deployment.pkg.PackageConfig; import io.quarkus.deployment.pkg.builditem.OutputTargetBuildItem; import io.quarkus.deployment.util.FileUtil; -import io.quarkus.kubernetes.deployment.Annotations.Prometheus; +import io.quarkus.kubernetes.spi.ConfiguratorBuildItem; import io.quarkus.kubernetes.spi.DecoratorBuildItem; -import io.quarkus.kubernetes.spi.KubernetesAnnotationBuildItem; -import io.quarkus.kubernetes.spi.KubernetesCommandBuildItem; import io.quarkus.kubernetes.spi.KubernetesDeploymentTargetBuildItem; -import io.quarkus.kubernetes.spi.KubernetesEnvBuildItem; -import io.quarkus.kubernetes.spi.KubernetesHealthLivenessPathBuildItem; -import io.quarkus.kubernetes.spi.KubernetesHealthReadinessPathBuildItem; -import io.quarkus.kubernetes.spi.KubernetesLabelBuildItem; import io.quarkus.kubernetes.spi.KubernetesPortBuildItem; -import io.quarkus.kubernetes.spi.KubernetesRoleBindingBuildItem; -import io.quarkus.kubernetes.spi.KubernetesRoleBuildItem; import io.quarkus.runtime.LaunchMode; class KubernetesProcessor { @@ -154,42 +53,11 @@ class KubernetesProcessor { private static final String OUTPUT_ARTIFACT_FORMAT = "%s%s.jar"; public static final String DEFAULT_HASH_ALGORITHM = "SHA-256"; - private static final int MINIKUBE_PRIORITY = DEFAULT_PRIORITY + 20; - private static final int OPENSHIFT_PRIORITY = DEFAULT_PRIORITY + 10; - private static final int KNATIVE_PRIORITY = DEFAULT_PRIORITY; - @BuildStep FeatureBuildItem produceFeature() { return new FeatureBuildItem(Feature.KUBERNETES); } - @BuildStep - public void deploymentTargets(BuildProducer deploymentTargets) { - List userSpecifiedDeploymentTargets = KubernetesConfigUtil.getUserSpecifiedDeploymentTargets(); - if (userSpecifiedDeploymentTargets.isEmpty()) { - // when nothing was selected by the user, we enable vanilla Kubernetes by default - deploymentTargets.produce( - new KubernetesDeploymentTargetBuildItem(KUBERNETES, DEPLOYMENT, VANILLA_KUBERNETES_PRIORITY, true)); - } - - // even if these are disabled, they serve the purpose of setting the proper priorities - - deploymentTargets - .produce(new KubernetesDeploymentTargetBuildItem(MINIKUBE, DEPLOYMENT, MINIKUBE_PRIORITY, - userSpecifiedDeploymentTargets.contains(MINIKUBE))); - - deploymentTargets - .produce(new KubernetesDeploymentTargetBuildItem(OPENSHIFT, DEPLOYMENT_CONFIG, OPENSHIFT_PRIORITY, - userSpecifiedDeploymentTargets.contains(OPENSHIFT))); - - deploymentTargets.produce(new KubernetesDeploymentTargetBuildItem(KNATIVE, SERVICE, KNATIVE_PRIORITY, - userSpecifiedDeploymentTargets.contains(KNATIVE))); - - deploymentTargets.produce( - new KubernetesDeploymentTargetBuildItem(KUBERNETES, DEPLOYMENT, VANILLA_KUBERNETES_PRIORITY, - userSpecifiedDeploymentTargets.contains(KUBERNETES))); - } - @BuildStep public EnabledKubernetesDeploymentTargetsBuildItem enabledKubernetesDeploymentTargets( List allDeploymentTargets) { @@ -206,69 +74,6 @@ public EnabledKubernetesDeploymentTargetsBuildItem enabledKubernetesDeploymentTa return new EnabledKubernetesDeploymentTargetsBuildItem(entries); } - @BuildStep - public List createAnnotations(KubernetesConfig kubernetesConfig, - OpenshiftConfig openshiftConfig, KnativeConfig knativeConfig, - Optional metricsConfiguration, List kubernetesPorts) { - List result = new ArrayList(); - addAnnotations(kubernetesConfig, KUBERNETES, metricsConfiguration, kubernetesPorts, result); - addAnnotations(kubernetesConfig, MINIKUBE, metricsConfiguration, kubernetesPorts, result); - addAnnotations(openshiftConfig, OPENSHIFT, metricsConfiguration, kubernetesPorts, result); - addAnnotations(knativeConfig, KNATIVE, metricsConfiguration, kubernetesPorts, result); - return result; - } - - private void addAnnotations(PlatformConfiguration config, String target, - Optional metricsConfigurationBuildItem, - List kubernetesPorts, - List result) { - for (Map.Entry entry : config.getAnnotations().entrySet()) { - result.add(new KubernetesAnnotationBuildItem(entry.getKey(), entry.getValue(), target)); - } - if (metricsConfigurationBuildItem.isPresent() && !kubernetesPorts.isEmpty()) { - String path = metricsConfigurationBuildItem.get().metricsEndpoint(); - if (path != null) { - result.add(new KubernetesAnnotationBuildItem(Prometheus.SCRAPE, "true", target)); - result.add(new KubernetesAnnotationBuildItem(Prometheus.PATH, path, target)); - result.add(new KubernetesAnnotationBuildItem(Prometheus.PORT, "" + kubernetesPorts.get(0).getPort(), target)); - } - } - } - - @BuildStep - public void createLabels(KubernetesConfig kubernetesConfig, OpenshiftConfig openshiftConfig, - KnativeConfig knativeConfig, - BuildProducer kubernetesLabelsProducer, - BuildProducer containerImageLabelsProducer) { - kubernetesConfig.labels.forEach((k, v) -> { - kubernetesLabelsProducer.produce(new KubernetesLabelBuildItem(k, v, KUBERNETES)); - kubernetesLabelsProducer.produce(new KubernetesLabelBuildItem(k, v, MINIKUBE)); - containerImageLabelsProducer.produce(new ContainerImageLabelBuildItem(k, v)); - }); - openshiftConfig.labels.forEach((k, v) -> { - kubernetesLabelsProducer.produce(new KubernetesLabelBuildItem(k, v, OPENSHIFT)); - containerImageLabelsProducer.produce(new ContainerImageLabelBuildItem(k, v)); - }); - knativeConfig.labels.forEach((k, v) -> { - kubernetesLabelsProducer.produce(new KubernetesLabelBuildItem(k, v, KNATIVE)); - containerImageLabelsProducer.produce(new ContainerImageLabelBuildItem(k, v)); - }); - } - - @BuildStep - public List createEnv(KubernetesConfig kubernetesConfig, OpenshiftConfig openshiftConfig, - KnativeConfig knativeConfig) { - - Collection kubernetesEnvBuildItems = kubernetesConfig.convertToBuildItems(); - List items = new ArrayList<>(kubernetesEnvBuildItems); - for (KubernetesEnvBuildItem kubernetesEnvBuildItem : kubernetesEnvBuildItems) { - items.add(kubernetesEnvBuildItem.newWithTarget(MINIKUBE)); - } - items.addAll(openshiftConfig.convertToBuildItems()); - items.addAll(knativeConfig.convertToBuildItems()); - return items; - } - @BuildStep public void build(ApplicationInfoBuildItem applicationInfo, OutputTargetBuildItem outputTarget, @@ -278,21 +83,14 @@ public void build(ApplicationInfoBuildItem applicationInfo, KnativeConfig knativeConfig, Capabilities capabilities, LaunchModeBuildItem launchMode, - List kubernetesAnnotations, - List kubernetesLabels, - List kubernetesEnvs, - List kubernetesRoles, - List kubernetesRoleBindings, List kubernetesPorts, EnabledKubernetesDeploymentTargetsBuildItem kubernetesDeploymentTargets, + List configurators, List decorators, - Optional baseImage, - Optional containerImage, - Optional command, - Optional kubernetesHealthLivenessPath, - Optional kubernetesHealthReadinessPath, BuildProducer generatedResourceProducer) { + List allConfigurators = new ArrayList<>(configurators); + List allDecorators = new ArrayList<>(decorators); if (launchMode.getLaunchMode() == LaunchMode.TEST) { return; } @@ -314,13 +112,13 @@ public void build(ApplicationInfoBuildItem applicationInfo, .map(DeploymentTargetEntry::getName) .collect(Collectors.toSet()); - Path artifactPath = outputTarget.getOutputDirectory().resolve( - String.format(OUTPUT_ARTIFACT_FORMAT, outputTarget.getBaseName(), packageConfig.runnerSuffix)); + Path artifactPath = outputTarget.getOutputDirectory() + .resolve(String.format(OUTPUT_ARTIFACT_FORMAT, outputTarget.getBaseName(), packageConfig.runnerSuffix)); try { final Map generatedResourcesMap; // by passing false to SimpleFileWriter, we ensure that no files are actually written during this phase - Project project = createProject(applicationInfo, artifactPath); + Project project = KubernetesCommonHelper.createProject(applicationInfo, artifactPath); final SessionWriter sessionWriter = new SimpleFileWriter(project, false); final SessionReader sessionReader = new SimpleFileReader( project.getRoot().resolve("src").resolve("main").resolve("kubernetes"), kubernetesDeploymentTargets @@ -340,53 +138,16 @@ public void build(ApplicationInfoBuildItem applicationInfo, session.feed(Maps.fromProperties(config)); - //Apply configuration - applyGlobalConfig(session, kubernetesConfig); - - ZonedDateTime now = ZonedDateTime.now(ZoneOffset.UTC); - - boolean needToForceUpdateImagePullPolicy = needToForceUpdateImagePullPolicy(deploymentTargets, containerImage, - capabilities); - applyConfig(session, project, KUBERNETES, getResourceName(kubernetesConfig, applicationInfo), kubernetesConfig, - now, determineImagePullPolicy(kubernetesConfig, needToForceUpdateImagePullPolicy)); - applyConfig(session, project, MINIKUBE, getResourceName(kubernetesConfig, applicationInfo), kubernetesConfig, - now, ImagePullPolicy.IfNotPresent); - applyConfig(session, project, OPENSHIFT, getResourceName(openshiftConfig, applicationInfo), openshiftConfig, now, - determineImagePullPolicy(openshiftConfig, needToForceUpdateImagePullPolicy)); - applyConfig(session, project, KNATIVE, getResourceName(knativeConfig, applicationInfo), knativeConfig, now, - determineImagePullPolicy(knativeConfig, needToForceUpdateImagePullPolicy)); - - applyVanillaKubernetesSpecificConfig(session, kubernetesConfig); - applyOpenshiftSpecificConfig(session, openshiftConfig); - applyKnativeSpecificConfig(session, getResourceName(knativeConfig, applicationInfo), knativeConfig); - - if (!capabilities.isCapabilityPresent(Capabilities.CONTAINER_IMAGE_S2I) - && !capabilities.isCapabilityPresent(Capabilities.CONTAINER_IMAGE_OPENSHIFT)) { - handleNonS2IOpenshift(containerImage, session); - } - - //apply build item configurations to the dekorate session. - applyBuildItems(session, - applicationInfo, - kubernetesConfig, - openshiftConfig, - knativeConfig, - deploymentTargets, - kubernetesAnnotations, - kubernetesLabels, - kubernetesEnvs, - kubernetesRoles, - kubernetesRoleBindings, - kubernetesPorts, - baseImage, - containerImage, - command, - kubernetesHealthLivenessPath, - kubernetesHealthReadinessPath); + //We need to verify to filter out anything that doesn't extend the Configurator class. + //The ConfiguratorBuildItem is a wrapper to Object. + allConfigurators.stream().filter(d -> d.matches(Configurator.class)).forEach(i -> { + Configurator configurator = (Configurator) i.getConfigurator(); + session.configurators().add(configurator); + }); //We need to verify to filter out anything that doesn't extend the Decorator class. //The DecoratorBuildItem is a wrapper to Object. - decorators.stream().filter(d -> d.matches(Decorator.class)).forEach(i -> { + allDecorators.stream().filter(d -> d.matches(Decorator.class)).forEach(i -> { String group = i.getGroup(); Decorator decorator = (Decorator) i.getDecorator(); if (Strings.isNullOrEmpty(group)) { @@ -398,7 +159,6 @@ public void build(ApplicationInfoBuildItem applicationInfo, // write the generated resources to the filesystem generatedResourcesMap = session.close(); - List generatedFileNames = new ArrayList<>(generatedResourcesMap.size()); for (Map.Entry resourceEntry : generatedResourcesMap.entrySet()) { Path path = Paths.get(resourceEntry.getKey()); @@ -446,543 +206,4 @@ public void build(ApplicationInfoBuildItem applicationInfo, log.warn("Failed to generate Kubernetes resources", e); } } - - private void handleNonS2IOpenshift(Optional containerImage, Session session) { - //When S2i is disabled we need to pass that information to dekorate. - //Also we need to make sure that the alternatives (instances of ImageConfiguration) - //are properly configured. - session.configurators().add(new Configurator>() { - @Override - public void visit(ImageConfigurationFluent image) { - containerImage.ifPresent(i -> { - String group = ImageUtil.getRepository(i.getImage()).split("/")[0]; - image.withGroup(group); - i.getRegistry().ifPresent(r -> { - image.withRegistry(r); - }); - }); - } - }); - - session.configurators().add(new Configurator>() { - @Override - public void visit(S2iBuildConfigFluent s2i) { - s2i.withEnabled(false); - } - }); - - // remove the ImageChange trigger of the DeploymentConfig - session.resources().decorate(OPENSHIFT, new RemoveDeploymentTriggerDecorator()); - } - - /** - * Apply global changes - * - * @param session The session to apply the changes - * @param config The {@link KubernetesConfig} instance - */ - private void applyGlobalConfig(Session session, KubernetesConfig config) { - //Ports - config.ports.entrySet().forEach(e -> session.configurators().add(new AddPort(PortConverter.convert(e)))); - } - - /** - * Apply changes to the target resource group - * - * @param session The session to apply the changes - * @param target The deployment target (e.g. kubernetes, openshift, knative) - * @param name The name of the resource to accept the configuration - * @param config The {@link PlatformConfiguration} instance - * @param now ZonedDateTime indicating the current time - * @param imagePullPolicy Kubernetes ImagePullPolicy to be used - */ - private void applyConfig(Session session, Project project, String target, String name, PlatformConfiguration config, - ZonedDateTime now, ImagePullPolicy imagePullPolicy) { - - if (config.getNamespace().isPresent()) { - session.resources().decorate(target, new AddNamespaceDecorator(config.getNamespace().get())); - } - - applyAnnotations(session, project, target, config, now); - - config.getWorkingDir().ifPresent(w -> { - session.resources().decorate(target, new ApplyWorkingDirDecorator(name, w)); - }); - - config.getCommand().ifPresent(c -> { - session.resources().decorate(target, new ApplyCommandDecorator(name, c.toArray(new String[0]))); - }); - - config.getArguments().ifPresent(a -> { - session.resources().decorate(target, new ApplyArgsDecorator(name, a.toArray(new String[0]))); - }); - - config.getServiceAccount().ifPresent(s -> { - session.resources().decorate(target, new ApplyServiceAccountNamedDecorator(name, s)); - }); - - //Image Pull - session.resources().decorate(target, new ApplyImagePullPolicyDecorator(imagePullPolicy)); - config.getImagePullSecrets().ifPresent(l -> { - l.forEach(s -> session.resources().decorate(target, new AddImagePullSecretDecorator(name, s))); - }); - - // Mounts and Volumes - config.getMounts().entrySet().forEach(e -> { - session.resources().decorate(target, new AddMountDecorator(MountConverter.convert(e))); - }); - - config.getSecretVolumes().entrySet().forEach(e -> { - session.resources().decorate(target, new AddSecretVolumeDecorator(SecretVolumeConverter.convert(e))); - }); - - config.getConfigMapVolumes().entrySet().forEach(e -> { - session.resources().decorate(target, new AddConfigMapVolumeDecorator(ConfigMapVolumeConverter.convert(e))); - }); - - config.getPvcVolumes().entrySet().forEach(e -> { - session.resources().decorate(target, new AddPvcVolumeDecorator(PvcVolumeConverter.convert(e))); - }); - - config.getAwsElasticBlockStoreVolumes().entrySet().forEach(e -> { - session.resources().decorate(target, - new AddAwsElasticBlockStoreVolumeDecorator(AwsElasticBlockStoreVolumeConverter.convert(e))); - }); - - config.getAzureFileVolumes().entrySet().forEach(e -> { - session.resources().decorate(target, new AddAzureFileVolumeDecorator(AzureFileVolumeConverter.convert(e))); - }); - - config.getAzureDiskVolumes().entrySet().forEach(e -> { - session.resources().decorate(target, new AddAzureDiskVolumeDecorator(AzureDiskVolumeConverter.convert(e))); - }); - - config.getInitContainers().entrySet().forEach(e -> { - session.resources().decorate(target, new AddInitContainerDecorator(name, ContainerConverter.convert(e))); - }); - - config.getSidecars().entrySet().forEach(e -> { - session.resources().decorate(target, new AddSidecarDecorator(name, ContainerConverter.convert(e))); - }); - - config.getHostAliases().entrySet().forEach(e -> { - session.resources().decorate(target, new AddHostAliasesDecorator(name, HostAliasConverter.convert(e))); - }); - - // The presence of optional is causing issues in OCP 3.11, so we better remove them. - // The following 4 decorator will set the optional property to null, so that it won't make it into the file. - session.resources().decorate(target, new RemoveOptionalFromSecretEnvSourceDecorator()); - session.resources().decorate(target, new RemoveOptionalFromConfigMapEnvSourceDecorator()); - session.resources().decorate(target, new RemoveOptionalFromSecretKeySelectorDecorator()); - session.resources().decorate(target, new RemoveOptionalFromConfigMapKeySelectorDecorator()); - } - - private void applyAnnotations(Session session, Project project, String target, PlatformConfiguration config, - ZonedDateTime now) { - ScmInfo scm = project.getScmInfo(); - String vcsUrl = scm != null ? scm.getUrl() : null; - String commitId = scm != null ? scm.getCommit() : null; - - //Dekorate uses its own annotations. Let's replace them with the quarkus ones. - session.resources().decorate(target, new RemoveAnnotationDecorator(Annotations.VCS_URL)); - session.resources().decorate(target, new RemoveAnnotationDecorator(Annotations.COMMIT_ID)); - //Add quarkus vcs annotations - if (commitId != null) { - session.resources().decorate(target, - new AddAnnotationDecorator(new Annotation(QUARKUS_ANNOTATIONS_COMMIT_ID, commitId))); - } - if (vcsUrl != null) { - session.resources().decorate(target, - new AddAnnotationDecorator(new Annotation(QUARKUS_ANNOTATIONS_VCS_URL, vcsUrl))); - } - - if (config.isAddBuildTimestamp()) { - session.resources().decorate(target, new AddAnnotationDecorator(new Annotation(QUARKUS_ANNOTATIONS_BUILD_TIMESTAMP, - now.format(DateTimeFormatter.ofPattern("yyyy-MM-dd - HH:mm:ss Z"))))); - } - } - - // apply Openshift specific configuration - private void applyVanillaKubernetesSpecificConfig(Session session, KubernetesConfig kubernetesConfig) { - if (!kubernetesConfig.addVersionToLabelSelectors) { - session.resources().decorate(KUBERNETES, new RemoveLabelDecorator(Labels.VERSION)); - session.resources().decorate(KUBERNETES, new RemoveFromSelectorDecorator(Labels.VERSION)); - session.resources().decorate(KUBERNETES, new RemoveFromMatchingLabelsDecorator(Labels.VERSION)); - } - } - - // apply Openshift specific configuration - private void applyOpenshiftSpecificConfig(Session session, OpenshiftConfig openshiftConfig) { - session.resources().decorate(OPENSHIFT, new AddLabelDecorator(new Label(OPENSHIFT_APP_RUNTIME, QUARKUS))); - - if (!openshiftConfig.addVersionToLabelSelectors) { - session.resources().decorate(OPENSHIFT, new RemoveLabelDecorator(Labels.VERSION)); - session.resources().decorate(OPENSHIFT, new RemoveFromSelectorDecorator(Labels.VERSION)); - } - } - - // apply Knative specific configuration - private void applyKnativeSpecificConfig(Session session, String name, KnativeConfig config) { - if (config.clusterLocal) { - session.resources().decorate(KNATIVE, new AddLabelDecorator(name, - new LabelBuilder() - .withKey("serving.knative.dev/visibility") - .withValue("cluster-local") - .build())); - } - - config.minScale.ifPresent(min -> session.resources().decorate(KNATIVE, new ApplyMinScaleDecorator(name, min))); - - config.maxScale.ifPresent(max -> session.resources().decorate(KNATIVE, new ApplyMaxScaleDecorator(name, max))); - - config.revisionAutoScaling.autoScalerClass.map(AutoScalerClassConverter::convert) - .ifPresent(a -> session.resources().decorate(KNATIVE, new ApplyLocalAutoscalingClassDecorator(name, a))); - - config.revisionAutoScaling.metric.map(AutoScalingMetricConverter::convert) - .ifPresent(m -> session.resources().decorate(KNATIVE, new ApplyLocalAutoscalingMetricDecorator(name, m))); - - config.revisionAutoScaling.containerConcurrency - .ifPresent(c -> session.resources().decorate(KNATIVE, new ApplyLocalContainerConcurrencyDecorator(name, c))); - - config.revisionAutoScaling.targetUtilizationPercentage - .ifPresent(t -> session.resources().decorate(KNATIVE, - new ApplyLocalTargetUtilizationPercentageDecorator(name, t))); - config.revisionAutoScaling.target - .ifPresent(t -> session.resources().decorate(KNATIVE, new ApplyLocalAutoscalingTargetDecorator(name, t))); - - config.globalAutoScaling.autoScalerClass - .map(AutoScalerClassConverter::convert) - .ifPresent(a -> { - session.resources().decorate(new AddConfigMapResourceProvidingDecorator("config-autoscaler")); - session.resources().decorate(new ApplyGlobalAutoscalingClassDecorator(a)); - }); - - config.globalAutoScaling.containerConcurrency - .ifPresent(c -> { - session.resources().decorate(new AddConfigMapResourceProvidingDecorator("config-defaults")); - session.resources().decorate(new ApplyGlobalContainerConcurrencyDecorator(c)); - }); - - config.globalAutoScaling.requestsPerSecond - .ifPresent(r -> { - session.resources().decorate(new AddConfigMapResourceProvidingDecorator("config-autoscaler")); - session.resources().decorate(new ApplyGlobalRequestsPerSecondTargetDecorator(r)); - }); - - config.globalAutoScaling.targetUtilizationPercentage - .ifPresent(t -> { - session.resources().decorate(new AddConfigMapResourceProvidingDecorator("config-autoscaler")); - session.resources().decorate(new ApplyGlobalTargetUtilizationDecorator(t)); - }); - - if (!config.scaleToZeroEnabled) { - session.resources().decorate(new AddConfigMapResourceProvidingDecorator("config-autoscaler")); - session.resources().decorate(new AddConfigMapDataDecorator("config-autoscaler", "enable-scale-to-zero", - String.valueOf(config.scaleToZeroEnabled))); - } - } - - /** - * When there is no registry defined and s2i isn't being used, the only ImagePullPolicy that can work is 'IfNotPresent'. - * This case comes up when users want to deploy their application to a cluster like Minikube where no registry is used - * and instead they rely on the image being built directly into the docker daemon that the cluster uses. - */ - private boolean needToForceUpdateImagePullPolicy(Collection deploymentTargets, - Optional containerImage, - Capabilities capabilities) { - - // no need to change when we use Minikube only - if ((deploymentTargets.size() == 1) && deploymentTargets.contains(MINIKUBE)) { - return false; - } - - boolean result = containerImage.isPresent() - && ContainerImageUtil.isRegistryMissingAndNotS2I(capabilities, containerImage.get()); - if (result) { - log.warn("No registry was set for the container image, so 'ImagePullPolicy' is being force-set to 'IfNotPresent'."); - return true; - } - return false; - } - - private ImagePullPolicy determineImagePullPolicy(PlatformConfiguration config, boolean needToForceUpdateImagePullPolicy) { - if (needToForceUpdateImagePullPolicy) { - return ImagePullPolicy.IfNotPresent; - } - return config.getImagePullPolicy(); - } - - private void applyBuildItems(Session session, - ApplicationInfoBuildItem applicationInfo, - KubernetesConfig kubernetesConfig, - OpenshiftConfig openshiftConfig, - KnativeConfig knativeConfig, - Set deploymentTargets, - List kubernetesAnnotations, - List kubernetesLabels, - List kubernetesEnvs, - List kubernetesRoles, - List kubernetesRoleBindings, - List kubernetesPorts, - Optional baseImage, - Optional containerImage, - Optional command, - Optional kubernetesHealthLivenessPath, - Optional kubernetesHealthReadinessPath) { - - String kubernetesName = getResourceName(kubernetesConfig, applicationInfo); - String openshiftName = getResourceName(openshiftConfig, applicationInfo); - String knativeName = getResourceName(knativeConfig, applicationInfo); - - Map configMap = new HashMap<>(); - configMap.put(KUBERNETES, kubernetesConfig); - configMap.put(MINIKUBE, kubernetesConfig); - configMap.put(OPENSHIFT, openshiftConfig); - configMap.put(KNATIVE, knativeConfig); - - //Replicas - if (kubernetesConfig.getReplicas() != 1) { - session.resources().decorate(new io.dekorate.kubernetes.decorator.ApplyReplicasDecorator(kubernetesName, - kubernetesConfig.getReplicas())); - } - if (openshiftConfig.getReplicas() != 1) { - session.resources() - .decorate(new io.dekorate.openshift.decorator.ApplyReplicasDecorator(openshiftName, - openshiftConfig.getReplicas())); - } - - kubernetesAnnotations.forEach(a -> { - session.resources().decorate(a.getTarget(), new AddAnnotationDecorator(new Annotation(a.getKey(), a.getValue()))); - }); - - kubernetesLabels.forEach(l -> { - session.resources().decorate(l.getTarget(), new AddLabelDecorator(new Label(l.getKey(), l.getValue()))); - }); - - containerImage.ifPresent(c -> { - session.resources().decorate(OPENSHIFT, new ApplyContainerImageDecorator(openshiftName, c.getImage())); - session.resources().decorate(KUBERNETES, new ApplyContainerImageDecorator(kubernetesName, c.getImage())); - session.resources().decorate(MINIKUBE, new ApplyContainerImageDecorator(kubernetesName, c.getImage())); - session.resources().decorate(KNATIVE, new ApplyContainerImageDecorator(knativeName, c.getImage())); - }); - - kubernetesEnvs.forEach(e -> { - String containerName = kubernetesName; - if (e.getTarget().equals(OPENSHIFT)) { - containerName = openshiftName; - } else if (e.getTarget().equals(KNATIVE)) { - containerName = knativeName; - } - session.resources().decorate(e.getTarget(), createAddEnvDecorator(e, containerName)); - }); - - //Handle Command and arguments - command.ifPresent(c -> { - session.resources().decorate(new ApplyCommandDecorator(kubernetesName, new String[] { c.getCommand() })); - session.resources().decorate(KUBERNETES, new ApplyArgsDecorator(kubernetesName, c.getArgs())); - session.resources().decorate(MINIKUBE, new ApplyArgsDecorator(kubernetesName, c.getArgs())); - - session.resources().decorate(new ApplyCommandDecorator(openshiftName, new String[] { c.getCommand() })); - session.resources().decorate(OPENSHIFT, new ApplyArgsDecorator(openshiftName, c.getArgs())); - - session.resources().decorate(new ApplyCommandDecorator(knativeName, new String[] { c.getCommand() })); - session.resources().decorate(KNATIVE, new ApplyArgsDecorator(knativeName, c.getArgs())); - }); - - //Handle ports - final Map ports = verifyPorts(kubernetesPorts); - ports.entrySet().stream() - .map(e -> new PortBuilder().withName(e.getKey()).withContainerPort(e.getValue()).build()) - .forEach(p -> session.configurators().add(new AddPort(p))); - - //Handle RBAC - if (!kubernetesRoleBindings.isEmpty()) { - session.resources().decorate(new ApplyServiceAccountNamedDecorator()); - session.resources().decorate(new AddServiceAccountResourceDecorator()); - kubernetesRoles.forEach(r -> session.resources().decorate(new AddRoleResourceDecorator(r))); - kubernetesRoleBindings.forEach(rb -> session.resources().decorate( - new AddRoleBindingResourceDecorator(rb.getName(), null, rb.getRole(), - rb.isClusterWide() - ? AddRoleBindingResourceDecorator.RoleKind.ClusterRole - : AddRoleBindingResourceDecorator.RoleKind.Role))); - } - - handleServices(session, kubernetesConfig, openshiftConfig, knativeConfig, kubernetesName, openshiftName, knativeName); - - //Handle custom s2i builder images - if (deploymentTargets.contains(OPENSHIFT)) { - baseImage.map(BaseImageInfoBuildItem::getImage).ifPresent(builderImage -> { - String builderImageName = ImageUtil.getName(builderImage); - S2iBuildConfig s2iBuildConfig = new S2iBuildConfigBuilder().withBuilderImage(builderImage).build(); - if (!DEFAULT_S2I_IMAGE_NAME.equals(builderImageName)) { - session.resources().decorate(OPENSHIFT, new RemoveBuilderImageResourceDecorator(DEFAULT_S2I_IMAGE_NAME)); - } - session.resources().decorate(OPENSHIFT, new AddBuilderImageStreamResourceDecorator(s2iBuildConfig)); - session.resources().decorate(OPENSHIFT, new ApplyBuilderImageDecorator(openshiftName, builderImage)); - }); - } - - // only use the probe config - handleProbes(applicationInfo, kubernetesConfig, openshiftConfig, knativeConfig, deploymentTargets, ports, - kubernetesHealthLivenessPath, - kubernetesHealthReadinessPath, session); - } - - private AddEnvVarDecorator createAddEnvDecorator(KubernetesEnvBuildItem e, String containerName) { - return new AddEnvVarDecorator(ApplicationContainerDecorator.ANY, containerName, new EnvBuilder() - .withName(EnvConverter.convertName(e.getName())) - .withValue(e.getValue()) - .withSecret(e.getSecret()) - .withConfigmap(e.getConfigMap()) - .withField(e.getField()) - .build()); - } - - private void handleServices(Session session, KubernetesConfig kubernetesConfig, OpenshiftConfig openshiftConfig, - KnativeConfig knativeConfig, String kubernetesName, String openshiftName, String knativeName) { - session.resources().decorate(KUBERNETES, - new ApplyServiceTypeDecorator(kubernetesName, kubernetesConfig.getServiceType().name())); - if ((kubernetesConfig.getServiceType() == ServiceType.NodePort) && kubernetesConfig.nodePort.isPresent()) { - session.resources().decorate(KUBERNETES, - new AddNodePortDecorator(openshiftName, kubernetesConfig.nodePort.getAsInt())); - } - session.resources().decorate(MINIKUBE, - new ApplyServiceTypeDecorator(kubernetesName, ServiceType.NodePort.name())); - session.resources().decorate(MINIKUBE, new AddNodePortDecorator(kubernetesName, kubernetesConfig.nodePort - .orElseGet(() -> getStablePortNumberInRange(kubernetesName, MIN_NODE_PORT_VALUE, MAX_NODE_PORT_VALUE)))); - - session.resources().decorate(OPENSHIFT, - new ApplyServiceTypeDecorator(openshiftName, openshiftConfig.getServiceType().name())); - if ((openshiftConfig.getServiceType() == ServiceType.NodePort) && openshiftConfig.nodePort.isPresent()) { - session.resources().decorate(OPENSHIFT, - new AddNodePortDecorator(openshiftName, openshiftConfig.nodePort.getAsInt())); - } - - session.resources().decorate(KNATIVE, - new ApplyServiceTypeDecorator(knativeName, knativeConfig.getServiceType().name())); - } - - /** - * Given a string, generate a port number within the supplied range - * The output is always the same (between {@code min} and {@code max}) - * given the same input and it's useful when we need to generate a port number - * which needs to stay the same but we don't care about the exact value - */ - private int getStablePortNumberInRange(String input, int min, int max) { - if (min < MIN_PORT_NUMBER || max > MAX_PORT_NUMBER) { - throw new IllegalArgumentException( - String.format("Port number range must be within [%d-%d]", MIN_PORT_NUMBER, MAX_PORT_NUMBER)); - } - - try { - byte[] hash = MessageDigest.getInstance(DEFAULT_HASH_ALGORITHM).digest(input.getBytes(StandardCharsets.UTF_8)); - return min + new BigInteger(hash).mod(BigInteger.valueOf(max - min)).intValue(); - } catch (Exception e) { - throw new RuntimeException("Unable to generate stable port number from input string: '" + input + "'", e); - } - } - - private void handleProbes(ApplicationInfoBuildItem applicationInfo, KubernetesConfig kubernetesConfig, - OpenshiftConfig openshiftConfig, - KnativeConfig knativeConfig, - Set deploymentTargets, Map ports, - Optional kubernetesHealthLivenessPathBuildItem, - Optional kubernetesHealthReadinessPathBuildItem, - Session session) { - if (deploymentTargets.contains(KUBERNETES)) { - doHandleProbes(getResourceName(kubernetesConfig, applicationInfo), KUBERNETES, ports, - kubernetesConfig.livenessProbe, kubernetesConfig.readinessProbe, kubernetesHealthLivenessPathBuildItem, - kubernetesHealthReadinessPathBuildItem, session); - } - if (deploymentTargets.contains(MINIKUBE)) { - doHandleProbes(getResourceName(kubernetesConfig, applicationInfo), MINIKUBE, ports, - kubernetesConfig.livenessProbe, kubernetesConfig.readinessProbe, kubernetesHealthLivenessPathBuildItem, - kubernetesHealthReadinessPathBuildItem, session); - } - if (deploymentTargets.contains(OPENSHIFT)) { - doHandleProbes(getResourceName(kubernetesConfig, applicationInfo), OPENSHIFT, ports, openshiftConfig.livenessProbe, - openshiftConfig.readinessProbe, kubernetesHealthLivenessPathBuildItem, - kubernetesHealthReadinessPathBuildItem, session); - } - if (deploymentTargets.contains(KNATIVE)) { - doHandleProbes(getResourceName(kubernetesConfig, applicationInfo), KNATIVE, ports, knativeConfig.livenessProbe, - knativeConfig.readinessProbe, kubernetesHealthLivenessPathBuildItem, kubernetesHealthReadinessPathBuildItem, - session); - } - } - - private void doHandleProbes(String name, String target, Map ports, ProbeConfig livenessProbe, - ProbeConfig readinessProbe, Optional kubernetesHealthLivenessPathBuildItem, - Optional kubernetesHealthReadinessPathBuildItem, Session session) { - handleLivenessProbe(name, target, livenessProbe, kubernetesHealthLivenessPathBuildItem, session); - handleReadinessProbe(name, target, readinessProbe, kubernetesHealthReadinessPathBuildItem, - session); - - //For knative we want the port to be null - Integer port = KNATIVE.equals(target) ? null : ports.getOrDefault(HTTP_PORT, DEFAULT_HTTP_PORT); - session.resources().decorate(target, new ApplyHttpGetActionPortDecorator(port)); - } - - private void handleLivenessProbe(String name, String target, ProbeConfig livenessProbe, - Optional kubernetesHealthLivenessPathBuildItem, Session session) { - AddLivenessProbeDecorator livenessProbeDecorator = null; - if (livenessProbe.hasUserSuppliedAction()) { - livenessProbeDecorator = new AddLivenessProbeDecorator(name, - ProbeConverter.convert(livenessProbe)); - } else if (kubernetesHealthLivenessPathBuildItem.isPresent()) { - livenessProbeDecorator = new AddLivenessProbeDecorator(name, - ProbeConverter.builder(livenessProbe) - .withHttpActionPath(kubernetesHealthLivenessPathBuildItem.get().getPath()).build()); - } - if (livenessProbeDecorator != null) { - session.resources().decorate(target, livenessProbeDecorator); - } - } - - private void handleReadinessProbe(String name, String target, ProbeConfig readinessProbe, - Optional healthReadinessPathBuildItem, Session session) { - AddReadinessProbeDecorator readinessProbeDecorator = null; - if (readinessProbe.hasUserSuppliedAction()) { - readinessProbeDecorator = new AddReadinessProbeDecorator(name, ProbeConverter.convert(readinessProbe)); - } else if (healthReadinessPathBuildItem.isPresent()) { - readinessProbeDecorator = new AddReadinessProbeDecorator(name, ProbeConverter.builder(readinessProbe) - .withHttpActionPath(healthReadinessPathBuildItem.get().getPath()).build()); - } - if (readinessProbeDecorator != null) { - session.resources().decorate(target, readinessProbeDecorator); - } - } - - private Map verifyPorts(List kubernetesPortBuildItems) { - final Map result = new HashMap<>(); - final Set usedPorts = new HashSet<>(); - for (KubernetesPortBuildItem entry : kubernetesPortBuildItems) { - final String name = entry.getName(); - if (result.containsKey(name)) { - throw new IllegalArgumentException( - "All Kubernetes ports must have unique names - " + name + "has been used multiple times"); - } - final Integer port = entry.getPort(); - if (usedPorts.contains(port)) { - throw new IllegalArgumentException( - "All Kubernetes ports must be unique - " + port + "has been used multiple times"); - } - result.put(name, port); - usedPorts.add(port); - } - return result; - } - - private Project createProject(ApplicationInfoBuildItem app, Path artifactPath) { - //Let dekorate create a Project instance and then override with what is found in ApplicationInfoBuildItem. - Project project = FileProjectFactory.create(artifactPath.toFile()); - BuildInfo buildInfo = new BuildInfo(app.getName(), app.getVersion(), - "jar", project.getBuildInfo().getBuildTool(), - artifactPath.toAbsolutePath().toString(), - artifactPath, - project.getBuildInfo().getResourceDir()); - - return new Project(project.getRoot(), buildInfo, project.getScmInfo()); - } - } diff --git a/extensions/kubernetes/vanilla/deployment/src/main/java/io/quarkus/kubernetes/deployment/MinikubeHandler.java b/extensions/kubernetes/vanilla/deployment/src/main/java/io/quarkus/kubernetes/deployment/MinikubeHandler.java index e0b56ccba9071..d2a1a8955f351 100644 --- a/extensions/kubernetes/vanilla/deployment/src/main/java/io/quarkus/kubernetes/deployment/MinikubeHandler.java +++ b/extensions/kubernetes/vanilla/deployment/src/main/java/io/quarkus/kubernetes/deployment/MinikubeHandler.java @@ -1,6 +1,5 @@ package io.quarkus.kubernetes.deployment; -import static io.dekorate.utils.Labels.createLabels; import static io.quarkus.kubernetes.deployment.Constants.MINIKUBE; import java.util.Optional; @@ -125,7 +124,7 @@ protected void addDecorators(String group, KubernetesConfig config) { resources.decorate(group, new AddServiceResourceDecorator(config)); } - resources.decorate(group, new AddIngressDecorator(config, Labels.createLabels(config))); + resources.decorate(group, new AddIngressDecorator(config, Labels.createLabelsAsMap(config, "Ingress"))); resources.decorate(group, new ApplyLabelSelectorDecorator(createSelector(config))); } @@ -139,7 +138,7 @@ public Deployment createDeployment(KubernetesConfig appConfig, ImageConfiguratio return new DeploymentBuilder() .withNewMetadata() .withName(appConfig.getName()) - .withLabels(Labels.createLabels(appConfig)) + .withLabels(Labels.createLabelsAsMap(appConfig, "Deployment")) .endMetadata() .withNewSpec() .withReplicas(1) @@ -156,7 +155,7 @@ public Deployment createDeployment(KubernetesConfig appConfig, ImageConfiguratio */ public LabelSelector createSelector(KubernetesConfig config) { return new LabelSelectorBuilder() - .withMatchLabels(Labels.createLabels(config)) + .withMatchLabels(Labels.createLabelsAsMap(config, "Deployment")) .build(); } @@ -170,7 +169,7 @@ public static PodTemplateSpec createPodTemplateSpec(KubernetesConfig appConfig, return new PodTemplateSpecBuilder() .withSpec(createPodSpec(appConfig, imageConfig)) .withNewMetadata() - .withLabels(createLabels(appConfig)) + .withLabels(Labels.createLabelsAsMap(appConfig, "Deployment")) .endMetadata() .build(); } @@ -236,4 +235,5 @@ private static ImageConfiguration merge(KubernetesConfig appConfig, ImageConfigu .withAutoPushEnabled(imageConfig.isAutoPushEnabled() ? imageConfig.isAutoPushEnabled() : false) .build(); } + } diff --git a/extensions/kubernetes/vanilla/deployment/src/main/java/io/quarkus/kubernetes/deployment/OpenshiftProcessor.java b/extensions/kubernetes/vanilla/deployment/src/main/java/io/quarkus/kubernetes/deployment/OpenshiftProcessor.java new file mode 100644 index 0000000000000..5c6134593ca93 --- /dev/null +++ b/extensions/kubernetes/vanilla/deployment/src/main/java/io/quarkus/kubernetes/deployment/OpenshiftProcessor.java @@ -0,0 +1,186 @@ + +package io.quarkus.kubernetes.deployment; + +import static io.quarkus.kubernetes.deployment.Constants.DEFAULT_HTTP_PORT; +import static io.quarkus.kubernetes.deployment.Constants.DEFAULT_S2I_IMAGE_NAME; +import static io.quarkus.kubernetes.deployment.Constants.DEPLOYMENT; +import static io.quarkus.kubernetes.deployment.Constants.HTTP_PORT; +import static io.quarkus.kubernetes.deployment.Constants.OPENSHIFT; +import static io.quarkus.kubernetes.deployment.Constants.OPENSHIFT_APP_RUNTIME; +import static io.quarkus.kubernetes.deployment.Constants.QUARKUS; +import static io.quarkus.kubernetes.spi.KubernetesDeploymentTargetBuildItem.DEFAULT_PRIORITY; + +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; +import java.util.stream.Stream; + +import io.dekorate.kubernetes.annotation.ServiceType; +import io.dekorate.kubernetes.config.EnvBuilder; +import io.dekorate.kubernetes.decorator.AddEnvVarDecorator; +import io.dekorate.kubernetes.decorator.AddLabelDecorator; +import io.dekorate.kubernetes.decorator.ApplicationContainerDecorator; +import io.dekorate.kubernetes.decorator.RemoveFromSelectorDecorator; +import io.dekorate.kubernetes.decorator.RemoveLabelDecorator; +import io.dekorate.openshift.decorator.ApplyReplicasDecorator; +import io.dekorate.project.Project; +import io.dekorate.s2i.config.S2iBuildConfig; +import io.dekorate.s2i.config.S2iBuildConfigBuilder; +import io.dekorate.s2i.decorator.AddBuilderImageStreamResourceDecorator; +import io.dekorate.utils.Labels; +import io.quarkus.container.image.deployment.util.ImageUtil; +import io.quarkus.container.spi.BaseImageInfoBuildItem; +import io.quarkus.container.spi.ContainerImageInfoBuildItem; +import io.quarkus.container.spi.ContainerImageLabelBuildItem; +import io.quarkus.deployment.Capabilities; +import io.quarkus.deployment.annotations.BuildProducer; +import io.quarkus.deployment.annotations.BuildStep; +import io.quarkus.deployment.builditem.ApplicationInfoBuildItem; +import io.quarkus.deployment.metrics.MetricsCapabilityBuildItem; +import io.quarkus.deployment.pkg.PackageConfig; +import io.quarkus.deployment.pkg.builditem.OutputTargetBuildItem; +import io.quarkus.kubernetes.spi.ConfiguratorBuildItem; +import io.quarkus.kubernetes.spi.DecoratorBuildItem; +import io.quarkus.kubernetes.spi.KubernetesAnnotationBuildItem; +import io.quarkus.kubernetes.spi.KubernetesCommandBuildItem; +import io.quarkus.kubernetes.spi.KubernetesDeploymentTargetBuildItem; +import io.quarkus.kubernetes.spi.KubernetesEnvBuildItem; +import io.quarkus.kubernetes.spi.KubernetesHealthLivenessPathBuildItem; +import io.quarkus.kubernetes.spi.KubernetesHealthReadinessPathBuildItem; +import io.quarkus.kubernetes.spi.KubernetesLabelBuildItem; +import io.quarkus.kubernetes.spi.KubernetesPortBuildItem; +import io.quarkus.kubernetes.spi.KubernetesRoleBindingBuildItem; +import io.quarkus.kubernetes.spi.KubernetesRoleBuildItem; + +public class OpenshiftProcessor { + + private static final int OPENSHIFT_PRIORITY = DEFAULT_PRIORITY; + + @BuildStep + public void checkOpenshift(BuildProducer deploymentTargets) { + List targets = KubernetesConfigUtil.getUserSpecifiedDeploymentTargets(); + deploymentTargets.produce(new KubernetesDeploymentTargetBuildItem(OPENSHIFT, DEPLOYMENT, OPENSHIFT_PRIORITY, + targets.contains(OPENSHIFT))); + } + + @BuildStep + public void createAnnotations(OpenshiftConfig config, BuildProducer annotations) { + config.getAnnotations().forEach((k, v) -> { + annotations.produce(new KubernetesAnnotationBuildItem(k, v, OPENSHIFT)); + }); + } + + @BuildStep + public void createLabels(OpenshiftConfig config, BuildProducer labels, + BuildProducer imageLabels) { + config.getLabels().forEach((k, v) -> { + labels.produce(new KubernetesLabelBuildItem(k, v, OPENSHIFT)); + imageLabels.produce(new ContainerImageLabelBuildItem(k, v)); + }); + } + + @BuildStep + public List createConfigurators(ApplicationInfoBuildItem applicationInfo, + OpenshiftConfig config, Capabilities capabilities, Optional image, + List ports) { + + List result = new ArrayList<>(); + result.addAll(KubernetesCommonHelper.createPlatformConfigurators(config)); + result.addAll(KubernetesCommonHelper.createGlobalConfigurators(ports)); + + if (!capabilities.isCapabilityPresent(Capabilities.CONTAINER_IMAGE_S2I) + && !capabilities.isCapabilityPresent(Capabilities.CONTAINER_IMAGE_OPENSHIFT)) { + result.add(new ConfiguratorBuildItem(new DisableS2iConfigurator())); + + image.flatMap(ContainerImageInfoBuildItem::getRegistry).ifPresent(r -> { + result.add(new ConfiguratorBuildItem(new ApplyImageRegistryConfigurator(r))); + }); + + image.map(ContainerImageInfoBuildItem::getGroup).ifPresent(g -> { + result.add(new ConfiguratorBuildItem(new ApplyImageGroupConfigurator(g))); + }); + } + return result; + } + + @BuildStep + public List createDecorators(ApplicationInfoBuildItem applicationInfo, + OutputTargetBuildItem outputTarget, + OpenshiftConfig config, + PackageConfig packageConfig, + Optional metricsConfiguration, + Capabilities capabilities, + List annotations, + List labels, + List envs, + Optional baseImage, + Optional image, + Optional command, + List ports, + Optional livenessPath, + Optional readinessPath, + List roles, + List roleBindings) { + + List result = new ArrayList<>(); + String name = ResourceNameUtil.getResourceName(config, applicationInfo); + + Project project = KubernetesCommonHelper.createProject(applicationInfo, outputTarget, packageConfig); + result.addAll(KubernetesCommonHelper.createDecorators(project, OPENSHIFT, name, config, metricsConfiguration, + annotations, labels, command, + ports, livenessPath, readinessPath, roles, roleBindings)); + + if (config.getReplicas() != 1) { + result.add(new DecoratorBuildItem(OPENSHIFT, new ApplyReplicasDecorator(name, config.getReplicas()))); + } + image.ifPresent(i -> { + result.add(new DecoratorBuildItem(OPENSHIFT, new ApplyContainerImageDecorator(name, i.getImage()))); + }); + result.add(new DecoratorBuildItem(OPENSHIFT, new AddLabelDecorator(name, OPENSHIFT_APP_RUNTIME, QUARKUS))); + + Stream.concat(config.convertToBuildItems().stream(), + envs.stream().filter(e -> e.getTarget() == null || OPENSHIFT.equals(e.getTarget()))).forEach(e -> { + result.add(new DecoratorBuildItem(OPENSHIFT, + new AddEnvVarDecorator(ApplicationContainerDecorator.ANY, name, + new EnvBuilder().withName(EnvConverter.convertName(e.getName())).withValue(e.getValue()) + .withSecret(e.getSecret()).withConfigmap(e.getConfigMap()).withField(e.getField()) + .build()))); + }); + + // Handle custom s2i builder images + baseImage.map(BaseImageInfoBuildItem::getImage).ifPresent(builderImage -> { + String builderImageName = ImageUtil.getName(builderImage); + S2iBuildConfig s2iBuildConfig = new S2iBuildConfigBuilder().withBuilderImage(builderImage).build(); + if (!DEFAULT_S2I_IMAGE_NAME.equals(builderImageName)) { + result.add(new DecoratorBuildItem(OPENSHIFT, new RemoveBuilderImageResourceDecorator(DEFAULT_S2I_IMAGE_NAME))); + } + result.add(new DecoratorBuildItem(OPENSHIFT, new AddBuilderImageStreamResourceDecorator(s2iBuildConfig))); + result.add(new DecoratorBuildItem(OPENSHIFT, new ApplyBuilderImageDecorator(name, builderImage))); + }); + + if (!config.addVersionToLabelSelectors) { + result.add(new DecoratorBuildItem(OPENSHIFT, new RemoveLabelDecorator(name, Labels.VERSION))); + result.add(new DecoratorBuildItem(OPENSHIFT, new RemoveFromSelectorDecorator(name, Labels.VERSION))); + } + + // Service handling + result.add(new DecoratorBuildItem(OPENSHIFT, new ApplyServiceTypeDecorator(name, config.getServiceType().name()))); + if ((config.getServiceType() == ServiceType.NodePort) && config.nodePort.isPresent()) { + result.add(new DecoratorBuildItem(OPENSHIFT, new AddNodePortDecorator(name, config.nodePort.getAsInt()))); + } + + // Probe port handling + Integer port = ports.stream().filter(p -> HTTP_PORT.equals(p.getName())).map(KubernetesPortBuildItem::getPort) + .findFirst().orElse(DEFAULT_HTTP_PORT); + result.add(new DecoratorBuildItem(OPENSHIFT, new ApplyHttpGetActionPortDecorator(port))); + + // Hanlde non-s2i + if (!capabilities.isCapabilityPresent(Capabilities.CONTAINER_IMAGE_S2I) + && !capabilities.isCapabilityPresent(Capabilities.CONTAINER_IMAGE_OPENSHIFT)) { + result.add(new DecoratorBuildItem(OPENSHIFT, new RemoveDeploymentTriggerDecorator())); + } + + return result; + } + +} diff --git a/extensions/kubernetes/vanilla/deployment/src/main/java/io/quarkus/kubernetes/deployment/ResourceNameUtil.java b/extensions/kubernetes/vanilla/deployment/src/main/java/io/quarkus/kubernetes/deployment/ResourceNameUtil.java index e3479cfbef334..afd565d71b3ff 100644 --- a/extensions/kubernetes/vanilla/deployment/src/main/java/io/quarkus/kubernetes/deployment/ResourceNameUtil.java +++ b/extensions/kubernetes/vanilla/deployment/src/main/java/io/quarkus/kubernetes/deployment/ResourceNameUtil.java @@ -2,7 +2,7 @@ import io.quarkus.deployment.builditem.ApplicationInfoBuildItem; -final class ResourceNameUtil { +public final class ResourceNameUtil { private ResourceNameUtil() { } @@ -12,7 +12,8 @@ private ResourceNameUtil() { * Uses the value from the configuration object if it exists, otherwise falls back to * the application name */ - static String getResourceName(PlatformConfiguration platformConfiguration, ApplicationInfoBuildItem applicationInfo) { + public static String getResourceName(PlatformConfiguration platformConfiguration, + ApplicationInfoBuildItem applicationInfo) { return platformConfiguration.getName().orElse(applicationInfo.getName()); } } diff --git a/extensions/kubernetes/vanilla/deployment/src/main/java/io/quarkus/kubernetes/deployment/VanillaKubernetesProcessor.java b/extensions/kubernetes/vanilla/deployment/src/main/java/io/quarkus/kubernetes/deployment/VanillaKubernetesProcessor.java new file mode 100644 index 0000000000000..5499631b073e1 --- /dev/null +++ b/extensions/kubernetes/vanilla/deployment/src/main/java/io/quarkus/kubernetes/deployment/VanillaKubernetesProcessor.java @@ -0,0 +1,146 @@ + +package io.quarkus.kubernetes.deployment; + +import static io.quarkus.kubernetes.deployment.Constants.DEFAULT_HTTP_PORT; +import static io.quarkus.kubernetes.deployment.Constants.DEPLOYMENT; +import static io.quarkus.kubernetes.deployment.Constants.HTTP_PORT; +import static io.quarkus.kubernetes.deployment.Constants.KUBERNETES; +import static io.quarkus.kubernetes.spi.KubernetesDeploymentTargetBuildItem.VANILLA_KUBERNETES_PRIORITY; + +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; +import java.util.stream.Stream; + +import io.dekorate.kubernetes.annotation.ServiceType; +import io.dekorate.kubernetes.config.EnvBuilder; +import io.dekorate.kubernetes.decorator.AddEnvVarDecorator; +import io.dekorate.kubernetes.decorator.ApplicationContainerDecorator; +import io.dekorate.kubernetes.decorator.ApplyImagePullPolicyDecorator; +import io.dekorate.kubernetes.decorator.ApplyReplicasDecorator; +import io.dekorate.kubernetes.decorator.RemoveFromMatchingLabelsDecorator; +import io.dekorate.kubernetes.decorator.RemoveFromSelectorDecorator; +import io.dekorate.kubernetes.decorator.RemoveLabelDecorator; +import io.dekorate.project.Project; +import io.dekorate.utils.Labels; +import io.quarkus.container.spi.ContainerImageInfoBuildItem; +import io.quarkus.container.spi.ContainerImageLabelBuildItem; +import io.quarkus.deployment.annotations.BuildProducer; +import io.quarkus.deployment.annotations.BuildStep; +import io.quarkus.deployment.builditem.ApplicationInfoBuildItem; +import io.quarkus.deployment.metrics.MetricsCapabilityBuildItem; +import io.quarkus.deployment.pkg.PackageConfig; +import io.quarkus.deployment.pkg.builditem.OutputTargetBuildItem; +import io.quarkus.kubernetes.spi.ConfiguratorBuildItem; +import io.quarkus.kubernetes.spi.DecoratorBuildItem; +import io.quarkus.kubernetes.spi.KubernetesAnnotationBuildItem; +import io.quarkus.kubernetes.spi.KubernetesCommandBuildItem; +import io.quarkus.kubernetes.spi.KubernetesDeploymentTargetBuildItem; +import io.quarkus.kubernetes.spi.KubernetesEnvBuildItem; +import io.quarkus.kubernetes.spi.KubernetesHealthLivenessPathBuildItem; +import io.quarkus.kubernetes.spi.KubernetesHealthReadinessPathBuildItem; +import io.quarkus.kubernetes.spi.KubernetesLabelBuildItem; +import io.quarkus.kubernetes.spi.KubernetesPortBuildItem; +import io.quarkus.kubernetes.spi.KubernetesRoleBindingBuildItem; +import io.quarkus.kubernetes.spi.KubernetesRoleBuildItem; + +public class VanillaKubernetesProcessor { + + @BuildStep + public void checkVanillaKubernetes(BuildProducer deploymentTargets) { + List userSpecifiedDeploymentTargets = KubernetesConfigUtil.getUserSpecifiedDeploymentTargets(); + if (userSpecifiedDeploymentTargets.isEmpty()) { + // when nothing was selected by the user, we enable vanilla Kubernetes by + // default + deploymentTargets + .produce( + new KubernetesDeploymentTargetBuildItem(KUBERNETES, DEPLOYMENT, VANILLA_KUBERNETES_PRIORITY, true)); + } + + deploymentTargets.produce(new KubernetesDeploymentTargetBuildItem(KUBERNETES, DEPLOYMENT, + VANILLA_KUBERNETES_PRIORITY, userSpecifiedDeploymentTargets.contains(KUBERNETES))); + } + + @BuildStep + public void createAnnotations(KubernetesConfig config, BuildProducer annotations) { + config.annotations.forEach((k, v) -> { + annotations.produce(new KubernetesAnnotationBuildItem(k, v, KUBERNETES)); + }); + } + + @BuildStep + public void createLabels(KubernetesConfig config, BuildProducer labels, + BuildProducer imageLabels) { + config.labels.forEach((k, v) -> { + labels.produce(new KubernetesLabelBuildItem(k, v, KUBERNETES)); + imageLabels.produce(new ContainerImageLabelBuildItem(k, v)); + }); + } + + @BuildStep + public List createConfigurators(KubernetesConfig config, List ports) { + List result = new ArrayList<>(); + result.addAll(KubernetesCommonHelper.createPlatformConfigurators(config)); + result.addAll(KubernetesCommonHelper.createGlobalConfigurators(ports)); + return result; + + } + + @BuildStep + public List createDecorators(ApplicationInfoBuildItem applicationInfo, + OutputTargetBuildItem outputTarget, KubernetesConfig config, PackageConfig packageConfig, + Optional metricsConfiguration, List annotations, + List labels, List envs, + Optional image, Optional command, + List ports, Optional livenessPath, + Optional readinessPath, List roles, + List roleBindings) { + + final List result = new ArrayList<>(); + final String name = ResourceNameUtil.getResourceName(config, applicationInfo); + + Project project = KubernetesCommonHelper.createProject(applicationInfo, outputTarget, packageConfig); + result.addAll(KubernetesCommonHelper.createDecorators(project, KUBERNETES, name, config, metricsConfiguration, + annotations, labels, command, ports, livenessPath, readinessPath, roles, roleBindings)); + + if (config.getReplicas() != 1) { + result.add(new DecoratorBuildItem(KUBERNETES, new ApplyReplicasDecorator(name, config.getReplicas()))); + } + + image.ifPresent(i -> { + result.add(new DecoratorBuildItem(KUBERNETES, new ApplyContainerImageDecorator(name, i.getImage()))); + }); + + result + .add(new DecoratorBuildItem(KUBERNETES, new ApplyImagePullPolicyDecorator(name, config.getImagePullPolicy()))); + + Stream.concat(config.convertToBuildItems().stream(), + envs.stream().filter(e -> e.getTarget() == null || KUBERNETES.equals(e.getTarget()))).forEach(e -> { + result.add(new DecoratorBuildItem(KUBERNETES, + new AddEnvVarDecorator(ApplicationContainerDecorator.ANY, name, + new EnvBuilder().withName(EnvConverter.convertName(e.getName())).withValue(e.getValue()) + .withSecret(e.getSecret()).withConfigmap(e.getConfigMap()).withField(e.getField()) + .build()))); + }); + + if (!config.addVersionToLabelSelectors) { + result.add(new DecoratorBuildItem(KUBERNETES, new RemoveLabelDecorator(name, Labels.VERSION))); + result.add(new DecoratorBuildItem(KUBERNETES, new RemoveFromSelectorDecorator(name, Labels.VERSION))); + result.add(new DecoratorBuildItem(KUBERNETES, new RemoveFromMatchingLabelsDecorator(name, Labels.VERSION))); + } + + // Service handling + result.add(new DecoratorBuildItem(KUBERNETES, new ApplyServiceTypeDecorator(name, config.getServiceType().name()))); + if ((config.getServiceType() == ServiceType.NodePort) && config.nodePort.isPresent()) { + result.add(new DecoratorBuildItem(KUBERNETES, new AddNodePortDecorator(name, config.nodePort.getAsInt()))); + } + + // Probe port handling + Integer port = ports.stream().filter(p -> HTTP_PORT.equals(p.getName())).map(KubernetesPortBuildItem::getPort) + .findFirst().orElse(DEFAULT_HTTP_PORT); + result.add(new DecoratorBuildItem(KUBERNETES, new ApplyHttpGetActionPortDecorator(port))); + + return result; + } + +} diff --git a/integration-tests/kubernetes/quarkus-standard-way/src/test/java/io/quarkus/it/kubernetes/KubernetesAndMinikubeWithApplicationPropertiesTest.java b/integration-tests/kubernetes/quarkus-standard-way/src/test/java/io/quarkus/it/kubernetes/KubernetesAndMinikubeWithApplicationPropertiesTest.java index 7fbbca5a79594..819e2419d754d 100644 --- a/integration-tests/kubernetes/quarkus-standard-way/src/test/java/io/quarkus/it/kubernetes/KubernetesAndMinikubeWithApplicationPropertiesTest.java +++ b/integration-tests/kubernetes/quarkus-standard-way/src/test/java/io/quarkus/it/kubernetes/KubernetesAndMinikubeWithApplicationPropertiesTest.java @@ -31,8 +31,7 @@ public class KubernetesAndMinikubeWithApplicationPropertiesTest { .setApplicationVersion("0.1-SNAPSHOT") .withConfigurationResource("kubernetes-and-minikube-with-application.properties") .setForcedDependencies( - Collections.singletonList( - new AppArtifact("io.quarkus", "quarkus-minikube", Version.getVersion()))); + Collections.singletonList(new AppArtifact("io.quarkus", "quarkus-minikube", Version.getVersion()))); @ProdBuildResults private ProdModeTestResults prodModeTestResults; diff --git a/integration-tests/kubernetes/quarkus-standard-way/src/test/java/io/quarkus/it/kubernetes/KubernetesWithHealthAndJibTest.java b/integration-tests/kubernetes/quarkus-standard-way/src/test/java/io/quarkus/it/kubernetes/KubernetesWithHealthAndJibTest.java index 5049899538498..280350cf0dbe1 100644 --- a/integration-tests/kubernetes/quarkus-standard-way/src/test/java/io/quarkus/it/kubernetes/KubernetesWithHealthAndJibTest.java +++ b/integration-tests/kubernetes/quarkus-standard-way/src/test/java/io/quarkus/it/kubernetes/KubernetesWithHealthAndJibTest.java @@ -62,8 +62,7 @@ public void assertGeneratedResources() throws IOException { assertThat(container.getLivenessProbe()).isNotNull().satisfies(p -> { assertProbePath(p, "/health/live"); }); - // since no registry was set and a container-image extension exists, we force-set 'IfNotPresent' - assertThat(container.getImagePullPolicy()).isEqualTo("IfNotPresent"); + assertThat(container.getImagePullPolicy()).isEqualTo("Always"); }); }); }); diff --git a/integration-tests/kubernetes/quarkus-standard-way/src/test/java/io/quarkus/it/kubernetes/MinikubeWithApplicationPropertiesTest.java b/integration-tests/kubernetes/quarkus-standard-way/src/test/java/io/quarkus/it/kubernetes/MinikubeWithApplicationPropertiesTest.java index c43ed5132c57b..728e0d4844061 100644 --- a/integration-tests/kubernetes/quarkus-standard-way/src/test/java/io/quarkus/it/kubernetes/MinikubeWithApplicationPropertiesTest.java +++ b/integration-tests/kubernetes/quarkus-standard-way/src/test/java/io/quarkus/it/kubernetes/MinikubeWithApplicationPropertiesTest.java @@ -6,6 +6,7 @@ import java.io.IOException; import java.nio.file.Path; +import java.util.Collections; import java.util.List; import org.jboss.shrinkwrap.api.ShrinkWrap; @@ -16,6 +17,8 @@ import io.fabric8.kubernetes.api.model.HasMetadata; import io.fabric8.kubernetes.api.model.Service; import io.fabric8.kubernetes.api.model.apps.Deployment; +import io.quarkus.bootstrap.model.AppArtifact; +import io.quarkus.builder.Version; import io.quarkus.test.ProdBuildResults; import io.quarkus.test.ProdModeTestResults; import io.quarkus.test.QuarkusProdModeTest; @@ -27,7 +30,9 @@ public class MinikubeWithApplicationPropertiesTest { .setArchiveProducer(() -> ShrinkWrap.create(JavaArchive.class).addClasses(GreetingResource.class)) .setApplicationName("minikube-with-application-properties") .setApplicationVersion("0.1-SNAPSHOT") - .withConfigurationResource("minikube-with-application.properties"); + .withConfigurationResource("minikube-with-application.properties") + .setForcedDependencies( + Collections.singletonList(new AppArtifact("io.quarkus", "quarkus-minikube", Version.getVersion()))); @ProdBuildResults private ProdModeTestResults prodModeTestResults; diff --git a/integration-tests/kubernetes/quarkus-standard-way/src/test/java/io/quarkus/it/kubernetes/MinikubeWithDefaultsTest.java b/integration-tests/kubernetes/quarkus-standard-way/src/test/java/io/quarkus/it/kubernetes/MinikubeWithDefaultsTest.java index afe4837ca8007..4b027bf0f3786 100644 --- a/integration-tests/kubernetes/quarkus-standard-way/src/test/java/io/quarkus/it/kubernetes/MinikubeWithDefaultsTest.java +++ b/integration-tests/kubernetes/quarkus-standard-way/src/test/java/io/quarkus/it/kubernetes/MinikubeWithDefaultsTest.java @@ -5,6 +5,7 @@ import java.io.IOException; import java.nio.file.Path; +import java.util.Collections; import java.util.List; import org.jboss.shrinkwrap.api.ShrinkWrap; @@ -15,6 +16,8 @@ import io.fabric8.kubernetes.api.model.HasMetadata; import io.fabric8.kubernetes.api.model.Service; import io.fabric8.kubernetes.api.model.apps.Deployment; +import io.quarkus.bootstrap.model.AppArtifact; +import io.quarkus.builder.Version; import io.quarkus.test.ProdBuildResults; import io.quarkus.test.ProdModeTestResults; import io.quarkus.test.QuarkusProdModeTest; @@ -26,7 +29,9 @@ public class MinikubeWithDefaultsTest { .setArchiveProducer(() -> ShrinkWrap.create(JavaArchive.class).addClasses(GreetingResource.class)) .setApplicationName("minikube-with-defaults") .setApplicationVersion("0.1-SNAPSHOT") - .withConfigurationResource("minikube-with-defaults.properties"); + .withConfigurationResource("minikube-with-defaults.properties") + .setForcedDependencies( + Collections.singletonList(new AppArtifact("io.quarkus", "quarkus-minikube", Version.getVersion()))); @ProdBuildResults private ProdModeTestResults prodModeTestResults; diff --git a/integration-tests/kubernetes/quarkus-standard-way/src/test/java/io/quarkus/it/kubernetes/MinikubeWithMixedStyleEnvTest.java b/integration-tests/kubernetes/quarkus-standard-way/src/test/java/io/quarkus/it/kubernetes/MinikubeWithMixedStyleEnvTest.java index 3af990afdb88f..33f4d9f110455 100644 --- a/integration-tests/kubernetes/quarkus-standard-way/src/test/java/io/quarkus/it/kubernetes/MinikubeWithMixedStyleEnvTest.java +++ b/integration-tests/kubernetes/quarkus-standard-way/src/test/java/io/quarkus/it/kubernetes/MinikubeWithMixedStyleEnvTest.java @@ -4,6 +4,7 @@ import java.io.IOException; import java.nio.file.Path; +import java.util.Collections; import java.util.List; import org.jboss.shrinkwrap.api.ShrinkWrap; @@ -14,6 +15,8 @@ import io.fabric8.kubernetes.api.model.EnvFromSource; import io.fabric8.kubernetes.api.model.HasMetadata; import io.fabric8.kubernetes.api.model.apps.Deployment; +import io.quarkus.bootstrap.model.AppArtifact; +import io.quarkus.builder.Version; import io.quarkus.test.ProdBuildResults; import io.quarkus.test.ProdModeTestResults; import io.quarkus.test.QuarkusProdModeTest; @@ -25,7 +28,9 @@ public class MinikubeWithMixedStyleEnvTest { .setArchiveProducer(() -> ShrinkWrap.create(JavaArchive.class).addClasses(GreetingResource.class)) .setApplicationName("minikube-with-mixed-style-env") .setApplicationVersion("0.1-SNAPSHOT") - .withConfigurationResource("minikube-with-mixed-style-env.properties"); + .withConfigurationResource("minikube-with-mixed-style-env.properties") + .setForcedDependencies( + Collections.singletonList(new AppArtifact("io.quarkus", "quarkus-minikube", Version.getVersion()))); @ProdBuildResults private ProdModeTestResults prodModeTestResults;