allows normal reconciliation to continue even if secrets are not present (#22404)

* allows normal reconciliation to continue even if secrets are not present

Closes #22170

* adds polling if any secret (in particular optional) is not present

Closes #22170
This commit is contained in:
Steven Hawkins 2023-09-01 04:34:31 -04:00 committed by GitHub
parent 166e2e4c91
commit ffc6bc497a
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 30 additions and 4 deletions

View file

@ -38,6 +38,7 @@ public final class Constants {
public static final String KEYCLOAK_COMPONENT_LABEL = "operator.keycloak.org/component"; 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_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_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"; public static final String DEFAULT_LABELS_AS_STRING = "app=keycloak,app.kubernetes.io/managed-by=keycloak-operator";

View file

@ -130,7 +130,9 @@ public class KeycloakController implements Reconciler<Keycloak>, EventSourceInit
updateControl = UpdateControl.updateStatus(kc); 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); updateControl.rescheduleAfter(10, TimeUnit.SECONDS);
} }

View file

@ -44,6 +44,7 @@ import java.security.NoSuchAlgorithmException;
import java.util.Arrays; import java.util.Arrays;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Objects;
import java.util.Optional; import java.util.Optional;
import java.util.stream.Collectors; import java.util.stream.Collectors;
@ -103,6 +104,8 @@ public class WatchedSecretsController implements Reconciler<Secret>, EventSource
@Override @Override
public void annotateDeployment(List<String> desiredWatchedSecretsNames, Keycloak keycloakCR, StatefulSet deployment) { public void annotateDeployment(List<String> desiredWatchedSecretsNames, Keycloak keycloakCR, StatefulSet deployment) {
List<Secret> currentSecrets = fetchSecrets(desiredWatchedSecretsNames, keycloakCR.getMetadata().getNamespace()); List<Secret> 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.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)); deployment.getSpec().getTemplate().getMetadata().getAnnotations().put(Constants.KEYCLOAK_WATCHED_SECRET_HASH_ANNOTATION, getSecretHash(currentSecrets));
} }
@ -110,7 +113,8 @@ public class WatchedSecretsController implements Reconciler<Secret>, EventSource
private List<Secret> fetchSecrets(List<String> secretsNames, String namespace) { private List<Secret> fetchSecrets(List<String> secretsNames, String namespace) {
return secretsNames.stream() return secretsNames.stream()
.map(n -> Optional.ofNullable(secrets).flatMap(cache -> cache.get(new ResourceID(n, namespace))) .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()); .collect(Collectors.toList());
} }

View file

@ -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.StatefulSet;
import io.fabric8.kubernetes.api.model.apps.StatefulSetBuilder; import io.fabric8.kubernetes.api.model.apps.StatefulSetBuilder;
import io.fabric8.kubernetes.api.model.apps.StatefulSetSpecBuilder; import io.fabric8.kubernetes.api.model.apps.StatefulSetSpecBuilder;
import io.fabric8.kubernetes.client.dsl.Resource;
import io.quarkus.logging.Log; import io.quarkus.logging.Log;
import io.quarkus.test.junit.QuarkusTest; 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()); .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<StatefulSet> stsResource = k8sclient.resources(StatefulSet.class).withName(deploymentName);
Resource<Keycloak> 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 @Test
public void testCRFields() { public void testCRFields() {
var kc = getTestKeycloakDeployment(true); var kc = getTestKeycloakDeployment(true);

View file

@ -87,8 +87,6 @@ public class WatchedSecretsTest extends BaseOperatorTest {
var kc = getTestKeycloakDeployment(false); var kc = getTestKeycloakDeployment(false);
deployKeycloak(k8sclient, kc, true); deployKeycloak(k8sclient, kc, true);
var prevRevision = getStatefulSet(kc).getStatus().getUpdateRevision();
var dbSecret = getDbSecret(); var dbSecret = getDbSecret();
dbSecret.getData().put("username", dbSecret.getData().put("username",