From 3e2a4f7ce21360a4310ab7e00512e369363fbb5f Mon Sep 17 00:00:00 2001 From: carlosthe19916 <2582866+carlosthe19916@users.noreply.github.com> Date: Thu, 10 Oct 2024 10:09:48 +0200 Subject: [PATCH] Save changes --- .../java/org/trustify/operator/Constants.java | 13 +- .../operator/cdrs/v2alpha1/TrustifySpec.java | 19 ++ .../KeycloakDBActivationCondition.java | 17 ++ .../keycloak/KeycloakDBDeployment.java | 268 ++++++++++++++++++ ...ycloakDBDeploymentActivationCondition.java | 18 ++ .../KeycloakDBDeploymentDiscriminator.java | 20 ++ .../KeycloakDBPersistentVolumeClaim.java | 73 +++++ ...sistentVolumeClaimActivationCondition.java | 18 ++ ...kDBPersistentVolumeClaimDiscriminator.java | 20 ++ .../v2alpha1/keycloak/KeycloakDBSecret.java | 59 ++++ .../KeycloakDBSecretActivationCondition.java | 27 ++ .../v2alpha1/keycloak/KeycloakDBService.java | 64 +++++ .../KeycloakDBServiceActivationCondition.java | 18 ++ .../KeycloakDBServiceDiscriminator.java | 20 ++ .../server/ServerDeploymentDiscriminator.java | 2 +- .../server/ServerServiceDiscriminator.java | 2 +- .../controllers/TrustifyReconciler.java | 130 +++++---- 17 files changed, 737 insertions(+), 51 deletions(-) create mode 100644 src/main/java/org/trustify/operator/cdrs/v2alpha1/keycloak/KeycloakDBActivationCondition.java create mode 100644 src/main/java/org/trustify/operator/cdrs/v2alpha1/keycloak/KeycloakDBDeployment.java create mode 100644 src/main/java/org/trustify/operator/cdrs/v2alpha1/keycloak/KeycloakDBDeploymentActivationCondition.java create mode 100644 src/main/java/org/trustify/operator/cdrs/v2alpha1/keycloak/KeycloakDBDeploymentDiscriminator.java create mode 100644 src/main/java/org/trustify/operator/cdrs/v2alpha1/keycloak/KeycloakDBPersistentVolumeClaim.java create mode 100644 src/main/java/org/trustify/operator/cdrs/v2alpha1/keycloak/KeycloakDBPersistentVolumeClaimActivationCondition.java create mode 100644 src/main/java/org/trustify/operator/cdrs/v2alpha1/keycloak/KeycloakDBPersistentVolumeClaimDiscriminator.java create mode 100644 src/main/java/org/trustify/operator/cdrs/v2alpha1/keycloak/KeycloakDBSecret.java create mode 100644 src/main/java/org/trustify/operator/cdrs/v2alpha1/keycloak/KeycloakDBSecretActivationCondition.java create mode 100644 src/main/java/org/trustify/operator/cdrs/v2alpha1/keycloak/KeycloakDBService.java create mode 100644 src/main/java/org/trustify/operator/cdrs/v2alpha1/keycloak/KeycloakDBServiceActivationCondition.java create mode 100644 src/main/java/org/trustify/operator/cdrs/v2alpha1/keycloak/KeycloakDBServiceDiscriminator.java diff --git a/src/main/java/org/trustify/operator/Constants.java b/src/main/java/org/trustify/operator/Constants.java index 90cef2b..6a971a6 100644 --- a/src/main/java/org/trustify/operator/Constants.java +++ b/src/main/java/org/trustify/operator/Constants.java @@ -13,6 +13,8 @@ public class Constants { public static final String TRUSTI_SERVER_NAME = "trustify-server"; public static final String TRUSTI_DB_NAME = "trustify-db"; + public static final String OIDC_DB_NAME = "oidc-db"; + // public static final Map DB_SELECTOR_LABELS = Map.of( "trustify-operator/group", "db" @@ -21,6 +23,10 @@ public class Constants { "trustify-operator/group", "server" ); + public static final Map OIDC_DB_SELECTOR_LABELS = Map.of( + "trustify-operator/group", "oidc" + ); + // public static final Integer HTTP_PORT = 8080; public static final Integer HTTPS_PORT = 8443; @@ -33,6 +39,11 @@ public class Constants { public static final String DB_DEPLOYMENT_SUFFIX = "-" + TRUSTI_DB_NAME + "-deployment"; public static final String DB_SERVICE_SUFFIX = "-" + TRUSTI_DB_NAME + "-service"; + public static final String OIDC_DB_PVC_SUFFIX = "-" + OIDC_DB_NAME + "-pvc"; + public static final String OIDC_DB_SECRET_SUFFIX = "-" + OIDC_DB_NAME + "-secret"; + public static final String OIDC_DB_DEPLOYMENT_SUFFIX = "-" + OIDC_DB_NAME + "-deployment"; + public static final String OIDC_DB_SERVICE_SUFFIX = "-" + OIDC_DB_NAME + "-service"; + public static final String SERVER_DEPLOYMENT_SUFFIX = "-" + TRUSTI_SERVER_NAME + "-deployment"; public static final String SERVER_SERVICE_SUFFIX = "-" + TRUSTI_SERVER_NAME + "-service"; @@ -42,7 +53,7 @@ public class Constants { // public static final String DB_SECRET_USERNAME = "username"; public static final String DB_SECRET_PASSWORD = "password"; - public static final String DB_NAME = "trustify"; + public static final String DB_NAME = "database"; public static final Integer DB_PORT= 5432; public static final String POSTGRESQL_PVC_SIZE = "10G"; diff --git a/src/main/java/org/trustify/operator/cdrs/v2alpha1/TrustifySpec.java b/src/main/java/org/trustify/operator/cdrs/v2alpha1/TrustifySpec.java index 4b2673a..a4cbb6c 100644 --- a/src/main/java/org/trustify/operator/cdrs/v2alpha1/TrustifySpec.java +++ b/src/main/java/org/trustify/operator/cdrs/v2alpha1/TrustifySpec.java @@ -32,6 +32,10 @@ public record TrustifySpec( @JsonPropertyDescription("In this section you can configure features related to HTTP and HTTPS") HttpSpec httpSpec, + @JsonProperty("oidc") + @JsonPropertyDescription("In this section you can configure Oidc settings.") + OidcSpec oidcSpec, + @JsonProperty("serverResourceLimits") @JsonPropertyDescription("In this section you can configure resource limits settings for the Server.") ResourcesLimitSpec serverResourceLimitSpec @@ -46,6 +50,7 @@ public TrustifySpec() { null, null, null, + null, null ); } @@ -89,6 +94,20 @@ public record HttpSpec( ) { } + public record OidcSpec( + @JsonPropertyDescription("Enable Oidc Auth.") + String enabled, + @JsonPropertyDescription("Oidc server url.") + String serverUrl, + @JsonPropertyDescription("Oidc client id.") + String clientId, + + @JsonProperty("db") + @JsonPropertyDescription("In this section you can find all properties related to connect to a database.") + DatabaseSpec databaseSpec + ) { + } + public record ResourcesLimitSpec( @JsonPropertyDescription("Requested CPU.") String cpuRequest, diff --git a/src/main/java/org/trustify/operator/cdrs/v2alpha1/keycloak/KeycloakDBActivationCondition.java b/src/main/java/org/trustify/operator/cdrs/v2alpha1/keycloak/KeycloakDBActivationCondition.java new file mode 100644 index 0000000..40e22f9 --- /dev/null +++ b/src/main/java/org/trustify/operator/cdrs/v2alpha1/keycloak/KeycloakDBActivationCondition.java @@ -0,0 +1,17 @@ +package org.trustify.operator.cdrs.v2alpha1.keycloak; + +import org.trustify.operator.cdrs.v2alpha1.Trustify; +import org.trustify.operator.cdrs.v2alpha1.TrustifySpec; + +import java.util.Optional; + +public abstract class KeycloakDBActivationCondition { + + protected boolean isMet(Trustify cr) { + return !Optional.ofNullable(cr.getSpec().oidcSpec()) + .flatMap(oidcSpec -> Optional.ofNullable(oidcSpec.databaseSpec())) + .map(TrustifySpec.DatabaseSpec::externalDatabase) + .orElse(false); + } + +} diff --git a/src/main/java/org/trustify/operator/cdrs/v2alpha1/keycloak/KeycloakDBDeployment.java b/src/main/java/org/trustify/operator/cdrs/v2alpha1/keycloak/KeycloakDBDeployment.java new file mode 100644 index 0000000..f93c33a --- /dev/null +++ b/src/main/java/org/trustify/operator/cdrs/v2alpha1/keycloak/KeycloakDBDeployment.java @@ -0,0 +1,268 @@ +package org.trustify.operator.cdrs.v2alpha1.keycloak; + +import io.fabric8.kubernetes.api.model.*; +import io.fabric8.kubernetes.api.model.apps.*; +import io.javaoperatorsdk.operator.api.reconciler.Context; +import io.javaoperatorsdk.operator.api.reconciler.dependent.DependentResource; +import io.javaoperatorsdk.operator.processing.dependent.Matcher; +import io.javaoperatorsdk.operator.processing.dependent.kubernetes.CRUDKubernetesDependentResource; +import io.javaoperatorsdk.operator.processing.dependent.kubernetes.KubernetesDependent; +import io.javaoperatorsdk.operator.processing.dependent.workflow.Condition; +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.inject.Inject; +import org.trustify.operator.Config; +import org.trustify.operator.Constants; +import org.trustify.operator.cdrs.v2alpha1.Trustify; +import org.trustify.operator.cdrs.v2alpha1.TrustifySpec; +import org.trustify.operator.utils.CRDUtils; + +import java.util.Arrays; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +@KubernetesDependent(labelSelector = KeycloakDBDeployment.LABEL_SELECTOR, resourceDiscriminator = KeycloakDBDeploymentDiscriminator.class) +@ApplicationScoped +public class KeycloakDBDeployment extends CRUDKubernetesDependentResource + implements Matcher, Condition { + + public static final String LABEL_SELECTOR = "app.kubernetes.io/managed-by=trustify-operator,component=oidc"; + + @Inject + Config config; + + public KeycloakDBDeployment() { + super(Deployment.class); + } + + @Override + protected Deployment desired(Trustify cr, Context context) { + return newDeployment(cr, context); + } + + @Override + public Result match(Deployment actual, Trustify cr, Context context) { + final var container = actual.getSpec() + .getTemplate().getSpec().getContainers() + .stream() + .findFirst(); + + return Result.nonComputed(container + .map(c -> c.getImage() != null) + .orElse(false) + ); + } + + @Override + public boolean isMet(DependentResource dependentResource, Trustify primary, Context context) { + return context.getSecondaryResource(Deployment.class, new KeycloakDBDeploymentDiscriminator()) + .map(deployment -> { + final var status = deployment.getStatus(); + if (status != null) { + final var readyReplicas = status.getReadyReplicas(); + return readyReplicas != null && readyReplicas >= 1; + } + return false; + }) + .orElse(false); + } + + @SuppressWarnings("unchecked") + private Deployment newDeployment(Trustify cr, Context context) { + final var contextLabels = (Map) context.managedDependentResourceContext() + .getMandatory(Constants.CONTEXT_LABELS_KEY, Map.class); + + return new DeploymentBuilder() + .withNewMetadata() + .withName(getDeploymentName(cr)) + .withNamespace(cr.getMetadata().getNamespace()) + .withLabels(contextLabels) + .addToLabels("component", "oidc") + .addToLabels(Map.of( + "app.openshift.io/runtime", "postgresql" + )) + .withOwnerReferences(CRDUtils.getOwnerReference(cr)) + .endMetadata() + .withSpec(getDeploymentSpec(cr, context)) + .build(); + } + + @SuppressWarnings("unchecked") + private DeploymentSpec getDeploymentSpec(Trustify cr, Context context) { + final var contextLabels = (Map) context.managedDependentResourceContext() + .getMandatory(Constants.CONTEXT_LABELS_KEY, Map.class); + + Map selectorLabels = Constants.OIDC_DB_SELECTOR_LABELS; + String image = Optional.ofNullable(cr.getSpec().dbImage()).orElse(config.dbImage()); + String imagePullPolicy = Optional.ofNullable(cr.getSpec().imagePullPolicy()).orElse(config.imagePullPolicy()); + + TrustifySpec.DatabaseSpec databaseSpec = Optional.ofNullable(cr.getSpec().oidcSpec()) + .map(TrustifySpec.OidcSpec::databaseSpec) + .orElse(null); + TrustifySpec.ResourcesLimitSpec resourcesLimitSpec = CRDUtils.getValueFromSubSpec(databaseSpec, TrustifySpec.DatabaseSpec::resourceLimits) + .orElse(null); + + return new DeploymentSpecBuilder() + .withStrategy(new DeploymentStrategyBuilder() + .withType("Recreate") + .build() + ) + .withReplicas(1) + .withSelector(new LabelSelectorBuilder() + .withMatchLabels(selectorLabels) + .build() + ) + .withTemplate(new PodTemplateSpecBuilder() + .withNewMetadata() + .withLabels(Stream + .concat(contextLabels.entrySet().stream(), selectorLabels.entrySet().stream()) + .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)) + ) + .endMetadata() + .withSpec(new PodSpecBuilder() + .withRestartPolicy("Always") + .withTerminationGracePeriodSeconds(60L) + .withImagePullSecrets(cr.getSpec().imagePullSecrets()) + .withContainers(new ContainerBuilder() + .withName(Constants.OIDC_DB_NAME) + .withImage(image) + .withImagePullPolicy(imagePullPolicy) + .withEnv(getEnvVars(cr)) + .withPorts(new ContainerPortBuilder() + .withName("tcp") + .withProtocol(Constants.SERVICE_PROTOCOL) + .withContainerPort(getDatabasePort(cr)) + .build() + ) + .withLivenessProbe(new ProbeBuilder() + .withExec(new ExecActionBuilder() + .withCommand("/bin/sh", "-c", "psql -U $POSTGRESQL_USER -d $POSTGRESQL_DATABASE -c 'SELECT 1'") + .build() + ) + .withInitialDelaySeconds(10) + .withTimeoutSeconds(10) + .withPeriodSeconds(10) + .withSuccessThreshold(1) + .withFailureThreshold(3) + .build() + ) + .withReadinessProbe(new ProbeBuilder() + .withExec(new ExecActionBuilder() + .withCommand("/bin/sh", "-c", "psql -U $POSTGRESQL_USER -d $POSTGRESQL_DATABASE -c 'SELECT 1'") + .build() + ) + .withInitialDelaySeconds(5) + .withTimeoutSeconds(1) + .withPeriodSeconds(10) + .withSuccessThreshold(1) + .withFailureThreshold(3) + .build() + ) + .withVolumeMounts(new VolumeMountBuilder() + .withName("db-pvol") + .withMountPath("/var/lib/pgsql/data") + .build() + ) + .withResources(new ResourceRequirementsBuilder() + .withRequests(Map.of( + "cpu", new Quantity(CRDUtils.getValueFromSubSpec(resourcesLimitSpec, TrustifySpec.ResourcesLimitSpec::cpuRequest).orElse("50m")), + "memory", new Quantity(CRDUtils.getValueFromSubSpec(resourcesLimitSpec, TrustifySpec.ResourcesLimitSpec::memoryRequest).orElse("64Mi")) + )) + .withLimits(Map.of( + "cpu", new Quantity(CRDUtils.getValueFromSubSpec(resourcesLimitSpec, TrustifySpec.ResourcesLimitSpec::cpuLimit).orElse("1")), + "memory", new Quantity(CRDUtils.getValueFromSubSpec(resourcesLimitSpec, TrustifySpec.ResourcesLimitSpec::memoryLimit).orElse("0.5Gi")) + )) + .build() + ) + .build() + ) + .withVolumes(new VolumeBuilder() + .withName("db-pvol") + .withPersistentVolumeClaim(new PersistentVolumeClaimVolumeSourceBuilder() + .withClaimName(KeycloakDBPersistentVolumeClaim.getPersistentVolumeClaimName(cr)) + .build() + ) + .build() + ) + .build() + ) + .build() + ) + .build(); + } + + private List getEnvVars(Trustify cr) { + return Arrays.asList( + new EnvVarBuilder() + .withName("POSTGRESQL_USER") + .withValueFrom(new EnvVarSourceBuilder() + .withSecretKeyRef(getUsernameSecretKeySelector(cr)) + .build() + ) + .build(), + new EnvVarBuilder() + .withName("POSTGRESQL_PASSWORD") + .withValueFrom(new EnvVarSourceBuilder() + .withSecretKeyRef(getPasswordSecretKeySelector(cr)) + .build() + ) + .build(), + new EnvVarBuilder() + .withName("POSTGRESQL_DATABASE") + .withValue(getDatabaseName(cr)) + .build() + ); + } + + public static String getDeploymentName(Trustify cr) { + return cr.getMetadata().getName() + Constants.DB_DEPLOYMENT_SUFFIX; + } + + public static SecretKeySelector getUsernameSecretKeySelector(Trustify cr) { + return Optional.ofNullable(cr.getSpec().oidcSpec()) + .flatMap(oidcSpec -> Optional.ofNullable(oidcSpec.databaseSpec())) + .map(TrustifySpec.DatabaseSpec::usernameSecret) + .map(secret -> new SecretKeySelectorBuilder() + .withName(secret.getName()) + .withKey(secret.getKey()) + .withOptional(false) + .build() + ) + .orElseGet(() -> new SecretKeySelectorBuilder() + .withName(KeycloakDBSecret.getSecretName(cr)) + .withKey(Constants.DB_SECRET_USERNAME) + .withOptional(false) + .build() + ); + } + + public static SecretKeySelector getPasswordSecretKeySelector(Trustify cr) { + return Optional.ofNullable(cr.getSpec().oidcSpec()) + .flatMap(oidcSpec -> Optional.ofNullable(oidcSpec.databaseSpec())) + .map(TrustifySpec.DatabaseSpec::passwordSecret) + .map(secret -> new SecretKeySelectorBuilder() + .withName(secret.getName()) + .withKey(secret.getKey()) + .withOptional(false) + .build() + ) + .orElseGet(() -> new SecretKeySelectorBuilder() + .withName(KeycloakDBSecret.getSecretName(cr)) + .withKey(Constants.DB_SECRET_PASSWORD) + .withOptional(false) + .build() + ); + } + + public static String getDatabaseName(Trustify cr) { + return Optional.ofNullable(cr.getSpec().databaseSpec()) + .map(TrustifySpec.DatabaseSpec::name) + .orElse(Constants.DB_NAME); + } + + public static Integer getDatabasePort(Trustify cr) { + return Constants.DB_PORT; + } +} diff --git a/src/main/java/org/trustify/operator/cdrs/v2alpha1/keycloak/KeycloakDBDeploymentActivationCondition.java b/src/main/java/org/trustify/operator/cdrs/v2alpha1/keycloak/KeycloakDBDeploymentActivationCondition.java new file mode 100644 index 0000000..2ee7a9d --- /dev/null +++ b/src/main/java/org/trustify/operator/cdrs/v2alpha1/keycloak/KeycloakDBDeploymentActivationCondition.java @@ -0,0 +1,18 @@ +package org.trustify.operator.cdrs.v2alpha1.keycloak; + +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 jakarta.enterprise.context.ApplicationScoped; +import org.trustify.operator.cdrs.v2alpha1.Trustify; + +@ApplicationScoped +public class KeycloakDBDeploymentActivationCondition extends KeycloakDBActivationCondition implements Condition { + + @Override + public boolean isMet(DependentResource resource, Trustify cr, Context context) { + return super.isMet(cr); + } + +} diff --git a/src/main/java/org/trustify/operator/cdrs/v2alpha1/keycloak/KeycloakDBDeploymentDiscriminator.java b/src/main/java/org/trustify/operator/cdrs/v2alpha1/keycloak/KeycloakDBDeploymentDiscriminator.java new file mode 100644 index 0000000..91cc73b --- /dev/null +++ b/src/main/java/org/trustify/operator/cdrs/v2alpha1/keycloak/KeycloakDBDeploymentDiscriminator.java @@ -0,0 +1,20 @@ +package org.trustify.operator.cdrs.v2alpha1.keycloak; + +import io.fabric8.kubernetes.api.model.apps.Deployment; +import io.javaoperatorsdk.operator.api.reconciler.Context; +import io.javaoperatorsdk.operator.api.reconciler.ResourceDiscriminator; +import io.javaoperatorsdk.operator.processing.event.ResourceID; +import io.javaoperatorsdk.operator.processing.event.source.informer.InformerEventSource; +import org.trustify.operator.cdrs.v2alpha1.Trustify; + +import java.util.Optional; + +public class KeycloakDBDeploymentDiscriminator implements ResourceDiscriminator { + @Override + public Optional distinguish(Class resource, Trustify cr, Context context) { + String deploymentName = KeycloakDBDeployment.getDeploymentName(cr); + ResourceID resourceID = new ResourceID(deploymentName, cr.getMetadata().getNamespace()); + var informerEventSource = (InformerEventSource) context.eventSourceRetriever().getResourceEventSourceFor(Deployment.class, "db-deployment"); + return informerEventSource.get(resourceID); + } +} diff --git a/src/main/java/org/trustify/operator/cdrs/v2alpha1/keycloak/KeycloakDBPersistentVolumeClaim.java b/src/main/java/org/trustify/operator/cdrs/v2alpha1/keycloak/KeycloakDBPersistentVolumeClaim.java new file mode 100644 index 0000000..3da1ef3 --- /dev/null +++ b/src/main/java/org/trustify/operator/cdrs/v2alpha1/keycloak/KeycloakDBPersistentVolumeClaim.java @@ -0,0 +1,73 @@ +package org.trustify.operator.cdrs.v2alpha1.keycloak; + +import io.fabric8.kubernetes.api.model.*; +import io.javaoperatorsdk.operator.api.reconciler.Context; +import io.javaoperatorsdk.operator.processing.dependent.Creator; +import io.javaoperatorsdk.operator.processing.dependent.kubernetes.CRUDKubernetesDependentResource; +import io.javaoperatorsdk.operator.processing.dependent.kubernetes.KubernetesDependent; +import jakarta.enterprise.context.ApplicationScoped; +import org.trustify.operator.Constants; +import org.trustify.operator.cdrs.v2alpha1.Trustify; +import org.trustify.operator.cdrs.v2alpha1.TrustifySpec; +import org.trustify.operator.utils.CRDUtils; + +import java.util.Map; + +@KubernetesDependent(labelSelector = KeycloakDBPersistentVolumeClaim.LABEL_SELECTOR, resourceDiscriminator = KeycloakDBPersistentVolumeClaimDiscriminator.class) +@ApplicationScoped +public class KeycloakDBPersistentVolumeClaim extends CRUDKubernetesDependentResource + implements Creator { + + public static final String LABEL_SELECTOR = "app.kubernetes.io/managed-by=trustify-operator,component=oidc"; + + public KeycloakDBPersistentVolumeClaim() { + super(PersistentVolumeClaim.class); + } + + @Override + protected PersistentVolumeClaim desired(Trustify cr, Context context) { + return newPersistentVolumeClaim(cr, context); + } + + @SuppressWarnings("unchecked") + private PersistentVolumeClaim newPersistentVolumeClaim(Trustify cr, Context context) { + final var labels = (Map) context.managedDependentResourceContext() + .getMandatory(Constants.CONTEXT_LABELS_KEY, Map.class); + + String pvcStorageSize = CRDUtils.getValueFromSubSpec(cr.getSpec().databaseSpec(), TrustifySpec.DatabaseSpec::pvcSize) + .orElse(Constants.POSTGRESQL_PVC_SIZE); + + return new PersistentVolumeClaimBuilder() + .withNewMetadata() + .withName(getPersistentVolumeClaimName(cr)) + .withNamespace(cr.getMetadata().getNamespace()) + .withLabels(labels) + .addToLabels("component", "oidc") + .withOwnerReferences(CRDUtils.getOwnerReference(cr)) + .endMetadata() + .withSpec(new PersistentVolumeClaimSpecBuilder() + .withAccessModes("ReadWriteOnce") + .withResources(new VolumeResourceRequirementsBuilder() + .withRequests(Map.of("storage", new Quantity(pvcStorageSize))) + .build() + ) + .build() + ) + .build(); + } + + @Override + public Result match(PersistentVolumeClaim actual, Trustify cr, Context context) { + final var desiredPersistentVolumeClaimName = getPersistentVolumeClaimName(cr); + return Result.nonComputed(actual + .getMetadata() + .getName() + .equals(desiredPersistentVolumeClaimName) + ); + } + + public static String getPersistentVolumeClaimName(Trustify cr) { + return cr.getMetadata().getName() + Constants.OIDC_DB_PVC_SUFFIX; + } + +} diff --git a/src/main/java/org/trustify/operator/cdrs/v2alpha1/keycloak/KeycloakDBPersistentVolumeClaimActivationCondition.java b/src/main/java/org/trustify/operator/cdrs/v2alpha1/keycloak/KeycloakDBPersistentVolumeClaimActivationCondition.java new file mode 100644 index 0000000..e5f9d1a --- /dev/null +++ b/src/main/java/org/trustify/operator/cdrs/v2alpha1/keycloak/KeycloakDBPersistentVolumeClaimActivationCondition.java @@ -0,0 +1,18 @@ +package org.trustify.operator.cdrs.v2alpha1.keycloak; + +import io.fabric8.kubernetes.api.model.PersistentVolumeClaim; +import io.javaoperatorsdk.operator.api.reconciler.Context; +import io.javaoperatorsdk.operator.api.reconciler.dependent.DependentResource; +import io.javaoperatorsdk.operator.processing.dependent.workflow.Condition; +import jakarta.enterprise.context.ApplicationScoped; +import org.trustify.operator.cdrs.v2alpha1.Trustify; + +@ApplicationScoped +public class KeycloakDBPersistentVolumeClaimActivationCondition extends KeycloakDBActivationCondition implements Condition { + + @Override + public boolean isMet(DependentResource resource, Trustify cr, Context context) { + return super.isMet(cr); + } + +} diff --git a/src/main/java/org/trustify/operator/cdrs/v2alpha1/keycloak/KeycloakDBPersistentVolumeClaimDiscriminator.java b/src/main/java/org/trustify/operator/cdrs/v2alpha1/keycloak/KeycloakDBPersistentVolumeClaimDiscriminator.java new file mode 100644 index 0000000..3d21cbf --- /dev/null +++ b/src/main/java/org/trustify/operator/cdrs/v2alpha1/keycloak/KeycloakDBPersistentVolumeClaimDiscriminator.java @@ -0,0 +1,20 @@ +package org.trustify.operator.cdrs.v2alpha1.keycloak; + +import io.fabric8.kubernetes.api.model.PersistentVolumeClaim; +import io.javaoperatorsdk.operator.api.reconciler.Context; +import io.javaoperatorsdk.operator.api.reconciler.ResourceDiscriminator; +import io.javaoperatorsdk.operator.processing.event.ResourceID; +import io.javaoperatorsdk.operator.processing.event.source.informer.InformerEventSource; +import org.trustify.operator.cdrs.v2alpha1.Trustify; + +import java.util.Optional; + +public class KeycloakDBPersistentVolumeClaimDiscriminator implements ResourceDiscriminator { + @Override + public Optional distinguish(Class resource, Trustify cr, Context context) { + String persistentVolumeClaimName = KeycloakDBPersistentVolumeClaim.getPersistentVolumeClaimName(cr); + ResourceID resourceID = new ResourceID(persistentVolumeClaimName, cr.getMetadata().getNamespace()); + var informerEventSource = (InformerEventSource) context.eventSourceRetriever().getResourceEventSourceFor(PersistentVolumeClaim.class); + return informerEventSource.get(resourceID); + } +} diff --git a/src/main/java/org/trustify/operator/cdrs/v2alpha1/keycloak/KeycloakDBSecret.java b/src/main/java/org/trustify/operator/cdrs/v2alpha1/keycloak/KeycloakDBSecret.java new file mode 100644 index 0000000..03890ef --- /dev/null +++ b/src/main/java/org/trustify/operator/cdrs/v2alpha1/keycloak/KeycloakDBSecret.java @@ -0,0 +1,59 @@ +package org.trustify.operator.cdrs.v2alpha1.keycloak; + +import io.fabric8.kubernetes.api.model.Secret; +import io.fabric8.kubernetes.api.model.SecretBuilder; +import io.javaoperatorsdk.operator.api.reconciler.Context; +import io.javaoperatorsdk.operator.processing.dependent.Creator; +import io.javaoperatorsdk.operator.processing.dependent.kubernetes.CRUDKubernetesDependentResource; +import jakarta.enterprise.context.ApplicationScoped; +import org.trustify.operator.Constants; +import org.trustify.operator.cdrs.v2alpha1.Trustify; +import org.trustify.operator.cdrs.v2alpha1.db.DBSecret; +import org.trustify.operator.utils.CRDUtils; + +import java.util.Map; +import java.util.Random; + +@ApplicationScoped +public class KeycloakDBSecret extends CRUDKubernetesDependentResource implements Creator { + + public KeycloakDBSecret() { + super(Secret.class); + } + + @Override + protected Secret desired(Trustify cr, Context context) { + return newSecret(cr, context); + } + + @Override + public Result match(Secret actual, Trustify cr, Context context) { + final var desiredSecretName = getSecretName(cr); + return Result.nonComputed(actual.getMetadata().getName().equals(desiredSecretName)); + } + + @SuppressWarnings("unchecked") + private Secret newSecret(Trustify cr, Context context) { + final var labels = (Map) context.managedDependentResourceContext() + .getMandatory(Constants.CONTEXT_LABELS_KEY, Map.class); + + return new SecretBuilder() + .withNewMetadata() + .withName(getSecretName(cr)) + .withNamespace(cr.getMetadata().getNamespace()) + .withLabels(labels) + .withOwnerReferences(CRDUtils.getOwnerReference(cr)) + .endMetadata() + .addToStringData(Constants.DB_SECRET_USERNAME, generateRandomString(10)) + .addToStringData(Constants.DB_SECRET_PASSWORD, generateRandomString(10)) + .build(); + } + + public static String getSecretName(Trustify cr) { + return cr.getMetadata().getName() + Constants.OIDC_DB_SECRET_SUFFIX; + } + + public static String generateRandomString(int targetStringLength) { + return DBSecret.generateRandomString(targetStringLength); + } +} diff --git a/src/main/java/org/trustify/operator/cdrs/v2alpha1/keycloak/KeycloakDBSecretActivationCondition.java b/src/main/java/org/trustify/operator/cdrs/v2alpha1/keycloak/KeycloakDBSecretActivationCondition.java new file mode 100644 index 0000000..52d89d4 --- /dev/null +++ b/src/main/java/org/trustify/operator/cdrs/v2alpha1/keycloak/KeycloakDBSecretActivationCondition.java @@ -0,0 +1,27 @@ +package org.trustify.operator.cdrs.v2alpha1.keycloak; + +import io.fabric8.kubernetes.api.model.Secret; +import io.javaoperatorsdk.operator.api.reconciler.Context; +import io.javaoperatorsdk.operator.api.reconciler.dependent.DependentResource; +import io.javaoperatorsdk.operator.processing.dependent.workflow.Condition; +import jakarta.enterprise.context.ApplicationScoped; +import org.trustify.operator.cdrs.v2alpha1.Trustify; + +import java.util.Optional; + +@ApplicationScoped +public class KeycloakDBSecretActivationCondition extends KeycloakDBActivationCondition implements Condition { + + @Override + public boolean isMet(DependentResource resource, Trustify cr, Context context) { + boolean databaseRequired = super.isMet(cr); + + boolean manualSecretIsNotSet = Optional.ofNullable(cr.getSpec().oidcSpec()) + .flatMap(oidcSpec -> Optional.ofNullable(oidcSpec.databaseSpec())) + .map(databaseSpec -> databaseSpec.usernameSecret() == null || databaseSpec.passwordSecret() == null) + .orElse(true); + + return databaseRequired && manualSecretIsNotSet; + } + +} diff --git a/src/main/java/org/trustify/operator/cdrs/v2alpha1/keycloak/KeycloakDBService.java b/src/main/java/org/trustify/operator/cdrs/v2alpha1/keycloak/KeycloakDBService.java new file mode 100644 index 0000000..5384f75 --- /dev/null +++ b/src/main/java/org/trustify/operator/cdrs/v2alpha1/keycloak/KeycloakDBService.java @@ -0,0 +1,64 @@ +package org.trustify.operator.cdrs.v2alpha1.keycloak; + +import io.fabric8.kubernetes.api.model.Service; +import io.fabric8.kubernetes.api.model.ServiceBuilder; +import io.fabric8.kubernetes.api.model.ServiceSpec; +import io.fabric8.kubernetes.api.model.ServiceSpecBuilder; +import io.javaoperatorsdk.operator.api.reconciler.Context; +import io.javaoperatorsdk.operator.processing.dependent.kubernetes.CRUDKubernetesDependentResource; +import io.javaoperatorsdk.operator.processing.dependent.kubernetes.KubernetesDependent; +import jakarta.enterprise.context.ApplicationScoped; +import org.trustify.operator.Constants; +import org.trustify.operator.cdrs.v2alpha1.Trustify; +import org.trustify.operator.utils.CRDUtils; + +import java.util.Map; + +@KubernetesDependent(labelSelector = KeycloakDBService.LABEL_SELECTOR, resourceDiscriminator = KeycloakDBServiceDiscriminator.class) +@ApplicationScoped +public class KeycloakDBService extends CRUDKubernetesDependentResource { + + public static final String LABEL_SELECTOR = "app.kubernetes.io/managed-by=trustify-operator,component=db"; + + public KeycloakDBService() { + super(Service.class); + } + + @Override + public Service desired(Trustify cr, Context context) { + return newService(cr, context); + } + + @SuppressWarnings("unchecked") + private Service newService(Trustify cr, Context context) { + final var labels = (Map) context.managedDependentResourceContext() + .getMandatory(Constants.CONTEXT_LABELS_KEY, Map.class); + + return new ServiceBuilder() + .withNewMetadata() + .withName(getServiceName(cr)) + .withNamespace(cr.getMetadata().getNamespace()) + .withLabels(labels) + .addToLabels("component", "db") + .withOwnerReferences(CRDUtils.getOwnerReference(cr)) + .endMetadata() + .withSpec(getServiceSpec(cr)) + .build(); + } + + private ServiceSpec getServiceSpec(Trustify cr) { + return new ServiceSpecBuilder() + .addNewPort() + .withPort(KeycloakDBDeployment.getDatabasePort(cr)) + .withProtocol(Constants.SERVICE_PROTOCOL) + .endPort() + .withSelector(Constants.DB_SELECTOR_LABELS) + .withType("ClusterIP") + .build(); + } + + public static String getServiceName(Trustify cr) { + return cr.getMetadata().getName() + Constants.DB_SERVICE_SUFFIX; + } + +} diff --git a/src/main/java/org/trustify/operator/cdrs/v2alpha1/keycloak/KeycloakDBServiceActivationCondition.java b/src/main/java/org/trustify/operator/cdrs/v2alpha1/keycloak/KeycloakDBServiceActivationCondition.java new file mode 100644 index 0000000..dca8895 --- /dev/null +++ b/src/main/java/org/trustify/operator/cdrs/v2alpha1/keycloak/KeycloakDBServiceActivationCondition.java @@ -0,0 +1,18 @@ +package org.trustify.operator.cdrs.v2alpha1.keycloak; + +import io.fabric8.kubernetes.api.model.Service; +import io.javaoperatorsdk.operator.api.reconciler.Context; +import io.javaoperatorsdk.operator.api.reconciler.dependent.DependentResource; +import io.javaoperatorsdk.operator.processing.dependent.workflow.Condition; +import jakarta.enterprise.context.ApplicationScoped; +import org.trustify.operator.cdrs.v2alpha1.Trustify; + +@ApplicationScoped +public class KeycloakDBServiceActivationCondition extends KeycloakDBActivationCondition implements Condition { + + @Override + public boolean isMet(DependentResource resource, Trustify cr, Context context) { + return super.isMet(cr); + } + +} diff --git a/src/main/java/org/trustify/operator/cdrs/v2alpha1/keycloak/KeycloakDBServiceDiscriminator.java b/src/main/java/org/trustify/operator/cdrs/v2alpha1/keycloak/KeycloakDBServiceDiscriminator.java new file mode 100644 index 0000000..c26ca49 --- /dev/null +++ b/src/main/java/org/trustify/operator/cdrs/v2alpha1/keycloak/KeycloakDBServiceDiscriminator.java @@ -0,0 +1,20 @@ +package org.trustify.operator.cdrs.v2alpha1.keycloak; + +import io.fabric8.kubernetes.api.model.Service; +import io.javaoperatorsdk.operator.api.reconciler.Context; +import io.javaoperatorsdk.operator.api.reconciler.ResourceDiscriminator; +import io.javaoperatorsdk.operator.processing.event.ResourceID; +import io.javaoperatorsdk.operator.processing.event.source.informer.InformerEventSource; +import org.trustify.operator.cdrs.v2alpha1.Trustify; + +import java.util.Optional; + +public class KeycloakDBServiceDiscriminator implements ResourceDiscriminator { + @Override + public Optional distinguish(Class resource, Trustify cr, Context context) { + String serviceName = KeycloakDBService.getServiceName(cr); + ResourceID resourceID = new ResourceID(serviceName, cr.getMetadata().getNamespace()); + var informerEventSource = (InformerEventSource) context.eventSourceRetriever().getResourceEventSourceFor(Service.class, "db-service"); + return informerEventSource.get(resourceID); + } +} diff --git a/src/main/java/org/trustify/operator/cdrs/v2alpha1/server/ServerDeploymentDiscriminator.java b/src/main/java/org/trustify/operator/cdrs/v2alpha1/server/ServerDeploymentDiscriminator.java index 6f5fce6..8c8ab6c 100644 --- a/src/main/java/org/trustify/operator/cdrs/v2alpha1/server/ServerDeploymentDiscriminator.java +++ b/src/main/java/org/trustify/operator/cdrs/v2alpha1/server/ServerDeploymentDiscriminator.java @@ -15,7 +15,7 @@ public class ServerDeploymentDiscriminator implements ResourceDiscriminator distinguish(Class resource, Trustify cr, Context context) { String deploymentName = ServerDeployment.getDeploymentName(cr); ResourceID resourceID = new ResourceID(deploymentName, cr.getMetadata().getNamespace()); - var informerEventSource = (InformerEventSource) context.eventSourceRetriever().getResourceEventSourceFor(Deployment.class, TrustifyReconciler.SERVER_DEPLOYMENT_EVENT_SOURCE); + var informerEventSource = (InformerEventSource) context.eventSourceRetriever().getResourceEventSourceFor(Deployment.class, TrustifyReconciler.DEPLOYMENT_EVENT_SOURCE); return informerEventSource.get(resourceID); } } \ No newline at end of file diff --git a/src/main/java/org/trustify/operator/cdrs/v2alpha1/server/ServerServiceDiscriminator.java b/src/main/java/org/trustify/operator/cdrs/v2alpha1/server/ServerServiceDiscriminator.java index 29ea024..15df354 100644 --- a/src/main/java/org/trustify/operator/cdrs/v2alpha1/server/ServerServiceDiscriminator.java +++ b/src/main/java/org/trustify/operator/cdrs/v2alpha1/server/ServerServiceDiscriminator.java @@ -15,7 +15,7 @@ public class ServerServiceDiscriminator implements ResourceDiscriminator distinguish(Class resource, Trustify cr, Context context) { String serviceName = ServerService.getServiceName(cr); ResourceID resourceID = new ResourceID(serviceName, cr.getMetadata().getNamespace()); - var informerEventSource = (InformerEventSource) context.eventSourceRetriever().getResourceEventSourceFor(Service.class, TrustifyReconciler.SERVER_SERVICE_EVENT_SOURCE); + var informerEventSource = (InformerEventSource) context.eventSourceRetriever().getResourceEventSourceFor(Service.class, TrustifyReconciler.SERVICE_EVENT_SOURCE); return informerEventSource.get(resourceID); } } \ No newline at end of file diff --git a/src/main/java/org/trustify/operator/controllers/TrustifyReconciler.java b/src/main/java/org/trustify/operator/controllers/TrustifyReconciler.java index bcbd157..bf38b08 100644 --- a/src/main/java/org/trustify/operator/controllers/TrustifyReconciler.java +++ b/src/main/java/org/trustify/operator/controllers/TrustifyReconciler.java @@ -1,5 +1,6 @@ package org.trustify.operator.controllers; +import io.fabric8.kubernetes.api.model.PersistentVolumeClaim; import io.fabric8.kubernetes.api.model.Service; import io.fabric8.kubernetes.api.model.apps.Deployment; import io.javaoperatorsdk.operator.api.config.informer.InformerConfiguration; @@ -11,6 +12,7 @@ import org.trustify.operator.cdrs.v2alpha1.Trustify; import org.trustify.operator.cdrs.v2alpha1.TrustifyStatusCondition; import org.trustify.operator.cdrs.v2alpha1.db.*; +import org.trustify.operator.cdrs.v2alpha1.keycloak.*; import org.trustify.operator.cdrs.v2alpha1.server.ServerDeployment; import org.trustify.operator.cdrs.v2alpha1.server.ServerIngress; import org.trustify.operator.cdrs.v2alpha1.server.ServerService; @@ -24,58 +26,87 @@ namespaces = WATCH_CURRENT_NAMESPACE, name = "trustify", dependents = { + // OIDC @Dependent( - name = "db-pvc", - type = DBPersistentVolumeClaim.class, - activationCondition = DBPersistentVolumeClaimActivationCondition.class - ), - @Dependent( - name = "db-secret", - type = DBSecret.class, - activationCondition = DBSecretActivationCondition.class + name = "oidc-db-pvc", + type = KeycloakDBPersistentVolumeClaim.class, + activationCondition = KeycloakDBPersistentVolumeClaimActivationCondition.class, + useEventSourceWithName = TrustifyReconciler.PVC_EVENT_SOURCE ), +// @Dependent( +// name = "oidc-db-secret", +// type = KeycloakDBSecret.class, +// activationCondition = KeycloakDBSecretActivationCondition.class +// ), +// @Dependent( +// name = "oidc-db-deployment", +// type = KeycloakDBDeployment.class, +// dependsOn = {"db-pvc", "db-secret"}, +// readyPostcondition = KeycloakDBDeployment.class, +// activationCondition = KeycloakDBDeploymentActivationCondition.class +// ), +// @Dependent( +// name = "oidc-db-service", +// type = KeycloakDBService.class, +// dependsOn = {"oidc-db-deployment"}, +// activationCondition = KeycloakDBServiceActivationCondition.class +// ), + + // Trustify @Dependent( - name = "db-deployment", - type = DBDeployment.class, - dependsOn = {"db-pvc", "db-secret"}, - readyPostcondition = DBDeployment.class, - activationCondition = DBDeploymentActivationCondition.class - ), - @Dependent( - name = "db-service", - type = DBService.class, - dependsOn = {"db-deployment"}, - activationCondition = DBServiceActivationCondition.class - ), - - @Dependent( - name = "server-deployment", - type = ServerDeployment.class, -// dependsOn = {"db-service"}, - readyPostcondition = ServerDeployment.class, - useEventSourceWithName = "server-deployment" - ), - @Dependent( - name = "server-service", - type = ServerService.class, - dependsOn = {"server-deployment"}, - useEventSourceWithName = "server-service" + name = "db-pvc", + type = DBPersistentVolumeClaim.class, + activationCondition = DBPersistentVolumeClaimActivationCondition.class, + useEventSourceWithName = TrustifyReconciler.PVC_EVENT_SOURCE ), - - @Dependent( - name = "ingress", - type = ServerIngress.class, - dependsOn = {"server-service"}, - readyPostcondition = ServerIngress.class - ) +// @Dependent( +// name = "db-secret", +// type = DBSecret.class, +// activationCondition = DBSecretActivationCondition.class +// ), +// @Dependent( +// name = "db-deployment", +// type = DBDeployment.class, +// dependsOn = {"db-pvc", "db-secret"}, +// readyPostcondition = DBDeployment.class, +// activationCondition = DBDeploymentActivationCondition.class +// ), +// @Dependent( +// name = "db-service", +// type = DBService.class, +// dependsOn = {"db-deployment"}, +// activationCondition = DBServiceActivationCondition.class +// ), +// +// @Dependent( +// name = "server-deployment", +// type = ServerDeployment.class, +//// dependsOn = {"db-service"}, +// readyPostcondition = ServerDeployment.class, +// useEventSourceWithName = "server-deployment" +// ), +// @Dependent( +// name = "server-service", +// type = ServerService.class, +// dependsOn = {"server-deployment"}, +// useEventSourceWithName = "server-service" +// ), +// +// @Dependent( +// name = "ingress", +// type = ServerIngress.class, +// dependsOn = {"server-service"}, +// readyPostcondition = ServerIngress.class +// ) } ) public class TrustifyReconciler implements Reconciler, ContextInitializer, EventSourceInitializer { private static final Logger logger = Logger.getLogger(TrustifyReconciler.class); - public static final String SERVER_DEPLOYMENT_EVENT_SOURCE = "server-deployment"; - public static final String SERVER_SERVICE_EVENT_SOURCE = "server-service"; + public static final String PVC_EVENT_SOURCE = "pvcSource"; + public static final String DEPLOYMENT_EVENT_SOURCE = "deploymentSource"; + public static final String SERVICE_EVENT_SOURCE = "serviceSource"; @Override public void initContext(Trustify cr, Context context) { @@ -121,15 +152,18 @@ public UpdateControl reconcile(Trustify cr, Context context) { @Override public Map prepareEventSources(EventSourceContext context) { - var serverDeploymentInformerConfiguration = InformerConfiguration.from(Deployment.class, context).build(); - var serverServiceInformerConfiguration = InformerConfiguration.from(Service.class, context).build(); + var pvcInformerConfiguration = InformerConfiguration.from(PersistentVolumeClaim.class, context).build(); + var deploymentInformerConfiguration = InformerConfiguration.from(Deployment.class, context).build(); + var serviceInformerConfiguration = InformerConfiguration.from(Service.class, context).build(); - var serverDeploymentInformerEventSource = new InformerEventSource<>(serverDeploymentInformerConfiguration, context); - var serverServiceInformerEventSource = new InformerEventSource<>(serverServiceInformerConfiguration, context); + var pvcInformerEventSource = new InformerEventSource<>(pvcInformerConfiguration, context); + var deploymentInformerEventSource = new InformerEventSource<>(deploymentInformerConfiguration, context); + var serviceInformerEventSource = new InformerEventSource<>(serviceInformerConfiguration, context); return Map.of( - SERVER_DEPLOYMENT_EVENT_SOURCE, serverDeploymentInformerEventSource, - SERVER_SERVICE_EVENT_SOURCE, serverServiceInformerEventSource + PVC_EVENT_SOURCE, pvcInformerEventSource, + DEPLOYMENT_EVENT_SOURCE, deploymentInformerEventSource, + SERVICE_EVENT_SOURCE, serviceInformerEventSource ); } }