mirror of
https://github.com/keycloak/keycloak.git
synced 2026-01-10 15:32:05 -03:30
fix: adding imagePullSecret to update job
closes: #39015 Signed-off-by: Steve Hawkins <shawkins@redhat.com> (cherry picked from commit 26de9efff334f70dbfc3bcbe7f0c6417afa3a32e)
This commit is contained in:
parent
d35792d39e
commit
d46b72267a
@ -17,15 +17,18 @@
|
||||
|
||||
package org.keycloak.operator.controllers;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
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;
|
||||
|
||||
import io.fabric8.kubernetes.api.model.ContainerFluent;
|
||||
import io.fabric8.kubernetes.api.model.ObjectMeta;
|
||||
@ -33,6 +36,7 @@ import io.fabric8.kubernetes.api.model.ObjectMetaBuilder;
|
||||
import io.fabric8.kubernetes.api.model.PodSpec;
|
||||
import io.fabric8.kubernetes.api.model.PodSpecBuilder;
|
||||
import io.fabric8.kubernetes.api.model.Volume;
|
||||
import io.fabric8.kubernetes.api.model.apps.StatefulSet;
|
||||
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;
|
||||
@ -41,18 +45,13 @@ import io.javaoperatorsdk.operator.api.reconciler.Context;
|
||||
import io.javaoperatorsdk.operator.processing.dependent.kubernetes.CRUDKubernetesDependentResource;
|
||||
import io.javaoperatorsdk.operator.processing.dependent.kubernetes.KubernetesDependentResourceConfigBuilder;
|
||||
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
|
||||
private static final String WORK_DIR_VOLUME_NAME = "workdir";
|
||||
private static final String WORK_DIR_VOLUME_MOUNT_PATH = "/mnt/workdir";
|
||||
private static final String WORK_DIR_VOLUME_NAME = "keycloak-update-job-temporary-workdir"; // unlikely to conflict
|
||||
private static final String WORK_DIR_VOLUME_MOUNT_PATH = "/mnt/" + WORK_DIR_VOLUME_NAME; // unlikely to conflict
|
||||
private static final String UPDATES_FILE_PATH = WORK_DIR_VOLUME_MOUNT_PATH + "/updates.json";
|
||||
|
||||
// Annotations
|
||||
@ -70,9 +69,6 @@ public class KeycloakUpdateJobDependentResource extends CRUDKubernetesDependentR
|
||||
// Job time to live
|
||||
private static final int JOB_TIME_TO_LIVE_SECONDS = (int) TimeUnit.MINUTES.toSeconds(30);
|
||||
|
||||
// container args to replace
|
||||
private static final Set<String> START_ARGS = Set.of("start", "start-dev");
|
||||
|
||||
public KeycloakUpdateJobDependentResource() {
|
||||
super(Job.class);
|
||||
this.configureWith(new KubernetesDependentResourceConfigBuilder<Job>()
|
||||
@ -127,21 +123,36 @@ public class KeycloakUpdateJobDependentResource extends CRUDKubernetesDependentR
|
||||
}
|
||||
|
||||
private PodSpec createPodSpec(Context<Keycloak> context) {
|
||||
var allVolumes = getAllVolumes(context);
|
||||
Collection<String> requiredVolumes = new HashSet<>();
|
||||
var builder = new PodSpecBuilder();
|
||||
StatefulSet current = ContextUtils.getCurrentStatefulSet(context).orElseThrow();
|
||||
StatefulSet desired = ContextUtils.getDesiredStatefulSet(context);
|
||||
|
||||
// start off with the desired statefulset state
|
||||
var builder = desired.getSpec().getTemplate().getSpec().edit();
|
||||
builder.withRestartPolicy("Never");
|
||||
addInitContainer(builder, context, allVolumes.keySet(), requiredVolumes);
|
||||
addContainer(builder, context, allVolumes.keySet(), requiredVolumes);
|
||||
|
||||
// remove things we don't want - the main keycloak container, and any sidecars added via the unsupported PodTemplate
|
||||
builder.withContainers();
|
||||
|
||||
// We'll leave the scheduling fields alone - it should be fine to run these jobs where ever the keycloak workload is restricted to,
|
||||
// if there is some corner case we are not considering that would leave the upgrade job unscheduable, we'll address that later
|
||||
|
||||
// mix in the existing state
|
||||
var desiredPullSecrets = builder.buildImagePullSecrets();
|
||||
current.getSpec().getTemplate().getSpec().getImagePullSecrets().stream().filter(s -> !desiredPullSecrets.contains(s)).forEach(builder::addToImagePullSecrets);
|
||||
// TODO: if the name is the same, but the volume has changed this merging behavior will be inconsistent / incorrect. For example is someone changes which
|
||||
// configmap the cache config is using
|
||||
var desiredVolumes = builder.buildVolumes().stream().map(Volume::getName).collect(Collectors.toSet());
|
||||
current.getSpec().getTemplate().getSpec().getVolumes().stream().filter(v -> !desiredVolumes.contains(v.getName())).forEach(builder::addToVolumes);
|
||||
// TODO: what else should get merged - there could be additional stuff from the unsupported PodTemplate
|
||||
|
||||
addInitContainer(builder, current);
|
||||
addContainer(builder, desired);
|
||||
builder.addNewVolume()
|
||||
.withName(WORK_DIR_VOLUME_NAME)
|
||||
.withNewEmptyDir()
|
||||
.endEmptyDir()
|
||||
.endVolume();
|
||||
// add volumes to the pod
|
||||
requiredVolumes.stream()
|
||||
.map(allVolumes::get)
|
||||
.forEach(volume -> builder.addNewVolumeLike(volume).endVolume());
|
||||
|
||||
// For test KeycloakDeploymentTest#testDeploymentDurability
|
||||
// it uses a pause image, which never ends.
|
||||
// After this seconds, the job is terminated allowing the test to complete.
|
||||
@ -149,34 +160,28 @@ public class KeycloakUpdateJobDependentResource extends CRUDKubernetesDependentR
|
||||
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()).orElseThrow();
|
||||
private static void addInitContainer(PodSpecBuilder builder, StatefulSet current) {
|
||||
var existing = CRDUtils.firstContainerOf(current).orElseThrow();
|
||||
var containerBuilder = builder.addNewInitContainerLike(existing);
|
||||
configureContainer(containerBuilder, INIT_CONTAINER_NAME, INIT_CONTAINER_ARGS, availableVolumes, requiredVolumes);
|
||||
configureContainer(containerBuilder, INIT_CONTAINER_NAME, INIT_CONTAINER_ARGS);
|
||||
containerBuilder.endInitContainer();
|
||||
}
|
||||
|
||||
private static void addContainer(PodSpecBuilder builder, Context<Keycloak> context, Collection<String> availableVolumes, Collection<String> requiredVolumes) {
|
||||
var existing = CRDUtils.firstContainerOf(ContextUtils.getDesiredStatefulSet(context)).orElseThrow();
|
||||
private static void addContainer(PodSpecBuilder builder, StatefulSet desired) {
|
||||
var existing = CRDUtils.firstContainerOf(desired).orElseThrow();
|
||||
var containerBuilder = builder.addNewContainerLike(existing);
|
||||
configureContainer(containerBuilder, CONTAINER_NAME, CONTAINER_ARGS, availableVolumes, requiredVolumes);
|
||||
configureContainer(containerBuilder, CONTAINER_NAME, CONTAINER_ARGS);
|
||||
containerBuilder.endContainer();
|
||||
}
|
||||
|
||||
private static void configureContainer(ContainerFluent<?> containerBuilder, String name, List<String> args, Collection<String> availableVolumes, Collection<String> requiredVolumes) {
|
||||
private static void configureContainer(ContainerFluent<?> containerBuilder, String name, List<String> args) {
|
||||
containerBuilder.withName(name);
|
||||
containerBuilder.withArgs(replaceStartWithUpdateCommand(containerBuilder.getArgs(), args));
|
||||
|
||||
// remove volume devices
|
||||
containerBuilder.withVolumeDevices();
|
||||
|
||||
// add existing volume mounts
|
||||
var volumeMounts = containerBuilder.buildVolumeMounts();
|
||||
if (volumeMounts != null) {
|
||||
var newVolumeMounts = volumeMounts.stream()
|
||||
.filter(volumeMount -> availableVolumes.contains(volumeMount.getName()))
|
||||
.filter(volumeMount -> !volumeMount.getName().startsWith("kube-api"))
|
||||
.peek(volumeMount -> requiredVolumes.add(volumeMount.getName()))
|
||||
.toList();
|
||||
containerBuilder.withVolumeMounts(newVolumeMounts);
|
||||
}
|
||||
@ -194,24 +199,10 @@ public class KeycloakUpdateJobDependentResource extends CRUDKubernetesDependentR
|
||||
.endVolumeMount();
|
||||
}
|
||||
|
||||
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).orElseThrow()).forEach(volumeConsumer);
|
||||
CRDUtils.volumesFromStatefulSet(ContextUtils.getDesiredStatefulSet(context)).forEach(volumeConsumer);
|
||||
return allVolumes;
|
||||
}
|
||||
|
||||
|
||||
private static List<String> replaceStartWithUpdateCommand(List<String> currentArgs, List<String> updateArgs) {
|
||||
return currentArgs.stream().
|
||||
<String>mapMulti((arg, downstream) -> {
|
||||
if (START_ARGS.contains(arg)) {
|
||||
updateArgs.forEach(downstream);
|
||||
return;
|
||||
}
|
||||
downstream.accept(arg);
|
||||
}).toList();
|
||||
// note that using start-dev via the unsupported podTemplate will fail - that is fine as rolling updates shouldn't apply
|
||||
// TODO: reuse ConfigArgConfigSource parsing, so that we don't confuse what the command is
|
||||
return Stream.concat(updateArgs.stream(), currentArgs.stream().filter(arg -> !arg.equals("start"))).toList();
|
||||
}
|
||||
|
||||
private static String keycloakHash(Keycloak keycloak) {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user