From d202c8233a129abeb510e46c4bce05d03aa0e151 Mon Sep 17 00:00:00 2001 From: Jakub Senko Date: Fri, 4 Oct 2024 14:19:30 +0200 Subject: [PATCH 1/4] feat: operator disables Ingress when spec.(component).host is empty --- .../operator/ApicurioRegistry3Reconciler.java | 11 +- .../resource/ActivationConditions.java | 45 ++++++ .../resource/ui/UIDeploymentResource.java | 2 +- .../registry/operator/util/IngressUtil.java | 8 +- .../registry/operator/it/SmokeITTest.java | 132 ++++++++++++++++-- .../api/v1/ApicurioRegistry3Spec.java | 18 +-- .../api/v1/ApicurioRegistry3SpecApp.java | 40 ++++++ .../api/v1/ApicurioRegistry3SpecUI.java | 33 +++++ 8 files changed, 260 insertions(+), 29 deletions(-) create mode 100644 operator/controller/src/main/java/io/apicurio/registry/operator/resource/ActivationConditions.java create mode 100644 operator/model/src/main/java/io/apicurio/registry/operator/api/v1/ApicurioRegistry3SpecApp.java create mode 100644 operator/model/src/main/java/io/apicurio/registry/operator/api/v1/ApicurioRegistry3SpecUI.java diff --git a/operator/controller/src/main/java/io/apicurio/registry/operator/ApicurioRegistry3Reconciler.java b/operator/controller/src/main/java/io/apicurio/registry/operator/ApicurioRegistry3Reconciler.java index 5199688907..39a983a467 100644 --- a/operator/controller/src/main/java/io/apicurio/registry/operator/ApicurioRegistry3Reconciler.java +++ b/operator/controller/src/main/java/io/apicurio/registry/operator/ApicurioRegistry3Reconciler.java @@ -1,6 +1,7 @@ package io.apicurio.registry.operator; import io.apicurio.registry.operator.api.v1.ApicurioRegistry3; +import io.apicurio.registry.operator.resource.ActivationConditions.UIIngressActivationCondition; import io.apicurio.registry.operator.resource.LabelDiscriminators.AppDeploymentDiscriminator; import io.apicurio.registry.operator.resource.app.AppDeploymentResource; import io.apicurio.registry.operator.resource.app.AppIngressResource; @@ -14,6 +15,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import static io.apicurio.registry.operator.resource.ActivationConditions.*; import static io.apicurio.registry.operator.resource.ResourceKey.*; // spotless:off @@ -32,13 +34,13 @@ @Dependent( type = AppIngressResource.class, name = APP_INGRESS_ID, - dependsOn = {APP_SERVICE_ID} + dependsOn = {APP_SERVICE_ID}, + activationCondition = AppIngressActivationCondition.class ), // UI @Dependent( type = UIDeploymentResource.class, - name = UI_DEPLOYMENT_ID, - dependsOn = {APP_INGRESS_ID} + name = UI_DEPLOYMENT_ID ), @Dependent( type = UIServiceResource.class, @@ -48,7 +50,8 @@ @Dependent( type = UIIngressResource.class, name = UI_INGRESS_ID, - dependsOn = {UI_SERVICE_ID} + dependsOn = {UI_SERVICE_ID}, + activationCondition = UIIngressActivationCondition.class ) } ) diff --git a/operator/controller/src/main/java/io/apicurio/registry/operator/resource/ActivationConditions.java b/operator/controller/src/main/java/io/apicurio/registry/operator/resource/ActivationConditions.java new file mode 100644 index 0000000000..be6a18b22e --- /dev/null +++ b/operator/controller/src/main/java/io/apicurio/registry/operator/resource/ActivationConditions.java @@ -0,0 +1,45 @@ +package io.apicurio.registry.operator.resource; + +import io.apicurio.registry.operator.api.v1.ApicurioRegistry3; +import io.apicurio.registry.operator.resource.app.AppIngressResource; +import io.apicurio.registry.operator.resource.ui.UIIngressResource; +import io.fabric8.kubernetes.api.model.networking.v1.Ingress; +import io.javaoperatorsdk.operator.api.reconciler.Context; +import io.javaoperatorsdk.operator.api.reconciler.dependent.DependentResource; +import io.javaoperatorsdk.operator.processing.dependent.workflow.Condition; + +import static io.apicurio.registry.operator.util.Util.isBlank; + +public class ActivationConditions { + + private ActivationConditions() { + } + + public static class AppIngressActivationCondition implements Condition { + + @Override + public boolean isMet(DependentResource resource, + ApicurioRegistry3 primary, Context context) { + + var disabled = isBlank(primary.getSpec().getApp().getHost()); + if (disabled) { + ((AppIngressResource) resource).delete(primary, context); + } + return !disabled; + } + } + + public static class UIIngressActivationCondition implements Condition { + + @Override + public boolean isMet(DependentResource resource, + ApicurioRegistry3 primary, Context context) { + + var disabled = isBlank(primary.getSpec().getUi().getHost()); + if (disabled) { + ((UIIngressResource) resource).delete(primary, context); + } + return !disabled; + } + } +} diff --git a/operator/controller/src/main/java/io/apicurio/registry/operator/resource/ui/UIDeploymentResource.java b/operator/controller/src/main/java/io/apicurio/registry/operator/resource/ui/UIDeploymentResource.java index d38c21e3d8..63b8dc7f78 100644 --- a/operator/controller/src/main/java/io/apicurio/registry/operator/resource/ui/UIDeploymentResource.java +++ b/operator/controller/src/main/java/io/apicurio/registry/operator/resource/ui/UIDeploymentResource.java @@ -13,7 +13,7 @@ import java.util.ArrayList; import static io.apicurio.registry.operator.Mapper.toYAML; -import static io.apicurio.registry.operator.resource.LabelDiscriminators.*; +import static io.apicurio.registry.operator.resource.LabelDiscriminators.UIDeploymentDiscriminator; import static io.apicurio.registry.operator.resource.ResourceFactory.COMPONENT_UI; import static io.apicurio.registry.operator.resource.ResourceKey.*; import static io.apicurio.registry.operator.util.IngressUtil.withIngressRule; diff --git a/operator/controller/src/main/java/io/apicurio/registry/operator/util/IngressUtil.java b/operator/controller/src/main/java/io/apicurio/registry/operator/util/IngressUtil.java index 62bd863ec3..24878b4a45 100644 --- a/operator/controller/src/main/java/io/apicurio/registry/operator/util/IngressUtil.java +++ b/operator/controller/src/main/java/io/apicurio/registry/operator/util/IngressUtil.java @@ -26,12 +26,12 @@ private IngressUtil() { public static String getHost(String component, ApicurioRegistry3 p) { String host = null; if (COMPONENT_APP.equals(component)) { - if (!isBlank(p.getSpec().getAppHost())) { - host = p.getSpec().getAppHost(); + if (!isBlank(p.getSpec().getApp().getHost())) { + host = p.getSpec().getApp().getHost(); } } else if (COMPONENT_UI.equals(component)) { - if (!isBlank(p.getSpec().getUiHost())) { - host = p.getSpec().getUiHost(); + if (!isBlank(p.getSpec().getUi().getHost())) { + host = p.getSpec().getUi().getHost(); } } else { throw new OperatorException("Unexpected value: " + component); diff --git a/operator/controller/src/test/java/io/apicurio/registry/operator/it/SmokeITTest.java b/operator/controller/src/test/java/io/apicurio/registry/operator/it/SmokeITTest.java index f08a4e17b8..8f78fd4c10 100644 --- a/operator/controller/src/test/java/io/apicurio/registry/operator/it/SmokeITTest.java +++ b/operator/controller/src/test/java/io/apicurio/registry/operator/it/SmokeITTest.java @@ -2,8 +2,14 @@ import io.apicurio.registry.operator.api.v1.ApicurioRegistry3; import io.apicurio.registry.operator.api.v1.ApicurioRegistry3Spec; +import io.apicurio.registry.operator.api.v1.ApicurioRegistry3SpecApp; +import io.apicurio.registry.operator.api.v1.ApicurioRegistry3SpecUI; +import io.fabric8.kubernetes.api.model.Container; +import io.fabric8.kubernetes.api.model.EnvVar; import io.fabric8.kubernetes.api.model.ObjectMeta; +import io.fabric8.kubernetes.api.model.apps.Deployment; import io.quarkus.test.junit.QuarkusTest; +import org.assertj.core.api.InstanceOfAssertFactories; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.condition.DisabledIfSystemProperty; import org.slf4j.Logger; @@ -11,6 +17,7 @@ import java.net.URI; +import static io.apicurio.registry.operator.resource.ResourceFactory.UI_CONTAINER_NAME; import static io.restassured.RestAssured.given; import static org.assertj.core.api.Assertions.assertThat; import static org.awaitility.Awaitility.await; @@ -29,13 +36,17 @@ void demoDeployment() { meta.setNamespace(getNamespace()); registry.setMetadata(meta); registry.setSpec(ApicurioRegistry3Spec.builder() - .appHost(ingressManager.getIngressHost("demo-app")) - .uiHost(ingressManager.getIngressHost("demo-ui")) + .app(ApicurioRegistry3SpecApp.builder() + .host(ingressManager.getIngressHost("demo-app")) + .build()) + .ui(ApicurioRegistry3SpecUI.builder() + .host(ingressManager.getIngressHost("demo-ui")) + .build()) .build()); // spotless:on // Act - client.resources(ApicurioRegistry3.class).inNamespace(getNamespace()).create(registry); + client.resource(registry).create(); // Deployments await().ignoreExceptions().until(() -> { @@ -59,10 +70,10 @@ void demoDeployment() { await().ignoreExceptions().until(() -> { assertThat(client.network().v1().ingresses().inNamespace(getNamespace()) .withName("demo-app-ingress").get().getSpec().getRules().get(0).getHost()) - .isEqualTo(registry.getSpec().getAppHost()); + .isEqualTo(registry.getSpec().getApp().getHost()); assertThat(client.network().v1().ingresses().inNamespace(getNamespace()) .withName("demo-ui-ingress").get().getSpec().getRules().get(0).getHost()) - .isEqualTo(registry.getSpec().getUiHost()); + .isEqualTo(registry.getSpec().getUi().getHost()); return true; }); } @@ -76,8 +87,12 @@ void testService() { meta.setNamespace(namespace); registry.setMetadata(meta); registry.setSpec(ApicurioRegistry3Spec.builder() - .appHost(ingressManager.getIngressHost("demo-app")) - .uiHost(ingressManager.getIngressHost("demo-ui")) + .app(ApicurioRegistry3SpecApp.builder() + .host(ingressManager.getIngressHost("demo-app")) + .build()) + .ui(ApicurioRegistry3SpecUI.builder() + .host(ingressManager.getIngressHost("demo-ui")) + .build()) .build()); // spotless:on @@ -119,8 +134,12 @@ void testIngress() { meta.setNamespace(namespace); registry.setMetadata(meta); registry.setSpec(ApicurioRegistry3Spec.builder() - .appHost(ingressManager.getIngressHost("demo-app")) - .uiHost(ingressManager.getIngressHost("demo-ui")) + .app(ApicurioRegistry3SpecApp.builder() + .host(ingressManager.getIngressHost("demo-app")) + .build()) + .ui(ApicurioRegistry3SpecUI.builder() + .host(ingressManager.getIngressHost("demo-ui")) + .build()) .build()); // spotless:on @@ -128,12 +147,11 @@ void testIngress() { client.resources(ApicurioRegistry3.class).inNamespace(namespace).create(registry); // Wait for Ingresses - await().ignoreExceptions().until(() -> { + await().untilAsserted(() -> { assertThat(client.network().v1().ingresses().inNamespace(namespace).withName("demo-app-ingress") .get()).isNotNull(); assertThat(client.network().v1().ingresses().inNamespace(namespace).withName("demo-ui-ingress") .get()).isNotNull(); - return true; }); await().ignoreExceptions().until(() -> { @@ -148,4 +166,96 @@ void testIngress() { return true; }); } + + @Test + void testEmptyHostDisablesIngress() { + // spotless:off + var registry = new ApicurioRegistry3(); + var meta = new ObjectMeta(); + meta.setName("demo"); + meta.setNamespace(namespace); + registry.setMetadata(meta); + registry.setSpec(ApicurioRegistry3Spec.builder() + .app(ApicurioRegistry3SpecApp.builder() + .host(ingressManager.getIngressHost("demo-app")) + .build()) + .ui(ApicurioRegistry3SpecUI.builder() + .host(ingressManager.getIngressHost("demo-ui")) + .build()) + .build()); + // spotless:on + + client.resource(registry).create(); + + // Wait for Ingresses + await().untilAsserted(() -> { + assertThat(client.network().v1().ingresses().inNamespace(namespace).withName("demo-app-ingress") + .get()).isNotNull(); + assertThat(client.network().v1().ingresses().inNamespace(namespace).withName("demo-ui-ingress") + .get()).isNotNull(); + }); + + // Check that REGISTRY_API_URL is set + var uiDeployment = client.apps().deployments().inNamespace(namespace).withName("demo-ui-deployment") + .get(); + verify_REGISTRY_API_URL_isSet(registry, uiDeployment); + + // Disable host and therefore Ingress + registry.getSpec().getApp().setHost(""); + registry.getSpec().getUi().setHost(""); + client.resource(registry).update(); + + await().untilAsserted(() -> { + assertThat(client.network().v1().ingresses().inNamespace(namespace).withName("demo-app-ingress") + .get()).isNull(); + }); + await().untilAsserted(() -> { + assertThat(client.network().v1().ingresses().inNamespace(namespace).withName("demo-ui-ingress") + .get()).isNull(); + }); + + uiDeployment = client.apps().deployments().inNamespace(namespace).withName("demo-ui-deployment") + .get(); + assertThat(uiDeployment).isNotNull(); + // spotless:off + assertThat(uiDeployment.getSpec().getTemplate().getSpec().getContainers()) + .filteredOn(c -> UI_CONTAINER_NAME.equals(c.getName())) + .flatMap(Container::getEnv) + .filteredOn(e -> "REGISTRY_API_URL".equals(e.getName())) + .isEmpty(); + // spotless:on + + // Enable again + registry.getSpec().getApp().setHost(ingressManager.getIngressHost("demo-app")); + registry.getSpec().getUi().setHost(ingressManager.getIngressHost("demo-ui")); + client.resource(registry).update(); + + // Verify Ingresses are back + await().untilAsserted(() -> { + assertThat(client.network().v1().ingresses().inNamespace(namespace).withName("demo-app-ingress") + .get()).isNotNull(); + assertThat(client.network().v1().ingresses().inNamespace(namespace).withName("demo-ui-ingress") + .get()).isNotNull(); + }); + + // Check that REGISTRY_API_URL is set again + uiDeployment = client.apps().deployments().inNamespace(namespace).withName("demo-ui-deployment") + .get(); + verify_REGISTRY_API_URL_isSet(registry, uiDeployment); + } + + private void verify_REGISTRY_API_URL_isSet(ApicurioRegistry3 registry, Deployment deployment) { + // spotless:off + assertThat(deployment).isNotNull(); + assertThat(deployment.getSpec().getTemplate().getSpec().getContainers()) + .filteredOn(c -> UI_CONTAINER_NAME.equals(c.getName())) + .flatMap(Container::getEnv) + .filteredOn(e -> "REGISTRY_API_URL".equals(e.getName())) + .hasSize(1) + .map(EnvVar::getValue) + .first() + .asInstanceOf(InstanceOfAssertFactories.STRING) + .startsWith("http://" + registry.getSpec().getApp().getHost()); + // spotless:on + } } diff --git a/operator/model/src/main/java/io/apicurio/registry/operator/api/v1/ApicurioRegistry3Spec.java b/operator/model/src/main/java/io/apicurio/registry/operator/api/v1/ApicurioRegistry3Spec.java index 6660c4da7c..fc37ce65c3 100644 --- a/operator/model/src/main/java/io/apicurio/registry/operator/api/v1/ApicurioRegistry3Spec.java +++ b/operator/model/src/main/java/io/apicurio/registry/operator/api/v1/ApicurioRegistry3Spec.java @@ -8,7 +8,7 @@ import lombok.*; @JsonInclude(Include.NON_NULL) -@JsonPropertyOrder({ "appHost", "uiHost" }) +@JsonPropertyOrder({ "app", "ui" }) @JsonDeserialize(using = None.class) @NoArgsConstructor @AllArgsConstructor(access = AccessLevel.PRIVATE) @@ -19,18 +19,18 @@ public class ApicurioRegistry3Spec implements KubernetesResource { /** - * Apicurio Registry backend base URL + * Configuration specific to Apicurio Registry backend component. */ - @JsonProperty("appHost") - @JsonPropertyDescription("Apicurio Registry backend base URL") + @JsonProperty("app") + @JsonPropertyDescription("Configuration specific to Apicurio Registry backend component.") @JsonSetter(nulls = Nulls.SKIP) - private String appHost; + private ApicurioRegistry3SpecApp app = new ApicurioRegistry3SpecApp(); /** - * Apicurio Registry UI URL + * Configuration specific to Apicurio Registry UI component. */ - @JsonProperty("uiHost") - @JsonPropertyDescription("Apicurio Registry UI base URL") + @JsonProperty("ui") + @JsonPropertyDescription("Configuration specific to Apicurio Registry UI component.") @JsonSetter(nulls = Nulls.SKIP) - private String uiHost; + private ApicurioRegistry3SpecUI ui = new ApicurioRegistry3SpecUI(); } diff --git a/operator/model/src/main/java/io/apicurio/registry/operator/api/v1/ApicurioRegistry3SpecApp.java b/operator/model/src/main/java/io/apicurio/registry/operator/api/v1/ApicurioRegistry3SpecApp.java new file mode 100644 index 0000000000..bcc082ea52 --- /dev/null +++ b/operator/model/src/main/java/io/apicurio/registry/operator/api/v1/ApicurioRegistry3SpecApp.java @@ -0,0 +1,40 @@ +package io.apicurio.registry.operator.api.v1; + +import com.fasterxml.jackson.annotation.*; +import com.fasterxml.jackson.annotation.JsonInclude.Include; +import com.fasterxml.jackson.databind.JsonDeserializer.None; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import io.fabric8.kubernetes.api.model.KubernetesResource; +import lombok.*; + +@JsonInclude(Include.NON_NULL) +@JsonPropertyOrder({ "host" }) +@JsonDeserialize(using = None.class) +@NoArgsConstructor +@AllArgsConstructor(access = AccessLevel.PRIVATE) +@Builder +@Getter +@Setter +@ToString +public class ApicurioRegistry3SpecApp implements KubernetesResource { + + /** + * Apicurio Registry backend component hostname. If the value is empty, the Operator will not create an + * Ingress resource for the component. IMPORTANT: If the Ingress already exists and the value becomes + * empty, the Ingress will be deleted. + *

