diff --git a/integration-tests/cdi/pom.xml b/integration-tests/cdi/pom.xml new file mode 100644 index 000000000..acaf34a98 --- /dev/null +++ b/integration-tests/cdi/pom.xml @@ -0,0 +1,160 @@ + + + 4.0.0 + + io.quarkiverse.operatorsdk + quarkus-operator-sdk-integration-tests-parent + 7.3.1-SNAPSHOT + ../pom.xml + + quarkus-operator-sdk-integration-tests-cdi + Quarkus - Operator SDK - Integration Tests - CDI + + + io.quarkus + quarkus-resteasy + + + io.quarkiverse.operatorsdk + quarkus-operator-sdk + + + io.quarkus + quarkus-junit5 + test + + + io.quarkus + quarkus-test-kubernetes-client + test + + + io.quarkus + quarkus-resteasy-jackson + + + io.rest-assured + rest-assured + test + + + org.awaitility + awaitility + test + + + + io.quarkus + quarkus-micrometer-registry-prometheus + + + org.bouncycastle + bcpkix-jdk18on + true + + + + + + io.quarkus + quarkus-maven-plugin + + + + build + + + + + + org.apache.maven.plugins + maven-jar-plugin + + + + test-jar + + + + + + maven-surefire-plugin + + + fromEnvVarNS + fromEnv1,fromEnv2 + + variableNSFromEnv + JOSDK_ALL_NAMESPACES + + + + + + + + + native-image + + + native + + + + + + maven-surefire-plugin + + ${native.surefire.skip} + + org.jboss.logmanager.LogManager + + ${maven.home} + + + fromEnvVarNS + fromEnv1,fromEnv2 + + variableNSFromEnv + JOSDK_ALL_NAMESPACES + + + + + + maven-failsafe-plugin + + + + integration-test + verify + + + + + ${project.build.directory}/${project.build.finalName}-runner + + org.jboss.logmanager.LogManager + + ${maven.home} + + + fromEnvVarNS + fromEnv1,fromEnv2 + + variableNSFromEnv + JOSDK_ALL_NAMESPACES + + + + + + + + + + native + + + + diff --git a/integration-tests/cdi/src/main/java/io/quarkiverse/operatorsdk/it/cdi/ConfigMapDependent.java b/integration-tests/cdi/src/main/java/io/quarkiverse/operatorsdk/it/cdi/ConfigMapDependent.java new file mode 100644 index 000000000..a37c47652 --- /dev/null +++ b/integration-tests/cdi/src/main/java/io/quarkiverse/operatorsdk/it/cdi/ConfigMapDependent.java @@ -0,0 +1,57 @@ +package io.quarkiverse.operatorsdk.it.cdi; + +import java.util.Map; +import java.util.Optional; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import io.fabric8.kubernetes.api.model.ConfigMap; +import io.fabric8.kubernetes.api.model.ObjectMetaBuilder; +import io.javaoperatorsdk.operator.api.reconciler.Context; +import io.javaoperatorsdk.operator.processing.dependent.kubernetes.CRUDKubernetesDependentResource; +import io.javaoperatorsdk.operator.processing.event.ResourceID; + +public class ConfigMapDependent extends CRUDKubernetesDependentResource { + + private static final Logger log = LoggerFactory.getLogger(ConfigMapDependent.class); + + @Override + protected ConfigMap desired(TestResource primary, Context context) { + Optional optionalConfigMap = getConfigMap(primary, context); + if (!optionalConfigMap.isPresent()) { + ConfigMap configMap = new ConfigMap(); + configMap.setMetadata( + new ObjectMetaBuilder() + .withName(primary.getMetadata().getName() + "-cm") + .withNamespace(primary.getMetadata().getNamespace()) + .build()); + configMap.setData(Map.of("key", "data")); + return configMap; + } + return optionalConfigMap.get(); + } + + @Override + public void delete(TestResource primary, Context context) { + Optional optionalConfigMap = getConfigMap(primary, context); + if (optionalConfigMap == null) + return; + optionalConfigMap.ifPresent( + (configMap -> { + if (configMap.getMetadata().getAnnotations() != null) { + context.getClient().resource(configMap).delete(); + } + })); + + } + + private static Optional getConfigMap(TestResource primary, Context context) { + Optional optionalConfigMap = context.getSecondaryResource(ConfigMap.class); + if (optionalConfigMap.isEmpty()) { + log.debug("Config Map not found for primary: {}", ResourceID.fromResource(primary)); + return Optional.empty(); + } + return optionalConfigMap; + } +} diff --git a/integration-tests/cdi/src/main/java/io/quarkiverse/operatorsdk/it/cdi/CustomActionvationCondition.java b/integration-tests/cdi/src/main/java/io/quarkiverse/operatorsdk/it/cdi/CustomActionvationCondition.java new file mode 100644 index 000000000..2d8fdaa28 --- /dev/null +++ b/integration-tests/cdi/src/main/java/io/quarkiverse/operatorsdk/it/cdi/CustomActionvationCondition.java @@ -0,0 +1,30 @@ +package io.quarkiverse.operatorsdk.it.cdi; + +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.inject.Inject; + +import io.fabric8.kubernetes.api.model.HasMetadata; +import io.javaoperatorsdk.operator.api.reconciler.Context; +import io.javaoperatorsdk.operator.api.reconciler.dependent.DependentResource; +import io.javaoperatorsdk.operator.processing.dependent.workflow.Condition; +import io.quarkus.arc.Unremovable; + +@ApplicationScoped +@Unremovable +public class CustomActionvationCondition implements Condition { + + @Inject + TestUUIDBean testUUIDBean; + + private String uuid; + + @Override + public boolean isMet(DependentResource dependentResource, HasMetadata primary, Context context) { + uuid = testUUIDBean.uuid(); + return true; + } + + public String getUuid() { + return uuid; + } +} diff --git a/integration-tests/cdi/src/main/java/io/quarkiverse/operatorsdk/it/cdi/DeletePostCondition.java b/integration-tests/cdi/src/main/java/io/quarkiverse/operatorsdk/it/cdi/DeletePostCondition.java new file mode 100644 index 000000000..9a98697f6 --- /dev/null +++ b/integration-tests/cdi/src/main/java/io/quarkiverse/operatorsdk/it/cdi/DeletePostCondition.java @@ -0,0 +1,30 @@ +package io.quarkiverse.operatorsdk.it.cdi; + +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.inject.Inject; + +import io.fabric8.kubernetes.api.model.HasMetadata; +import io.javaoperatorsdk.operator.api.reconciler.Context; +import io.javaoperatorsdk.operator.api.reconciler.dependent.DependentResource; +import io.javaoperatorsdk.operator.processing.dependent.workflow.Condition; +import io.quarkus.arc.Unremovable; + +@ApplicationScoped +@Unremovable +public class DeletePostCondition implements Condition { + + @Inject + TestUUIDBean testUUIDBean; + + private String uuid; + + @Override + public boolean isMet(DependentResource dependentResource, HasMetadata primary, Context context) { + this.uuid = testUUIDBean.uuid(); + return true; + } + + public String getUuid() { + return uuid; + } +} diff --git a/integration-tests/cdi/src/main/java/io/quarkiverse/operatorsdk/it/cdi/DeploymentDependent.java b/integration-tests/cdi/src/main/java/io/quarkiverse/operatorsdk/it/cdi/DeploymentDependent.java new file mode 100644 index 000000000..27a9d5dce --- /dev/null +++ b/integration-tests/cdi/src/main/java/io/quarkiverse/operatorsdk/it/cdi/DeploymentDependent.java @@ -0,0 +1,37 @@ +package io.quarkiverse.operatorsdk.it.cdi; + +import java.util.Map; + +import org.jboss.logging.Logger; + +import io.fabric8.kubernetes.api.model.ObjectMetaBuilder; +import io.fabric8.kubernetes.api.model.apps.Deployment; +import io.fabric8.kubernetes.api.model.apps.DeploymentBuilder; +import io.javaoperatorsdk.operator.api.reconciler.Context; +import io.javaoperatorsdk.operator.processing.dependent.kubernetes.CRUDNoGCKubernetesDependentResource; + +public class DeploymentDependent extends CRUDNoGCKubernetesDependentResource { + + private static final Logger LOG = Logger.getLogger(DeploymentDependent.class); + + @Override + protected Deployment desired(TestResource primary, Context context) { + return new DeploymentBuilder() + .withMetadata(new ObjectMetaBuilder() + .withName(primary.getMetadata().getName() + "-deployment") + .withNamespace("default") + .withLabels(Map.of("app", primary.getMetadata().getName())) + .build()) + .withNewSpec() + .withNewSelector().withMatchLabels(Map.of("app", primary.getMetadata().getName())).endSelector() + .withNewTemplate().withNewMetadata().withLabels(Map.of("app", primary.getMetadata().getName())).endMetadata() + .withNewSpec().addNewContainer() + .withName("nginx").withImage("nginx:1.14.2") + .addNewPort().withName("http").withProtocol("TCP").withContainerPort(8080).endPort() + .endContainer() + .endSpec() + .endTemplate() + .endSpec() + .build(); + } +} diff --git a/integration-tests/cdi/src/main/java/io/quarkiverse/operatorsdk/it/cdi/ReadyPostCondition.java b/integration-tests/cdi/src/main/java/io/quarkiverse/operatorsdk/it/cdi/ReadyPostCondition.java new file mode 100644 index 000000000..c3ea0b013 --- /dev/null +++ b/integration-tests/cdi/src/main/java/io/quarkiverse/operatorsdk/it/cdi/ReadyPostCondition.java @@ -0,0 +1,34 @@ +package io.quarkiverse.operatorsdk.it.cdi; + +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.inject.Inject; + +import io.fabric8.kubernetes.api.model.apps.Deployment; +import io.javaoperatorsdk.operator.api.reconciler.Context; +import io.javaoperatorsdk.operator.api.reconciler.dependent.DependentResource; +import io.javaoperatorsdk.operator.processing.dependent.workflow.Condition; +import io.quarkus.arc.Unremovable; + +@ApplicationScoped +@Unremovable +public class ReadyPostCondition implements Condition { + + @Inject + TestUUIDBean testUUIDBean; + + private String uuid; + + @Override + public boolean isMet(DependentResource dependentResource, TestResource primary, + Context context) { + uuid = testUUIDBean.uuid(); + return dependentResource + .getSecondaryResource(primary, context) + .map(deployment -> deployment.getSpec().getReplicas().equals(1)) + .orElse(false); + } + + public String getUuid() { + return uuid; + } +} diff --git a/integration-tests/cdi/src/main/java/io/quarkiverse/operatorsdk/it/cdi/ReconcilePrecondition.java b/integration-tests/cdi/src/main/java/io/quarkiverse/operatorsdk/it/cdi/ReconcilePrecondition.java new file mode 100644 index 000000000..f17dbe697 --- /dev/null +++ b/integration-tests/cdi/src/main/java/io/quarkiverse/operatorsdk/it/cdi/ReconcilePrecondition.java @@ -0,0 +1,31 @@ +package io.quarkiverse.operatorsdk.it.cdi; + +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.inject.Inject; + +import io.fabric8.kubernetes.api.model.HasMetadata; +import io.javaoperatorsdk.operator.api.reconciler.Context; +import io.javaoperatorsdk.operator.api.reconciler.dependent.DependentResource; +import io.javaoperatorsdk.operator.processing.dependent.workflow.Condition; +import io.quarkus.arc.Unremovable; + +@ApplicationScoped +@Unremovable +public class ReconcilePrecondition implements Condition { + + @Inject + TestUUIDBean testUUIDBean; + + private String uuid; + + @Override + + public boolean isMet(DependentResource dependentResource, HasMetadata primary, Context context) { + uuid = testUUIDBean.uuid(); + return true; + } + + public String getUuid() { + return uuid; + } +} diff --git a/integration-tests/cdi/src/main/java/io/quarkiverse/operatorsdk/it/cdi/TestReconciler.java b/integration-tests/cdi/src/main/java/io/quarkiverse/operatorsdk/it/cdi/TestReconciler.java new file mode 100644 index 000000000..86a957b85 --- /dev/null +++ b/integration-tests/cdi/src/main/java/io/quarkiverse/operatorsdk/it/cdi/TestReconciler.java @@ -0,0 +1,30 @@ +package io.quarkiverse.operatorsdk.it.cdi; + +import io.javaoperatorsdk.operator.api.reconciler.Cleaner; +import io.javaoperatorsdk.operator.api.reconciler.Context; +import io.javaoperatorsdk.operator.api.reconciler.ControllerConfiguration; +import io.javaoperatorsdk.operator.api.reconciler.DeleteControl; +import io.javaoperatorsdk.operator.api.reconciler.Reconciler; +import io.javaoperatorsdk.operator.api.reconciler.UpdateControl; +import io.javaoperatorsdk.operator.api.reconciler.Workflow; +import io.javaoperatorsdk.operator.api.reconciler.dependent.Dependent; + +@Workflow(dependents = { + @Dependent(name = TestReconciler.DEPLOYMENT, type = DeploymentDependent.class, activationCondition = CustomActionvationCondition.class, readyPostcondition = ReadyPostCondition.class, reconcilePrecondition = ReconcilePrecondition.class), + @Dependent(type = ConfigMapDependent.class, deletePostcondition = DeletePostCondition.class, dependsOn = TestReconciler.DEPLOYMENT) +}) +@ControllerConfiguration +public class TestReconciler implements Reconciler, Cleaner { + + public static final String DEPLOYMENT = "deployment"; + + @Override + public UpdateControl reconcile(TestResource resource, Context context) throws Exception { + return UpdateControl.noUpdate(); + } + + @Override + public DeleteControl cleanup(TestResource resource, Context context) throws Exception { + return DeleteControl.defaultDelete(); + } +} diff --git a/integration-tests/cdi/src/main/java/io/quarkiverse/operatorsdk/it/cdi/TestResource.java b/integration-tests/cdi/src/main/java/io/quarkiverse/operatorsdk/it/cdi/TestResource.java new file mode 100644 index 000000000..fec60654a --- /dev/null +++ b/integration-tests/cdi/src/main/java/io/quarkiverse/operatorsdk/it/cdi/TestResource.java @@ -0,0 +1,12 @@ +package io.quarkiverse.operatorsdk.it.cdi; + +import io.fabric8.kubernetes.client.CustomResource; +import io.fabric8.kubernetes.model.annotation.Group; +import io.fabric8.kubernetes.model.annotation.ShortNames; +import io.fabric8.kubernetes.model.annotation.Version; + +@Group("josdk.quarkiverse.io") +@Version("v1alpha1") +@ShortNames("tr") +public class TestResource extends CustomResource { +} diff --git a/integration-tests/cdi/src/main/java/io/quarkiverse/operatorsdk/it/cdi/TestSpec.java b/integration-tests/cdi/src/main/java/io/quarkiverse/operatorsdk/it/cdi/TestSpec.java new file mode 100644 index 000000000..6e8d7188c --- /dev/null +++ b/integration-tests/cdi/src/main/java/io/quarkiverse/operatorsdk/it/cdi/TestSpec.java @@ -0,0 +1,4 @@ +package io.quarkiverse.operatorsdk.it.cdi; + +public class TestSpec { +} diff --git a/integration-tests/cdi/src/main/java/io/quarkiverse/operatorsdk/it/cdi/TestStatus.java b/integration-tests/cdi/src/main/java/io/quarkiverse/operatorsdk/it/cdi/TestStatus.java new file mode 100644 index 000000000..f0cda6bf4 --- /dev/null +++ b/integration-tests/cdi/src/main/java/io/quarkiverse/operatorsdk/it/cdi/TestStatus.java @@ -0,0 +1,12 @@ +package io.quarkiverse.operatorsdk.it.cdi; + +public class TestStatus { + public String value; + + public TestStatus() { + } + + public TestStatus(String value) { + this.value = value; + } +} diff --git a/integration-tests/cdi/src/main/java/io/quarkiverse/operatorsdk/it/cdi/TestUUIDBean.java b/integration-tests/cdi/src/main/java/io/quarkiverse/operatorsdk/it/cdi/TestUUIDBean.java new file mode 100644 index 000000000..686f74e4c --- /dev/null +++ b/integration-tests/cdi/src/main/java/io/quarkiverse/operatorsdk/it/cdi/TestUUIDBean.java @@ -0,0 +1,15 @@ +package io.quarkiverse.operatorsdk.it.cdi; + +import java.util.UUID; + +import jakarta.enterprise.context.ApplicationScoped; + +@ApplicationScoped +public class TestUUIDBean { + + private final String id = UUID.randomUUID().toString(); + + public String uuid() { + return id; + } +} diff --git a/integration-tests/cdi/src/main/resources/application.properties b/integration-tests/cdi/src/main/resources/application.properties new file mode 100644 index 000000000..e69de29bb diff --git a/integration-tests/cdi/src/test/java/io/quarkiverse/operatorsdk/it/cdi/ConditionsCDITest.java b/integration-tests/cdi/src/test/java/io/quarkiverse/operatorsdk/it/cdi/ConditionsCDITest.java new file mode 100644 index 000000000..4da761bb6 --- /dev/null +++ b/integration-tests/cdi/src/test/java/io/quarkiverse/operatorsdk/it/cdi/ConditionsCDITest.java @@ -0,0 +1,80 @@ +package io.quarkiverse.operatorsdk.it.cdi; + +import static org.awaitility.Awaitility.await; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; + +import java.time.Duration; + +import jakarta.inject.Inject; + +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Test; + +import io.fabric8.kubernetes.api.model.ObjectMetaBuilder; +import io.fabric8.kubernetes.client.KubernetesClient; +import io.quarkus.test.junit.QuarkusTest; + +/** + * Verify that the Condition subclasses are properly recognized as CDI beans. + */ +@QuarkusTest +class ConditionsCDITest { + + private static final Duration timeout = Duration.ofSeconds(30); + + @Inject + KubernetesClient kubernetesClient; + + @Inject + TestUUIDBean testUUIDBean; + + @Inject + ReadyPostCondition readyPostCondition; + + @Inject + CustomActionvationCondition customActionvationCondition; + + @Inject + ReconcilePrecondition reconcilePrecondition; + + @Inject + DeletePostCondition deletePostCondition; + + @AfterEach + void cleanup() { + kubernetesClient.apiextensions().v1().customResourceDefinitions().withName("testresources.josdk.quarkiverse.io") + .delete(); + } + + @Test + void conditionsInjectionsTest() { + String expectedUUID = testUUIDBean.uuid(); + + TestResource testResource = createTestResource(); + kubernetesClient.resource(testResource).create(); + + // All conditions should be ready after ReadyPostCondition is evaluated, so waiting for it is enough + await().atMost(timeout) + .untilAsserted(() -> assertNotNull(readyPostCondition.getUuid())); + + assertEquals(expectedUUID, readyPostCondition.getUuid(), "ReadyPostCondition injection not processed"); + assertEquals(expectedUUID, customActionvationCondition.getUuid(), "CustomActivationCondition injection not processed"); + assertEquals(expectedUUID, reconcilePrecondition.getUuid(), "ReconcilePrecondition injection not processed"); + + kubernetesClient.resource(testResource).delete(); + + await().atMost(timeout) + .untilAsserted(() -> assertEquals(expectedUUID, deletePostCondition.getUuid(), + "DeletePostCondition injection not processed")); + + } + + private static TestResource createTestResource() { + var tr = new TestResource(); + tr.setMetadata(new ObjectMetaBuilder() + .withName("test-resource-sample").build()); + tr.setSpec(new TestSpec()); + return tr; + } +} diff --git a/integration-tests/pom.xml b/integration-tests/pom.xml index 29dc54c4d..6a466da25 100644 --- a/integration-tests/pom.xml +++ b/integration-tests/pom.xml @@ -12,5 +12,6 @@ pom basic + cdi