diff --git a/operator/src/main/java/org/keycloak/operator/Constants.java b/operator/src/main/java/org/keycloak/operator/Constants.java index 3c89114fa9..a9b2f33d4b 100644 --- a/operator/src/main/java/org/keycloak/operator/Constants.java +++ b/operator/src/main/java/org/keycloak/operator/Constants.java @@ -38,6 +38,7 @@ public final class Constants { public static final String KEYCLOAK_COMPONENT_LABEL = "operator.keycloak.org/component"; public static final String KEYCLOAK_WATCHED_SECRET_HASH_ANNOTATION = "operator.keycloak.org/watched-secret-hash"; public static final String KEYCLOAK_WATCHING_ANNOTATION = "operator.keycloak.org/watching-secrets"; + public static final String KEYCLOAK_MISSING_SECRETS_ANNOTATION = "operator.keycloak.org/missing-secrets"; public static final String DEFAULT_LABELS_AS_STRING = "app=keycloak,app.kubernetes.io/managed-by=keycloak-operator"; diff --git a/operator/src/main/java/org/keycloak/operator/controllers/KeycloakController.java b/operator/src/main/java/org/keycloak/operator/controllers/KeycloakController.java index 536be4d905..0eb8f83f09 100644 --- a/operator/src/main/java/org/keycloak/operator/controllers/KeycloakController.java +++ b/operator/src/main/java/org/keycloak/operator/controllers/KeycloakController.java @@ -130,7 +130,9 @@ public class KeycloakController implements Reconciler, EventSourceInit updateControl = UpdateControl.updateStatus(kc); } - if (!status.isReady()) { + if (!status.isReady() || context.getSecondaryResource(StatefulSet.class) + .map(s -> s.getMetadata().getAnnotations().get(Constants.KEYCLOAK_MISSING_SECRETS_ANNOTATION)) + .filter(Boolean::valueOf).isPresent()) { updateControl.rescheduleAfter(10, TimeUnit.SECONDS); } diff --git a/operator/src/main/java/org/keycloak/operator/controllers/WatchedSecretsController.java b/operator/src/main/java/org/keycloak/operator/controllers/WatchedSecretsController.java index 460d0f3012..4d1b92813d 100644 --- a/operator/src/main/java/org/keycloak/operator/controllers/WatchedSecretsController.java +++ b/operator/src/main/java/org/keycloak/operator/controllers/WatchedSecretsController.java @@ -44,6 +44,7 @@ import java.security.NoSuchAlgorithmException; import java.util.Arrays; import java.util.List; import java.util.Map; +import java.util.Objects; import java.util.Optional; import java.util.stream.Collectors; @@ -103,6 +104,8 @@ public class WatchedSecretsController implements Reconciler, EventSource @Override public void annotateDeployment(List desiredWatchedSecretsNames, Keycloak keycloakCR, StatefulSet deployment) { List currentSecrets = fetchSecrets(desiredWatchedSecretsNames, keycloakCR.getMetadata().getNamespace()); + deployment.getMetadata().getAnnotations().put(Constants.KEYCLOAK_MISSING_SECRETS_ANNOTATION, + Boolean.valueOf(currentSecrets.size() < desiredWatchedSecretsNames.size()).toString()); deployment.getMetadata().getAnnotations().put(Constants.KEYCLOAK_WATCHING_ANNOTATION, desiredWatchedSecretsNames.stream().collect(Collectors.joining(";"))); deployment.getSpec().getTemplate().getMetadata().getAnnotations().put(Constants.KEYCLOAK_WATCHED_SECRET_HASH_ANNOTATION, getSecretHash(currentSecrets)); } @@ -110,7 +113,8 @@ public class WatchedSecretsController implements Reconciler, EventSource private List fetchSecrets(List secretsNames, String namespace) { return secretsNames.stream() .map(n -> Optional.ofNullable(secrets).flatMap(cache -> cache.get(new ResourceID(n, namespace))) - .orElseGet(() -> client.secrets().inNamespace(namespace).withName(n).require())) + .orElseGet(() -> client.secrets().inNamespace(namespace).withName(n).get())) + .filter(Objects::nonNull) .collect(Collectors.toList()); } diff --git a/operator/src/test/java/org/keycloak/operator/testsuite/integration/KeycloakDeploymentTest.java b/operator/src/test/java/org/keycloak/operator/testsuite/integration/KeycloakDeploymentTest.java index 0a3cdb960c..03855bc460 100644 --- a/operator/src/test/java/org/keycloak/operator/testsuite/integration/KeycloakDeploymentTest.java +++ b/operator/src/test/java/org/keycloak/operator/testsuite/integration/KeycloakDeploymentTest.java @@ -28,6 +28,7 @@ import io.fabric8.kubernetes.api.model.ServicePort; import io.fabric8.kubernetes.api.model.apps.StatefulSet; import io.fabric8.kubernetes.api.model.apps.StatefulSetBuilder; import io.fabric8.kubernetes.api.model.apps.StatefulSetSpecBuilder; +import io.fabric8.kubernetes.client.dsl.Resource; import io.quarkus.logging.Log; import io.quarkus.test.junit.QuarkusTest; @@ -94,6 +95,26 @@ public class KeycloakDeploymentTest extends BaseOperatorTest { .untilAsserted(() -> assertThat(k8sclient.apps().statefulSets().inNamespace(namespace).withName(deploymentName).get()).isNull()); } + @Test + public void testKeycloakDeploymentBeforeSecret() { + // CR + var kc = getTestKeycloakDeployment(true); + var deploymentName = kc.getMetadata().getName(); + deployKeycloak(k8sclient, kc, false, false); + + // Check Operator has deployed Keycloak and the statefulset exists, this allows for the watched secret to be picked up + Log.info("Checking Operator has deployed Keycloak deployment"); + Resource stsResource = k8sclient.resources(StatefulSet.class).withName(deploymentName); + Resource keycloakResource = k8sclient.resources(Keycloak.class).withName(deploymentName); + // expect no errors and not ready, which means we'll keep reconciling + Awaitility.await().untilAsserted(() -> { + assertThat(stsResource.get()).isNotNull(); + Keycloak keycloak = keycloakResource.get(); + CRAssert.assertKeycloakStatusCondition(keycloak, KeycloakStatusCondition.HAS_ERRORS, false); + CRAssert.assertKeycloakStatusCondition(keycloak, KeycloakStatusCondition.READY, false); + }); + } + @Test public void testCRFields() { var kc = getTestKeycloakDeployment(true); diff --git a/operator/src/test/java/org/keycloak/operator/testsuite/integration/WatchedSecretsTest.java b/operator/src/test/java/org/keycloak/operator/testsuite/integration/WatchedSecretsTest.java index 37fee860ef..866df23473 100644 --- a/operator/src/test/java/org/keycloak/operator/testsuite/integration/WatchedSecretsTest.java +++ b/operator/src/test/java/org/keycloak/operator/testsuite/integration/WatchedSecretsTest.java @@ -87,8 +87,6 @@ public class WatchedSecretsTest extends BaseOperatorTest { var kc = getTestKeycloakDeployment(false); deployKeycloak(k8sclient, kc, true); - var prevRevision = getStatefulSet(kc).getStatus().getUpdateRevision(); - var dbSecret = getDbSecret(); dbSecret.getData().put("username",