diff --git a/extensions/kubernetes/vanilla/deployment/src/main/java/io/quarkus/kubernetes/deployment/DevClusterHelper.java b/extensions/kubernetes/vanilla/deployment/src/main/java/io/quarkus/kubernetes/deployment/DevClusterHelper.java index 6af8cd9d32003..1f6dabfc93d77 100644 --- a/extensions/kubernetes/vanilla/deployment/src/main/java/io/quarkus/kubernetes/deployment/DevClusterHelper.java +++ b/extensions/kubernetes/vanilla/deployment/src/main/java/io/quarkus/kubernetes/deployment/DevClusterHelper.java @@ -1,5 +1,6 @@ package io.quarkus.kubernetes.deployment; +import static io.quarkus.kubernetes.deployment.Constants.INGRESS; import static io.quarkus.kubernetes.deployment.Constants.KUBERNETES; import static io.quarkus.kubernetes.deployment.Constants.LIVENESS_PROBE; import static io.quarkus.kubernetes.deployment.Constants.MAX_NODE_PORT_VALUE; @@ -22,8 +23,11 @@ import io.dekorate.kubernetes.annotation.ServiceType; import io.dekorate.kubernetes.config.EnvBuilder; +import io.dekorate.kubernetes.config.IngressRuleBuilder; import io.dekorate.kubernetes.config.Port; +import io.dekorate.kubernetes.decorator.AddAnnotationDecorator; import io.dekorate.kubernetes.decorator.AddEnvVarDecorator; +import io.dekorate.kubernetes.decorator.AddIngressRuleDecorator; import io.dekorate.kubernetes.decorator.ApplicationContainerDecorator; import io.dekorate.kubernetes.decorator.ApplyImagePullPolicyDecorator; import io.dekorate.project.Project; @@ -155,6 +159,23 @@ public static List createDecorators(String clusterKind, ports, config.ports())); + for (Map.Entry annotation : config.ingress().annotations().entrySet()) { + result.add(new DecoratorBuildItem(clusterKind, + new AddAnnotationDecorator(name, annotation.getKey(), annotation.getValue(), INGRESS))); + } + + for (IngressConfig.IngressRuleConfig rule : config.ingress().rules().values()) { + result.add(new DecoratorBuildItem(clusterKind, new AddIngressRuleDecorator(name, port, + new IngressRuleBuilder() + .withHost(rule.host()) + .withPath(rule.path()) + .withPathType(rule.pathType()) + .withServiceName(rule.serviceName().orElse(null)) + .withServicePortName(rule.servicePortName().orElse(null)) + .withServicePortNumber(rule.servicePortNumber().orElse(-1)) + .build()))); + } + // Handle init Containers result.addAll(KubernetesCommonHelper.createInitContainerDecorators(clusterKind, name, initContainers, result)); result.addAll(KubernetesCommonHelper.createInitJobDecorators(clusterKind, name, jobs, result)); diff --git a/integration-tests/kubernetes/quarkus-standard-way/src/test/java/io/quarkus/it/kubernetes/KubernetesWithKindIngress.java b/integration-tests/kubernetes/quarkus-standard-way/src/test/java/io/quarkus/it/kubernetes/KubernetesWithKindIngress.java new file mode 100644 index 0000000000000..bd907a7a276d1 --- /dev/null +++ b/integration-tests/kubernetes/quarkus-standard-way/src/test/java/io/quarkus/it/kubernetes/KubernetesWithKindIngress.java @@ -0,0 +1,97 @@ +package io.quarkus.it.kubernetes; + +import static org.assertj.core.api.AssertionsForClassTypes.assertThat; + +import java.io.IOException; +import java.nio.file.Path; +import java.util.Arrays; +import java.util.List; +import java.util.function.Predicate; + +import org.assertj.core.api.SoftAssertions; +import org.jboss.shrinkwrap.api.ShrinkWrap; +import org.jboss.shrinkwrap.api.spec.JavaArchive; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import io.fabric8.kubernetes.api.model.HasMetadata; +import io.fabric8.kubernetes.api.model.networking.v1.Ingress; +import io.fabric8.kubernetes.api.model.networking.v1.IngressRule; +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 KubernetesWithKindIngress { + + private static final String LOCALHOST = "localhost"; + @RegisterExtension + static final QuarkusProdModeTest config = new QuarkusProdModeTest() + .setArchiveProducer(() -> ShrinkWrap.create(JavaArchive.class).addClasses(GreetingResource.class)) + .setApplicationName("kind-ingress") + .setApplicationVersion("0.1-SNAPSHOT") + .setLogFileName("k8s.log") + // Configuration provided by issue: https://github.com/quarkusio/quarkus/issues/42294 + .overrideConfigKey("quarkus.kubernetes.ingress.expose", "true") + .overrideConfigKey("quarkus.kubernetes.ingress.annotations.\"nginx.ingress.kubernetes.io/rewrite-target\"", "/$2") + .overrideConfigKey("quarkus.kubernetes.ingress.rules.1.host", LOCALHOST) + .overrideConfigKey("quarkus.kubernetes.ingress.rules.1.path", "/game(/|$)(.*)") + .overrideConfigKey("quarkus.kubernetes.ingress.rules.1.path-type", "ImplementationSpecific") + .overrideConfigKey("quarkus.kubernetes.ingress.ingress-class-name", "Nginx") + .setForcedDependencies(Arrays.asList( + Dependency.of("io.quarkus", "quarkus-kind", Version.getVersion()), + Dependency.of("io.quarkus", "quarkus-kubernetes", Version.getVersion()))); + private static final Logger log = LoggerFactory.getLogger(KubernetesWithKindIngress.class); + + @ProdBuildResults + private ProdModeTestResults prodModeTestResults; + + @Test + void shouldCreateKindResourcesWithIngressAnnotationsCorrectly() throws IOException { + final Path kubernetesFile = prodModeTestResults.getBuildDir().resolve("kubernetes").resolve("kind.yml"); + List kubernetesList = DeserializationUtil.deserializeAsList(kubernetesFile); + + SoftAssertions.assertSoftly(softly -> { + softly.assertThat(kubernetesList).filteredOn(k -> k.getKind().equals("Ingress")) + .singleElement().isInstanceOfSatisfying(Ingress.class, ingress -> { + + softly.assertThat(ingress.getMetadata().getAnnotations()) + .anySatisfy((key, value) -> { + assertThat(key).isEqualTo("nginx.ingress.kubernetes.io/rewrite-target"); + assertThat(value).isEqualTo("/$2"); + }); + + softly.assertThat(ingress.getSpec()).satisfies(spec -> { + + softly.assertThat(spec.getIngressClassName()).isEqualTo("Nginx"); + + softly.assertThat(spec.getRules()).hasSize(2); + + softly.assertThat(spec.getRules()).filteredOn(byLocalhost()) + .singleElement() + .satisfies(rule -> { + + softly.assertThat(rule.getHttp().getPaths()).hasSize(1); + softly.assertThat(rule.getHttp().getPaths().get(0)).satisfies(path -> { + + softly.assertThat(path.getPathType()).isEqualTo("ImplementationSpecific"); + softly.assertThat(path.getPath()).isEqualTo("/game(/|$)(.*)"); + softly.assertThat(path.getBackend().getService().getPort().getName()) + .isEqualTo("http"); + softly.assertThat(path.getBackend().getService().getName()) + .isEqualTo("kind-ingress"); + + }); + }); + }); + }); + }); + } + + private static Predicate byLocalhost() { + return rule -> rule.getHost() != null && rule.getHost().equals(LOCALHOST); + } +} diff --git a/integration-tests/kubernetes/quarkus-standard-way/src/test/java/io/quarkus/it/kubernetes/KubernetesWithMinikubeIngress.java b/integration-tests/kubernetes/quarkus-standard-way/src/test/java/io/quarkus/it/kubernetes/KubernetesWithMinikubeIngress.java new file mode 100644 index 0000000000000..cac12d185bf58 --- /dev/null +++ b/integration-tests/kubernetes/quarkus-standard-way/src/test/java/io/quarkus/it/kubernetes/KubernetesWithMinikubeIngress.java @@ -0,0 +1,94 @@ +package io.quarkus.it.kubernetes; + +import static org.assertj.core.api.AssertionsForClassTypes.assertThat; + +import java.io.IOException; +import java.nio.file.Path; +import java.util.Arrays; +import java.util.List; +import java.util.function.Predicate; + +import org.assertj.core.api.SoftAssertions; +import org.jboss.shrinkwrap.api.ShrinkWrap; +import org.jboss.shrinkwrap.api.spec.JavaArchive; +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.networking.v1.Ingress; +import io.fabric8.kubernetes.api.model.networking.v1.IngressRule; +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 KubernetesWithMinikubeIngress { + + private static final String LOCALHOST = "localhost"; + @RegisterExtension + static final QuarkusProdModeTest config = new QuarkusProdModeTest() + .setArchiveProducer(() -> ShrinkWrap.create(JavaArchive.class).addClasses(GreetingResource.class)) + .setApplicationName("kind-ingress") + .setApplicationVersion("0.1-SNAPSHOT") + .setLogFileName("k8s.log") + // Configuration provided by issue: https://github.com/quarkusio/quarkus/issues/42294 + .overrideConfigKey("quarkus.kubernetes.ingress.expose", "true") + .overrideConfigKey("quarkus.kubernetes.ingress.annotations.\"nginx.ingress.kubernetes.io/rewrite-target\"", "/$2") + .overrideConfigKey("quarkus.kubernetes.ingress.rules.1.host", LOCALHOST) + .overrideConfigKey("quarkus.kubernetes.ingress.rules.1.path", "/game(/|$)(.*)") + .overrideConfigKey("quarkus.kubernetes.ingress.rules.1.path-type", "ImplementationSpecific") + .overrideConfigKey("quarkus.kubernetes.ingress.ingress-class-name", "Nginx") + .setForcedDependencies(Arrays.asList( + Dependency.of("io.quarkus", "quarkus-minikube", Version.getVersion()), + Dependency.of("io.quarkus", "quarkus-kubernetes", Version.getVersion()))); + + @ProdBuildResults + private ProdModeTestResults prodModeTestResults; + + @Test + void shouldCreateKindResourcesWithIngressAnnotationsCorrectly() throws IOException { + final Path kubernetesFile = prodModeTestResults.getBuildDir().resolve("kubernetes").resolve("minikube.yml"); + List kubernetesList = DeserializationUtil.deserializeAsList(kubernetesFile); + + SoftAssertions.assertSoftly(softly -> { + softly.assertThat(kubernetesList).filteredOn(k -> k.getKind().equals("Ingress")) + .singleElement().isInstanceOfSatisfying(Ingress.class, ingress -> { + + softly.assertThat(ingress.getMetadata().getAnnotations()) + .anySatisfy((key, value) -> { + assertThat(key).isEqualTo("nginx.ingress.kubernetes.io/rewrite-target"); + assertThat(value).isEqualTo("/$2"); + }); + + softly.assertThat(ingress.getSpec()).satisfies(spec -> { + + softly.assertThat(spec.getIngressClassName()).isEqualTo("Nginx"); + + softly.assertThat(spec.getRules()).hasSize(2); + + softly.assertThat(spec.getRules()).filteredOn(byLocalhost()) + .singleElement() + .satisfies(rule -> { + + softly.assertThat(rule.getHttp().getPaths()).hasSize(1); + softly.assertThat(rule.getHttp().getPaths().get(0)).satisfies(path -> { + + softly.assertThat(path.getPathType()).isEqualTo("ImplementationSpecific"); + softly.assertThat(path.getPath()).isEqualTo("/game(/|$)(.*)"); + softly.assertThat(path.getBackend().getService().getPort().getName()) + .isEqualTo("http"); + softly.assertThat(path.getBackend().getService().getName()) + .isEqualTo("kind-ingress"); + + }); + }); + }); + }); + }); + } + + private static Predicate byLocalhost() { + return rule -> rule.getHost() != null && rule.getHost().equals(LOCALHOST); + } +}