fix: detecting when we can set the serviceName (#40894)

closes: #40890

Signed-off-by: Steve Hawkins <shawkins@redhat.com>
This commit is contained in:
Steven Hawkins 2025-07-04 03:03:42 -04:00 committed by GitHub
parent 81a7f38a76
commit 919838089f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 45 additions and 4 deletions

View File

@ -151,14 +151,18 @@ public class KeycloakDeploymentDependentResource extends CRUDKubernetesDependent
var existingDeployment = ContextUtils.getCurrentStatefulSet(context).orElse(null);
String serviceName = KeycloakDiscoveryServiceDependentResource.getName(primary);
if (existingDeployment != null) {
// copy the existing annotations to keep the status consistent
CRDUtils.findUpdateReason(existingDeployment).ifPresent(r -> baseDeployment.getMetadata().getAnnotations()
.put(Constants.KEYCLOAK_UPDATE_REASON_ANNOTATION, r));
CRDUtils.fetchIsRecreateUpdate(existingDeployment).ifPresent(b -> baseDeployment.getMetadata()
.getAnnotations().put(Constants.KEYCLOAK_RECREATE_UPDATE_ANNOTATION, b.toString()));
serviceName = existingDeployment.getSpec().getServiceName();
}
baseDeployment.getSpec().setServiceName(serviceName);
var updateType = ContextUtils.getUpdateType(context);
if (existingDeployment == null || updateType.isEmpty()) {
@ -287,7 +291,6 @@ public class KeycloakDeploymentDependentResource extends CRUDKubernetesDependent
.editOrNewSpec().withImagePullSecrets(keycloakCR.getSpec().getImagePullSecrets()).endSpec()
.endTemplate()
.withReplicas(keycloakCR.getSpec().getInstances())
.withServiceName(KeycloakDiscoveryServiceDependentResource.getName(keycloakCR))
.endSpec();
var specBuilder = baseDeploymentBuilder.editSpec().editTemplate().editOrNewSpec();

View File

@ -47,7 +47,6 @@ import org.keycloak.operator.crds.v2alpha1.deployment.KeycloakStatusCondition;
import org.keycloak.operator.crds.v2alpha1.deployment.ValueOrSecret;
import org.keycloak.operator.crds.v2alpha1.deployment.spec.BootstrapAdminSpec;
import org.keycloak.operator.crds.v2alpha1.deployment.spec.HostnameSpecBuilder;
import org.keycloak.operator.crds.v2alpha1.deployment.spec.ProbeSpec;
import org.keycloak.operator.testsuite.apiserver.DisabledIfApiServerTest;
import org.keycloak.operator.testsuite.unit.WatchedResourcesTest;
import org.keycloak.operator.testsuite.utils.CRAssert;
@ -213,6 +212,7 @@ public class KeycloakDeploymentTest extends BaseOperatorTest {
@Test
public void testDeploymentDurability() {
var kc = getTestKeycloakDeployment(true);
KeycloakDeploymentTest.initCustomBootstrapAdminUser(kc);
var deploymentName = kc.getMetadata().getName();
// create a dummy StatefulSet representing the pre-multiinstance state that we'll be forced to delete
@ -395,14 +395,19 @@ public class KeycloakDeploymentTest extends BaseOperatorTest {
@Test
public void testCustomBootstrapAdminUser() {
var kc = getTestKeycloakDeployment(true);
String secretName = initCustomBootstrapAdminUser(kc);
assertInitialAdminUser(secretName, kc, true);
}
static String initCustomBootstrapAdminUser(Keycloak kc) {
String secretName = "my-secret";
// fluents don't seem to work here because of the inner classes
kc.getSpec().setBootstrapAdminSpec(new BootstrapAdminSpec());
kc.getSpec().getBootstrapAdminSpec().setUser(new BootstrapAdminSpec.User());
kc.getSpec().getBootstrapAdminSpec().getUser().setSecret(secretName);
k8sclient.resource(new SecretBuilder().withNewMetadata().withName(secretName).endMetadata()
.addToStringData("username", "user").addToStringData("password", "pass20rd").build()).create();
assertInitialAdminUser(secretName, kc, true);
.addToStringData("username", "user").addToStringData("password", "pass20rd").build()).serverSideApply();
return secretName;
}
// Reference curl command:

View File

@ -22,6 +22,8 @@ import io.fabric8.kubernetes.api.model.LocalObjectReferenceBuilder;
import io.fabric8.kubernetes.api.model.NamespaceBuilder;
import io.fabric8.kubernetes.api.model.PodTemplateSpecBuilder;
import io.fabric8.kubernetes.api.model.Secret;
import io.fabric8.kubernetes.api.model.apps.StatefulSet;
import io.fabric8.kubernetes.api.model.apps.StatefulSetBuilder;
import io.fabric8.kubernetes.client.dsl.Resource;
import io.fabric8.kubernetes.client.utils.Serialization;
import io.quarkus.logging.Log;
@ -29,16 +31,20 @@ import io.quarkus.test.junit.QuarkusTest;
import org.awaitility.Awaitility;
import org.junit.jupiter.api.Test;
import org.keycloak.operator.Utils;
import org.keycloak.operator.crds.v2alpha1.deployment.Keycloak;
import org.keycloak.operator.testsuite.apiserver.DisabledIfApiServerTest;
import org.keycloak.operator.testsuite.utils.CRAssert;
import org.keycloak.operator.testsuite.utils.K8sUtils;
import java.util.Collections;
import java.util.concurrent.TimeUnit;
import static java.util.concurrent.TimeUnit.MINUTES;
import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.keycloak.operator.crds.v2alpha1.deployment.KeycloakStatusCondition.HAS_ERRORS;
import static org.keycloak.operator.testsuite.utils.K8sUtils.deployKeycloak;
import static org.keycloak.operator.testsuite.utils.K8sUtils.getResourceFromFile;
@QuarkusTest
@ -229,4 +235,31 @@ public class PodTemplateTest extends BaseOperatorTest {
});
}
@Test
public void testDeploymentUpgrade() {
var kc = getTestKeycloakDeployment(true);
kc.getSpec().setInstances(2);
// all preconditions must be met, otherwise the operator sdk will remove the existing statefulset
KeycloakDeploymentTest.initCustomBootstrapAdminUser(kc);
// create a dummy StatefulSet representing the 26.0 state that we'll be forced to delete
StatefulSet statefulSet = new StatefulSetBuilder().withMetadata(kc.getMetadata()).editMetadata()
.addToLabels(Utils.allInstanceLabels(kc)).endMetadata().withNewSpec().withNewSelector()
.withMatchLabels(Utils.allInstanceLabels(kc)).endSelector().withReplicas(0)
.withNewTemplate().withNewMetadata().withLabels(Utils.allInstanceLabels(kc)).endMetadata()
.withNewSpec().addNewContainer().withName("pause").withImage("registry.k8s.io/pause:3.1")
.endContainer().endSpec().endTemplate().endSpec().build();
var ss = 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()));
Awaitility.await().atMost(1, TimeUnit.MINUTES).until(() -> k8sclient.resource(statefulSet).get().getSpec().getReplicas() == 2);
// we don't expect a recreate - that would indicate the operator sdk saw a precondition failing
assertEquals(ss.getMetadata().getUid(), k8sclient.resource(statefulSet).get().getMetadata().getUid());
}
}