mirror of
https://github.com/keycloak/keycloak.git
synced 2026-01-09 23:12:06 -03:30
Upgrade to Quarkus 3.18.2 (#37300)
* Upgrade to Quarkus 3.18.2 Closes #37056 Signed-off-by: Václav Muzikář <vmuzikar@redhat.com> * Update docs/documentation/upgrading/topics/changes/changes-26_2_0.adoc Co-authored-by: Alexander Schwartz <alexander.schwartz@gmx.net> Signed-off-by: Václav Muzikář <vaclav@muzikari.cz> --------- Signed-off-by: Václav Muzikář <vmuzikar@redhat.com> Signed-off-by: Václav Muzikář <vaclav@muzikari.cz> Co-authored-by: Alexander Schwartz <alexander.schwartz@gmx.net>
This commit is contained in:
parent
2b74e6148e
commit
764ca50fc4
@ -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
|
||||
https://www.keycloak.org/high-availability/concepts-memory-and-cpu-sizing#_measuring_the_activity_of_a_running_keycloak_instance
|
||||
http://example.com:8080
|
||||
@ -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.
|
||||
@ -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]
|
||||
|
||||
@ -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<Keycloak> 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<Keycloak> context) {
|
||||
return context.managedDependentResourceContext().getMandatory(OLD_DEPLOYMENT_KEY, StatefulSet.class);
|
||||
public static Optional<StatefulSet> getCurrentStatefulSet(Context<?> context) {
|
||||
return context.managedWorkflowAndDependentResourceContext().get(OLD_DEPLOYMENT_KEY, StatefulSet.class);
|
||||
}
|
||||
|
||||
public static void storeDesiredStatefulSet(Context<Keycloak> 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<Keycloak> 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<Keycloak> 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<UpgradeType> getUpgradeType(Context<Keycloak> context) {
|
||||
return context.managedDependentResourceContext().get(UPGRADE_TYPE_KEY, UpgradeType.class);
|
||||
public static Optional<UpgradeType> 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);
|
||||
}
|
||||
}
|
||||
|
||||
@ -80,13 +80,6 @@ public final class Utils {
|
||||
return labels;
|
||||
}
|
||||
|
||||
public static <T extends HasMetadata> Optional<T> getByName(Class<T> clazz, Function<Keycloak, String> nameFunction, Keycloak primary, Context<Keycloak> context) {
|
||||
InformerEventSource<T, Keycloak> ies = (InformerEventSource<T, Keycloak>) context
|
||||
.eventSourceRetriever().getResourceEventSourceFor(clazz);
|
||||
|
||||
return ies.get(new ResourceID(nameFunction.apply(primary), primary.getMetadata().getNamespace()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Set resources requests/limits for Keycloak container
|
||||
* </p>
|
||||
|
||||
@ -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<Secret, Keycloak> implements Creator<Secret, Keycloak>, GarbageCollected<Keycloak> {
|
||||
|
||||
|
||||
public static class EnabledCondition implements Condition<Secret, Keycloak> {
|
||||
@Override
|
||||
public boolean isMet(DependentResource<Secret, Keycloak> dependentResource, Keycloak primary,
|
||||
Context<Keycloak> 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<Secret, Keycloak> {
|
||||
@Override
|
||||
public Optional<Secret> distinguish(Class<Secret> resource, Keycloak primary, Context<Keycloak> 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();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -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<Keycloak>, EventSourceInitializer<Keycloak>, ErrorStatusHandler<Keycloak> {
|
||||
public class KeycloakController implements Reconciler<Keycloak> {
|
||||
|
||||
public static final String OPENSHIFT_DEFAULT = "openshift-default";
|
||||
|
||||
@ -85,32 +80,12 @@ public class KeycloakController implements Reconciler<Keycloak>, EventSourceInit
|
||||
@Inject
|
||||
UpgradeLogicFactory upgradeLogicFactory;
|
||||
|
||||
volatile KeycloakDeploymentDependentResource deploymentDependentResource;
|
||||
volatile KeycloakUpdateJobDependentResource updateJobDependentResource;
|
||||
@Inject
|
||||
KeycloakUpdateJobDependentResource updateJobDependentResource;
|
||||
|
||||
@Override
|
||||
public Map<String, EventSource> prepareEventSources(EventSourceContext<Keycloak> context) {
|
||||
var namespaces = context.getControllerConfiguration().getNamespaces();
|
||||
|
||||
InformerConfiguration<Service> servicesIC = InformerConfiguration
|
||||
.from(Service.class)
|
||||
.withLabelSelector(Constants.DEFAULT_LABELS_AS_STRING)
|
||||
.withNamespaces(namespaces)
|
||||
.withSecondaryToPrimaryMapper(Mappers.fromOwnerReference())
|
||||
.build();
|
||||
|
||||
EventSource servicesEvent = new InformerEventSource<>(servicesIC, context);
|
||||
|
||||
Map<String, EventSource> 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<EventSource<?, Keycloak>> prepareEventSources(EventSourceContext<Keycloak> context) {
|
||||
return EventSourceUtils.dependentEventSources(context, updateJobDependentResource);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -141,10 +116,25 @@ public class KeycloakController implements Reconciler<Keycloak>, 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<Keycloak>, 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<Keycloak>, 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<Keycloak>, EventSourceInit
|
||||
|
||||
kc.setStatus(status);
|
||||
|
||||
return ErrorStatusUpdateControl.updateStatus(kc);
|
||||
return ErrorStatusUpdateControl.patchStatus(kc);
|
||||
}
|
||||
|
||||
public static Optional<String> generateOpenshiftHostname(Keycloak keycloak, Context<Keycloak> context) {
|
||||
|
||||
@ -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<StatefulSet, Keycloak> {
|
||||
|
||||
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<StatefulSet, Keycloak> {
|
||||
@Override
|
||||
public boolean isMet(DependentResource<StatefulSet, Keycloak> dependentResource, Keycloak primary, Context<Keycloak> 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<StatefulSet>()
|
||||
.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<Keycloak> context) {
|
||||
StatefulSet baseDeployment = createBaseDeployment(primary, context);
|
||||
Config operatorConfig = ContextUtils.getOperatorConfig(context);
|
||||
WatchedResources watchedResources = ContextUtils.getWatchedResources(context);
|
||||
|
||||
StatefulSet baseDeployment = createBaseDeployment(primary, context, operatorConfig);
|
||||
TreeSet<String> 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<Keycloak> context) {
|
||||
private StatefulSet createBaseDeployment(Keycloak keycloakCR, Context<Keycloak> context, Config operatorConfig) {
|
||||
Map<String, String> labels = Utils.allInstanceLabels(keycloakCR);
|
||||
labels.put("app.kubernetes.io/component", "server");
|
||||
Map<String, String> 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<String> allSecrets) {
|
||||
private void addEnvVars(StatefulSet baseDeployment, Keycloak keycloakCR, TreeSet<String> allSecrets, Context<Keycloak> context) {
|
||||
var distConfigurator = ContextUtils.getDistConfigurator(context);
|
||||
var firstClasssEnvVars = distConfigurator.configureDistOptions(keycloakCR);
|
||||
|
||||
var additionalEnvVars = getDefaultAndAdditionalEnvVars(keycloakCR);
|
||||
|
||||
@ -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<Service, Keycloak> {
|
||||
|
||||
public static class NameResourceDiscriminator implements ResourceDiscriminator<Service, Keycloak> {
|
||||
@Override
|
||||
public Optional<Service> distinguish(Class<Service> resource, Keycloak primary, Context<Keycloak> context) {
|
||||
return Utils.getByName(Service.class, KeycloakDiscoveryServiceDependentResource::getName, primary, context);
|
||||
}
|
||||
}
|
||||
|
||||
public KeycloakDiscoveryServiceDependentResource() {
|
||||
super(Service.class);
|
||||
}
|
||||
|
||||
@ -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<Ingress, Keycloak> {
|
||||
|
||||
private static final Logger LOG = Logger.getLogger(KeycloakIngressDependentResource.class.getName());
|
||||
|
||||
@ -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<NetworkPolicy, Keycloak> {
|
||||
|
||||
private static final Logger LOG = Logger.getLogger(KeycloakNetworkPolicyDependentResource.class.getName());
|
||||
|
||||
@ -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<KeycloakRealmImport>, ErrorStatusHandler<KeycloakRealmImport>, EventSourceInitializer<KeycloakRealmImport> {
|
||||
public class KeycloakRealmImportController implements Reconciler<KeycloakRealmImport> {
|
||||
|
||||
@Inject
|
||||
Config config;
|
||||
|
||||
@Inject
|
||||
KubernetesClient client;
|
||||
|
||||
volatile KeycloakRealmImportJobDependentResource jobDependentResource;
|
||||
|
||||
@Override
|
||||
public Map<String, EventSource> prepareEventSources(EventSourceContext<KeycloakRealmImport> context) {
|
||||
this.jobDependentResource = new KeycloakRealmImportJobDependentResource(config);
|
||||
return EventSourceInitializer.nameEventSourcesFromDependentResource(context, jobDependentResource);
|
||||
}
|
||||
|
||||
@Override
|
||||
public UpdateControl<KeycloakRealmImport> reconcile(KeycloakRealmImport realm, Context<KeycloakRealmImport> context) {
|
||||
String realmName = realm.getMetadata().getName();
|
||||
@ -78,13 +63,14 @@ public class KeycloakRealmImportController implements Reconciler<KeycloakRealmIm
|
||||
.withName(realm.getSpec().getKeycloakCRName()).get();
|
||||
|
||||
if (existingDeployment != null) {
|
||||
context.managedDependentResourceContext().put(StatefulSet.class, existingDeployment);
|
||||
ContextUtils.storeOperatorConfig(context, config);
|
||||
ContextUtils.storeCurrentStatefulSet(context, existingDeployment);
|
||||
if (getReadyReplicas(existingDeployment) > 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<KeycloakRealmIm
|
||||
updateControl = UpdateControl.noUpdate();
|
||||
} else {
|
||||
realm.setStatus(status);
|
||||
updateControl = UpdateControl.updateStatus(realm);
|
||||
updateControl = UpdateControl.patchStatus(realm);
|
||||
}
|
||||
|
||||
if (status
|
||||
@ -116,10 +102,10 @@ public class KeycloakRealmImportController implements Reconciler<KeycloakRealmIm
|
||||
.build();
|
||||
|
||||
realm.setStatus(status);
|
||||
return ErrorStatusUpdateControl.updateStatus(realm);
|
||||
return ErrorStatusUpdateControl.patchStatus(realm);
|
||||
}
|
||||
|
||||
public void updateStatus(KeycloakRealmImportStatusBuilder status, KeycloakRealmImport realmCR, Job existingJob, StatefulSet existingDeployment) {
|
||||
public void updateStatus(KeycloakRealmImportStatusBuilder status, KeycloakRealmImport realmCR, Job existingJob, StatefulSet existingDeployment, KubernetesClient client) {
|
||||
if (existingDeployment == null) {
|
||||
status.addErrorMessage("No existing Deployment found, waiting for it to be created");
|
||||
return;
|
||||
@ -144,7 +130,7 @@ public class KeycloakRealmImportController implements Reconciler<KeycloakRealmIm
|
||||
} else if (oldStatus.getSucceeded() != null && oldStatus.getSucceeded() > 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<KeycloakRealmIm
|
||||
return Optional.ofNullable(existingDeployment.getStatus()).map(StatefulSetStatus::getReadyReplicas).orElse(0);
|
||||
}
|
||||
|
||||
private void rollingRestart(KeycloakRealmImport realmCR) {
|
||||
private void rollingRestart(KeycloakRealmImport realmCR, KubernetesClient client) {
|
||||
client.apps().statefulSets()
|
||||
.inNamespace(realmCR.getMetadata().getNamespace())
|
||||
.withName(realmCR.getSpec().getKeycloakCRName())
|
||||
|
||||
@ -30,11 +30,10 @@ import io.fabric8.kubernetes.client.utils.KubernetesResourceUtil;
|
||||
import io.javaoperatorsdk.operator.api.reconciler.Context;
|
||||
import io.javaoperatorsdk.operator.api.reconciler.dependent.GarbageCollected;
|
||||
import io.javaoperatorsdk.operator.processing.dependent.Creator;
|
||||
import io.javaoperatorsdk.operator.processing.dependent.kubernetes.KubernetesDependent;
|
||||
import io.javaoperatorsdk.operator.processing.dependent.kubernetes.KubernetesDependentResource;
|
||||
|
||||
import io.javaoperatorsdk.operator.processing.dependent.kubernetes.KubernetesDependentResourceConfigBuilder;
|
||||
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.realmimport.KeycloakRealmImport;
|
||||
import org.keycloak.operator.crds.v2alpha1.realmimport.Placeholder;
|
||||
@ -46,21 +45,17 @@ import java.util.Set;
|
||||
import static org.keycloak.operator.Utils.addResources;
|
||||
import static org.keycloak.operator.controllers.KeycloakDistConfigurator.getKeycloakOptionEnvVarName;
|
||||
|
||||
@KubernetesDependent
|
||||
public class KeycloakRealmImportJobDependentResource extends KubernetesDependentResource<Job, KeycloakRealmImport> implements Creator<Job, KeycloakRealmImport>, GarbageCollected<KeycloakRealmImport> {
|
||||
|
||||
private final Config config;
|
||||
|
||||
KeycloakRealmImportJobDependentResource(Config config) {
|
||||
KeycloakRealmImportJobDependentResource() {
|
||||
super(Job.class);
|
||||
this.config = config;
|
||||
this.configureWith(new KubernetesDependentResourceConfigBuilder<Job>()
|
||||
.withLabelSelector(Constants.DEFAULT_LABELS_AS_STRING)
|
||||
.build());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Job desired(KeycloakRealmImport primary, Context<KeycloakRealmImport> context) {
|
||||
StatefulSet existingDeployment = context.managedDependentResourceContext().get(StatefulSet.class, StatefulSet.class).orElseThrow();
|
||||
Config config = ContextUtils.getOperatorConfig(context);
|
||||
StatefulSet existingDeployment = ContextUtils.getCurrentStatefulSet(context).orElseThrow();
|
||||
Map<String, Placeholder> 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");
|
||||
|
||||
@ -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<Secret, KeycloakRealmImport> {
|
||||
|
||||
public static final String DEPENDENT_NAME = "realm-import-secret";
|
||||
|
||||
public KeycloakRealmImportSecretDependentResource() {
|
||||
super(Secret.class);
|
||||
}
|
||||
|
||||
@ -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<Service, Keycloak> {
|
||||
|
||||
public static class NameResourceDiscriminator implements ResourceDiscriminator<Service, Keycloak> {
|
||||
@Override
|
||||
public Optional<Service> distinguish(Class<Service> resource, Keycloak primary, Context<Keycloak> context) {
|
||||
return Utils.getByName(Service.class, KeycloakServiceDependentResource::getServiceName, primary, context);
|
||||
}
|
||||
}
|
||||
|
||||
public KeycloakServiceDependentResource() {
|
||||
super(Service.class);
|
||||
}
|
||||
|
||||
@ -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<Job, Keycloak> {
|
||||
|
||||
// shared volume configuration
|
||||
@ -71,13 +73,12 @@ public class KeycloakUpdateJobDependentResource extends CRUDKubernetesDependentR
|
||||
// container args to replace
|
||||
private static final Set<String> 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<Job>()
|
||||
.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<Keycloak> context, Collection<String> availableVolumes, Collection<String> 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<String, Volume> getAllVolumes(Context<Keycloak> context) {
|
||||
Map<String, Volume> allVolumes = new HashMap<>();
|
||||
Consumer<Volume> 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;
|
||||
}
|
||||
|
||||
@ -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 <vmuzikar@redhat.com>
|
||||
*/
|
||||
@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;
|
||||
}
|
||||
|
||||
@ -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<String, KeycloakStatusCondition> existingConditions, String now) {
|
||||
|
||||
@ -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<Keycloak> context, KeycloakDeploymentDependentResource dependentResource, KeycloakUpdateJobDependentResource updateJobDependentResource) {
|
||||
public UpgradeLogic create(Keycloak keycloak, Context<Keycloak> 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);
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@ -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<Keycloak> context, Keycloak keycloak, KeycloakDeploymentDependentResource statefulSetResource, KeycloakUpdateJobDependentResource updateJobResource) {
|
||||
super(context, keycloak, statefulSetResource);
|
||||
public AutoUpgradeLogic(Context<Keycloak> context, Keycloak keycloak, KeycloakUpdateJobDependentResource updateJobResource) {
|
||||
super(context, keycloak);
|
||||
this.updateJobResource = updateJobResource;
|
||||
}
|
||||
|
||||
|
||||
@ -46,23 +46,21 @@ abstract class BaseUpgradeLogic implements UpgradeLogic {
|
||||
|
||||
protected final Context<Keycloak> context;
|
||||
protected final Keycloak keycloak;
|
||||
protected final KeycloakDeploymentDependentResource statefulSetResource;
|
||||
|
||||
BaseUpgradeLogic(Context<Keycloak> context, Keycloak keycloak, KeycloakDeploymentDependentResource statefulSetResource) {
|
||||
BaseUpgradeLogic(Context<Keycloak> context, Keycloak keycloak) {
|
||||
this.context = context;
|
||||
this.keycloak = keycloak;
|
||||
this.statefulSetResource = statefulSetResource;
|
||||
}
|
||||
|
||||
@Override
|
||||
public final Optional<UpdateControl<Keycloak>> 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();
|
||||
}
|
||||
|
||||
|
||||
@ -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<Keycloak> context, Keycloak keycloak, KeycloakDeploymentDependentResource statefulSetResource) {
|
||||
super(context, keycloak, statefulSetResource);
|
||||
public ForceRecreateUpgradeLogic(Context<Keycloak> context, Keycloak keycloak) {
|
||||
super(context, keycloak);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@ -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<Keycloak> context, Keycloak keycloak, KeycloakDeploymentDependentResource dependentResource) {
|
||||
super(context, keycloak, dependentResource);
|
||||
public RecreateOnImageChangeUpgradeLogic(Context<Keycloak> context, Keycloak keycloak) {
|
||||
super(context, keycloak);
|
||||
}
|
||||
|
||||
@Override
|
||||
Optional<UpdateControl<Keycloak>> 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)) {
|
||||
|
||||
@ -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<ControllerConfiguration<?>>() {
|
||||
@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();
|
||||
|
||||
@ -53,9 +53,9 @@ class KeycloakControllerTest {
|
||||
// both the instances and hostname should be updated
|
||||
UpdateControl<Keycloak> 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());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -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<KeycloakSpecBuilder> additionalSpec) {
|
||||
@ -108,12 +110,15 @@ public class PodTemplateTest {
|
||||
|
||||
kc.setSpec(keycloakSpecBuilder.build());
|
||||
|
||||
var managedDependentResourceContext = new DefaultManagedDependentResourceContext();
|
||||
//noinspection unchecked
|
||||
Context<Keycloak> 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);
|
||||
}
|
||||
|
||||
|
||||
@ -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))
|
||||
|
||||
6
pom.xml
6
pom.xml
@ -52,8 +52,8 @@
|
||||
<jboss.snapshots.repo.id>jboss-snapshots-repository</jboss.snapshots.repo.id>
|
||||
<jboss.snapshots.repo.url>https://s01.oss.sonatype.org/content/repositories/snapshots/</jboss.snapshots.repo.url>
|
||||
|
||||
<quarkus.version>3.17.8</quarkus.version>
|
||||
<quarkus.build.version>3.17.8</quarkus.build.version>
|
||||
<quarkus.version>3.18.2</quarkus.version>
|
||||
<quarkus.build.version>3.18.2</quarkus.build.version>
|
||||
|
||||
<project.build-time>${timestamp}</project.build-time>
|
||||
|
||||
@ -165,7 +165,7 @@
|
||||
<postgresql.container>mirror.gcr.io/postgres:${postgresql.version}</postgresql.container>
|
||||
<aurora-postgresql.version>16.1</aurora-postgresql.version>
|
||||
<aws-jdbc-wrapper.version>2.3.1</aws-jdbc-wrapper.version>
|
||||
<postgresql-jdbc.version>42.7.4</postgresql-jdbc.version>
|
||||
<postgresql-jdbc.version>42.7.5</postgresql-jdbc.version>
|
||||
<mariadb.version>11.4</mariadb.version>
|
||||
<mariadb.container>mirror.gcr.io/mariadb:${mariadb.version}</mariadb.container>
|
||||
<mariadb-jdbc.version>3.4.1</mariadb-jdbc.version>
|
||||
|
||||
@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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();
|
||||
}
|
||||
|
||||
|
||||
@ -111,7 +111,8 @@ public class UserPropertyFileStorage implements UserLookupProvider, UserStorageP
|
||||
public int getUsersCount(RealmModel realm, Map<String, String> 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
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user