From 5e0e743a0c0543dc22f86172d54edb35b62aad1a Mon Sep 17 00:00:00 2001 From: Jose Date: Wed, 19 Oct 2022 12:15:51 +0200 Subject: [PATCH] Allow configuring Ingress rules Fix https://github.com/dekorateio/dekorate/issues/1083 --- .../kubernetes/annotation/Ingress.java | 7 + .../kubernetes/annotation/IngressRule.java | 54 +++++++ .../decorator/AddIngressRuleDecorator.java | 138 +++++++++++++----- .../manifest/KubernetesManifestGenerator.java | 21 ++- assets/config.md | 17 ++- docs/configuration-guide.md | 13 ++ tests/feat-1083-ingress-rules/pom.xml | 74 ++++++++++ .../annotationless/DemoApplication.java | 27 ++++ .../src/main/resources/application.properties | 19 +++ .../dekorate/annotationless/Feat1083Test.java | 85 +++++++++++ tests/pom.xml | 1 + 11 files changed, 411 insertions(+), 45 deletions(-) create mode 100644 annotations/kubernetes-annotations/src/main/java/io/dekorate/kubernetes/annotation/IngressRule.java create mode 100644 tests/feat-1083-ingress-rules/pom.xml create mode 100644 tests/feat-1083-ingress-rules/src/main/java/io/dekorate/annotationless/DemoApplication.java create mode 100644 tests/feat-1083-ingress-rules/src/main/resources/application.properties create mode 100644 tests/feat-1083-ingress-rules/src/test/java/io/dekorate/annotationless/Feat1083Test.java diff --git a/annotations/kubernetes-annotations/src/main/java/io/dekorate/kubernetes/annotation/Ingress.java b/annotations/kubernetes-annotations/src/main/java/io/dekorate/kubernetes/annotation/Ingress.java index 9ce0e9315..82fb5a43c 100644 --- a/annotations/kubernetes-annotations/src/main/java/io/dekorate/kubernetes/annotation/Ingress.java +++ b/annotations/kubernetes-annotations/src/main/java/io/dekorate/kubernetes/annotation/Ingress.java @@ -15,8 +15,10 @@ */ package io.dekorate.kubernetes.annotation; +import io.sundr.builder.annotations.Buildable; import io.sundr.builder.annotations.Pojo; +@Buildable(builderPackage = "io.fabric8.kubernetes.api.builder") @Pojo(relativePath = "../config", autobox = true, mutable = true, withStaticBuilderMethod = true, withStaticAdapterMethod = false) public @interface Ingress { @@ -42,4 +44,9 @@ */ String[] tlsHosts() default {}; + /** + * Controls the generated ingress rules to be exposed as part of the Ingress resource. + */ + IngressRule[] rules() default {}; + } diff --git a/annotations/kubernetes-annotations/src/main/java/io/dekorate/kubernetes/annotation/IngressRule.java b/annotations/kubernetes-annotations/src/main/java/io/dekorate/kubernetes/annotation/IngressRule.java new file mode 100644 index 000000000..9dfe1fad3 --- /dev/null +++ b/annotations/kubernetes-annotations/src/main/java/io/dekorate/kubernetes/annotation/IngressRule.java @@ -0,0 +1,54 @@ +/** + * Copyright 2018 The original authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.dekorate.kubernetes.annotation; + +import io.sundr.builder.annotations.Pojo; + +@Pojo(relativePath = "../config", autobox = true, mutable = true, withStaticBuilderMethod = true, withStaticAdapterMethod = false) +public @interface IngressRule { + + /** + * The host under which the rule is going to be used. + */ + String host(); + + /** + * The path under which the rule is going to be used. Default is "/". + */ + String path() default "/"; + + /** + * The path type strategy to use by the Ingress rule. Default is "Prefix". + */ + String pathType() default "Prefix"; + + /** + * The service name to be used by this Ingress rule. Default is the generated service name of the application. + */ + String serviceName() default ""; + + /** + * The service port name to be used by this Ingress rule. Default is the port name of the generated service + * of the application. + */ + String servicePortName() default ""; + + /** + * The service port number to be used by this Ingress rule. This is only used when the servicePortName is not set. + */ + int servicePortNumber() default -1; + +} diff --git a/annotations/kubernetes-annotations/src/main/java/io/dekorate/kubernetes/decorator/AddIngressRuleDecorator.java b/annotations/kubernetes-annotations/src/main/java/io/dekorate/kubernetes/decorator/AddIngressRuleDecorator.java index 308396326..0165a7de6 100644 --- a/annotations/kubernetes-annotations/src/main/java/io/dekorate/kubernetes/decorator/AddIngressRuleDecorator.java +++ b/annotations/kubernetes-annotations/src/main/java/io/dekorate/kubernetes/decorator/AddIngressRuleDecorator.java @@ -15,8 +15,13 @@ */ package io.dekorate.kubernetes.decorator; -import java.util.function.Predicate; +import static io.dekorate.kubernetes.decorator.AddServiceResourceDecorator.distinct; +import java.util.Arrays; +import java.util.Optional; + +import io.dekorate.kubernetes.config.BaseConfig; +import io.dekorate.kubernetes.config.IngressRule; import io.dekorate.kubernetes.config.Port; import io.dekorate.utils.Strings; import io.fabric8.kubernetes.api.builder.TypedVisitor; @@ -25,67 +30,110 @@ import io.fabric8.kubernetes.api.model.networking.v1.IngressRuleBuilder; import io.fabric8.kubernetes.api.model.networking.v1.IngressServiceBackendBuilder; import io.fabric8.kubernetes.api.model.networking.v1.IngressSpecBuilder; +import io.fabric8.kubernetes.api.model.networking.v1.ServiceBackendPort; +import io.fabric8.kubernetes.api.model.networking.v1.ServiceBackendPortBuilder; public class AddIngressRuleDecorator extends NamedResourceDecorator { - private final String host; - private final Port port; + private static final String DEFAULT_PREFIX = "Prefix"; + private static final String DEFAULT_PATH = "/"; + + private final BaseConfig config; + private final IngressRule rule; - public AddIngressRuleDecorator(String name, String host, Port port) { - super(name); - this.host = host; - this.port = port; + public AddIngressRuleDecorator(BaseConfig config, IngressRule rule) { + super(config.getName()); + this.config = config; + this.rule = rule; } @Override public void andThenVisit(IngressSpecBuilder spec, ObjectMeta meta) { - Predicate matchingHost = r -> host == null && r.getHost() == null - || (r.getHost() != null && r.getHost().equals(host)); - - if (!spec.hasMatchingRule(matchingHost)) { - spec.addNewRule().withHost(host).withNewHttp().addNewPath().withPathType("Prefix").withPath(port.getPath()) + Optional defaultHostPort = Arrays.asList(config.getPorts()).stream() + .filter(distinct(p -> p.getName())) + .findFirst(); + if (!spec.hasMatchingRule(existingRule -> Strings.equals(rule.getHost(), existingRule.getHost()))) { + spec.addNewRule() + .withHost(rule.getHost()) + .withNewHttp() + .addNewPath() + .withPathType(Strings.defaultIfEmpty(rule.getPathType(), DEFAULT_PREFIX)) + .withPath(Strings.defaultIfEmpty(rule.getPath(), DEFAULT_PATH)) .withNewBackend() .withNewService() - .withName(name) - .withNewPort().withName(port.getName()) - .withNumber(Strings.isNullOrEmpty(port.getName()) ? port.getHostPort() : null).endPort() + .withName(serviceName()) + .withPort(createPort(defaultHostPort)) .endService() .endBackend() .endPath() .endHttp() .endRule(); } else { - spec.accept(new HostVisitor(meta)); + spec.accept(new HostVisitor(defaultHostPort)); } } + private String serviceName() { + return Strings.defaultIfEmpty(rule.getServiceName(), name); + } + + private ServiceBackendPort createPort(Optional defaultHostPort) { + ServiceBackendPortBuilder builder = new ServiceBackendPortBuilder(); + if (Strings.isNotNullOrEmpty(rule.getServicePortName())) { + builder.withName(rule.getServicePortName()); + } else if (rule.getServicePortNumber() != null && rule.getServicePortNumber() >= 0) { + builder.withNumber(rule.getServicePortNumber()); + } else if (Strings.isNullOrEmpty(rule.getServiceName()) || Strings.equals(rule.getServiceName(), name)) { + // Trying to get the port from the service + Port servicePort = defaultHostPort + .orElseThrow(() -> new RuntimeException("Could not find any matching port to configure the Ingress Rule. Specify the " + + "service port using `kubernetes.ingress.service-port-name`")); + builder.withName(servicePort.getName()); + } else { + throw new RuntimeException("The service port for '" + rule.getServiceName() + "' was not set. Specify one " + + "using `kubernetes.ingress.service-port-name`"); + } + + return builder.build(); + } + private class HostVisitor extends TypedVisitor { - private final ObjectMeta meta; + private final Optional defaultHostPort; - public HostVisitor(ObjectMeta meta) { - this.meta = meta; + public HostVisitor(Optional defaultHostPort) { + this.defaultHostPort = defaultHostPort; } @Override - public void visit(IngressRuleBuilder rule) { - Predicate matchingPath = r -> r.getPath() != null && r.getPath().equals(port.getPath()); - if (rule.getHost() != null && rule.getHost().equals(host)) { - if (!rule.hasHttp()) { - rule.withNewHttp() + public void visit(IngressRuleBuilder existingRule) { + if (Strings.equals(existingRule.getHost(), rule.getHost())) { + if (!existingRule.hasHttp()) { + existingRule.withNewHttp() + .addNewPath() + .withPathType(Strings.defaultIfEmpty(rule.getPathType(), DEFAULT_PREFIX)) + .withPath(Strings.defaultIfEmpty(rule.getPath(), DEFAULT_PATH)) + .withNewBackend() + .withNewService() + .withName(serviceName()) + .withPort(createPort(defaultHostPort)) + .endService() + .endBackend() + .endPath().endHttp(); + } else if (existingRule.getHttp().getPaths().stream().noneMatch(p -> Strings.equals(p.getPath(), rule.getPath()))) { + existingRule.editHttp() .addNewPath() - .withPathType("Prefix") - .withPath(Strings.isNotNullOrEmpty(port.getPath()) ? port.getPath() : "/") + .withPathType(Strings.defaultIfEmpty(rule.getPathType(), DEFAULT_PREFIX)) + .withPath(Strings.defaultIfEmpty(rule.getPath(), DEFAULT_PATH)) .withNewBackend() .withNewService() - .withName(name) - .withNewPort().withName(port.getName()) - .withNumber(Strings.isNullOrEmpty(port.getName()) ? port.getHostPort() : null).endPort() + .withName(serviceName()) + .withPort(createPort(defaultHostPort)) .endService() .endBackend() .endPath().endHttp(); } else { - rule.accept(new PathVisitor()); + existingRule.accept(new PathVisitor(defaultHostPort)); } } } @@ -93,19 +141,24 @@ public void visit(IngressRuleBuilder rule) { private class PathVisitor extends TypedVisitor { + private final Optional defaultHostPort; + + public PathVisitor(Optional defaultHostPort) { + this.defaultHostPort = defaultHostPort; + } + @Override - public void visit(HTTPIngressPathBuilder path) { - if (path.equals(port.getPath())) { - if (!path.hasBackend()) { - path.withNewBackend() + public void visit(HTTPIngressPathBuilder existingPath) { + if (Strings.equals(existingPath.getPath(), rule.getPath())) { + if (!existingPath.hasBackend()) { + existingPath.withNewBackend() .withNewService() - .withName(name) - .withNewPort().withName(port.getName()) - .withNumber(Strings.isNullOrEmpty(port.getName()) ? port.getHostPort() : null).endPort() + .withName(serviceName()) + .withPort(createPort(defaultHostPort)) .endService() .endBackend(); } else { - path.accept(new ServiceVisitor()); + existingPath.accept(new ServiceVisitor(defaultHostPort)); } } } @@ -113,10 +166,15 @@ public void visit(HTTPIngressPathBuilder path) { private class ServiceVisitor extends TypedVisitor { + private final Optional defaultHostPort; + + public ServiceVisitor(Optional defaultHostPort) { + this.defaultHostPort = defaultHostPort; + } + @Override public void visit(IngressServiceBackendBuilder service) { - service.withName(port.getName()).withNewPort() - .withNumber(Strings.isNullOrEmpty(port.getName()) ? port.getHostPort() : null).endPort(); + service.withName(Strings.defaultIfEmpty(rule.getServiceName(), name)).withPort(createPort(defaultHostPort)); } } } diff --git a/annotations/kubernetes-annotations/src/main/java/io/dekorate/kubernetes/manifest/KubernetesManifestGenerator.java b/annotations/kubernetes-annotations/src/main/java/io/dekorate/kubernetes/manifest/KubernetesManifestGenerator.java index 6e273d24c..9029b06db 100644 --- a/annotations/kubernetes-annotations/src/main/java/io/dekorate/kubernetes/manifest/KubernetesManifestGenerator.java +++ b/annotations/kubernetes-annotations/src/main/java/io/dekorate/kubernetes/manifest/KubernetesManifestGenerator.java @@ -28,6 +28,8 @@ import io.dekorate.kubernetes.config.ContainerBuilder; import io.dekorate.kubernetes.config.EditableKubernetesConfig; import io.dekorate.kubernetes.config.ImageConfiguration; +import io.dekorate.kubernetes.config.IngressRule; +import io.dekorate.kubernetes.config.IngressRuleBuilder; import io.dekorate.kubernetes.config.KubernetesConfig; import io.dekorate.kubernetes.config.KubernetesConfigBuilder; import io.dekorate.kubernetes.configurator.ApplyDeployToApplicationConfiguration; @@ -138,11 +140,24 @@ protected void addDecorators(String group, KubernetesConfig config) { Ports.getHttpPort(config).ifPresent(p -> { resourceRegistry.decorate(group, new AddIngressDecorator(config, Labels.createLabelsAsMap(config, "Ingress"))); - resourceRegistry.decorate(group, new AddIngressRuleDecorator(config.getName(), config.getIngress().getHost(), p)); + resourceRegistry.decorate(group, + new AddIngressRuleDecorator(config, new IngressRuleBuilder() + .withHost(config.getIngress().getHost()) + .withPath(p.getPath()) + .withServicePortName(p.getName()) + .withServicePortNumber(p.getHostPort()).build())); }); - if (config.getIngress() != null && Strings.isNotNullOrEmpty(config.getIngress().getTlsSecretName())) { - resourceRegistry.decorate(group, new AddIngressTlsDecorator(config.getName(), config.getIngress())); + if (config.getIngress() != null) { + if (Strings.isNotNullOrEmpty(config.getIngress().getTlsSecretName())) { + resourceRegistry.decorate(group, new AddIngressTlsDecorator(config.getName(), config.getIngress())); + } + + if (config.getIngress().getRules() != null) { + for (IngressRule ingressRule : config.getIngress().getRules()) { + resourceRegistry.decorate(group, new AddIngressRuleDecorator(config, ingressRule)); + } + } } if (config.isHeadless()) { diff --git a/assets/config.md b/assets/config.md index 313bf4c9c..d07506ccb 100644 --- a/assets/config.md +++ b/assets/config.md @@ -71,8 +71,11 @@ The document is structured as follows. | dekorate.kubernetes.deployment-strategy | DeploymentStrategy | Specifies the deployment strategy. | None | | dekorate.kubernetes.rolling-update | RollingUpdate | Specifies rolling update configuration. The configuration is applied when DeploymentStrategy == Rolling update, or when explicit configuration has been provided. In the later case RollingUpdate is assumed. | ( see RollingUpdate ) | | dekorate.kubernetes.service-account | String | The service account. | | -| dekorate.kubernetes.ingress.expose | boolean | Controls whether the application should be exposed via Ingress | false | -| dekorate.kubernetes.ingress.host | String | The host under which the application is going to be exposed. | | +| dekorate.kubernetes.ingress.expose | boolean | Controls whether the application should be exposed via Ingress | false | +| dekorate.kubernetes.ingress.host | String | The host under which the application is going to be exposed. | | +| dekorate.kubernetes.ingress.tls-secret-name | String | The name of the secret used to configure TLS. | | +| dekorate.kubernetes.ingress.tls-hosts | String[] | The list of hosts to be included in the TLS certificate. By default, it will use the application host. | | +| dekorate.kubernetes.ingress.rules | IngressRule[] | Controls the generated ingress rules to be exposed as part of the Ingress resource. | | | dekorate.kubernetes.ports | Port[] | The application ports. | | | dekorate.kubernetes.service-type | ServiceType | The type of service that will be generated for the application. | ClusterIP | | dekorate.kubernetes.pvc-volumes | PersistentVolumeClaimVolume[] | PersistentVolumeClaim volumes to add to all containers. | | @@ -121,6 +124,16 @@ The section below describes all the available subtypes. | configmap | String | | | | field | String | | | +#### IngressRule +| Property | Type | Description | Default Value | +|----------------------|----------|--------------------------------------------------------------------------------------------------------------------|---------------| +| host | String | The host under which the rule is going to be used. | | +| path | String | The path under which the rule is going to be used. | / | +| path-type | String | The path type strategy to use by the Ingress rule. | Prefix | +| service-name | String | The service name to be used by this Ingress rule. | | +| service-port-name | String | The service port name to be used by this Ingress rule. | http | +| service-port-number | int | The service port number to be used by this Ingress rule. This is only used when the servicePortName is not set. | | + #### Port | Property | Type | Description | Default Value | |----------------|----------|----------------------------------------------------------------------------------------------------------|---------------| diff --git a/docs/configuration-guide.md b/docs/configuration-guide.md index e870a83a4..ca557d642 100644 --- a/docs/configuration-guide.md +++ b/docs/configuration-guide.md @@ -95,6 +95,9 @@ The document is structured as follows. | dekorate.kubernetes.service-account | String | The service account. | | | dekorate.kubernetes.ingress.expose | boolean | Controls whether the application should be exposed via Ingress | false | | dekorate.kubernetes.ingress.host | String | The host under which the application is going to be exposed. | | +| dekorate.kubernetes.ingress.tls-secret-name | String | The name of the secret used to configure TLS. | | +| dekorate.kubernetes.ingress.tls-hosts | String[] | The list of hosts to be included in the TLS certificate. By default, it will use the application host. | | +| dekorate.kubernetes.ingress.rules | IngressRule[] | Controls the generated ingress rules to be exposed as part of the Ingress resource. | | | dekorate.kubernetes.ports | Port[] | The application ports. | | | dekorate.kubernetes.service-type | ServiceType | The type of service that will be generated for the application. | ClusterIP | | dekorate.kubernetes.pvc-volumes | PersistentVolumeClaimVolume[] | PersistentVolumeClaim volumes to add to all containers. | | @@ -162,6 +165,16 @@ The section below describes all the available subtypes. | configmap | String | | | | field | String | | | +#### IngressRule +| Property | Type | Description | Default Value | +|----------------------|----------|--------------------------------------------------------------------------------------------------------------------|---------------| +| host | String | The host under which the rule is going to be used. | | +| path | String | The path under which the rule is going to be used. | / | +| path-type | String | The path type strategy to use by the Ingress rule. | Prefix | +| service-name | String | The service name to be used by this Ingress rule. | | +| service-port-name | String | The service port name to be used by this Ingress rule. | http | +| service-port-number | int | The service port number to be used by this Ingress rule. This is only used when the servicePortName is not set. | | + #### Port | Property | Type | Description | Default Value | diff --git a/tests/feat-1083-ingress-rules/pom.xml b/tests/feat-1083-ingress-rules/pom.xml new file mode 100644 index 000000000..73e2ac3d1 --- /dev/null +++ b/tests/feat-1083-ingress-rules/pom.xml @@ -0,0 +1,74 @@ + + + 4.0.0 + + + dekorate-tests + io.dekorate + 3.1-SNAPSHOT + ../ + + + io.dekorate + feat-1083-ingress-rules + Dekorate :: Tests :: Annotations :: Kubernetes :: Ingress with rules #1083 + Integration test that tests the rules configuration for Ingress. + + + + io.dekorate + kubernetes-annotations + ${project.version} + + + io.dekorate + dekorate-spring-boot + ${project.version} + + + + org.springframework.boot + spring-boot-starter-actuator + ${version.spring-boot} + + + + org.springframework.boot + spring-boot-starter-web + ${version.spring-boot} + + + + + org.junit.jupiter + junit-jupiter-api + ${version.junit-jupiter} + test + + + org.junit.jupiter + junit-jupiter-engine + ${version.junit-jupiter} + test + + + + + + + org.apache.maven.plugins + maven-surefire-plugin + true + + false + + + + org.springframework.boot + spring-boot-maven-plugin + ${version.spring-boot} + + + + diff --git a/tests/feat-1083-ingress-rules/src/main/java/io/dekorate/annotationless/DemoApplication.java b/tests/feat-1083-ingress-rules/src/main/java/io/dekorate/annotationless/DemoApplication.java new file mode 100644 index 000000000..b1f732aad --- /dev/null +++ b/tests/feat-1083-ingress-rules/src/main/java/io/dekorate/annotationless/DemoApplication.java @@ -0,0 +1,27 @@ +/** + * Copyright 2018 The original authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.dekorate.annotationless; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +public class DemoApplication { + + public static void main(String[] args) { + SpringApplication.run(DemoApplication.class, args); + } +} diff --git a/tests/feat-1083-ingress-rules/src/main/resources/application.properties b/tests/feat-1083-ingress-rules/src/main/resources/application.properties new file mode 100644 index 000000000..cee55d177 --- /dev/null +++ b/tests/feat-1083-ingress-rules/src/main/resources/application.properties @@ -0,0 +1,19 @@ +dekorate.kubernetes.ports[0].name=http +dekorate.kubernetes.ports[0].container-port=8080 +dekorate.kubernetes.ingress.expose=true +dekorate.kubernetes.ingress.host=prod.svc.url +# Case 1: Add new rule for the default host +dekorate.kubernetes.ingress.rules[0].host=prod.svc.url +dekorate.kubernetes.ingress.rules[0].path=/prod + +# Case 2: Add a new rule +dekorate.kubernetes.ingress.rules[1].host=dev.svc.url +dekorate.kubernetes.ingress.rules[1].path=/dev +dekorate.kubernetes.ingress.rules[1].pathType=ImplementationSpecific +# by default, path type is Prefix + +# Case 2: Add a new rule using another service +dekorate.kubernetes.ingress.rules[2].host=alt.svc.url +dekorate.kubernetes.ingress.rules[2].path=/ea +dekorate.kubernetes.ingress.rules[2].service-name=updated-service +dekorate.kubernetes.ingress.rules[2].service-port-name=tcp diff --git a/tests/feat-1083-ingress-rules/src/test/java/io/dekorate/annotationless/Feat1083Test.java b/tests/feat-1083-ingress-rules/src/test/java/io/dekorate/annotationless/Feat1083Test.java new file mode 100644 index 000000000..4d1846d1d --- /dev/null +++ b/tests/feat-1083-ingress-rules/src/test/java/io/dekorate/annotationless/Feat1083Test.java @@ -0,0 +1,85 @@ +/** + * Copyright 2018 The original authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.dekorate.annotationless; + +import static org.junit.jupiter.api.Assertions.*; + +import java.util.Optional; + +import org.junit.jupiter.api.Test; + +import io.dekorate.utils.Serialization; +import io.fabric8.kubernetes.api.model.HasMetadata; +import io.fabric8.kubernetes.api.model.KubernetesList; +import io.fabric8.kubernetes.api.model.networking.v1.Ingress; +import io.fabric8.kubernetes.api.model.networking.v1.IngressRule; + +public class Feat1083Test { + + private static final String APP_NAME = "feat-1083-ingress-rules"; + private static final String PROD_INGRESS_HOST = "prod.svc.url"; + private static final String DEV_INGRESS_HOST = "dev.svc.url"; + private static final String ALT_INGRESS_HOST = "alt.svc.url"; + private static final String PREFIX = "Prefix"; + private static final String HTTP = "http"; + + @Test + public void shouldIngressHaveTlsConfiguration() { + KubernetesList list = Serialization + .unmarshalAsList(getClass().getClassLoader().getResourceAsStream("META-INF/dekorate/kubernetes.yml")); + assertNotNull(list); + Ingress i = findFirst(list, Ingress.class).orElseThrow(() -> new IllegalStateException()); + assertNotNull(i); + assertEquals(3, i.getSpec().getRules().size(), "There are more rules than expected"); + assertTrue(i.getSpec().getRules().stream().anyMatch(this::generatedRuleWasUpdated)); + assertTrue(i.getSpec().getRules().stream().anyMatch(this::newRuleWasAdded)); + assertTrue(i.getSpec().getRules().stream().anyMatch(this::newRuleWasAddedWithCustomService)); + } + + private boolean newRuleWasAddedWithCustomService(IngressRule rule) { + return ALT_INGRESS_HOST.equals(rule.getHost()) + && rule.getHttp().getPaths().size() == 1 + && rule.getHttp().getPaths().stream().anyMatch(p -> p.getPath().equals("/ea") + && p.getPathType().equals(PREFIX) + && p.getBackend().getService().getName().equals("updated-service") + && p.getBackend().getService().getPort().getName().equals("tcp")); + } + + private boolean newRuleWasAdded(IngressRule rule) { + return DEV_INGRESS_HOST.equals(rule.getHost()) + && rule.getHttp().getPaths().size() == 1 + && rule.getHttp().getPaths().stream().anyMatch(p -> p.getPath().equals("/dev") + && p.getPathType().equals("ImplementationSpecific") + && p.getBackend().getService().getName().equals(APP_NAME) + && p.getBackend().getService().getPort().getName().equals(HTTP)); + } + + private boolean generatedRuleWasUpdated(IngressRule rule) { + return PROD_INGRESS_HOST.equals(rule.getHost()) + && rule.getHttp().getPaths().size() == 2 + && rule.getHttp().getPaths().stream().anyMatch(p -> p.getPath().equals("/prod") + && p.getPathType().equals(PREFIX) + && p.getBackend().getService().getName().equals(APP_NAME) + && p.getBackend().getService().getPort().getName().equals(HTTP)); + } + + Optional findFirst(KubernetesList list, Class t) { + return (Optional) list.getItems().stream() + .filter(i -> t.isInstance(i)) + .findFirst(); + } +} diff --git a/tests/pom.xml b/tests/pom.xml index c2557a33a..b38ed1bf7 100644 --- a/tests/pom.xml +++ b/tests/pom.xml @@ -85,6 +85,7 @@ feat-kubernetes-emptydir-volumes issue-spring-boot-openshift-named-port issue-fetch-helm-dependencies + feat-1083-ingress-rules