diff --git a/extensions/kubernetes/vanilla/deployment/src/main/java/io/quarkus/kubernetes/deployment/ContainerImageUtil.java b/extensions/kubernetes/vanilla/deployment/src/main/java/io/quarkus/kubernetes/deployment/ContainerImageUtil.java new file mode 100644 index 0000000000000..7e4cef8d92fd6 --- /dev/null +++ b/extensions/kubernetes/vanilla/deployment/src/main/java/io/quarkus/kubernetes/deployment/ContainerImageUtil.java @@ -0,0 +1,26 @@ +package io.quarkus.kubernetes.deployment; + +import static io.quarkus.container.image.deployment.util.ImageUtil.hasRegistry; + +import java.util.Optional; + +import io.quarkus.container.image.deployment.ContainerImageCapabilitiesUtil; +import io.quarkus.container.spi.ContainerImageInfoBuildItem; +import io.quarkus.deployment.Capabilities; + +final class ContainerImageUtil { + + private ContainerImageUtil() { + } + + static boolean isRegistryMissingAndNotS2I(Capabilities capabilities, ContainerImageInfoBuildItem containerImageInfo) { + Optional activeContainerImageCapability = ContainerImageCapabilitiesUtil + .getActiveContainerImageCapability(capabilities); + if (!activeContainerImageCapability.isPresent()) { // shouldn't ever happen when this method is called + return false; + } + + return !hasRegistry(containerImageInfo.getImage()) + && !Capabilities.CONTAINER_IMAGE_S2I.equals(activeContainerImageCapability.get()); + } +} diff --git a/extensions/kubernetes/vanilla/deployment/src/main/java/io/quarkus/kubernetes/deployment/KubernetesDeployer.java b/extensions/kubernetes/vanilla/deployment/src/main/java/io/quarkus/kubernetes/deployment/KubernetesDeployer.java index 6f8b4bf05ceb5..c12615465171d 100644 --- a/extensions/kubernetes/vanilla/deployment/src/main/java/io/quarkus/kubernetes/deployment/KubernetesDeployer.java +++ b/extensions/kubernetes/vanilla/deployment/src/main/java/io/quarkus/kubernetes/deployment/KubernetesDeployer.java @@ -1,7 +1,6 @@ package io.quarkus.kubernetes.deployment; -import static io.quarkus.container.image.deployment.util.ImageUtil.hasRegistry; import static io.quarkus.kubernetes.deployment.Constants.DEPLOYMENT; import static io.quarkus.kubernetes.deployment.Constants.KUBERNETES; import static io.quarkus.kubernetes.deployment.Constants.OPENSHIFT; @@ -33,6 +32,7 @@ import io.quarkus.deployment.IsNormal; import io.quarkus.deployment.annotations.BuildProducer; import io.quarkus.deployment.annotations.BuildStep; +import io.quarkus.deployment.pkg.builditem.ArtifactResultBuildItem; import io.quarkus.deployment.pkg.builditem.DeploymentResultBuildItem; import io.quarkus.deployment.pkg.builditem.OutputTargetBuildItem; import io.quarkus.kubernetes.client.deployment.KubernetesClientErrorHanlder; @@ -52,7 +52,9 @@ public void deploy(KubernetesClientBuildItem kubernetesClient, List kubernetesDeploymentTargets, OutputTargetBuildItem outputTarget, Capabilities capabilities, - BuildProducer deploymentResult) { + BuildProducer deploymentResult, + // needed to ensure that this step runs after the container image has been built + @SuppressWarnings("unused") List artifactResults) { Optional activeContainerImageCapability = ContainerImageCapabilitiesUtil .getActiveContainerImageCapability(capabilities); @@ -64,11 +66,11 @@ public void deploy(KubernetesClientBuildItem kubernetesClient, } boolean isContainerImageS2IPresent = Capabilities.CONTAINER_IMAGE_S2I.equals(activeContainerImageCapability.get()); - if (!hasRegistry(containerImageInfo.getImage()) && !isContainerImageS2IPresent) { + if (ContainerImageUtil.isRegistryMissingAndNotS2I(capabilities, containerImageInfo)) { log.warn( "A Kubernetes deployment was requested, but the container image to be built will not be pushed to any registry" + " because \"quarkus.container-image.registry\" has not been set. The Kubernetes deployment will only work properly" - + " if the cluster is using the local Docker daemon."); + + " if the cluster is using the local Docker daemon. For that reason 'ImagePullPolicy' is being force-set to 'IfNotPresent'"); } //Get any build item but if the build was s2i, use openshift 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 dfdeb0d93abb0..2b46a2640238d 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 @@ -37,6 +37,7 @@ import io.dekorate.Session; import io.dekorate.SessionWriter; +import io.dekorate.kubernetes.annotation.ImagePullPolicy; import io.dekorate.kubernetes.config.Annotation; import io.dekorate.kubernetes.config.EnvBuilder; import io.dekorate.kubernetes.config.Label; @@ -79,6 +80,7 @@ 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.IsNormal; import io.quarkus.deployment.annotations.BuildProducer; import io.quarkus.deployment.annotations.BuildStep; @@ -179,6 +181,7 @@ public void build(ApplicationInfoBuildItem applicationInfo, KubernetesConfig kubernetesConfig, OpenshiftConfig openshiftConfig, KnativeConfig knativeConfig, + Capabilities capabilities, List kubernetesAnnotations, List kubernetesLabels, List kubernetesEnvs, @@ -224,11 +227,16 @@ public void build(ApplicationInfoBuildItem applicationInfo, //Apply configuration applyGlobalConfig(session, kubernetesConfig); + ZonedDateTime now = ZonedDateTime.now(ZoneOffset.UTC); + + boolean needToForceUpdateImagePullPolicy = needToForceUpdateImagePullPolicy(containerImage, capabilities); applyConfig(session, project, KUBERNETES, getResourceName(kubernetesConfig, applicationInfo), kubernetesConfig, - now); - applyConfig(session, project, OPENSHIFT, getResourceName(openshiftConfig, applicationInfo), openshiftConfig, now); - applyConfig(session, project, KNATIVE, getResourceName(knativeConfig, applicationInfo), knativeConfig, now); + now, determineImagePullPolicy(kubernetesConfig, needToForceUpdateImagePullPolicy)); + applyConfig(session, project, OPENSHIFT, getResourceName(openshiftConfig, applicationInfo), openshiftConfig, now, + determineImagePullPolicy(openshiftConfig, needToForceUpdateImagePullPolicy)); + applyConfig(session, project, KNATIVE, getResourceName(knativeConfig, applicationInfo), knativeConfig, now, + determineImagePullPolicy(knativeConfig, needToForceUpdateImagePullPolicy)); //apply build item configurations to the dekorate session. applyBuildItems(session, @@ -299,15 +307,16 @@ private void applyGlobalConfig(Session session, KubernetesConfig config) { /** * 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 + * @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) { + ZonedDateTime now, ImagePullPolicy imagePullPolicy) { if (OPENSHIFT.equals(target)) { session.resources().decorate(OPENSHIFT, new AddLabelDecorator(new Label(OPENSHIFT_APP_RUNTIME, QUARKUS))); } @@ -347,7 +356,7 @@ private void applyConfig(Session session, Project project, String target, String }); //Image Pull - session.resources().decorate(target, new ApplyImagePullPolicyDecorator(config.getImagePullPolicy())); + session.resources().decorate(target, new ApplyImagePullPolicyDecorator(imagePullPolicy)); config.getImagePullSecrets().ifPresent(l -> { l.forEach(s -> session.resources().decorate(target, new AddImagePullSecretDecorator(name, s))); }); @@ -391,6 +400,29 @@ private void applyConfig(Session session, Project project, String target, String }); } + /** + * 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(Optional containerImage, + Capabilities capabilities) { + 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, diff --git a/integration-tests/kubernetes/quarkus-standard-way/src/test/java/io/quarkus/it/kubernetes/BasicKubernetesTest.java b/integration-tests/kubernetes/quarkus-standard-way/src/test/java/io/quarkus/it/kubernetes/BasicKubernetesTest.java index ec39fca041cff..c02f3a6fca933 100644 --- a/integration-tests/kubernetes/quarkus-standard-way/src/test/java/io/quarkus/it/kubernetes/BasicKubernetesTest.java +++ b/integration-tests/kubernetes/quarkus-standard-way/src/test/java/io/quarkus/it/kubernetes/BasicKubernetesTest.java @@ -70,6 +70,7 @@ public void assertGeneratedResources() throws IOException { assertThat(deploymentSpec.getTemplate()).satisfies(t -> { assertThat(t.getSpec()).satisfies(podSpec -> { assertThat(podSpec.getContainers()).hasOnlyOneElementSatisfying(container -> { + assertThat(container.getImagePullPolicy()).isEqualTo("Always"); // expect the default value assertThat(container.getPorts()).hasOnlyOneElementSatisfying(p -> { assertThat(p.getContainerPort()).isEqualTo(8080); }); 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 3a207f5711bbf..83b0ef96309c9 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,6 +62,8 @@ 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"); }); }); });