From 764ca50fc4a32bd3942ee3e675a1d41f3cb0ed86 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?V=C3=A1clav=20Muzik=C3=A1=C5=99?= Date: Mon, 17 Feb 2025 16:30:05 +0100 Subject: [PATCH] Upgrade to Quarkus 3.18.2 (#37300) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Upgrade to Quarkus 3.18.2 Closes #37056 Signed-off-by: Václav Muzikář * Update docs/documentation/upgrading/topics/changes/changes-26_2_0.adoc Co-authored-by: Alexander Schwartz Signed-off-by: Václav Muzikář --------- Signed-off-by: Václav Muzikář Signed-off-by: Václav Muzikář Co-authored-by: Alexander Schwartz --- .../tests/src/test/resources/ignored-links | 3 +- .../topics/changes/changes-26_2_0.adoc | 15 ++++ .../upgrading/topics/changes/changes.adoc | 4 + .../org/keycloak/operator/ContextUtils.java | 64 ++++++++++---- .../java/org/keycloak/operator/Utils.java | 7 -- .../KeycloakAdminSecretDependentResource.java | 23 +++-- .../controllers/KeycloakController.java | 84 ++++++++----------- .../KeycloakDeploymentDependentResource.java | 53 +++++++----- ...loakDiscoveryServiceDependentResource.java | 15 +--- .../KeycloakIngressDependentResource.java | 5 +- ...eycloakNetworkPolicyDependentResource.java | 5 +- .../KeycloakRealmImportController.java | 48 ++++------- ...ycloakRealmImportJobDependentResource.java | 21 ++--- ...oakRealmImportSecretDependentResource.java | 7 +- .../KeycloakServiceDependentResource.java | 13 +-- .../KeycloakUpdateJobDependentResource.java | 19 +++-- .../v2alpha1/deployment/KeycloakStatus.java | 5 +- .../deployment/KeycloakStatusAggregator.java | 6 +- .../operator/upgrade/UpgradeLogicFactory.java | 12 +-- .../upgrade/impl/AutoUpgradeLogic.java | 5 +- .../upgrade/impl/BaseUpgradeLogic.java | 11 +-- .../impl/ForceRecreateUpgradeLogic.java | 5 +- .../RecreateOnImageChangeUpgradeLogic.java | 7 +- .../integration/BaseOperatorTest.java | 26 ++---- .../unit/KeycloakControllerTest.java | 12 +-- .../testsuite/unit/PodTemplateTest.java | 25 +++--- .../operator/testsuite/utils/CRAssert.java | 1 + pom.xml | 6 +- .../it/cli/dist/ProxyHostnameV2DistTest.java | 8 +- .../keycloak/testframework/LogHandler.java | 10 ++- .../federation/UserPropertyFileStorage.java | 3 +- 31 files changed, 273 insertions(+), 255 deletions(-) create mode 100644 docs/documentation/upgrading/topics/changes/changes-26_2_0.adoc diff --git a/docs/documentation/tests/src/test/resources/ignored-links b/docs/documentation/tests/src/test/resources/ignored-links index 48f76390110..ce82ee3d13b 100644 --- a/docs/documentation/tests/src/test/resources/ignored-links +++ b/docs/documentation/tests/src/test/resources/ignored-links @@ -42,4 +42,5 @@ https://saml.xml.org* # To be removed once KC 26.1 has been released https://www.keycloak.org/server/logging#_configuring_levels_as_individual_options https://www.keycloak.org/observability/* -https://www.keycloak.org/high-availability/concepts-memory-and-cpu-sizing#_measuring_the_activity_of_a_running_keycloak_instance \ No newline at end of file +https://www.keycloak.org/high-availability/concepts-memory-and-cpu-sizing#_measuring_the_activity_of_a_running_keycloak_instance +http://example.com:8080 \ No newline at end of file diff --git a/docs/documentation/upgrading/topics/changes/changes-26_2_0.adoc b/docs/documentation/upgrading/topics/changes/changes-26_2_0.adoc new file mode 100644 index 00000000000..98d0f96aad3 --- /dev/null +++ b/docs/documentation/upgrading/topics/changes/changes-26_2_0.adoc @@ -0,0 +1,15 @@ +== Breaking changes + +Breaking changes are identified as requiring changes from existing users to their configurations. + +=== Changes to port behaviour with the `X-Forwarded-Host` header + +The `X-Forwarded-Host` header can optionally also contain the port. In previous versions when the port was omitted from the header, +{project_name} fell back to the actual request port. For example if {project_name} was listening on port 8080 and the request contained +`X-Forwarded-Host: example.com` header, the resolved URL was `+http://example.com:8080+`. + +This is now changed and omitting the port results in removing it from the resolved URL. The resolved URL from the previous example +would now be `http://example.com`. + +To mitigate that, either make your reverse proxy include the port in the `X-Forwarded-Host` header or configure it to set +the `X-Forwarded-Port` header with the desired port. diff --git a/docs/documentation/upgrading/topics/changes/changes.adoc b/docs/documentation/upgrading/topics/changes/changes.adoc index ad9d6b90058..d659bfdc194 100644 --- a/docs/documentation/upgrading/topics/changes/changes.adoc +++ b/docs/documentation/upgrading/topics/changes/changes.adoc @@ -1,6 +1,10 @@ [[migration-changes]] == Migration Changes +=== Migrating to 26.2.0 + +include::changes-26_2_0.adoc[leveloffset=2] + === Migrating to 26.1.3 include::changes-26_1_3.adoc[leveloffset=2] diff --git a/operator/src/main/java/org/keycloak/operator/ContextUtils.java b/operator/src/main/java/org/keycloak/operator/ContextUtils.java index d7bb42f5fcb..de017c19f0d 100644 --- a/operator/src/main/java/org/keycloak/operator/ContextUtils.java +++ b/operator/src/main/java/org/keycloak/operator/ContextUtils.java @@ -17,44 +17,72 @@ package org.keycloak.operator; -import java.util.Optional; - import io.fabric8.kubernetes.api.model.apps.StatefulSet; import io.javaoperatorsdk.operator.api.reconciler.Context; -import org.keycloak.operator.crds.v2alpha1.deployment.Keycloak; +import org.keycloak.operator.controllers.KeycloakDistConfigurator; +import org.keycloak.operator.controllers.WatchedResources; import org.keycloak.operator.upgrade.UpgradeType; +import java.util.Optional; + public final class ContextUtils { // context keys - private static final String OLD_DEPLOYMENT_KEY = "current_stateful_set"; - private static final String NEW_DEPLOYMENT_KEY = "desired_new_stateful_set"; - private static final String UPGRADE_TYPE_KEY = "upgrade_type"; + public static final String OLD_DEPLOYMENT_KEY = "current_stateful_set"; + public static final String NEW_DEPLOYMENT_KEY = "desired_new_stateful_set"; + public static final String UPGRADE_TYPE_KEY = "upgrade_type"; + public static final String OPERATOR_CONFIG_KEY = "operator_config"; + public static final String WATCHED_RESOURCES_KEY = "watched_resources"; + public static final String DIST_CONFIGURATOR_KEY = "dist_configurator"; private ContextUtils() {} - public static void storeCurrentStatefulSet(Context context, StatefulSet statefulSet) { - context.managedDependentResourceContext().put(OLD_DEPLOYMENT_KEY, statefulSet); + public static void storeCurrentStatefulSet(Context context, StatefulSet statefulSet) { + context.managedWorkflowAndDependentResourceContext().put(OLD_DEPLOYMENT_KEY, statefulSet); } - public static StatefulSet getCurrentStatefulSet(Context context) { - return context.managedDependentResourceContext().getMandatory(OLD_DEPLOYMENT_KEY, StatefulSet.class); + public static Optional getCurrentStatefulSet(Context context) { + return context.managedWorkflowAndDependentResourceContext().get(OLD_DEPLOYMENT_KEY, StatefulSet.class); } - public static void storeDesiredStatefulSet(Context context, StatefulSet statefulSet) { - context.managedDependentResourceContext().put(NEW_DEPLOYMENT_KEY, statefulSet); + public static void storeDesiredStatefulSet(Context context, StatefulSet statefulSet) { + context.managedWorkflowAndDependentResourceContext().put(NEW_DEPLOYMENT_KEY, statefulSet); } - public static StatefulSet getDesiredStatefulSet(Context context) { - return context.managedDependentResourceContext().getMandatory(NEW_DEPLOYMENT_KEY, StatefulSet.class); + public static StatefulSet getDesiredStatefulSet(Context context) { + return context.managedWorkflowAndDependentResourceContext().getMandatory(NEW_DEPLOYMENT_KEY, StatefulSet.class); } - public static void storeUpgradeType(Context context, UpgradeType upgradeType) { - context.managedDependentResourceContext().put(UPGRADE_TYPE_KEY, upgradeType); + public static void storeUpgradeType(Context context, UpgradeType upgradeType) { + context.managedWorkflowAndDependentResourceContext().put(UPGRADE_TYPE_KEY, upgradeType); } - public static Optional getUpgradeType(Context context) { - return context.managedDependentResourceContext().get(UPGRADE_TYPE_KEY, UpgradeType.class); + public static Optional getUpgradeType(Context context) { + return context.managedWorkflowAndDependentResourceContext().get(UPGRADE_TYPE_KEY, UpgradeType.class); + } + + public static void storeOperatorConfig(Context context, Config operatorConfig) { + context.managedWorkflowAndDependentResourceContext().put(OPERATOR_CONFIG_KEY, operatorConfig); + } + + public static Config getOperatorConfig(Context context) { + return context.managedWorkflowAndDependentResourceContext().getMandatory(OPERATOR_CONFIG_KEY, Config.class); + } + + public static void storeWatchedResources(Context context, WatchedResources watchedResources) { + context.managedWorkflowAndDependentResourceContext().put(WATCHED_RESOURCES_KEY, watchedResources); + } + + public static WatchedResources getWatchedResources(Context context) { + return context.managedWorkflowAndDependentResourceContext().getMandatory(WATCHED_RESOURCES_KEY, WatchedResources.class); + } + + public static void storeDistConfigurator(Context context, KeycloakDistConfigurator distConfigurator) { + context.managedWorkflowAndDependentResourceContext().put(DIST_CONFIGURATOR_KEY, distConfigurator); + } + + public static KeycloakDistConfigurator getDistConfigurator(Context context) { + return context.managedWorkflowAndDependentResourceContext().getMandatory(DIST_CONFIGURATOR_KEY, KeycloakDistConfigurator.class); } } diff --git a/operator/src/main/java/org/keycloak/operator/Utils.java b/operator/src/main/java/org/keycloak/operator/Utils.java index 7d8de6ef7df..65bb318b833 100644 --- a/operator/src/main/java/org/keycloak/operator/Utils.java +++ b/operator/src/main/java/org/keycloak/operator/Utils.java @@ -80,13 +80,6 @@ public final class Utils { return labels; } - public static Optional getByName(Class clazz, Function nameFunction, Keycloak primary, Context context) { - InformerEventSource ies = (InformerEventSource) context - .eventSourceRetriever().getResourceEventSourceFor(clazz); - - return ies.get(new ResourceID(nameFunction.apply(primary), primary.getMetadata().getNamespace())); - } - /** * Set resources requests/limits for Keycloak container *

diff --git a/operator/src/main/java/org/keycloak/operator/controllers/KeycloakAdminSecretDependentResource.java b/operator/src/main/java/org/keycloak/operator/controllers/KeycloakAdminSecretDependentResource.java index 5d667884de7..c9786420cf0 100644 --- a/operator/src/main/java/org/keycloak/operator/controllers/KeycloakAdminSecretDependentResource.java +++ b/operator/src/main/java/org/keycloak/operator/controllers/KeycloakAdminSecretDependentResource.java @@ -3,8 +3,8 @@ package org.keycloak.operator.controllers; import io.fabric8.kubernetes.api.model.Secret; import io.fabric8.kubernetes.api.model.SecretBuilder; import io.fabric8.kubernetes.client.utils.KubernetesResourceUtil; +import io.javaoperatorsdk.operator.api.config.informer.Informer; import io.javaoperatorsdk.operator.api.reconciler.Context; -import io.javaoperatorsdk.operator.api.reconciler.ResourceDiscriminator; import io.javaoperatorsdk.operator.api.reconciler.dependent.DependentResource; import io.javaoperatorsdk.operator.api.reconciler.dependent.GarbageCollected; import io.javaoperatorsdk.operator.processing.dependent.Creator; @@ -20,22 +20,16 @@ import org.keycloak.operator.crds.v2alpha1.deployment.spec.BootstrapAdminSpec; import java.util.Optional; import java.util.UUID; -@KubernetesDependent(labelSelector = Constants.DEFAULT_LABELS_AS_STRING, resourceDiscriminator = KeycloakAdminSecretDependentResource.NameResourceDiscriminator.class) +@KubernetesDependent( + informer = @Informer(labelSelector = Constants.DEFAULT_LABELS_AS_STRING) +) public class KeycloakAdminSecretDependentResource extends KubernetesDependentResource implements Creator, GarbageCollected { - + public static class EnabledCondition implements Condition { @Override public boolean isMet(DependentResource dependentResource, Keycloak primary, Context context) { - return Optional.ofNullable(primary.getSpec().getBootstrapAdminSpec()).map(BootstrapAdminSpec::getUser) - .map(BootstrapAdminSpec.User::getSecret).filter(s -> !s.equals(KeycloakAdminSecretDependentResource.getName(primary))).isEmpty(); - } - } - - public static class NameResourceDiscriminator implements ResourceDiscriminator { - @Override - public Optional distinguish(Class resource, Keycloak primary, Context context) { - return Utils.getByName(Secret.class, KeycloakAdminSecretDependentResource::getName, primary, context); + return !hasCustomAdminSecret(primary); } } @@ -62,4 +56,9 @@ public class KeycloakAdminSecretDependentResource extends KubernetesDependentRes return KubernetesResourceUtil.sanitizeName(keycloak.getMetadata().getName() + "-initial-admin"); } + public static boolean hasCustomAdminSecret(Keycloak keycloak) { + return Optional.ofNullable(keycloak.getSpec().getBootstrapAdminSpec()).map(BootstrapAdminSpec::getUser) + .map(BootstrapAdminSpec.User::getSecret).filter(s -> !s.equals(KeycloakAdminSecretDependentResource.getName(keycloak))).isPresent(); + } + } 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 9333f8d7fd8..e27d9f142a3 100644 --- a/operator/src/main/java/org/keycloak/operator/controllers/KeycloakController.java +++ b/operator/src/main/java/org/keycloak/operator/controllers/KeycloakController.java @@ -21,55 +21,50 @@ import io.fabric8.kubernetes.api.model.ContainerStateWaiting; import io.fabric8.kubernetes.api.model.ContainerStatus; import io.fabric8.kubernetes.api.model.PodSpec; import io.fabric8.kubernetes.api.model.PodStatus; -import io.fabric8.kubernetes.api.model.Service; import io.fabric8.kubernetes.api.model.apps.StatefulSet; import io.fabric8.kubernetes.client.readiness.Readiness; import io.fabric8.kubernetes.client.utils.KubernetesResourceUtil; import io.fabric8.kubernetes.client.utils.Serialization; -import io.javaoperatorsdk.operator.api.config.informer.InformerConfiguration; import io.javaoperatorsdk.operator.api.reconciler.Context; -import io.javaoperatorsdk.operator.api.reconciler.ControllerConfiguration; -import io.javaoperatorsdk.operator.api.reconciler.ErrorStatusHandler; import io.javaoperatorsdk.operator.api.reconciler.ErrorStatusUpdateControl; import io.javaoperatorsdk.operator.api.reconciler.EventSourceContext; -import io.javaoperatorsdk.operator.api.reconciler.EventSourceInitializer; +import io.javaoperatorsdk.operator.api.reconciler.EventSourceUtils; import io.javaoperatorsdk.operator.api.reconciler.Reconciler; import io.javaoperatorsdk.operator.api.reconciler.UpdateControl; +import io.javaoperatorsdk.operator.api.reconciler.Workflow; import io.javaoperatorsdk.operator.api.reconciler.dependent.Dependent; import io.javaoperatorsdk.operator.processing.event.source.EventSource; -import io.javaoperatorsdk.operator.processing.event.source.informer.InformerEventSource; -import io.javaoperatorsdk.operator.processing.event.source.informer.Mappers; import io.quarkus.logging.Log; - +import jakarta.inject.Inject; import org.keycloak.common.util.CollectionUtil; import org.keycloak.operator.Config; import org.keycloak.operator.Constants; +import org.keycloak.operator.ContextUtils; import org.keycloak.operator.Utils; import org.keycloak.operator.crds.v2alpha1.deployment.Keycloak; +import org.keycloak.operator.crds.v2alpha1.deployment.KeycloakBuilder; import org.keycloak.operator.crds.v2alpha1.deployment.KeycloakStatus; import org.keycloak.operator.crds.v2alpha1.deployment.KeycloakStatusAggregator; import org.keycloak.operator.crds.v2alpha1.deployment.spec.HostnameSpec; import org.keycloak.operator.crds.v2alpha1.deployment.spec.HostnameSpecBuilder; +import org.keycloak.operator.upgrade.UpgradeLogicFactory; import java.util.Comparator; -import java.util.HashMap; import java.util.List; -import java.util.Map; import java.util.Optional; import java.util.concurrent.TimeUnit; -import jakarta.inject.Inject; -import org.keycloak.operator.upgrade.UpgradeLogicFactory; - -@ControllerConfiguration( +@Workflow( + explicitInvocation = true, dependents = { + @Dependent(type = KeycloakDeploymentDependentResource.class, reconcilePrecondition = KeycloakDeploymentDependentResource.ReconcilePrecondition.class), @Dependent(type = KeycloakAdminSecretDependentResource.class, reconcilePrecondition = KeycloakAdminSecretDependentResource.EnabledCondition.class), @Dependent(type = KeycloakIngressDependentResource.class, reconcilePrecondition = KeycloakIngressDependentResource.EnabledCondition.class), - @Dependent(type = KeycloakServiceDependentResource.class, useEventSourceWithName = "serviceSource"), - @Dependent(type = KeycloakDiscoveryServiceDependentResource.class, useEventSourceWithName = "serviceSource"), + @Dependent(type = KeycloakServiceDependentResource.class), + @Dependent(type = KeycloakDiscoveryServiceDependentResource.class), @Dependent(type = KeycloakNetworkPolicyDependentResource.class, reconcilePrecondition = KeycloakNetworkPolicyDependentResource.EnabledCondition.class) }) -public class KeycloakController implements Reconciler, EventSourceInitializer, ErrorStatusHandler { +public class KeycloakController implements Reconciler { public static final String OPENSHIFT_DEFAULT = "openshift-default"; @@ -85,32 +80,12 @@ public class KeycloakController implements Reconciler, EventSourceInit @Inject UpgradeLogicFactory upgradeLogicFactory; - volatile KeycloakDeploymentDependentResource deploymentDependentResource; - volatile KeycloakUpdateJobDependentResource updateJobDependentResource; + @Inject + KeycloakUpdateJobDependentResource updateJobDependentResource; @Override - public Map prepareEventSources(EventSourceContext context) { - var namespaces = context.getControllerConfiguration().getNamespaces(); - - InformerConfiguration servicesIC = InformerConfiguration - .from(Service.class) - .withLabelSelector(Constants.DEFAULT_LABELS_AS_STRING) - .withNamespaces(namespaces) - .withSecondaryToPrimaryMapper(Mappers.fromOwnerReference()) - .build(); - - EventSource servicesEvent = new InformerEventSource<>(servicesIC, context); - - Map sources = new HashMap<>(); - sources.put("serviceSource", servicesEvent); - - this.deploymentDependentResource = new KeycloakDeploymentDependentResource(config, watchedResources, distConfigurator); - sources.putAll(EventSourceInitializer.nameEventSourcesFromDependentResource(context, this.deploymentDependentResource)); - - updateJobDependentResource = new KeycloakUpdateJobDependentResource(config); - sources.putAll(EventSourceInitializer.nameEventSourcesFromDependentResource(context, updateJobDependentResource)); - - return sources; + public List> prepareEventSources(EventSourceContext context) { + return EventSourceUtils.dependentEventSources(context, updateJobDependentResource); } @Override @@ -141,10 +116,25 @@ public class KeycloakController implements Reconciler, EventSourceInit } if (modifiedSpec) { - return UpdateControl.updateResource(kc); + // just patch spec using SSA, nothing more + Keycloak patchedKc = new KeycloakBuilder() + .withNewMetadata() + .withName(kc.getMetadata().getName()) + .withNamespace(kc.getMetadata().getNamespace()) + .endMetadata() + .withSpec(kc.getSpec()) + .build(); + return UpdateControl.patchResource(patchedKc); } - var upgradeLogicControl = upgradeLogicFactory.create(kc, context, deploymentDependentResource, updateJobDependentResource) + var existingDeployment = context.getSecondaryResource(StatefulSet.class).orElse(null); + ContextUtils.storeOperatorConfig(context, config); + ContextUtils.storeWatchedResources(context, watchedResources); + ContextUtils.storeDistConfigurator(context, distConfigurator); + ContextUtils.storeCurrentStatefulSet(context, existingDeployment); + ContextUtils.storeDesiredStatefulSet(context, new KeycloakDeploymentDependentResource().desired(kc, context)); + + var upgradeLogicControl = upgradeLogicFactory.create(kc, context) .decideUpgrade(); if (upgradeLogicControl.isPresent()) { Log.debug("--- Reconciliation interrupted due to upgrade logic"); @@ -152,11 +142,11 @@ public class KeycloakController implements Reconciler, EventSourceInit } // after the spec has possibly been updated, reconcile the StatefulSet - this.deploymentDependentResource.reconcile(kc, context); + context.managedWorkflowAndDependentResourceContext().reconcileManagedWorkflow(); var statusAggregator = new KeycloakStatusAggregator(kc.getStatus(), kc.getMetadata().getGeneration()); - updateStatus(kc, context.getSecondaryResource(StatefulSet.class).orElse(null), statusAggregator, context); + updateStatus(kc, existingDeployment, statusAggregator, context); var status = statusAggregator.build(); Log.debug("--- Reconciliation finished successfully"); @@ -167,7 +157,7 @@ public class KeycloakController implements Reconciler, EventSourceInit } else { kc.setStatus(status); - updateControl = UpdateControl.updateStatus(kc); + updateControl = UpdateControl.patchStatus(kc); } var statefulSet = context.getSecondaryResource(StatefulSet.class); @@ -190,7 +180,7 @@ public class KeycloakController implements Reconciler, EventSourceInit kc.setStatus(status); - return ErrorStatusUpdateControl.updateStatus(kc); + return ErrorStatusUpdateControl.patchStatus(kc); } public static Optional generateOpenshiftHostname(Keycloak keycloak, Context context) { diff --git a/operator/src/main/java/org/keycloak/operator/controllers/KeycloakDeploymentDependentResource.java b/operator/src/main/java/org/keycloak/operator/controllers/KeycloakDeploymentDependentResource.java index 097df9f6558..3712924db01 100644 --- a/operator/src/main/java/org/keycloak/operator/controllers/KeycloakDeploymentDependentResource.java +++ b/operator/src/main/java/org/keycloak/operator/controllers/KeycloakDeploymentDependentResource.java @@ -31,11 +31,13 @@ import io.fabric8.kubernetes.api.model.VolumeMountBuilder; import io.fabric8.kubernetes.api.model.apps.StatefulSet; import io.fabric8.kubernetes.api.model.apps.StatefulSetBuilder; import io.fabric8.kubernetes.client.KubernetesClient; +import io.javaoperatorsdk.operator.api.config.informer.Informer; import io.javaoperatorsdk.operator.api.reconciler.Context; +import io.javaoperatorsdk.operator.api.reconciler.dependent.DependentResource; import io.javaoperatorsdk.operator.processing.dependent.kubernetes.CRUDKubernetesDependentResource; -import io.javaoperatorsdk.operator.processing.dependent.kubernetes.KubernetesDependentResourceConfigBuilder; +import io.javaoperatorsdk.operator.processing.dependent.kubernetes.KubernetesDependent; +import io.javaoperatorsdk.operator.processing.dependent.workflow.Condition; import io.quarkus.logging.Log; - import org.keycloak.operator.Config; import org.keycloak.operator.Constants; import org.keycloak.operator.ContextUtils; @@ -72,6 +74,9 @@ import static org.keycloak.operator.controllers.KeycloakDistConfigurator.getKeyc import static org.keycloak.operator.crds.v2alpha1.CRDUtils.isTlsConfigured; import static org.keycloak.operator.crds.v2alpha1.deployment.spec.TracingSpec.convertTracingAttributesToString; +@KubernetesDependent( + informer = @Informer(labelSelector = Constants.DEFAULT_LABELS_AS_STRING) +) public class KeycloakDeploymentDependentResource extends CRUDKubernetesDependentResource { public static final String POD_IP = "POD_IP"; @@ -95,23 +100,23 @@ public class KeycloakDeploymentDependentResource extends CRUDKubernetesDependent public static final String OPTIMIZED_ARG = "--optimized"; - Config operatorConfig; - - WatchedResources watchedResources; - - KeycloakDistConfigurator distConfigurator; - private boolean useServiceCaCrt; - public KeycloakDeploymentDependentResource(Config operatorConfig, WatchedResources watchedResources, KeycloakDistConfigurator distConfigurator) { + // Do not create the deployment before the initial admin secret is created to prevent the deployment from restarting. + // Not using native dependsOn as the initial admin secret may not be created by the operator and might be provided by the user, + // in which case we want to create the deployment immediately. + public static class ReconcilePrecondition implements Condition { + @Override + public boolean isMet(DependentResource dependentResource, Keycloak primary, Context context) { + return KeycloakAdminSecretDependentResource.hasCustomAdminSecret(primary) + || context.getSecondaryResourcesAsStream(Secret.class) + .anyMatch(s -> s.getMetadata().getName().equals(KeycloakAdminSecretDependentResource.getName(primary))); + } + } + + public KeycloakDeploymentDependentResource() { super(StatefulSet.class); - this.operatorConfig = operatorConfig; - this.watchedResources = watchedResources; - this.distConfigurator = distConfigurator; useServiceCaCrt = Files.exists(Path.of(SERVICE_CA_CRT)); - this.configureWith(new KubernetesDependentResourceConfigBuilder() - .withLabelSelector(Constants.DEFAULT_LABELS_AS_STRING) - .build()); } public void setUseServiceCaCrt(boolean useServiceCaCrt) { @@ -120,17 +125,20 @@ public class KeycloakDeploymentDependentResource extends CRUDKubernetesDependent @Override public StatefulSet desired(Keycloak primary, Context context) { - StatefulSet baseDeployment = createBaseDeployment(primary, context); + Config operatorConfig = ContextUtils.getOperatorConfig(context); + WatchedResources watchedResources = ContextUtils.getWatchedResources(context); + + StatefulSet baseDeployment = createBaseDeployment(primary, context, operatorConfig); TreeSet allSecrets = new TreeSet<>(); if (isTlsConfigured(primary)) { configureTLS(primary, baseDeployment, allSecrets); } Container kcContainer = baseDeployment.getSpec().getTemplate().getSpec().getContainers().get(0); addTruststores(primary, baseDeployment, kcContainer, allSecrets); - addEnvVars(baseDeployment, primary, allSecrets); + addEnvVars(baseDeployment, primary, allSecrets, context); addResources(primary.getSpec().getResourceRequirements(), operatorConfig, kcContainer); Optional.ofNullable(primary.getSpec().getCacheSpec()) - .ifPresent(c -> configureCache(baseDeployment, kcContainer, c, context.getClient())); + .ifPresent(c -> configureCache(baseDeployment, kcContainer, c, context.getClient(), watchedResources)); if (!allSecrets.isEmpty()) { watchedResources.annotateDeployment(new ArrayList<>(allSecrets), Secret.class, baseDeployment, context.getClient()); @@ -142,7 +150,7 @@ public class KeycloakDeploymentDependentResource extends CRUDKubernetesDependent return baseDeployment; } - var existingDeployment = ContextUtils.getCurrentStatefulSet(context); + var existingDeployment = ContextUtils.getCurrentStatefulSet(context).orElseThrow(); // version 22 changed the match labels, account for older versions if (!existingDeployment.isMarkedForDeletion() && !hasExpectedMatchLabels(existingDeployment, primary)) { @@ -156,7 +164,7 @@ public class KeycloakDeploymentDependentResource extends CRUDKubernetesDependent }; } - private void configureCache(StatefulSet deployment, Container kcContainer, CacheSpec spec, KubernetesClient client) { + private void configureCache(StatefulSet deployment, Container kcContainer, CacheSpec spec, KubernetesClient client, WatchedResources watchedResources) { Optional.ofNullable(spec.getConfigMapFile()).ifPresent(configFile -> { if (configFile.getName() == null || configFile.getKey() == null) { throw new IllegalStateException("Cache file ConfigMap requires both a name and a key"); @@ -236,7 +244,7 @@ public class KeycloakDeploymentDependentResource extends CRUDKubernetesDependent return Optional.ofNullable(keycloakCR.getSpec()).map(KeycloakSpec::getUnsupported).map(UnsupportedSpec::getPodTemplate); } - private StatefulSet createBaseDeployment(Keycloak keycloakCR, Context context) { + private StatefulSet createBaseDeployment(Keycloak keycloakCR, Context context, Config operatorConfig) { Map labels = Utils.allInstanceLabels(keycloakCR); labels.put("app.kubernetes.io/component", "server"); Map schedulingLabels = new LinkedHashMap<>(labels); @@ -404,7 +412,8 @@ public class KeycloakDeploymentDependentResource extends CRUDKubernetesDependent return JGROUPS_DNS_QUERY_PARAM + KeycloakDiscoveryServiceDependentResource.getName(keycloakCR) +"." + keycloakCR.getMetadata().getNamespace(); } - private void addEnvVars(StatefulSet baseDeployment, Keycloak keycloakCR, TreeSet allSecrets) { + private void addEnvVars(StatefulSet baseDeployment, Keycloak keycloakCR, TreeSet allSecrets, Context context) { + var distConfigurator = ContextUtils.getDistConfigurator(context); var firstClasssEnvVars = distConfigurator.configureDistOptions(keycloakCR); var additionalEnvVars = getDefaultAndAdditionalEnvVars(keycloakCR); diff --git a/operator/src/main/java/org/keycloak/operator/controllers/KeycloakDiscoveryServiceDependentResource.java b/operator/src/main/java/org/keycloak/operator/controllers/KeycloakDiscoveryServiceDependentResource.java index 8ebe25b44d3..66417a5e54a 100644 --- a/operator/src/main/java/org/keycloak/operator/controllers/KeycloakDiscoveryServiceDependentResource.java +++ b/operator/src/main/java/org/keycloak/operator/controllers/KeycloakDiscoveryServiceDependentResource.java @@ -20,8 +20,8 @@ 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.config.informer.Informer; import io.javaoperatorsdk.operator.api.reconciler.Context; -import io.javaoperatorsdk.operator.api.reconciler.ResourceDiscriminator; import io.javaoperatorsdk.operator.processing.dependent.kubernetes.CRUDKubernetesDependentResource; import io.javaoperatorsdk.operator.processing.dependent.kubernetes.KubernetesDependent; @@ -29,18 +29,11 @@ import org.keycloak.operator.Constants; import org.keycloak.operator.Utils; import org.keycloak.operator.crds.v2alpha1.deployment.Keycloak; -import java.util.Optional; - -@KubernetesDependent(labelSelector = Constants.DEFAULT_LABELS_AS_STRING, resourceDiscriminator = KeycloakDiscoveryServiceDependentResource.NameResourceDiscriminator.class) +@KubernetesDependent( + informer = @Informer(labelSelector = Constants.DEFAULT_LABELS_AS_STRING) +) public class KeycloakDiscoveryServiceDependentResource extends CRUDKubernetesDependentResource { - public static class NameResourceDiscriminator implements ResourceDiscriminator { - @Override - public Optional distinguish(Class resource, Keycloak primary, Context context) { - return Utils.getByName(Service.class, KeycloakDiscoveryServiceDependentResource::getName, primary, context); - } - } - public KeycloakDiscoveryServiceDependentResource() { super(Service.class); } diff --git a/operator/src/main/java/org/keycloak/operator/controllers/KeycloakIngressDependentResource.java b/operator/src/main/java/org/keycloak/operator/controllers/KeycloakIngressDependentResource.java index 01d28b4415c..822cbcc7e11 100644 --- a/operator/src/main/java/org/keycloak/operator/controllers/KeycloakIngressDependentResource.java +++ b/operator/src/main/java/org/keycloak/operator/controllers/KeycloakIngressDependentResource.java @@ -19,6 +19,7 @@ package org.keycloak.operator.controllers; import io.fabric8.kubernetes.api.model.networking.v1.Ingress; import io.fabric8.kubernetes.api.model.networking.v1.IngressBuilder; import io.fabric8.kubernetes.client.KubernetesClientException; +import io.javaoperatorsdk.operator.api.config.informer.Informer; import io.javaoperatorsdk.operator.api.reconciler.Context; import io.javaoperatorsdk.operator.api.reconciler.dependent.DependentResource; import io.javaoperatorsdk.operator.processing.dependent.kubernetes.CRUDKubernetesDependentResource; @@ -39,7 +40,9 @@ import java.util.Optional; import static org.keycloak.operator.crds.v2alpha1.CRDUtils.isTlsConfigured; -@KubernetesDependent(labelSelector = Constants.DEFAULT_LABELS_AS_STRING) +@KubernetesDependent( + informer = @Informer(labelSelector = Constants.DEFAULT_LABELS_AS_STRING) +) public class KeycloakIngressDependentResource extends CRUDKubernetesDependentResource { private static final Logger LOG = Logger.getLogger(KeycloakIngressDependentResource.class.getName()); diff --git a/operator/src/main/java/org/keycloak/operator/controllers/KeycloakNetworkPolicyDependentResource.java b/operator/src/main/java/org/keycloak/operator/controllers/KeycloakNetworkPolicyDependentResource.java index f6ba14cb64c..630d875692c 100644 --- a/operator/src/main/java/org/keycloak/operator/controllers/KeycloakNetworkPolicyDependentResource.java +++ b/operator/src/main/java/org/keycloak/operator/controllers/KeycloakNetworkPolicyDependentResource.java @@ -26,6 +26,7 @@ import io.fabric8.kubernetes.api.model.networking.v1.NetworkPolicy; import io.fabric8.kubernetes.api.model.networking.v1.NetworkPolicyBuilder; import io.fabric8.kubernetes.api.model.networking.v1.NetworkPolicyFluent; import io.fabric8.kubernetes.api.model.networking.v1.NetworkPolicyPeer; +import io.javaoperatorsdk.operator.api.config.informer.Informer; import io.javaoperatorsdk.operator.api.reconciler.Context; import io.javaoperatorsdk.operator.api.reconciler.dependent.DependentResource; import io.javaoperatorsdk.operator.processing.dependent.kubernetes.CRUDKubernetesDependentResource; @@ -46,7 +47,9 @@ import static org.keycloak.operator.Constants.KEYCLOAK_JGROUPS_FD_PORT; import static org.keycloak.operator.Constants.KEYCLOAK_JGROUPS_PROTOCOL; import static org.keycloak.operator.Constants.KEYCLOAK_SERVICE_PROTOCOL; -@KubernetesDependent(labelSelector = Constants.DEFAULT_LABELS_AS_STRING) +@KubernetesDependent( + informer = @Informer(labelSelector = Constants.DEFAULT_LABELS_AS_STRING) +) public class KeycloakNetworkPolicyDependentResource extends CRUDKubernetesDependentResource { private static final Logger LOG = Logger.getLogger(KeycloakNetworkPolicyDependentResource.class.getName()); diff --git a/operator/src/main/java/org/keycloak/operator/controllers/KeycloakRealmImportController.java b/operator/src/main/java/org/keycloak/operator/controllers/KeycloakRealmImportController.java index dcbda489fa4..4e137797643 100644 --- a/operator/src/main/java/org/keycloak/operator/controllers/KeycloakRealmImportController.java +++ b/operator/src/main/java/org/keycloak/operator/controllers/KeycloakRealmImportController.java @@ -21,49 +21,34 @@ import io.fabric8.kubernetes.api.model.apps.StatefulSetStatus; import io.fabric8.kubernetes.api.model.batch.v1.Job; import io.fabric8.kubernetes.client.KubernetesClient; import io.javaoperatorsdk.operator.api.reconciler.Context; -import io.javaoperatorsdk.operator.api.reconciler.ControllerConfiguration; -import io.javaoperatorsdk.operator.api.reconciler.ErrorStatusHandler; import io.javaoperatorsdk.operator.api.reconciler.ErrorStatusUpdateControl; -import io.javaoperatorsdk.operator.api.reconciler.EventSourceContext; -import io.javaoperatorsdk.operator.api.reconciler.EventSourceInitializer; import io.javaoperatorsdk.operator.api.reconciler.Reconciler; import io.javaoperatorsdk.operator.api.reconciler.UpdateControl; +import io.javaoperatorsdk.operator.api.reconciler.Workflow; import io.javaoperatorsdk.operator.api.reconciler.dependent.Dependent; -import io.javaoperatorsdk.operator.processing.event.source.EventSource; import io.quarkus.logging.Log; - +import jakarta.inject.Inject; import org.keycloak.operator.Config; +import org.keycloak.operator.ContextUtils; import org.keycloak.operator.crds.v2alpha1.realmimport.KeycloakRealmImport; import org.keycloak.operator.crds.v2alpha1.realmimport.KeycloakRealmImportStatus; import org.keycloak.operator.crds.v2alpha1.realmimport.KeycloakRealmImportStatusBuilder; import org.keycloak.operator.crds.v2alpha1.realmimport.KeycloakRealmImportStatusCondition; -import java.util.Map; import java.util.Optional; import java.util.concurrent.TimeUnit; -import jakarta.inject.Inject; - -@ControllerConfiguration( +@Workflow( +explicitInvocation = true, dependents = { - @Dependent(type = KeycloakRealmImportSecretDependentResource.class) + @Dependent(type = KeycloakRealmImportJobDependentResource.class, dependsOn = KeycloakRealmImportSecretDependentResource.DEPENDENT_NAME), + @Dependent(type = KeycloakRealmImportSecretDependentResource.class, name = KeycloakRealmImportSecretDependentResource.DEPENDENT_NAME) }) -public class KeycloakRealmImportController implements Reconciler, ErrorStatusHandler, EventSourceInitializer { +public class KeycloakRealmImportController implements Reconciler { @Inject Config config; - @Inject - KubernetesClient client; - - volatile KeycloakRealmImportJobDependentResource jobDependentResource; - - @Override - public Map prepareEventSources(EventSourceContext context) { - this.jobDependentResource = new KeycloakRealmImportJobDependentResource(config); - return EventSourceInitializer.nameEventSourcesFromDependentResource(context, jobDependentResource); - } - @Override public UpdateControl reconcile(KeycloakRealmImport realm, Context context) { String realmName = realm.getMetadata().getName(); @@ -78,13 +63,14 @@ public class KeycloakRealmImportController implements Reconciler 0) { - jobDependentResource.reconcile(realm, context); + context.managedWorkflowAndDependentResourceContext().reconcileManagedWorkflow(); } } - updateStatus(statusBuilder, realm, existingJob, existingDeployment); + updateStatus(statusBuilder, realm, existingJob, existingDeployment, context.getClient()); var status = statusBuilder.build(); @@ -95,7 +81,7 @@ public class KeycloakRealmImportController implements Reconciler 0) { if (!lastReportedStatus.isDone()) { Log.info("Job finished performing a rolling restart of the deployment"); - rollingRestart(realmCR); // could be based upon a hash annotation on the deployment instead + rollingRestart(realmCR, client); // could be based upon a hash annotation on the deployment instead } status.addDone(); } else if (oldStatus.getFailed() != null && oldStatus.getFailed() > 0) { @@ -161,7 +147,7 @@ public class KeycloakRealmImportController implements Reconciler implements Creator, GarbageCollected { - private final Config config; - - KeycloakRealmImportJobDependentResource(Config config) { + KeycloakRealmImportJobDependentResource() { super(Job.class); - this.config = config; - this.configureWith(new KubernetesDependentResourceConfigBuilder() - .withLabelSelector(Constants.DEFAULT_LABELS_AS_STRING) - .build()); } @Override protected Job desired(KeycloakRealmImport primary, Context context) { - StatefulSet existingDeployment = context.managedDependentResourceContext().get(StatefulSet.class, StatefulSet.class).orElseThrow(); + Config config = ContextUtils.getOperatorConfig(context); + StatefulSet existingDeployment = ContextUtils.getCurrentStatefulSet(context).orElseThrow(); Map placeholders = primary.getSpec().getPlaceholders(); boolean replacePlaceholders = (placeholders != null && !placeholders.isEmpty()); @@ -71,7 +66,7 @@ public class KeycloakRealmImportJobDependentResource extends KubernetesDependent String secretName = KeycloakRealmImportSecretDependentResource.getSecretName(primary); String volumeName = KubernetesResourceUtil.sanitizeName(secretName + "-volume"); - buildKeycloakJobContainer(keycloakPodTemplate.getSpec().getContainers().get(0), primary, volumeName, replacePlaceholders); + buildKeycloakJobContainer(keycloakPodTemplate.getSpec().getContainers().get(0), primary, volumeName, config); keycloakPodTemplate.getSpec().getVolumes().add(buildSecretVolume(volumeName, secretName)); var labels = keycloakPodTemplate.getMetadata().getLabels(); @@ -138,7 +133,7 @@ public class KeycloakRealmImportJobDependentResource extends KubernetesDependent .build(); } - private void buildKeycloakJobContainer(Container keycloakContainer, KeycloakRealmImport keycloakRealmImport, String volumeName, boolean replacePlaceholders) { + private void buildKeycloakJobContainer(Container keycloakContainer, KeycloakRealmImport keycloakRealmImport, String volumeName, Config config) { var importMntPath = "/mnt/realm-import/"; var command = List.of("/bin/bash"); diff --git a/operator/src/main/java/org/keycloak/operator/controllers/KeycloakRealmImportSecretDependentResource.java b/operator/src/main/java/org/keycloak/operator/controllers/KeycloakRealmImportSecretDependentResource.java index 16a0d40e183..0db50623d94 100644 --- a/operator/src/main/java/org/keycloak/operator/controllers/KeycloakRealmImportSecretDependentResource.java +++ b/operator/src/main/java/org/keycloak/operator/controllers/KeycloakRealmImportSecretDependentResource.java @@ -3,6 +3,7 @@ package org.keycloak.operator.controllers; import io.fabric8.kubernetes.api.model.Secret; import io.fabric8.kubernetes.api.model.SecretBuilder; import io.fabric8.kubernetes.client.utils.KubernetesResourceUtil; +import io.javaoperatorsdk.operator.api.config.informer.Informer; import io.javaoperatorsdk.operator.api.reconciler.Context; import io.javaoperatorsdk.operator.processing.dependent.kubernetes.CRUDKubernetesDependentResource; import io.javaoperatorsdk.operator.processing.dependent.kubernetes.KubernetesDependent; @@ -11,9 +12,13 @@ import org.keycloak.operator.Constants; import org.keycloak.operator.Utils; import org.keycloak.operator.crds.v2alpha1.realmimport.KeycloakRealmImport; -@KubernetesDependent(labelSelector = Constants.DEFAULT_LABELS_AS_STRING) +@KubernetesDependent( + informer = @Informer(labelSelector = Constants.DEFAULT_LABELS_AS_STRING) +) public class KeycloakRealmImportSecretDependentResource extends CRUDKubernetesDependentResource { + public static final String DEPENDENT_NAME = "realm-import-secret"; + public KeycloakRealmImportSecretDependentResource() { super(Secret.class); } diff --git a/operator/src/main/java/org/keycloak/operator/controllers/KeycloakServiceDependentResource.java b/operator/src/main/java/org/keycloak/operator/controllers/KeycloakServiceDependentResource.java index 5aefe119f37..7ed3549d2a3 100644 --- a/operator/src/main/java/org/keycloak/operator/controllers/KeycloakServiceDependentResource.java +++ b/operator/src/main/java/org/keycloak/operator/controllers/KeycloakServiceDependentResource.java @@ -23,8 +23,8 @@ 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.config.informer.Informer; import io.javaoperatorsdk.operator.api.reconciler.Context; -import io.javaoperatorsdk.operator.api.reconciler.ResourceDiscriminator; import io.javaoperatorsdk.operator.processing.dependent.kubernetes.CRUDKubernetesDependentResource; import io.javaoperatorsdk.operator.processing.dependent.kubernetes.KubernetesDependent; import org.keycloak.operator.Constants; @@ -35,16 +35,11 @@ import org.keycloak.operator.crds.v2alpha1.deployment.spec.HttpSpec; import static org.keycloak.operator.crds.v2alpha1.CRDUtils.isTlsConfigured; -@KubernetesDependent(labelSelector = Constants.DEFAULT_LABELS_AS_STRING, resourceDiscriminator = KeycloakServiceDependentResource.NameResourceDiscriminator.class) +@KubernetesDependent( + informer = @Informer(labelSelector = Constants.DEFAULT_LABELS_AS_STRING) +) public class KeycloakServiceDependentResource extends CRUDKubernetesDependentResource { - public static class NameResourceDiscriminator implements ResourceDiscriminator { - @Override - public Optional distinguish(Class resource, Keycloak primary, Context context) { - return Utils.getByName(Service.class, KeycloakServiceDependentResource::getServiceName, primary, context); - } - } - public KeycloakServiceDependentResource() { super(Service.class); } diff --git a/operator/src/main/java/org/keycloak/operator/controllers/KeycloakUpdateJobDependentResource.java b/operator/src/main/java/org/keycloak/operator/controllers/KeycloakUpdateJobDependentResource.java index 41421c18b48..fbcc448c682 100644 --- a/operator/src/main/java/org/keycloak/operator/controllers/KeycloakUpdateJobDependentResource.java +++ b/operator/src/main/java/org/keycloak/operator/controllers/KeycloakUpdateJobDependentResource.java @@ -36,16 +36,18 @@ import io.fabric8.kubernetes.api.model.Volume; import io.fabric8.kubernetes.api.model.batch.v1.Job; import io.fabric8.kubernetes.api.model.batch.v1.JobBuilder; import io.fabric8.kubernetes.api.model.batch.v1.JobSpecFluent; +import io.javaoperatorsdk.operator.api.config.informer.InformerConfiguration; import io.javaoperatorsdk.operator.api.reconciler.Context; import io.javaoperatorsdk.operator.processing.dependent.kubernetes.CRUDKubernetesDependentResource; import io.javaoperatorsdk.operator.processing.dependent.kubernetes.KubernetesDependentResourceConfigBuilder; -import org.keycloak.operator.Config; +import jakarta.enterprise.context.ApplicationScoped; import org.keycloak.operator.Constants; import org.keycloak.operator.ContextUtils; import org.keycloak.operator.Utils; import org.keycloak.operator.crds.v2alpha1.CRDUtils; import org.keycloak.operator.crds.v2alpha1.deployment.Keycloak; +@ApplicationScoped public class KeycloakUpdateJobDependentResource extends CRUDKubernetesDependentResource { // shared volume configuration @@ -71,13 +73,12 @@ public class KeycloakUpdateJobDependentResource extends CRUDKubernetesDependentR // container args to replace private static final Set START_ARGS = Set.of("start", "start-dev"); - private final Config operatorConfig; - - public KeycloakUpdateJobDependentResource(Config config) { + public KeycloakUpdateJobDependentResource() { super(Job.class); - operatorConfig = config; this.configureWith(new KubernetesDependentResourceConfigBuilder() - .withLabelSelector(Constants.DEFAULT_LABELS_AS_STRING) + .withKubernetesDependentInformerConfig(InformerConfiguration.builder(resourceType()) + .withLabelSelector(Constants.DEFAULT_LABELS_AS_STRING) + .build()) .build()); } @@ -144,12 +145,12 @@ public class KeycloakUpdateJobDependentResource extends CRUDKubernetesDependentR // For test KeycloakDeploymentTest#testDeploymentDurability // it uses a pause image, which never ends. // After this seconds, the job is terminated allowing the test to complete. - builder.withActiveDeadlineSeconds(operatorConfig.keycloak().updatePodDeadlineSeconds()); + builder.withActiveDeadlineSeconds(ContextUtils.getOperatorConfig(context).keycloak().updatePodDeadlineSeconds()); return builder.build(); } private static void addInitContainer(PodSpecBuilder builder, Context context, Collection availableVolumes, Collection requiredVolumes) { - var existing = CRDUtils.firstContainerOf(ContextUtils.getCurrentStatefulSet(context)).orElseThrow(); + var existing = CRDUtils.firstContainerOf(ContextUtils.getCurrentStatefulSet(context).orElseThrow()).orElseThrow(); var containerBuilder = builder.addNewInitContainerLike(existing); configureContainer(containerBuilder, INIT_CONTAINER_NAME, INIT_CONTAINER_ARGS, availableVolumes, requiredVolumes); containerBuilder.endInitContainer(); @@ -196,7 +197,7 @@ public class KeycloakUpdateJobDependentResource extends CRUDKubernetesDependentR private Map getAllVolumes(Context context) { Map allVolumes = new HashMap<>(); Consumer volumeConsumer = volume -> allVolumes.put(volume.getName(), volume); - CRDUtils.volumesFromStatefulSet(ContextUtils.getCurrentStatefulSet(context)).forEach(volumeConsumer); + CRDUtils.volumesFromStatefulSet(ContextUtils.getCurrentStatefulSet(context).orElseThrow()).forEach(volumeConsumer); CRDUtils.volumesFromStatefulSet(ContextUtils.getDesiredStatefulSet(context)).forEach(volumeConsumer); return allVolumes; } diff --git a/operator/src/main/java/org/keycloak/operator/crds/v2alpha1/deployment/KeycloakStatus.java b/operator/src/main/java/org/keycloak/operator/crds/v2alpha1/deployment/KeycloakStatus.java index aa9c9604406..5eb24f6b9fa 100644 --- a/operator/src/main/java/org/keycloak/operator/crds/v2alpha1/deployment/KeycloakStatus.java +++ b/operator/src/main/java/org/keycloak/operator/crds/v2alpha1/deployment/KeycloakStatus.java @@ -24,14 +24,13 @@ import com.fasterxml.jackson.annotation.JsonIgnore; import io.fabric8.kubernetes.model.annotation.LabelSelector; import io.fabric8.kubernetes.model.annotation.StatusReplicas; -import io.javaoperatorsdk.operator.api.ObservedGenerationAware; import io.sundr.builder.annotations.Buildable; /** * @author Vaclav Muzikar */ @Buildable(editableEnabled = false, builderPackage = "io.fabric8.kubernetes.api.builder", lazyCollectionInitEnabled = false) -public class KeycloakStatus implements ObservedGenerationAware { +public class KeycloakStatus { @LabelSelector private String selector; @@ -77,12 +76,10 @@ public class KeycloakStatus implements ObservedGenerationAware { return findCondition(KeycloakStatusCondition.READY).map(KeycloakStatusCondition::getStatus).orElse(false); } - @Override public Long getObservedGeneration() { return observedGeneration; } - @Override public void setObservedGeneration(Long generation) { this.observedGeneration = generation; } diff --git a/operator/src/main/java/org/keycloak/operator/crds/v2alpha1/deployment/KeycloakStatusAggregator.java b/operator/src/main/java/org/keycloak/operator/crds/v2alpha1/deployment/KeycloakStatusAggregator.java index 892327f16b6..83a5d8533d5 100644 --- a/operator/src/main/java/org/keycloak/operator/crds/v2alpha1/deployment/KeycloakStatusAggregator.java +++ b/operator/src/main/java/org/keycloak/operator/crds/v2alpha1/deployment/KeycloakStatusAggregator.java @@ -64,7 +64,6 @@ public class KeycloakStatusAggregator { existingConditions = Map.of(); } - // we're not setting this on the statusBuilder as we're letting the sdk manage that observedGeneration = generation; readyCondition.setType(KeycloakStatusCondition.READY); @@ -144,7 +143,10 @@ public class KeycloakStatusAggregator { updateConditionFromExisting(hasErrorsCondition, existingConditions, now); updateConditionFromExisting(rollingUpdate, existingConditions, now); - return statusBuilder.withConditions(List.of(readyCondition, hasErrorsCondition, rollingUpdate)).build(); + return statusBuilder + .withObservedGeneration(observedGeneration) + .withConditions(List.of(readyCondition, hasErrorsCondition, rollingUpdate)) + .build(); } static void updateConditionFromExisting(KeycloakStatusCondition condition, Map existingConditions, String now) { diff --git a/operator/src/main/java/org/keycloak/operator/upgrade/UpgradeLogicFactory.java b/operator/src/main/java/org/keycloak/operator/upgrade/UpgradeLogicFactory.java index 98f15415adc..7f0cdf1b347 100644 --- a/operator/src/main/java/org/keycloak/operator/upgrade/UpgradeLogicFactory.java +++ b/operator/src/main/java/org/keycloak/operator/upgrade/UpgradeLogicFactory.java @@ -19,7 +19,7 @@ package org.keycloak.operator.upgrade; import io.javaoperatorsdk.operator.api.reconciler.Context; import jakarta.enterprise.context.ApplicationScoped; -import org.keycloak.operator.controllers.KeycloakDeploymentDependentResource; +import jakarta.inject.Inject; import org.keycloak.operator.controllers.KeycloakUpdateJobDependentResource; import org.keycloak.operator.crds.v2alpha1.deployment.Keycloak; import org.keycloak.operator.crds.v2alpha1.deployment.spec.UpdateSpec; @@ -32,13 +32,15 @@ import org.keycloak.operator.upgrade.impl.RecreateOnImageChangeUpgradeLogic; */ @ApplicationScoped public class UpgradeLogicFactory { + @Inject + KeycloakUpdateJobDependentResource updateJobDependentResource; - public UpgradeLogic create(Keycloak keycloak, Context context, KeycloakDeploymentDependentResource dependentResource, KeycloakUpdateJobDependentResource updateJobDependentResource) { + public UpgradeLogic create(Keycloak keycloak, Context context) { var strategy = UpdateSpec.getUpdateStrategy(keycloak); return switch (strategy) { - case RECREATE_ON_IMAGE_CHANGE -> new RecreateOnImageChangeUpgradeLogic(context, keycloak, dependentResource); - case FORCE_RECREATE -> new ForceRecreateUpgradeLogic(context, keycloak, dependentResource); - case AUTO -> new AutoUpgradeLogic(context, keycloak, dependentResource, updateJobDependentResource); + case RECREATE_ON_IMAGE_CHANGE -> new RecreateOnImageChangeUpgradeLogic(context, keycloak); + case FORCE_RECREATE -> new ForceRecreateUpgradeLogic(context, keycloak); + case AUTO -> new AutoUpgradeLogic(context, keycloak, updateJobDependentResource); }; } diff --git a/operator/src/main/java/org/keycloak/operator/upgrade/impl/AutoUpgradeLogic.java b/operator/src/main/java/org/keycloak/operator/upgrade/impl/AutoUpgradeLogic.java index 8c018cc0e01..6becfb7fb0d 100644 --- a/operator/src/main/java/org/keycloak/operator/upgrade/impl/AutoUpgradeLogic.java +++ b/operator/src/main/java/org/keycloak/operator/upgrade/impl/AutoUpgradeLogic.java @@ -32,7 +32,6 @@ import io.fabric8.kubernetes.client.KubernetesClient; import io.javaoperatorsdk.operator.api.reconciler.Context; import io.javaoperatorsdk.operator.api.reconciler.UpdateControl; import io.quarkus.logging.Log; -import org.keycloak.operator.controllers.KeycloakDeploymentDependentResource; import org.keycloak.operator.controllers.KeycloakUpdateJobDependentResource; import org.keycloak.operator.crds.v2alpha1.CRDUtils; import org.keycloak.operator.crds.v2alpha1.deployment.Keycloak; @@ -41,8 +40,8 @@ public class AutoUpgradeLogic extends BaseUpgradeLogic { private final KeycloakUpdateJobDependentResource updateJobResource; - public AutoUpgradeLogic(Context context, Keycloak keycloak, KeycloakDeploymentDependentResource statefulSetResource, KeycloakUpdateJobDependentResource updateJobResource) { - super(context, keycloak, statefulSetResource); + public AutoUpgradeLogic(Context context, Keycloak keycloak, KeycloakUpdateJobDependentResource updateJobResource) { + super(context, keycloak); this.updateJobResource = updateJobResource; } diff --git a/operator/src/main/java/org/keycloak/operator/upgrade/impl/BaseUpgradeLogic.java b/operator/src/main/java/org/keycloak/operator/upgrade/impl/BaseUpgradeLogic.java index ade25411058..a3a3a1e2871 100644 --- a/operator/src/main/java/org/keycloak/operator/upgrade/impl/BaseUpgradeLogic.java +++ b/operator/src/main/java/org/keycloak/operator/upgrade/impl/BaseUpgradeLogic.java @@ -46,23 +46,21 @@ abstract class BaseUpgradeLogic implements UpgradeLogic { protected final Context context; protected final Keycloak keycloak; - protected final KeycloakDeploymentDependentResource statefulSetResource; - BaseUpgradeLogic(Context context, Keycloak keycloak, KeycloakDeploymentDependentResource statefulSetResource) { + BaseUpgradeLogic(Context context, Keycloak keycloak) { this.context = context; this.keycloak = keycloak; - this.statefulSetResource = statefulSetResource; } @Override public final Optional> decideUpgrade() { - var existing = context.getSecondaryResource(StatefulSet.class); + var existing = ContextUtils.getCurrentStatefulSet(context); if (existing.isEmpty()) { // new deployment, no upgrade needed Log.debug("New deployment - skipping upgrade logic"); return Optional.empty(); } - var desiredStatefulSet = statefulSetResource.desired(keycloak, context); + var desiredStatefulSet = ContextUtils.getDesiredStatefulSet(context); var desiredContainer = CRDUtils.firstContainerOf(desiredStatefulSet).orElseThrow(BaseUpgradeLogic::containerNotFound); var actualContainer = CRDUtils.firstContainerOf(existing.get()).orElseThrow(BaseUpgradeLogic::containerNotFound); @@ -72,9 +70,6 @@ abstract class BaseUpgradeLogic implements UpgradeLogic { return Optional.empty(); } - // store in context the current and desired stateful set for easy access. - ContextUtils.storeCurrentStatefulSet(context, existing.get()); - ContextUtils.storeDesiredStatefulSet(context, desiredStatefulSet); return onUpgrade(); } diff --git a/operator/src/main/java/org/keycloak/operator/upgrade/impl/ForceRecreateUpgradeLogic.java b/operator/src/main/java/org/keycloak/operator/upgrade/impl/ForceRecreateUpgradeLogic.java index 5387fe6e690..e0508c2ac53 100644 --- a/operator/src/main/java/org/keycloak/operator/upgrade/impl/ForceRecreateUpgradeLogic.java +++ b/operator/src/main/java/org/keycloak/operator/upgrade/impl/ForceRecreateUpgradeLogic.java @@ -21,7 +21,6 @@ import java.util.Optional; import io.javaoperatorsdk.operator.api.reconciler.Context; import io.javaoperatorsdk.operator.api.reconciler.UpdateControl; -import org.keycloak.operator.controllers.KeycloakDeploymentDependentResource; import org.keycloak.operator.crds.v2alpha1.deployment.Keycloak; import org.keycloak.operator.upgrade.UpgradeLogic; import org.keycloak.operator.upgrade.UpgradeType; @@ -32,8 +31,8 @@ import org.keycloak.operator.upgrade.UpgradeType; */ public class ForceRecreateUpgradeLogic extends BaseUpgradeLogic { - public ForceRecreateUpgradeLogic(Context context, Keycloak keycloak, KeycloakDeploymentDependentResource statefulSetResource) { - super(context, keycloak, statefulSetResource); + public ForceRecreateUpgradeLogic(Context context, Keycloak keycloak) { + super(context, keycloak); } @Override diff --git a/operator/src/main/java/org/keycloak/operator/upgrade/impl/RecreateOnImageChangeUpgradeLogic.java b/operator/src/main/java/org/keycloak/operator/upgrade/impl/RecreateOnImageChangeUpgradeLogic.java index 6404ff4bd10..12a8ffb000b 100644 --- a/operator/src/main/java/org/keycloak/operator/upgrade/impl/RecreateOnImageChangeUpgradeLogic.java +++ b/operator/src/main/java/org/keycloak/operator/upgrade/impl/RecreateOnImageChangeUpgradeLogic.java @@ -25,7 +25,6 @@ import io.fabric8.kubernetes.api.model.apps.StatefulSet; import io.javaoperatorsdk.operator.api.reconciler.Context; import io.javaoperatorsdk.operator.api.reconciler.UpdateControl; import org.keycloak.operator.ContextUtils; -import org.keycloak.operator.controllers.KeycloakDeploymentDependentResource; import org.keycloak.operator.crds.v2alpha1.CRDUtils; import org.keycloak.operator.crds.v2alpha1.deployment.Keycloak; import org.keycloak.operator.upgrade.UpgradeType; @@ -38,13 +37,13 @@ import org.keycloak.operator.upgrade.UpgradeType; @SuppressWarnings("ALL") public class RecreateOnImageChangeUpgradeLogic extends BaseUpgradeLogic { - public RecreateOnImageChangeUpgradeLogic(Context context, Keycloak keycloak, KeycloakDeploymentDependentResource dependentResource) { - super(context, keycloak, dependentResource); + public RecreateOnImageChangeUpgradeLogic(Context context, Keycloak keycloak) { + super(context, keycloak); } @Override Optional> onUpgrade() { - var currentImage = extractImage(ContextUtils.getCurrentStatefulSet(context)); + var currentImage = extractImage(ContextUtils.getCurrentStatefulSet(context).orElseThrow()); var desiredImage = extractImage(ContextUtils.getDesiredStatefulSet(context)); if (Objects.equals(currentImage, desiredImage)) { 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 fc2bed68e9c..1b3b889c4ea 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 @@ -62,7 +62,10 @@ import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.TestInfo; import org.keycloak.operator.Constants; +import org.keycloak.operator.controllers.KeycloakController; import org.keycloak.operator.controllers.KeycloakDeploymentDependentResource; +import org.keycloak.operator.controllers.KeycloakRealmImportController; +import org.keycloak.operator.controllers.KeycloakUpdateJobDependentResource; import org.keycloak.operator.crds.v2alpha1.deployment.Keycloak; import org.keycloak.operator.crds.v2alpha1.deployment.KeycloakSpecBuilder; import org.keycloak.operator.crds.v2alpha1.deployment.KeycloakStatus; @@ -213,24 +216,6 @@ public class BaseOperatorTest implements QuarkusTestAfterEachCallback { public KubernetesClient getKubernetesClient() { return k8sclient; } - - @Override - public DependentResourceFactory dependentResourceFactory() { - return new DependentResourceFactory>() { - @Override - public DependentResource createFrom(DependentResourceSpec spec, - ControllerConfiguration configuration) { - final var dependentResourceClass = spec.getDependentResourceClass(); - // workaround for https://github.com/operator-framework/java-operator-sdk/issues/2010 - // create a fresh instance of the dependentresource - CDI.current().destroy(CDI.current().select(dependentResourceClass).get()); - DependentResource instance = (DependentResource) CDI.current().select(dependentResourceClass).get(); - var context = Utils.contextFor(configuration, dependentResourceClass, Dependent.class); - DependentResourceConfigurationResolver.configure(instance, spec, configuration); - return instance; - } - }; - } }); } @@ -449,6 +434,11 @@ public class BaseOperatorTest implements QuarkusTestAfterEachCallback { Log.info("Stopping Operator"); operator.stop(); + // Avoid issues with Event Informers between tests + Log.info("Removing Controllers and application scoped DRs from CDI"); + Stream.of(KeycloakController.class, KeycloakRealmImportController.class, KeycloakUpdateJobDependentResource.class) + .forEach(c -> CDI.current().destroy(CDI.current().select(c).get())); + Log.info("Creating new K8s Client"); // create a new client bc operator has closed the old one createK8sClient(); diff --git a/operator/src/test/java/org/keycloak/operator/testsuite/unit/KeycloakControllerTest.java b/operator/src/test/java/org/keycloak/operator/testsuite/unit/KeycloakControllerTest.java index 3fb51fa43fe..9c6b3c37c5b 100644 --- a/operator/src/test/java/org/keycloak/operator/testsuite/unit/KeycloakControllerTest.java +++ b/operator/src/test/java/org/keycloak/operator/testsuite/unit/KeycloakControllerTest.java @@ -53,9 +53,9 @@ class KeycloakControllerTest { // both the instances and hostname should be updated UpdateControl update = controller.reconcile(kc, mockContext); - assertTrue(update.isUpdateResource()); - assertEquals(1, update.getResource().getSpec().getInstances()); - assertEquals("example-kc-ingress-ns.openshift.com", update.getResource().getSpec().getHostnameSpec().getHostname()); + assertTrue(update.isPatchResource()); + assertEquals(1, update.getResource().orElseThrow().getSpec().getInstances()); + assertEquals("example-kc-ingress-ns.openshift.com", update.getResource().orElseThrow().getSpec().getHostnameSpec().getHostname()); // just the instances should be updated if not openshift-default kc = K8sUtils.getDefaultKeycloakDeployment(); @@ -63,9 +63,9 @@ class KeycloakControllerTest { kc.getSpec().setInstances(null); kc.getSpec().getHostnameSpec().setHostname(null); update = controller.reconcile(kc, mockContext); - assertTrue(update.isUpdateResource()); - assertEquals(1, update.getResource().getSpec().getInstances()); - assertNull(update.getResource().getSpec().getHostnameSpec().getHostname()); + assertTrue(update.isPatchResource()); + assertEquals(1, update.getResource().orElseThrow().getSpec().getInstances()); + assertNull(update.getResource().orElseThrow().getSpec().getHostnameSpec().getHostname()); } } 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 beaf9ba6407..cffba0c04d7 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 @@ -32,12 +32,13 @@ import io.fabric8.kubernetes.api.model.Volume; import io.fabric8.kubernetes.api.model.VolumeMount; import io.fabric8.kubernetes.api.model.apps.StatefulSet; import io.fabric8.kubernetes.api.model.apps.StatefulSetBuilder; +import io.fabric8.kubernetes.client.KubernetesClient; import io.fabric8.kubernetes.client.utils.Serialization; import io.javaoperatorsdk.operator.api.reconciler.Context; -import io.javaoperatorsdk.operator.api.reconciler.dependent.managed.DefaultManagedDependentResourceContext; +import io.javaoperatorsdk.operator.api.reconciler.dependent.managed.ManagedWorkflowAndDependentResourceContext; import io.quarkus.test.InjectMock; import io.quarkus.test.junit.QuarkusTest; - +import jakarta.inject.Inject; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -57,17 +58,18 @@ import org.keycloak.operator.crds.v2alpha1.deployment.spec.UnsupportedSpec; import org.mockito.Mockito; import java.util.List; -import java.util.Optional; import java.util.function.Consumer; import java.util.function.Function; import java.util.stream.Collectors; -import jakarta.inject.Inject; - import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.keycloak.operator.ContextUtils.DIST_CONFIGURATOR_KEY; +import static org.keycloak.operator.ContextUtils.OLD_DEPLOYMENT_KEY; +import static org.keycloak.operator.ContextUtils.OPERATOR_CONFIG_KEY; +import static org.keycloak.operator.ContextUtils.WATCHED_RESOURCES_KEY; @QuarkusTest public class PodTemplateTest { @@ -85,7 +87,7 @@ public class PodTemplateTest { @BeforeEach protected void setup() { - this.deployment = new KeycloakDeploymentDependentResource(operatorConfig, watchedResources, distConfigurator); + this.deployment = new KeycloakDeploymentDependentResource(); } private StatefulSet getDeployment(PodTemplateSpec podTemplate, StatefulSet existingDeployment, Consumer additionalSpec) { @@ -108,12 +110,15 @@ public class PodTemplateTest { kc.setSpec(keycloakSpecBuilder.build()); - var managedDependentResourceContext = new DefaultManagedDependentResourceContext(); //noinspection unchecked Context context = Mockito.mock(Context.class); - Mockito.when(context.managedDependentResourceContext()).thenReturn(managedDependentResourceContext); - Mockito.when(context.getSecondaryResource(StatefulSet.class)).thenReturn(Optional.ofNullable(existingDeployment)); - + ManagedWorkflowAndDependentResourceContext managedWorkflowAndDependentResourceContext = Mockito.mock(ManagedWorkflowAndDependentResourceContext.class); + Mockito.when(context.managedWorkflowAndDependentResourceContext()).thenReturn(managedWorkflowAndDependentResourceContext); + Mockito.when(managedWorkflowAndDependentResourceContext.getMandatory(OLD_DEPLOYMENT_KEY, StatefulSet.class)).thenReturn(existingDeployment); + Mockito.when(managedWorkflowAndDependentResourceContext.getMandatory(OPERATOR_CONFIG_KEY, Config.class)).thenReturn(operatorConfig); + Mockito.when(managedWorkflowAndDependentResourceContext.getMandatory(WATCHED_RESOURCES_KEY, WatchedResources.class)).thenReturn(watchedResources); + Mockito.when(managedWorkflowAndDependentResourceContext.getMandatory(DIST_CONFIGURATOR_KEY, KeycloakDistConfigurator.class)).thenReturn(distConfigurator); + Mockito.when(context.getClient()).thenReturn(Mockito.mock(KubernetesClient.class)); return deployment.desired(kc, context); } diff --git a/operator/src/test/java/org/keycloak/operator/testsuite/utils/CRAssert.java b/operator/src/test/java/org/keycloak/operator/testsuite/utils/CRAssert.java index cc9a6b3f1dd..3b1d162c0c7 100644 --- a/operator/src/test/java/org/keycloak/operator/testsuite/utils/CRAssert.java +++ b/operator/src/test/java/org/keycloak/operator/testsuite/utils/CRAssert.java @@ -120,6 +120,7 @@ public final class CRAssert { .await() .pollInterval(1, TimeUnit.SECONDS) .timeout(Duration.ofMinutes(5)) + .ignoreExceptions() .untilAsserted(() -> client.pods() .inNamespace(namespaceOf(keycloak)) .withLabels(Utils.allInstanceLabels(keycloak)) diff --git a/pom.xml b/pom.xml index 06ec4a02da4..e6ef38e753d 100644 --- a/pom.xml +++ b/pom.xml @@ -52,8 +52,8 @@ jboss-snapshots-repository https://s01.oss.sonatype.org/content/repositories/snapshots/ - 3.17.8 - 3.17.8 + 3.18.2 + 3.18.2 ${timestamp} @@ -165,7 +165,7 @@ mirror.gcr.io/postgres:${postgresql.version} 16.1 2.3.1 - 42.7.4 + 42.7.5 11.4 mirror.gcr.io/mariadb:${mariadb.version} 3.4.1 diff --git a/quarkus/tests/integration/src/test/java/org/keycloak/it/cli/dist/ProxyHostnameV2DistTest.java b/quarkus/tests/integration/src/test/java/org/keycloak/it/cli/dist/ProxyHostnameV2DistTest.java index 72062e64d00..72358da2478 100644 --- a/quarkus/tests/integration/src/test/java/org/keycloak/it/cli/dist/ProxyHostnameV2DistTest.java +++ b/quarkus/tests/integration/src/test/java/org/keycloak/it/cli/dist/ProxyHostnameV2DistTest.java @@ -117,9 +117,9 @@ public class ProxyHostnameV2DistTest { } private void assertXForwardedHeaders() { - given().header("X-Forwarded-Host", "test").when().get("http://mykeycloak.org:8080").then().header(HttpHeaders.LOCATION, containsString("http://test:8080/admin")); - given().header("X-Forwarded-Host", "test").when().get("http://localhost:8080").then().header(HttpHeaders.LOCATION, containsString("http://test:8080/admin")); - given().header("X-Forwarded-Host", "test").when().get("https://localhost:8443").then().header(HttpHeaders.LOCATION, containsString("https://test:8443/admin")); + given().header("X-Forwarded-Host", "test:123").when().get("http://mykeycloak.org:8080").then().header(HttpHeaders.LOCATION, containsString("http://test:123/admin")); + given().header("X-Forwarded-Host", "test:123").when().get("http://localhost:8080").then().header(HttpHeaders.LOCATION, containsString("http://test:123/admin")); + given().header("X-Forwarded-Host", "test:123").when().get("https://localhost:8443").then().header(HttpHeaders.LOCATION, containsString("https://test:123/admin")); given().header("X-Forwarded-Proto", "https").when().get("http://localhost:8080").then().header(HttpHeaders.LOCATION, containsString("https://localhost/admin")); given().header("X-Forwarded-Proto", "https").header("X-Forwarded-Port", "8443").when().get("http://localhost:8080").then().header(HttpHeaders.LOCATION, containsString("https://localhost:8443/admin")); } @@ -140,4 +140,4 @@ public class ProxyHostnameV2DistTest { Assert.assertEquals(expectedBaseUrl + "realms/master/protocol/openid-connect/auth", getServerMetadata(requestBaseUrl) .getAuthorizationEndpoint()); } -} \ No newline at end of file +} diff --git a/test-framework/core/src/main/java/org/keycloak/testframework/LogHandler.java b/test-framework/core/src/main/java/org/keycloak/testframework/LogHandler.java index 4336b2c6cdd..0cfe91f3709 100644 --- a/test-framework/core/src/main/java/org/keycloak/testframework/LogHandler.java +++ b/test-framework/core/src/main/java/org/keycloak/testframework/LogHandler.java @@ -1,6 +1,7 @@ package org.keycloak.testframework; import io.quarkus.runtime.logging.LoggingSetupRecorder; +import io.smallrye.config.SmallRyeConfigProviderResolver; import org.eclipse.microprofile.config.spi.ConfigProviderResolver; import org.jboss.logging.Logger; import org.jboss.logmanager.LogManager; @@ -26,7 +27,14 @@ public class LogHandler { } private static void initializeQuarkusLogging() { - ConfigProviderResolver.instance().registerConfig(Config.getConfig(), Thread.currentThread().getContextClassLoader()); + // We do not care about Config that was created by Quarkus' TestConfigProviderResolver. + // Alternatively, a Customizer could be used so we could keep the Config created by Quarkus but this is not Quarkus tests, + // relying on Quarkus' Config is not necessary and might be fragile. + SmallRyeConfigProviderResolver configProviderResolver = (SmallRyeConfigProviderResolver) ConfigProviderResolver.instance(); + ClassLoader cl = Thread.currentThread().getContextClassLoader(); + + configProviderResolver.releaseConfig(cl); + ConfigProviderResolver.instance().registerConfig(Config.getConfig(), cl); LoggingSetupRecorder.handleFailedStart(); } diff --git a/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/federation/UserPropertyFileStorage.java b/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/federation/UserPropertyFileStorage.java index 604a78190be..7310081a5a2 100644 --- a/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/federation/UserPropertyFileStorage.java +++ b/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/federation/UserPropertyFileStorage.java @@ -111,7 +111,8 @@ public class UserPropertyFileStorage implements UserLookupProvider, UserStorageP public int getUsersCount(RealmModel realm, Map params) { addCall(COUNT_SEARCH_METHOD); - return (int) searchForUser(realm, params.get(UserModel.SEARCH), null, null, username -> username.contains(params.get(UserModel.SEARCH))).count(); + String search = params.get(UserModel.SEARCH); + return (int) searchForUser(realm, search, null, null, username -> search == null || username.contains(search)).count(); } @Override