From e9c9f80e8d5ff3e3cc4c07b6f42221ead2beb80f Mon Sep 17 00:00:00 2001 From: Steven Hawkins Date: Wed, 28 Jun 2023 12:05:23 -0400 Subject: [PATCH] adds an instance label to support multiple instances (#20906) Closes #10562 #14220 --- .../topics/keycloak/changes-22_0_0.adoc | 6 +- .../java/org/keycloak/operator/Constants.java | 6 +- .../java/org/keycloak/operator/Utils.java | 10 +++ .../controllers/KeycloakDeployment.java | 32 +++----- .../controllers/KeycloakDiscoveryService.java | 2 +- .../operator/controllers/KeycloakService.java | 2 +- .../controllers/OperatorManagedResource.java | 19 +++-- .../controllers/WatchedSecretsStore.java | 4 +- .../integration/BaseOperatorTest.java | 3 + .../testsuite/integration/ClusteringTest.java | 78 +++++++++++++++---- .../integration/KeycloakDeploymentTest.java | 19 ++++- .../testsuite/unit/PodTemplateTest.java | 9 ++- .../operator/testsuite/utils/K8sUtils.java | 6 +- 13 files changed, 141 insertions(+), 55 deletions(-) diff --git a/docs/documentation/upgrading/topics/keycloak/changes-22_0_0.adoc b/docs/documentation/upgrading/topics/keycloak/changes-22_0_0.adoc index 027784d64b..c21ac9ab96 100644 --- a/docs/documentation/upgrading/topics/keycloak/changes-22_0_0.adoc +++ b/docs/documentation/upgrading/topics/keycloak/changes-22_0_0.adoc @@ -349,4 +349,8 @@ their corresponding replacements. * `Streams` interfaces in federated storage provider classes were deprecated. * `KeycloakModelUtils#getClientScopeMappings` was removed. * Deprecated methods from `KeycloakSession` were removed. -* `UserQueryProvider#getUsersStream` methods were removed. \ No newline at end of file +* `UserQueryProvider#getUsersStream` methods were removed. + += Multiple Keycloak instances + +Multiple Keycloak CRs may be created in the same namespace and will be managed independently by the operator. To allow for this StatefulSets created by older versions of the operator must be re-created. This will happen automatically when the operator is upgraded and lead to small amount of downtime. \ No newline at end of file diff --git a/operator/src/main/java/org/keycloak/operator/Constants.java b/operator/src/main/java/org/keycloak/operator/Constants.java index 1e4597607c..31535bc991 100644 --- a/operator/src/main/java/org/keycloak/operator/Constants.java +++ b/operator/src/main/java/org/keycloak/operator/Constants.java @@ -22,7 +22,6 @@ import java.util.Collections; import java.util.List; import java.util.Map; import java.util.TreeMap; -import java.util.stream.Collectors; public final class Constants { public static final String CRDS_GROUP = "k8s.keycloak.org"; @@ -30,6 +29,7 @@ public final class Constants { public static final String SHORT_NAME = "kc"; public static final String NAME = "keycloak"; public static final String PLURAL_NAME = "keycloaks"; + public static final String INSTANCE_LABEL = "app.kubernetes.io/instance"; public static final String MANAGED_BY_LABEL = "app.kubernetes.io/managed-by"; public static final String MANAGED_BY_VALUE = "keycloak-operator"; public static final String COMPONENT_LABEL = "app.kubernetes.io/component"; @@ -40,9 +40,7 @@ public final class Constants { MANAGED_BY_LABEL, MANAGED_BY_VALUE ))); - public static final String DEFAULT_LABELS_AS_STRING = DEFAULT_LABELS.entrySet().stream() - .map(e -> e.getKey() + "=" + e.getValue()) - .collect(Collectors.joining(",")); + public static final String DEFAULT_LABELS_AS_STRING = Utils.toSelectorString(DEFAULT_LABELS); public static final List DEFAULT_DIST_CONFIG_LIST = List.of( new ValueOrSecret("health-enabled", "true"), diff --git a/operator/src/main/java/org/keycloak/operator/Utils.java b/operator/src/main/java/org/keycloak/operator/Utils.java index 78a8513f9c..497dbbd8cb 100644 --- a/operator/src/main/java/org/keycloak/operator/Utils.java +++ b/operator/src/main/java/org/keycloak/operator/Utils.java @@ -24,6 +24,8 @@ import java.time.ZoneOffset; import java.time.ZonedDateTime; import java.time.format.DateTimeFormatter; import java.util.Base64; +import java.util.Map; +import java.util.stream.Collectors; /** * @author Vaclav Muzikar @@ -45,4 +47,12 @@ public final class Utils { return Base64.getEncoder().encodeToString(toEncode.getBytes(StandardCharsets.UTF_8)); } + public static String toSelectorString(Map labels) { + if (labels == null || labels.isEmpty()) { + return null; + } + return labels.entrySet().stream().map(e -> e.getKey() + "=" + e.getValue()) + .collect(Collectors.joining(",")); + } + } diff --git a/operator/src/main/java/org/keycloak/operator/controllers/KeycloakDeployment.java b/operator/src/main/java/org/keycloak/operator/controllers/KeycloakDeployment.java index c9733caaee..34148a9a5e 100644 --- a/operator/src/main/java/org/keycloak/operator/controllers/KeycloakDeployment.java +++ b/operator/src/main/java/org/keycloak/operator/controllers/KeycloakDeployment.java @@ -33,6 +33,7 @@ import io.quarkus.logging.Log; import org.keycloak.common.util.CollectionUtil; import org.keycloak.operator.Config; import org.keycloak.operator.Constants; +import org.keycloak.operator.Utils; import org.keycloak.operator.crds.v2alpha1.deployment.Keycloak; import org.keycloak.operator.crds.v2alpha1.deployment.KeycloakStatusAggregator; import org.keycloak.operator.crds.v2alpha1.deployment.ValueOrSecret; @@ -42,7 +43,6 @@ import java.util.ArrayList; import java.util.Base64; import java.util.HashMap; import java.util.HashSet; -import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Optional; @@ -71,15 +71,7 @@ public class KeycloakDeployment extends OperatorManagedResource implements Statu this.operatorConfig = config; this.keycloakCR = keycloakCR; this.adminSecretName = adminSecretName; - - if (existingDeployment != null) { - Log.info("Existing Deployment provided by controller"); - this.existingDeployment = existingDeployment; - } else { - Log.info("Trying to fetch existing Deployment from the API"); - this.existingDeployment = fetchExistingDeployment(); - } - + this.existingDeployment = existingDeployment; this.baseDeployment = createBaseDeployment(); this.distConfigurator = configureDist(); mergePodTemplate(this.baseDeployment.getSpec().getTemplate()); @@ -93,18 +85,18 @@ public class KeycloakDeployment extends OperatorManagedResource implements Statu else { Log.info("Existing Deployment found, handling migration"); + if (!existingDeployment.isMarkedForDeletion() && !hasExpectedMatchLabels(existingDeployment)) { + client.resource(existingDeployment).lockResourceVersion().delete(); + Log.info("Existing Deployment found with old label selector, it will be recreated"); + } + migrateDeployment(existingDeployment, baseDeployment); } return Optional.of(baseDeployment); } - private StatefulSet fetchExistingDeployment() { - return client - .apps() - .statefulSets() - .inNamespace(getNamespace()) - .withName(getName()) - .get(); + private boolean hasExpectedMatchLabels(StatefulSet statefulSet) { + return Optional.ofNullable(statefulSet).map(s -> getInstanceLabels().equals(s.getSpec().getSelector().getMatchLabels())).orElse(true); } public void validatePodTemplate(KeycloakStatusAggregator status) { @@ -361,10 +353,10 @@ public class KeycloakDeployment extends OperatorManagedResource implements Statu baseDeployment.getMetadata().setName(getName()); baseDeployment.getMetadata().setNamespace(getNamespace()); - baseDeployment.getSpec().getSelector().setMatchLabels(Constants.DEFAULT_LABELS); + baseDeployment.getSpec().getSelector().setMatchLabels(getInstanceLabels()); baseDeployment.getSpec().setReplicas(keycloakCR.getSpec().getInstances()); - Map labels = new LinkedHashMap<>(Constants.DEFAULT_LABELS); + Map labels = getInstanceLabels(); if (operatorConfig.keycloak().podLabels() != null) { labels.putAll(operatorConfig.keycloak().podLabels()); } @@ -483,7 +475,7 @@ public class KeycloakDeployment extends OperatorManagedResource implements Statu @Override public void updateStatus(KeycloakStatusAggregator status) { - status.apply(b -> b.withSelector(Constants.DEFAULT_LABELS_AS_STRING)); + status.apply(b -> b.withSelector(Utils.toSelectorString(getInstanceLabels()))); validatePodTemplate(status); if (existingDeployment == null) { status.addNotReadyMessage("No existing StatefulSet found, waiting for creating a new one"); diff --git a/operator/src/main/java/org/keycloak/operator/controllers/KeycloakDiscoveryService.java b/operator/src/main/java/org/keycloak/operator/controllers/KeycloakDiscoveryService.java index 0122107db7..f18bb5453c 100644 --- a/operator/src/main/java/org/keycloak/operator/controllers/KeycloakDiscoveryService.java +++ b/operator/src/main/java/org/keycloak/operator/controllers/KeycloakDiscoveryService.java @@ -43,7 +43,7 @@ public class KeycloakDiscoveryService extends OperatorManagedResource implements .withProtocol("TCP") .withPort(Constants.KEYCLOAK_DISCOVERY_SERVICE_PORT) .endPort() - .withSelector(Constants.DEFAULT_LABELS) + .withSelector(getInstanceLabels()) .withClusterIP("None") .build(); } diff --git a/operator/src/main/java/org/keycloak/operator/controllers/KeycloakService.java b/operator/src/main/java/org/keycloak/operator/controllers/KeycloakService.java index 49c071381c..89b9ae1f3d 100644 --- a/operator/src/main/java/org/keycloak/operator/controllers/KeycloakService.java +++ b/operator/src/main/java/org/keycloak/operator/controllers/KeycloakService.java @@ -49,7 +49,7 @@ public class KeycloakService extends OperatorManagedResource implements StatusUp .withPort(getServicePort(keycloak)) .withProtocol(Constants.KEYCLOAK_SERVICE_PROTOCOL) .endPort() - .withSelector(Constants.DEFAULT_LABELS) + .withSelector(getInstanceLabels()) .build(); } diff --git a/operator/src/main/java/org/keycloak/operator/controllers/OperatorManagedResource.java b/operator/src/main/java/org/keycloak/operator/controllers/OperatorManagedResource.java index 839ceb45d8..b0c93fdccd 100644 --- a/operator/src/main/java/org/keycloak/operator/controllers/OperatorManagedResource.java +++ b/operator/src/main/java/org/keycloak/operator/controllers/OperatorManagedResource.java @@ -31,7 +31,7 @@ import io.quarkus.logging.Log; import org.keycloak.operator.Constants; import java.util.Collections; -import java.util.HashMap; +import java.util.LinkedHashMap; import java.util.Map; import java.util.Optional; @@ -55,7 +55,7 @@ public abstract class OperatorManagedResource { public void createOrUpdateReconciled() { getReconciledResource().ifPresent(resource -> { try { - setDefaultLabels(resource); + setInstanceLabels(resource); setOwnerReferences(resource); Log.debugf("Creating or updating resource: %s", resource); @@ -88,10 +88,19 @@ public abstract class OperatorManagedResource { }); } - protected void setDefaultLabels(HasMetadata resource) { - Map labels = Optional.ofNullable(resource.getMetadata().getLabels()).orElse(new HashMap<>()); + protected void setInstanceLabels(HasMetadata resource) { + resource.getMetadata().setLabels(updateWithInstanceLabels(resource.getMetadata().getLabels(), cr.getMetadata().getName())); + } + + protected Map getInstanceLabels() { + return updateWithInstanceLabels(null, cr.getMetadata().getName()); + } + + public static Map updateWithInstanceLabels(Map labels, String instanceName) { + labels = Optional.ofNullable(labels).orElse(new LinkedHashMap<>()); labels.putAll(Constants.DEFAULT_LABELS); - resource.getMetadata().setLabels(labels); + labels.put(Constants.INSTANCE_LABEL, instanceName); + return labels; } protected void setOwnerReferences(HasMetadata resource) { diff --git a/operator/src/main/java/org/keycloak/operator/controllers/WatchedSecretsStore.java b/operator/src/main/java/org/keycloak/operator/controllers/WatchedSecretsStore.java index 5784b6f0b9..0ca64302cf 100644 --- a/operator/src/main/java/org/keycloak/operator/controllers/WatchedSecretsStore.java +++ b/operator/src/main/java/org/keycloak/operator/controllers/WatchedSecretsStore.java @@ -90,8 +90,8 @@ public class WatchedSecretsStore extends OperatorManagedResource { } @Override - protected void setDefaultLabels(HasMetadata resource) { - super.setDefaultLabels(resource); + protected void setInstanceLabels(HasMetadata resource) { + super.setInstanceLabels(resource); resource.getMetadata().getLabels().put(Constants.COMPONENT_LABEL, COMPONENT); } diff --git a/operator/src/test/java/org/keycloak/operator/testsuite/integration/BaseOperatorTest.java b/operator/src/test/java/org/keycloak/operator/testsuite/integration/BaseOperatorTest.java index 33f0e72fa3..59d2355c12 100644 --- a/operator/src/test/java/org/keycloak/operator/testsuite/integration/BaseOperatorTest.java +++ b/operator/src/test/java/org/keycloak/operator/testsuite/integration/BaseOperatorTest.java @@ -24,6 +24,7 @@ import io.fabric8.kubernetes.client.Config; import io.fabric8.kubernetes.client.ConfigBuilder; import io.fabric8.kubernetes.client.KubernetesClient; import io.fabric8.kubernetes.client.KubernetesClientBuilder; +import io.fabric8.kubernetes.client.NamespacedKubernetesClient; import io.javaoperatorsdk.operator.Operator; import io.javaoperatorsdk.operator.api.config.ConfigurationServiceProvider; import io.javaoperatorsdk.operator.api.reconciler.Reconciler; @@ -166,6 +167,8 @@ public abstract class BaseOperatorTest { private static void createNamespace() { Log.info("Creating Namespace " + namespace); k8sclient.resource(new NamespaceBuilder().withNewMetadata().addToLabels("app","keycloak-test").withName(namespace).endMetadata().build()).create(); + // ensure that the client defaults to the namespace - eventually most of the test code usage of inNamespace can be removed + k8sclient = k8sclient.adapt(NamespacedKubernetesClient.class).inNamespace(namespace); } private static void calculateNamespace() { diff --git a/operator/src/test/java/org/keycloak/operator/testsuite/integration/ClusteringTest.java b/operator/src/test/java/org/keycloak/operator/testsuite/integration/ClusteringTest.java index fec1769445..4a90b57720 100644 --- a/operator/src/test/java/org/keycloak/operator/testsuite/integration/ClusteringTest.java +++ b/operator/src/test/java/org/keycloak/operator/testsuite/integration/ClusteringTest.java @@ -17,6 +17,12 @@ package org.keycloak.operator.testsuite.integration; +import io.fabric8.kubernetes.api.model.HasMetadata; +import io.fabric8.kubernetes.api.model.Secret; +import io.fabric8.kubernetes.api.model.Service; +import io.fabric8.kubernetes.api.model.apps.StatefulSet; +import io.fabric8.kubernetes.api.model.networking.v1.Ingress; +import io.fabric8.kubernetes.client.readiness.Readiness; import io.fabric8.kubernetes.client.utils.Serialization; import io.quarkus.logging.Log; import io.quarkus.test.junit.QuarkusTest; @@ -45,14 +51,66 @@ import static org.assertj.core.api.Assertions.assertThat; @QuarkusTest public class ClusteringTest extends BaseOperatorTest { + @Test + public void testMultipleDeployments() throws InterruptedException { + // given + var kc = K8sUtils.getDefaultKeycloakDeployment(); + + // another instance running off the same database + // - should eventually give this a separate schema + var kc1 = K8sUtils.getDefaultKeycloakDeployment(); + kc1.getMetadata().setName("another-example"); + kc1.getSpec().getHostnameSpec().setHostname("another-example.com"); + // this is using the wrong tls-secret, but simply removing http spec renders the pod unstartable + + try { + K8sUtils.deployKeycloak(k8sclient, kc, true); + K8sUtils.deployKeycloak(k8sclient, kc1, true); + } catch (Exception e) { + k8sclient.resources(Keycloak.class).list().getItems().stream().forEach(k -> { + Log.infof("Keycloak %s status: %s", k.getMetadata().getName(), Serialization.asYaml(k.getStatus())); + }); + k8sclient.pods().list().getItems().stream().filter(p -> !Readiness.isPodReady(p)).forEach(p -> { + Log.infof("Pod %s not ready: %s", p.getMetadata().getName(), Serialization.asYaml(p.getStatus())); + }); + throw e; + } + + assertThat(k8sclient.resources(Keycloak.class).list().getItems().size()).isEqualTo(2); + + // get the current version for the uid + kc = k8sclient.resource(kc).get(); + kc1 = k8sclient.resource(kc1).get(); + + // the main resources are ready, check for the expected dependents + checkInstanceCount(1, StatefulSet.class, kc, kc1); + checkInstanceCount(2, Secret.class, kc, kc1); + checkInstanceCount(1, Ingress.class, kc, kc1); + checkInstanceCount(2, Service.class, kc, kc1); + + // ensure they don't see each other's pods + assertThat(k8sclient.resource(kc).scale().getStatus().getReplicas()).isEqualTo(1); + assertThat(k8sclient.resource(kc1).scale().getStatus().getReplicas()).isEqualTo(1); + + // could also scale one instance to zero end ensure the services are no longer reachable + } + + private void checkInstanceCount(int count, Class type, HasMetadata... toCheck) { + var instances = k8sclient.resources(type).list().getItems(); + + for (HasMetadata hasMetadata : toCheck) { + assertThat(instances.stream() + .filter(h -> h.getOwnerReferenceFor(hasMetadata).isPresent() && hasMetadata.getMetadata() + .getName().equals(h.getMetadata().getLabels().get(Constants.INSTANCE_LABEL))) + .count()).isEqualTo(count); + } + } + @Test public void testKeycloakScaleAsExpected() { // given var kc = K8sUtils.getDefaultKeycloakDeployment(); - var crSelector = k8sclient - .resources(Keycloak.class) - .inNamespace(kc.getMetadata().getNamespace()) - .withName(kc.getMetadata().getName()); + var crSelector = k8sclient.resource(kc); K8sUtils.deployKeycloak(k8sclient, kc, true); var kcPodsSelector = k8sclient.pods().inNamespace(namespace).withLabel("app", "keycloak"); @@ -60,7 +118,7 @@ public class ClusteringTest extends BaseOperatorTest { var scale = crSelector.scale(); assertThat(scale.getSpec().getReplicas()).isEqualTo(1); assertThat(scale.getStatus().getReplicas()).isEqualTo(1); - assertThat(scale.getStatus().getSelector()).isEqualTo(Constants.DEFAULT_LABELS_AS_STRING); + assertThat(scale.getStatus().getSelector()).isEqualTo("app=keycloak,app.kubernetes.io/managed-by=keycloak-operator,app.kubernetes.io/instance=example-kc"); // when scale it to 0 Keycloak scaled = crSelector.scale(0); @@ -139,16 +197,10 @@ public class ClusteringTest extends BaseOperatorTest { // given Log.info("Setup"); var kc = K8sUtils.getDefaultKeycloakDeployment(); - var crSelector = k8sclient - .resources(Keycloak.class) - .inNamespace(kc.getMetadata().getNamespace()) - .withName(kc.getMetadata().getName()); + var crSelector = k8sclient.resource(kc); K8sUtils.deployKeycloak(k8sclient, kc, false); var targetInstances = 3; - crSelector.accept(keycloak -> { - keycloak.getMetadata().setResourceVersion(null); - keycloak.getSpec().setInstances(targetInstances); - }); + crSelector.scale(targetInstances); K8sUtils.set(k8sclient, getClass().getResourceAsStream("/token-test-realm.yaml")); var realmImportSelector = k8sclient.resources(KeycloakRealmImport.class).inNamespace(namespace).withName("example-token-test-kc"); 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 46f266a71c..b4407905ec 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 @@ -23,9 +23,12 @@ import io.fabric8.kubernetes.api.model.LocalObjectReferenceBuilder; import io.fabric8.kubernetes.api.model.Secret; import io.fabric8.kubernetes.api.model.SecretBuilder; import io.fabric8.kubernetes.api.model.SecretKeySelectorBuilder; +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.quarkus.logging.Log; import io.quarkus.test.junit.QuarkusTest; + import org.awaitility.Awaitility; import org.junit.jupiter.api.Assumptions; import org.junit.jupiter.api.Test; @@ -204,7 +207,21 @@ public class KeycloakDeploymentTest extends BaseOperatorTest { try { var kc = getDefaultKeycloakDeployment(); var deploymentName = kc.getMetadata().getName(); - deployKeycloak(k8sclient, kc, true); + + // create a dummy StatefulSet representing the pre-multiinstance state that we'll be forced to delete + StatefulSet statefulSet = new StatefulSetBuilder().withMetadata(kc.getMetadata()).editMetadata() + .addToLabels(Constants.DEFAULT_LABELS).endMetadata().withNewSpec().withNewSelector() + .withMatchLabels(Constants.DEFAULT_LABELS).endSelector().withServiceName("foo").withReplicas(0) + .withNewTemplate().withNewMetadata().withLabels(Constants.DEFAULT_LABELS).endMetadata() + .withNewSpec().addNewContainer().withName("pause").withImage("registry.k8s.io/pause:3.1") + .endContainer().endSpec().endTemplate().endSpec().build(); + k8sclient.resource(statefulSet).create(); + + // start will not be successful because the statefulSet is in the way + deployKeycloak(k8sclient, kc, false); + // once the statefulset is owned by the keycloak it will be picked up by the informer + k8sclient.resource(statefulSet).accept(s -> s.addOwnerReference(k8sclient.resource(kc).get())); + waitForKeycloakToBeReady(k8sclient, kc); Log.info("Trying to delete deployment"); assertThat(k8sclient.apps().statefulSets().withName(deploymentName).delete()).isNotNull(); diff --git a/operator/src/test/java/org/keycloak/operator/testsuite/unit/PodTemplateTest.java b/operator/src/test/java/org/keycloak/operator/testsuite/unit/PodTemplateTest.java index b7adaf8568..1de1f3a73c 100644 --- a/operator/src/test/java/org/keycloak/operator/testsuite/unit/PodTemplateTest.java +++ b/operator/src/test/java/org/keycloak/operator/testsuite/unit/PodTemplateTest.java @@ -28,7 +28,8 @@ import io.quarkus.test.junit.QuarkusTest; import org.junit.jupiter.api.Test; import org.keycloak.operator.Config; import org.keycloak.operator.controllers.KeycloakDeployment; -import org.keycloak.operator.crds.v2alpha1.deployment.Keycloak; +import org.keycloak.operator.controllers.OperatorManagedResource; +import org.keycloak.operator.crds.v2alpha1.deployment.KeycloakBuilder; import org.keycloak.operator.crds.v2alpha1.deployment.KeycloakSpecBuilder; import org.keycloak.operator.crds.v2alpha1.deployment.ValueOrSecret; import org.keycloak.operator.crds.v2alpha1.deployment.spec.HostnameSpecBuilder; @@ -67,7 +68,10 @@ public class PodTemplateTest { }; } }; - var kc = new Keycloak(); + var kc = new KeycloakBuilder().withNewMetadata().withName("instance").endMetadata().build(); + existingDeployment = new StatefulSetBuilder(existingDeployment).editOrNewSpec().editOrNewSelector() + .addToMatchLabels(OperatorManagedResource.updateWithInstanceLabels(null, kc.getMetadata().getName())) + .endSelector().endSpec().build(); var httpSpec = new HttpSpecBuilder().withTlsSecret("example-tls-secret").build(); var hostnameSpec = new HostnameSpecBuilder().withHostname("example.com").build(); @@ -84,6 +88,7 @@ public class PodTemplateTest { kc.setSpec(keycloakSpecBuilder.build()); var deployment = new KeycloakDeployment(null, config, kc, existingDeployment, "dummy-admin"); + return (StatefulSet) deployment.getReconciledResource().get(); } diff --git a/operator/src/test/java/org/keycloak/operator/testsuite/utils/K8sUtils.java b/operator/src/test/java/org/keycloak/operator/testsuite/utils/K8sUtils.java index 658afefeb7..7d9fa36db9 100644 --- a/operator/src/test/java/org/keycloak/operator/testsuite/utils/K8sUtils.java +++ b/operator/src/test/java/org/keycloak/operator/testsuite/utils/K8sUtils.java @@ -101,11 +101,7 @@ public final class K8sUtils { .timeout(5, TimeUnit.MINUTES) .ignoreExceptions() .untilAsserted(() -> { - var currentKc = client - .resources(Keycloak.class) - .inNamespace(kc.getMetadata().getNamespace()) - .withName(kc.getMetadata().getName()) - .get(); + var currentKc = client.resource(kc).get(); CRAssert.assertKeycloakStatusCondition(currentKc, KeycloakStatusCondition.READY, true); CRAssert.assertKeycloakStatusCondition(currentKc, KeycloakStatusCondition.HAS_ERRORS, false);