diff --git a/extensions/kubernetes/vanilla/deployment/src/main/java/io/quarkus/kubernetes/deployment/ApplyHttpGetActionPortDecorator.java b/extensions/kubernetes/vanilla/deployment/src/main/java/io/quarkus/kubernetes/deployment/ApplyHttpGetActionPortDecorator.java index 75c08c859eda6a..e5d09bf9b8760c 100644 --- a/extensions/kubernetes/vanilla/deployment/src/main/java/io/quarkus/kubernetes/deployment/ApplyHttpGetActionPortDecorator.java +++ b/extensions/kubernetes/vanilla/deployment/src/main/java/io/quarkus/kubernetes/deployment/ApplyHttpGetActionPortDecorator.java @@ -1,23 +1,34 @@ package io.quarkus.kubernetes.deployment; +import static io.dekorate.ConfigReference.joinProperties; import static io.dekorate.utils.Metadata.getMetadata; +import java.util.Collections; import java.util.List; import java.util.Map; import java.util.Optional; +import io.dekorate.ConfigReference; +import io.dekorate.WithConfigReferences; import io.dekorate.kubernetes.decorator.AbstractAddProbeDecorator; import io.dekorate.kubernetes.decorator.AddSidecarDecorator; import io.dekorate.kubernetes.decorator.Decorator; import io.dekorate.kubernetes.decorator.ResourceProvidingDecorator; +import io.dekorate.utils.Strings; import io.fabric8.kubernetes.api.builder.Builder; import io.fabric8.kubernetes.api.model.ContainerBuilder; import io.fabric8.kubernetes.api.model.HTTPGetActionFluent; -public class ApplyHttpGetActionPortDecorator extends Decorator> { +public class ApplyHttpGetActionPortDecorator extends Decorator> implements WithConfigReferences { + + private static final String PATH_ALL_EXPRESSION = "*.spec.containers."; + private static final String PATH_DEPLOYMENT_CONTAINER_EXPRESSION = "(metadata.name == %s).spec.template.spec.containers.(name == %s)."; + private static final String PATH_DEPLOYMENT_EXPRESSION = "(metadata.name == %s).spec.template.spec.containers."; + private static final String PATH_CONTAINER_EXPRESSION = "*.spec.containers.(name == %s)."; private final String deployment; private final String container; + private final String portName; private final Integer port; private final String scheme; private final String probeKind; @@ -43,12 +54,14 @@ public ApplyHttpGetActionPortDecorator(String deployment, String container, Inte } public ApplyHttpGetActionPortDecorator(String deployment, String container, Integer port, String probeKind) { - this(deployment, container, port, probeKind, port != null && (port == 443 || port == 8443) ? "HTTPS" : "HTTP"); // this is the original convention coming from dekorate + this(deployment, container, null, port, probeKind, port != null && (port == 443 || port == 8443) ? "HTTPS" : "HTTP"); // this is the original convention coming from dekorate } - public ApplyHttpGetActionPortDecorator(String deployment, String container, Integer port, String probeKind, String scheme) { + public ApplyHttpGetActionPortDecorator(String deployment, String container, String portName, Integer port, String probeKind, + String scheme) { this.deployment = deployment; this.container = container; + this.portName = portName; this.port = port; this.probeKind = probeKind; this.scheme = scheme; @@ -107,4 +120,28 @@ public void visit(HTTPGetActionFluent action) { public Class[] after() { return new Class[] { ResourceProvidingDecorator.class, AddSidecarDecorator.class, AbstractAddProbeDecorator.class }; } + + @Override + public List getConfigReferences() { + if (portName != null && probeKind != null) { + return List.of(buildConfigReference(joinProperties("ports." + portName), + "httpGet.port", port, "The http port to use for the probe.")); + } + + return Collections.emptyList(); + } + + private ConfigReference buildConfigReference(String property, String probeField, Object value, String description) { + String expression = PATH_ALL_EXPRESSION; + if (Strings.isNotNullOrEmpty(deployment) && Strings.isNotNullOrEmpty(container)) { + expression = String.format(PATH_DEPLOYMENT_CONTAINER_EXPRESSION, deployment, container); + } else if (Strings.isNotNullOrEmpty(deployment)) { + expression = String.format(PATH_DEPLOYMENT_EXPRESSION, deployment); + } else if (Strings.isNotNullOrEmpty(container)) { + expression = String.format(PATH_CONTAINER_EXPRESSION, container); + } + + String yamlPath = expression + probeKind + "." + probeField; + return new ConfigReference.Builder(property, yamlPath).withDescription(description).withValue(value).build(); + } } 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 index 31b67b3be770aa..ce3cae12c0304b 100644 --- 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 @@ -971,16 +971,23 @@ public static DecoratorBuildItem createProbeHttpPortDecorator(String name, Strin .or(() -> portName.map(KubernetesProbePortNameBuildItem::getName)) .orElse(HTTP_PORT); - Integer port = probeConfig.httpActionPort - .orElse(ports.stream().filter(p -> httpPortName.equals(p.getName())) - .map(KubernetesPortBuildItem::getPort).findFirst().orElse(DEFAULT_HTTP_PORT)); + Integer port; + PortConfig portFromConfig = portsFromConfig.get(httpPortName); + if (probeConfig.httpActionPort.isPresent()) { + port = probeConfig.httpActionPort.get(); + } else if (portFromConfig != null && portFromConfig.containerPort.isPresent()) { + port = portFromConfig.containerPort.getAsInt(); + } else { + port = ports.stream().filter(p -> httpPortName.equals(p.getName())) + .map(KubernetesPortBuildItem::getPort).findFirst().orElse(DEFAULT_HTTP_PORT); + } // Resolve scheme property from: String scheme; if (probeConfig.httpActionScheme.isPresent()) { // 1. User in Probe config scheme = probeConfig.httpActionScheme.get(); - } else if (portsFromConfig.containsKey(httpPortName) && portsFromConfig.get(httpPortName).tls) { + } else if (portFromConfig != null && portFromConfig.tls) { // 2. User in Ports config scheme = SCHEME_HTTPS; } else if (portName.isPresent() @@ -993,7 +1000,8 @@ public static DecoratorBuildItem createProbeHttpPortDecorator(String name, Strin scheme = port != null && (port == 443 || port == 8443) ? SCHEME_HTTPS : SCHEME_HTTP; } - return new DecoratorBuildItem(target, new ApplyHttpGetActionPortDecorator(name, name, port, probeKind, scheme)); + return new DecoratorBuildItem(target, + new ApplyHttpGetActionPortDecorator(name, name, httpPortName, port, probeKind, scheme)); } /** diff --git a/integration-tests/kubernetes/quarkus-standard-way/src/test/java/io/quarkus/it/kubernetes/KubernetesWithProbePortByNameTest.java b/integration-tests/kubernetes/quarkus-standard-way/src/test/java/io/quarkus/it/kubernetes/KubernetesWithProbePortByNameTest.java new file mode 100644 index 00000000000000..fd486386000e20 --- /dev/null +++ b/integration-tests/kubernetes/quarkus-standard-way/src/test/java/io/quarkus/it/kubernetes/KubernetesWithProbePortByNameTest.java @@ -0,0 +1,69 @@ +package io.quarkus.it.kubernetes; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.io.IOException; +import java.nio.file.Path; +import java.util.List; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.fabric8.kubernetes.api.model.HasMetadata; +import io.fabric8.kubernetes.api.model.apps.Deployment; +import io.quarkus.builder.Version; +import io.quarkus.maven.dependency.Dependency; +import io.quarkus.test.ProdBuildResults; +import io.quarkus.test.ProdModeTestResults; +import io.quarkus.test.QuarkusProdModeTest; + +public class KubernetesWithProbePortByNameTest { + + private static final String NAME = "kubernetes-with-probe-port-by-name"; + + @RegisterExtension + static final QuarkusProdModeTest config = new QuarkusProdModeTest() + .withApplicationRoot((jar) -> jar.addClasses(GreetingResource.class)) + .setApplicationName(NAME) + .setApplicationVersion("0.1-SNAPSHOT") + .overrideConfigKey("quarkus.kubernetes.ports.custom.container-port", "8888") + .overrideConfigKey("quarkus.kubernetes.readiness-probe.http-action-port-name", "custom") + .setLogFileName("k8s.log") + .setForcedDependencies(List.of( + Dependency.of("io.quarkus", "quarkus-kubernetes", Version.getVersion()), + Dependency.of("io.quarkus", "quarkus-smallrye-health", Version.getVersion()))); + + @ProdBuildResults + private ProdModeTestResults prodModeTestResults; + + @Test + public void assertGeneratedResources() throws IOException { + + final Path kubernetesDir = prodModeTestResults.getBuildDir().resolve("kubernetes"); + assertThat(kubernetesDir) + .isDirectoryContaining(p -> p.getFileName().endsWith("kubernetes.json")) + .isDirectoryContaining(p -> p.getFileName().endsWith("kubernetes.yml")); + List kubernetesList = DeserializationUtil + .deserializeAsList(kubernetesDir.resolve("kubernetes.yml")); + assertThat(kubernetesList.get(0)).isInstanceOfSatisfying(Deployment.class, d -> { + assertThat(d.getMetadata()).satisfies(m -> { + assertThat(m.getName()).isEqualTo(NAME); + }); + + assertThat(d.getSpec()).satisfies(deploymentSpec -> { + assertThat(deploymentSpec.getTemplate()).satisfies(t -> { + assertThat(t.getSpec()).satisfies(podSpec -> { + assertThat(podSpec.getContainers()).singleElement() + .satisfies(container -> { + assertThat(container.getReadinessProbe()).isNotNull().satisfies(p -> { + assertEquals(p.getHttpGet().getPort().getIntVal(), 8888); + assertEquals(p.getHttpGet().getScheme(), "HTTP"); + }); + }); + }); + }); + }); + }); + } +}