+ * Note that the UI component requires a browser-accessible URL to the Apicurio Registry backend to work + * properly. If you create the Ingress manually, you have to manually set the REGISTRY_API_URL environment + * variable for the backend component. + */ + @JsonProperty("host") + @JsonPropertyDescription(""" + Apicurio Registry backend component hostname. + If the value is empty, the Operator will not create an Ingress resource for the component. + IMPORTANT: If the Ingress already exists and the value becomes empty, the Ingress will be deleted. + + Note that the UI component requires a browser-accessible URL to the Apicurio Registry backend to work properly. + If you create the Ingress manually, you have to manually set the REGISTRY_API_URL environment variable for the backend component.""") + @JsonSetter(nulls = Nulls.SKIP) + private String host; +} diff --git a/operator/model/src/main/java/io/apicurio/registry/operator/api/v1/ApicurioRegistry3SpecUI.java b/operator/model/src/main/java/io/apicurio/registry/operator/api/v1/ApicurioRegistry3SpecUI.java new file mode 100644 index 0000000000..bb37237f2e --- /dev/null +++ b/operator/model/src/main/java/io/apicurio/registry/operator/api/v1/ApicurioRegistry3SpecUI.java @@ -0,0 +1,33 @@ +package io.apicurio.registry.operator.api.v1; + +import com.fasterxml.jackson.annotation.*; +import com.fasterxml.jackson.annotation.JsonInclude.Include; +import com.fasterxml.jackson.databind.JsonDeserializer.None; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import io.fabric8.kubernetes.api.model.KubernetesResource; +import lombok.*; + +@JsonInclude(Include.NON_NULL) +@JsonPropertyOrder({ "host" }) +@JsonDeserialize(using = None.class) +@NoArgsConstructor +@AllArgsConstructor(access = AccessLevel.PRIVATE) +@Builder +@Getter +@Setter +@ToString +public class ApicurioRegistry3SpecUI implements KubernetesResource { + + /** + * Apicurio Registry UI component hostname. If the value is empty, the Operator will not create an Ingress + * resource for the component. IMPORTANT: If the Ingress already exists and the value becomes empty, the + * Ingress will be deleted. + */ + @JsonProperty("host") + @JsonPropertyDescription(""" + Apicurio Registry UI component hostname. + If the value is empty, the Operator will not create an Ingress resource for the component. + IMPORTANT: If the Ingress already exists and the value becomes empty, the Ingress will be deleted.""") + @JsonSetter(nulls = Nulls.SKIP) + private String host; +} From f968e3d4f2b90211c0731e36d62c642781942092 Mon Sep 17 00:00:00 2001 From: Jakub Senko Date: Fri, 4 Oct 2024 15:58:07 +0200 Subject: [PATCH 2/4] test: fix for remote tests - remote operator was not actually deployed due to bad generated resources --- .../test/java/io/apicurio/registry/operator/it/ITBase.java | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/operator/controller/src/test/java/io/apicurio/registry/operator/it/ITBase.java b/operator/controller/src/test/java/io/apicurio/registry/operator/it/ITBase.java index 572bbbbb1c..2a979857d1 100644 --- a/operator/controller/src/test/java/io/apicurio/registry/operator/it/ITBase.java +++ b/operator/controller/src/test/java/io/apicurio/registry/operator/it/ITBase.java @@ -107,6 +107,13 @@ private static void createGeneratedResources() throws Exception { if (r.getKind().equals("ClusterRoleBinding") && r instanceof ClusterRoleBinding) { var crb = (ClusterRoleBinding) r; crb.getSubjects().stream().forEach(s -> s.setNamespace(getNamespace())); + // TODO: We need to patch the generated resources, because the referenced ClusterRole name + // is + // wrong. + if ("apicurioregistry3reconciler-cluster-role-binding" + .equals(crb.getMetadata().getName())) { + crb.getRoleRef().setName("apicurioregistry3reconciler-cluster-role"); + } } client.resource(r).inNamespace(getNamespace()).createOrReplace(); }); From 375529b0e44c43dd715fc8fa76c21713e7e9be64 Mon Sep 17 00:00:00 2001 From: Jakub Senko Date: Fri, 4 Oct 2024 16:26:21 +0200 Subject: [PATCH 3/4] test: workaround for unstable remote tests because 2 operator instances are deployed at once --- .../io/apicurio/registry/operator/it/SmokeITTest.java | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/operator/controller/src/test/java/io/apicurio/registry/operator/it/SmokeITTest.java b/operator/controller/src/test/java/io/apicurio/registry/operator/it/SmokeITTest.java index 8f78fd4c10..af0dfb09e4 100644 --- a/operator/controller/src/test/java/io/apicurio/registry/operator/it/SmokeITTest.java +++ b/operator/controller/src/test/java/io/apicurio/registry/operator/it/SmokeITTest.java @@ -203,7 +203,15 @@ void testEmptyHostDisablesIngress() { // Disable host and therefore Ingress registry.getSpec().getApp().setHost(""); registry.getSpec().getUi().setHost(""); - client.resource(registry).update(); + + // The remote test does not work properly. As a workaround the CR will be deleted and recreated + // instead of updated. + // client.resource(registry).update(); + client.resource(registry).delete(); + await().untilAsserted(() -> { + assertThat(client.resource(registry).get()).isNull(); + }); + client.resource(registry).create(); await().untilAsserted(() -> { assertThat(client.network().v1().ingresses().inNamespace(namespace).withName("demo-app-ingress") From 350b06fdb74bbbd8a7950a968ccf1e9f6f5fc995 Mon Sep 17 00:00:00 2001 From: Jakub Senko Date: Mon, 7 Oct 2024 12:32:31 +0200 Subject: [PATCH 4/4] test: load test resources from files --- .../operator/resource/ResourceFactory.java | 4 +- ...e-cr.yml => simple.apicurioregistry3.yaml} | 2 +- .../apicurio/registry/operator/it/ITBase.java | 13 +- .../registry/operator/it/SmokeITTest.java | 200 ++++++++---------- 4 files changed, 90 insertions(+), 129 deletions(-) rename operator/controller/src/main/resources/k8s/examples/{example-cr.yml => simple.apicurioregistry3.yaml} (69%) diff --git a/operator/controller/src/main/java/io/apicurio/registry/operator/resource/ResourceFactory.java b/operator/controller/src/main/java/io/apicurio/registry/operator/resource/ResourceFactory.java index a8faec9e8e..b0814bab8e 100644 --- a/operator/controller/src/main/java/io/apicurio/registry/operator/resource/ResourceFactory.java +++ b/operator/controller/src/main/java/io/apicurio/registry/operator/resource/ResourceFactory.java @@ -102,7 +102,7 @@ private void addSelectorLabels(Map labels, ApicurioRegistry3 pri // spotless:on } - private T deserialize(String path, Class klass) { + public static T deserialize(String path, Class klass) { try { return YAML_MAPPER.readValue(load(path), klass); } catch (JsonProcessingException ex) { @@ -110,7 +110,7 @@ private T deserialize(String path, Class klass) { } } - private String load(String path) { + private static String load(String path) { try (var stream = Thread.currentThread().getContextClassLoader().getResourceAsStream(path)) { return new String(stream.readAllBytes(), Charset.defaultCharset()); } catch (Exception ex) { diff --git a/operator/controller/src/main/resources/k8s/examples/example-cr.yml b/operator/controller/src/main/resources/k8s/examples/simple.apicurioregistry3.yaml similarity index 69% rename from operator/controller/src/main/resources/k8s/examples/example-cr.yml rename to operator/controller/src/main/resources/k8s/examples/simple.apicurioregistry3.yaml index 8a6b545f4d..53e6d43ef7 100644 --- a/operator/controller/src/main/resources/k8s/examples/example-cr.yml +++ b/operator/controller/src/main/resources/k8s/examples/simple.apicurioregistry3.yaml @@ -1,5 +1,5 @@ apiVersion: registry.apicur.io/v1 kind: ApicurioRegistry3 metadata: - name: example-apicurioregistry3 + name: simple spec: {} diff --git a/operator/controller/src/test/java/io/apicurio/registry/operator/it/ITBase.java b/operator/controller/src/test/java/io/apicurio/registry/operator/it/ITBase.java index 2a979857d1..ca026f587d 100644 --- a/operator/controller/src/test/java/io/apicurio/registry/operator/it/ITBase.java +++ b/operator/controller/src/test/java/io/apicurio/registry/operator/it/ITBase.java @@ -106,7 +106,7 @@ private static void createGeneratedResources() throws Exception { resources.getItems().stream().forEach(r -> { if (r.getKind().equals("ClusterRoleBinding") && r instanceof ClusterRoleBinding) { var crb = (ClusterRoleBinding) r; - crb.getSubjects().stream().forEach(s -> s.setNamespace(getNamespace())); + crb.getSubjects().stream().forEach(s -> s.setNamespace(namespace)); // TODO: We need to patch the generated resources, because the referenced ClusterRole name // is // wrong. @@ -115,7 +115,7 @@ private static void createGeneratedResources() throws Exception { crb.getRoleRef().setName("apicurioregistry3reconciler-cluster-role"); } } - client.resource(r).inNamespace(getNamespace()).createOrReplace(); + client.resource(r).inNamespace(namespace).createOrReplace(); }); } } @@ -129,9 +129,9 @@ private static void cleanGeneratedResources() throws Exception { resources.getItems().stream().forEach(r -> { if (r.getKind().equals("ClusterRoleBinding") && r instanceof ClusterRoleBinding) { var crb = (ClusterRoleBinding) r; - crb.getSubjects().stream().forEach(s -> s.setNamespace(getNamespace())); + crb.getSubjects().stream().forEach(s -> s.setNamespace(namespace)); } - client.resource(r).inNamespace(getNamespace()).delete(); + client.resource(r).inNamespace(namespace).delete(); }); } } @@ -213,9 +213,4 @@ public static void after() throws Exception { } client.close(); } - - public static String getNamespace() { - return namespace; - } - } diff --git a/operator/controller/src/test/java/io/apicurio/registry/operator/it/SmokeITTest.java b/operator/controller/src/test/java/io/apicurio/registry/operator/it/SmokeITTest.java index af0dfb09e4..01cd2198fd 100644 --- a/operator/controller/src/test/java/io/apicurio/registry/operator/it/SmokeITTest.java +++ b/operator/controller/src/test/java/io/apicurio/registry/operator/it/SmokeITTest.java @@ -1,12 +1,9 @@ package io.apicurio.registry.operator.it; import io.apicurio.registry.operator.api.v1.ApicurioRegistry3; -import io.apicurio.registry.operator.api.v1.ApicurioRegistry3Spec; -import io.apicurio.registry.operator.api.v1.ApicurioRegistry3SpecApp; -import io.apicurio.registry.operator.api.v1.ApicurioRegistry3SpecUI; +import io.apicurio.registry.operator.resource.ResourceFactory; import io.fabric8.kubernetes.api.model.Container; import io.fabric8.kubernetes.api.model.EnvVar; -import io.fabric8.kubernetes.api.model.ObjectMeta; import io.fabric8.kubernetes.api.model.apps.Deployment; import io.quarkus.test.junit.QuarkusTest; import org.assertj.core.api.InstanceOfAssertFactories; @@ -29,86 +26,73 @@ public class SmokeITTest extends ITBase { @Test void demoDeployment() { - // spotless:off - var registry = new ApicurioRegistry3(); - var meta = new ObjectMeta(); - meta.setName("demo"); - meta.setNamespace(getNamespace()); - registry.setMetadata(meta); - registry.setSpec(ApicurioRegistry3Spec.builder() - .app(ApicurioRegistry3SpecApp.builder() - .host(ingressManager.getIngressHost("demo-app")) - .build()) - .ui(ApicurioRegistry3SpecUI.builder() - .host(ingressManager.getIngressHost("demo-ui")) - .build()) - .build()); - // spotless:on - // Act + var registry = ResourceFactory.deserialize("/k8s/examples/simple.apicurioregistry3.yaml", + ApicurioRegistry3.class); + registry.getMetadata().setNamespace(namespace); + registry.getSpec().getApp().setHost(ingressManager.getIngressHost("app")); + registry.getSpec().getUi().setHost(ingressManager.getIngressHost("ui")); + client.resource(registry).create(); // Deployments await().ignoreExceptions().until(() -> { - assertThat(client.apps().deployments().inNamespace(getNamespace()).withName("demo-app-deployment") - .get().getStatus().getReadyReplicas()).isEqualTo(1); - assertThat(client.apps().deployments().inNamespace(getNamespace()).withName("demo-ui-deployment") - .get().getStatus().getReadyReplicas()).isEqualTo(1); + assertThat(client.apps().deployments().inNamespace(namespace) + .withName(registry.getMetadata().getName() + "-app-deployment").get().getStatus() + .getReadyReplicas()).isEqualTo(1); + assertThat(client.apps().deployments().inNamespace(namespace) + .withName(registry.getMetadata().getName() + "-ui-deployment").get().getStatus() + .getReadyReplicas()).isEqualTo(1); return true; }); // Services await().ignoreExceptions().until(() -> { - assertThat(client.services().inNamespace(getNamespace()).withName("demo-app-service").get() - .getSpec().getClusterIP()).isNotBlank(); - assertThat(client.services().inNamespace(getNamespace()).withName("demo-ui-service").get() - .getSpec().getClusterIP()).isNotBlank(); + assertThat(client.services().inNamespace(namespace) + .withName(registry.getMetadata().getName() + "-app-service").get().getSpec() + .getClusterIP()).isNotBlank(); + assertThat(client.services().inNamespace(namespace) + .withName(registry.getMetadata().getName() + "-ui-service").get().getSpec() + .getClusterIP()).isNotBlank(); return true; }); // Ingresses await().ignoreExceptions().until(() -> { - assertThat(client.network().v1().ingresses().inNamespace(getNamespace()) - .withName("demo-app-ingress").get().getSpec().getRules().get(0).getHost()) - .isEqualTo(registry.getSpec().getApp().getHost()); - assertThat(client.network().v1().ingresses().inNamespace(getNamespace()) - .withName("demo-ui-ingress").get().getSpec().getRules().get(0).getHost()) - .isEqualTo(registry.getSpec().getUi().getHost()); + assertThat(client.network().v1().ingresses().inNamespace(namespace) + .withName(registry.getMetadata().getName() + "-app-ingress").get().getSpec().getRules() + .get(0).getHost()).isEqualTo(registry.getSpec().getApp().getHost()); + assertThat(client.network().v1().ingresses().inNamespace(namespace) + .withName(registry.getMetadata().getName() + "-ui-ingress").get().getSpec().getRules() + .get(0).getHost()).isEqualTo(registry.getSpec().getUi().getHost()); return true; }); } @Test void testService() { - // spotless:off - var registry = new ApicurioRegistry3(); - var meta = new ObjectMeta(); - meta.setName("demo"); - meta.setNamespace(namespace); - registry.setMetadata(meta); - registry.setSpec(ApicurioRegistry3Spec.builder() - .app(ApicurioRegistry3SpecApp.builder() - .host(ingressManager.getIngressHost("demo-app")) - .build()) - .ui(ApicurioRegistry3SpecUI.builder() - .host(ingressManager.getIngressHost("demo-ui")) - .build()) - .build()); - // spotless:on - // Act - client.resources(ApicurioRegistry3.class).inNamespace(namespace).create(registry); + var registry = ResourceFactory.deserialize("/k8s/examples/simple.apicurioregistry3.yaml", + ApicurioRegistry3.class); + registry.getMetadata().setNamespace(namespace); + registry.getSpec().getApp().setHost(ingressManager.getIngressHost("app")); + registry.getSpec().getUi().setHost(ingressManager.getIngressHost("ui")); + + client.resource(registry).create(); // Wait for Services await().ignoreExceptions().until(() -> { - assertThat(client.services().inNamespace(namespace).withName("demo-app-service").get().getSpec() + assertThat(client.services().inNamespace(namespace) + .withName(registry.getMetadata().getName() + "-app-service").get().getSpec() .getClusterIP()).isNotBlank(); - assertThat(client.services().inNamespace(namespace).withName("demo-ui-service").get().getSpec() + assertThat(client.services().inNamespace(namespace) + .withName(registry.getMetadata().getName() + "-ui-service").get().getSpec() .getClusterIP()).isNotBlank(); return true; }); - int appServicePort = portForwardManager.startPortForward("demo-app-service", 8080); + int appServicePort = portForwardManager + .startPortForward(registry.getMetadata().getName() + "-app-service", 8080); await().ignoreExceptions().until(() -> { given().get(new URI("http://localhost:" + appServicePort + "/apis/registry/v3/system/info")) @@ -116,7 +100,8 @@ void testService() { return true; }); - int uiServicePort = portForwardManager.startPortForward("demo-ui-service", 8080); + int uiServicePort = portForwardManager + .startPortForward(registry.getMetadata().getName() + "-ui-service", 8080); await().ignoreExceptions().until(() -> { given().get(new URI("http://localhost:" + uiServicePort + "/config.js")).then().statusCode(200); @@ -127,85 +112,66 @@ void testService() { @Test @DisabledIfSystemProperty(named = INGRESS_SKIP_PROP, matches = "true") void testIngress() { - // spotless:off - var registry = new ApicurioRegistry3(); - var meta = new ObjectMeta(); - meta.setName("demo"); - meta.setNamespace(namespace); - registry.setMetadata(meta); - registry.setSpec(ApicurioRegistry3Spec.builder() - .app(ApicurioRegistry3SpecApp.builder() - .host(ingressManager.getIngressHost("demo-app")) - .build()) - .ui(ApicurioRegistry3SpecUI.builder() - .host(ingressManager.getIngressHost("demo-ui")) - .build()) - .build()); - // spotless:on - // Act - client.resources(ApicurioRegistry3.class).inNamespace(namespace).create(registry); + var registry = ResourceFactory.deserialize("/k8s/examples/simple.apicurioregistry3.yaml", + ApicurioRegistry3.class); + registry.getMetadata().setNamespace(namespace); + registry.getSpec().getApp().setHost(ingressManager.getIngressHost("app")); + registry.getSpec().getUi().setHost(ingressManager.getIngressHost("ui")); + + client.resource(registry).create(); // Wait for Ingresses await().untilAsserted(() -> { - assertThat(client.network().v1().ingresses().inNamespace(namespace).withName("demo-app-ingress") - .get()).isNotNull(); - assertThat(client.network().v1().ingresses().inNamespace(namespace).withName("demo-ui-ingress") - .get()).isNotNull(); + assertThat(client.network().v1().ingresses().inNamespace(namespace) + .withName(registry.getMetadata().getName() + "-app-ingress").get()).isNotNull(); + assertThat(client.network().v1().ingresses().inNamespace(namespace) + .withName(registry.getMetadata().getName() + "-ui-ingress").get()).isNotNull(); }); await().ignoreExceptions().until(() -> { - ingressManager.startHttpRequest("demo-app-ingress").basePath("/apis/registry/v3/system/info") - .get().then().statusCode(200); + ingressManager.startHttpRequest(registry.getMetadata().getName() + "-app-ingress") + .basePath("/apis/registry/v3/system/info").get().then().statusCode(200); return true; }); await().ignoreExceptions().until(() -> { - ingressManager.startHttpRequest("demo-ui-ingress").basePath("/config.js").get().then() - .statusCode(200); + ingressManager.startHttpRequest(registry.getMetadata().getName() + "-ui-ingress") + .basePath("/config.js").get().then().statusCode(200); return true; }); } @Test void testEmptyHostDisablesIngress() { - // spotless:off - var registry = new ApicurioRegistry3(); - var meta = new ObjectMeta(); - meta.setName("demo"); - meta.setNamespace(namespace); - registry.setMetadata(meta); - registry.setSpec(ApicurioRegistry3Spec.builder() - .app(ApicurioRegistry3SpecApp.builder() - .host(ingressManager.getIngressHost("demo-app")) - .build()) - .ui(ApicurioRegistry3SpecUI.builder() - .host(ingressManager.getIngressHost("demo-ui")) - .build()) - .build()); - // spotless:on + + var registry = ResourceFactory.deserialize("/k8s/examples/simple.apicurioregistry3.yaml", + ApicurioRegistry3.class); + registry.getMetadata().setNamespace(namespace); + registry.getSpec().getApp().setHost(ingressManager.getIngressHost("app")); + registry.getSpec().getUi().setHost(ingressManager.getIngressHost("ui")); client.resource(registry).create(); // Wait for Ingresses await().untilAsserted(() -> { - assertThat(client.network().v1().ingresses().inNamespace(namespace).withName("demo-app-ingress") - .get()).isNotNull(); - assertThat(client.network().v1().ingresses().inNamespace(namespace).withName("demo-ui-ingress") - .get()).isNotNull(); + assertThat(client.network().v1().ingresses().inNamespace(namespace) + .withName(registry.getMetadata().getName() + "-app-ingress").get()).isNotNull(); + assertThat(client.network().v1().ingresses().inNamespace(namespace) + .withName(registry.getMetadata().getName() + "-ui-ingress").get()).isNotNull(); }); // Check that REGISTRY_API_URL is set - var uiDeployment = client.apps().deployments().inNamespace(namespace).withName("demo-ui-deployment") - .get(); + var uiDeployment = client.apps().deployments().inNamespace(namespace) + .withName(registry.getMetadata().getName() + "-ui-deployment").get(); verify_REGISTRY_API_URL_isSet(registry, uiDeployment); // Disable host and therefore Ingress registry.getSpec().getApp().setHost(""); registry.getSpec().getUi().setHost(""); - // The remote test does not work properly. As a workaround the CR will be deleted and recreated - // instead of updated. + // TODO: The remote test does not work properly. As a workaround the CR will be deleted and recreated + // instead of updated: // client.resource(registry).update(); client.resource(registry).delete(); await().untilAsserted(() -> { @@ -214,16 +180,16 @@ void testEmptyHostDisablesIngress() { client.resource(registry).create(); await().untilAsserted(() -> { - assertThat(client.network().v1().ingresses().inNamespace(namespace).withName("demo-app-ingress") - .get()).isNull(); + assertThat(client.network().v1().ingresses().inNamespace(namespace) + .withName(registry.getMetadata().getName() + "-app-ingress").get()).isNull(); }); await().untilAsserted(() -> { - assertThat(client.network().v1().ingresses().inNamespace(namespace).withName("demo-ui-ingress") - .get()).isNull(); + assertThat(client.network().v1().ingresses().inNamespace(namespace) + .withName(registry.getMetadata().getName() + "-ui-ingress").get()).isNull(); }); - uiDeployment = client.apps().deployments().inNamespace(namespace).withName("demo-ui-deployment") - .get(); + uiDeployment = client.apps().deployments().inNamespace(namespace) + .withName(registry.getMetadata().getName() + "-ui-deployment").get(); assertThat(uiDeployment).isNotNull(); // spotless:off assertThat(uiDeployment.getSpec().getTemplate().getSpec().getContainers()) @@ -234,21 +200,21 @@ void testEmptyHostDisablesIngress() { // spotless:on // Enable again - registry.getSpec().getApp().setHost(ingressManager.getIngressHost("demo-app")); - registry.getSpec().getUi().setHost(ingressManager.getIngressHost("demo-ui")); + registry.getSpec().getApp().setHost(ingressManager.getIngressHost("app")); + registry.getSpec().getUi().setHost(ingressManager.getIngressHost("ui")); client.resource(registry).update(); // Verify Ingresses are back await().untilAsserted(() -> { - assertThat(client.network().v1().ingresses().inNamespace(namespace).withName("demo-app-ingress") - .get()).isNotNull(); - assertThat(client.network().v1().ingresses().inNamespace(namespace).withName("demo-ui-ingress") - .get()).isNotNull(); + assertThat(client.network().v1().ingresses().inNamespace(namespace) + .withName(registry.getMetadata().getName() + "-app-ingress").get()).isNotNull(); + assertThat(client.network().v1().ingresses().inNamespace(namespace) + .withName(registry.getMetadata().getName() + "-ui-ingress").get()).isNotNull(); }); // Check that REGISTRY_API_URL is set again - uiDeployment = client.apps().deployments().inNamespace(namespace).withName("demo-ui-deployment") - .get(); + uiDeployment = client.apps().deployments().inNamespace(namespace) + .withName(registry.getMetadata().getName() + "-ui-deployment").get(); verify_REGISTRY_API_URL_isSet(registry, uiDeployment); }