parent
835ad30c83
commit
e516d27f24
8 changed files with 257 additions and 306 deletions
|
@ -62,6 +62,7 @@ import static org.keycloak.operator.crds.v2alpha1.CRDUtils.isTlsConfigured;
|
|||
|
||||
public class KeycloakDeployment extends OperatorManagedResource<StatefulSet> {
|
||||
|
||||
public static final String OPTIMIZED_ARG = "--optimized";
|
||||
private final Config operatorConfig;
|
||||
private final KeycloakDistConfigurator distConfigurator;
|
||||
|
||||
|
@ -226,7 +227,7 @@ public class KeycloakDeployment extends OperatorManagedResource<StatefulSet> {
|
|||
containerBuilder.withArgs("--verbose", "start");
|
||||
}
|
||||
if (customImage.isPresent()) {
|
||||
containerBuilder.addToArgs("--optimized");
|
||||
containerBuilder.addToArgs(OPTIMIZED_ARG);
|
||||
}
|
||||
|
||||
// probes
|
||||
|
|
|
@ -16,10 +16,9 @@
|
|||
*/
|
||||
package org.keycloak.operator.controllers;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import io.fabric8.kubernetes.api.model.apps.StatefulSet;
|
||||
import io.fabric8.kubernetes.api.model.batch.v1.Job;
|
||||
import io.fabric8.kubernetes.client.KubernetesClient;
|
||||
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;
|
||||
|
@ -28,42 +27,38 @@ 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.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 org.keycloak.operator.Constants;
|
||||
|
||||
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 jakarta.inject.Inject;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import jakarta.inject.Inject;
|
||||
|
||||
import static io.javaoperatorsdk.operator.api.reconciler.Constants.WATCH_CURRENT_NAMESPACE;
|
||||
|
||||
@ControllerConfiguration(namespaces = WATCH_CURRENT_NAMESPACE)
|
||||
public class KeycloakRealmImportController implements Reconciler<KeycloakRealmImport>, EventSourceInitializer<KeycloakRealmImport>, ErrorStatusHandler<KeycloakRealmImport> {
|
||||
@ControllerConfiguration(namespaces = WATCH_CURRENT_NAMESPACE,
|
||||
dependents = {
|
||||
@Dependent(type = KeycloakRealmImportSecretDependentResource.class)
|
||||
})
|
||||
public class KeycloakRealmImportController implements Reconciler<KeycloakRealmImport>, ErrorStatusHandler<KeycloakRealmImport>, EventSourceInitializer<KeycloakRealmImport> {
|
||||
|
||||
@Inject
|
||||
KubernetesClient client;
|
||||
|
||||
@Inject
|
||||
ObjectMapper jsonMapper;
|
||||
volatile KeycloakRealmImportJobDependentResource jobDependentResource;
|
||||
|
||||
@Override
|
||||
public Map<String, EventSource> prepareEventSources(EventSourceContext<KeycloakRealmImport> context) {
|
||||
InformerConfiguration<Job> jobIC = InformerConfiguration
|
||||
.from(Job.class)
|
||||
.withLabelSelector(Constants.DEFAULT_LABELS_AS_STRING)
|
||||
.withNamespaces(context.getControllerConfiguration().getConfigurationService().getKubernetesClient().getNamespace())
|
||||
.withSecondaryToPrimaryMapper(Mappers.fromOwnerReference())
|
||||
.withOnUpdateFilter(new MetadataAwareOnUpdateFilter<>())
|
||||
.build();
|
||||
|
||||
return EventSourceInitializer.nameEventSources(new InformerEventSource<>(jobIC, context));
|
||||
this.jobDependentResource = new KeycloakRealmImportJobDependentResource();
|
||||
this.jobDependentResource.setKubernetesClient(context.getClient());
|
||||
return EventSourceInitializer.nameEventSourcesFromDependentResource(context, jobDependentResource);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -75,12 +70,16 @@ public class KeycloakRealmImportController implements Reconciler<KeycloakRealmIm
|
|||
|
||||
var statusBuilder = new KeycloakRealmImportStatusBuilder();
|
||||
|
||||
var realmImportSecret = new KeycloakRealmImportSecret(client, realm, jsonMapper);
|
||||
realmImportSecret.createOrUpdateReconciled();
|
||||
Job existingJob = context.getSecondaryResource(Job.class).orElse(null);
|
||||
StatefulSet existingDeployment = context.getClient().resources(StatefulSet.class).inNamespace(realm.getMetadata().getNamespace())
|
||||
.withName(realm.getSpec().getKeycloakCRName()).get();
|
||||
|
||||
var realmImportJob = new KeycloakRealmImportJob(client, realm, realmImportSecret.getSecretName());
|
||||
realmImportJob.createOrUpdateReconciled();
|
||||
realmImportJob.updateStatus(statusBuilder);
|
||||
if (existingDeployment != null) {
|
||||
context.managedDependentResourceContext().put(StatefulSet.class, existingDeployment);
|
||||
jobDependentResource.reconcile(realm, context);
|
||||
}
|
||||
|
||||
updateStatus(statusBuilder, realm, existingJob, existingDeployment);
|
||||
|
||||
var status = statusBuilder.build();
|
||||
|
||||
|
@ -114,4 +113,45 @@ public class KeycloakRealmImportController implements Reconciler<KeycloakRealmIm
|
|||
realm.setStatus(status);
|
||||
return ErrorStatusUpdateControl.updateStatus(realm);
|
||||
}
|
||||
|
||||
public void updateStatus(KeycloakRealmImportStatusBuilder status, KeycloakRealmImport realmCR, Job existingJob, StatefulSet existingDeployment) {
|
||||
if (existingDeployment == null) {
|
||||
status.addErrorMessage("No existing Deployment found, waiting for it to be created");
|
||||
return;
|
||||
}
|
||||
|
||||
if (existingJob == null) {
|
||||
Log.info("Job about to start");
|
||||
status.addStartedMessage("Import Job will start soon");
|
||||
} else {
|
||||
Log.info("Job already executed - not recreating");
|
||||
var oldStatus = existingJob.getStatus();
|
||||
var lastReportedStatus = realmCR.getStatus();
|
||||
|
||||
if (oldStatus == null) {
|
||||
Log.info("Job started");
|
||||
status.addStartedMessage("Import Job started");
|
||||
} 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
|
||||
}
|
||||
status.addDone();
|
||||
} else if (oldStatus.getFailed() != null && oldStatus.getFailed() > 0) {
|
||||
Log.info("Job Failed");
|
||||
status.addErrorMessage("Import Job failed");
|
||||
} else {
|
||||
Log.info("Job running");
|
||||
status.addStartedMessage("Import Job running");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void rollingRestart(KeycloakRealmImport realmCR) {
|
||||
client.apps().statefulSets()
|
||||
.inNamespace(realmCR.getMetadata().getNamespace())
|
||||
.withName(realmCR.getSpec().getKeycloakCRName())
|
||||
.rolling().restart();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -1,217 +0,0 @@
|
|||
/*
|
||||
* Copyright 2021 Red Hat, Inc. and/or its affiliates
|
||||
* and other contributors as indicated by the @author tags.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.keycloak.operator.controllers;
|
||||
|
||||
import io.fabric8.kubernetes.api.model.Container;
|
||||
import io.fabric8.kubernetes.api.model.EnvVarBuilder;
|
||||
import io.fabric8.kubernetes.api.model.HasMetadata;
|
||||
import io.fabric8.kubernetes.api.model.PodTemplateSpec;
|
||||
import io.fabric8.kubernetes.api.model.SecretVolumeSourceBuilder;
|
||||
import io.fabric8.kubernetes.api.model.Volume;
|
||||
import io.fabric8.kubernetes.api.model.VolumeBuilder;
|
||||
import io.fabric8.kubernetes.api.model.VolumeMountBuilder;
|
||||
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.client.KubernetesClient;
|
||||
import io.fabric8.kubernetes.client.utils.KubernetesResourceUtil;
|
||||
import io.quarkus.logging.Log;
|
||||
|
||||
import org.keycloak.operator.crds.v2alpha1.deployment.Keycloak;
|
||||
import org.keycloak.operator.crds.v2alpha1.realmimport.KeycloakRealmImport;
|
||||
import org.keycloak.operator.crds.v2alpha1.realmimport.KeycloakRealmImportStatusBuilder;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
import static org.keycloak.operator.controllers.KeycloakDistConfigurator.getKeycloakOptionEnvVarName;
|
||||
|
||||
public class KeycloakRealmImportJob extends OperatorManagedResource {
|
||||
|
||||
private final Keycloak keycloak;
|
||||
private final KeycloakRealmImport realmCR;
|
||||
private final StatefulSet existingDeployment;
|
||||
private final Job existingJob;
|
||||
private final String secretName;
|
||||
private final String volumeName;
|
||||
|
||||
public KeycloakRealmImportJob(KubernetesClient client, KeycloakRealmImport realmCR, String secretName) {
|
||||
super(client, realmCR);
|
||||
this.realmCR = realmCR;
|
||||
this.secretName = secretName;
|
||||
this.volumeName = KubernetesResourceUtil.sanitizeName(secretName + "-volume");
|
||||
|
||||
this.existingJob = fetchExisting(Job.class, getName());
|
||||
this.existingDeployment = fetchExisting(StatefulSet.class, getKeycloakName());
|
||||
this.keycloak = fetchExisting(Keycloak.class, getKeycloakName());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Optional<HasMetadata> getReconciledResource() {
|
||||
if (existingDeployment == null) {
|
||||
return Optional.empty(); // handled in the status
|
||||
} else if (existingJob == null) {
|
||||
Log.info("Creating a new Job");
|
||||
return Optional.of(createImportJob());
|
||||
} else {
|
||||
Log.info("Job already available");
|
||||
return Optional.empty();
|
||||
}
|
||||
}
|
||||
|
||||
private <T extends HasMetadata> T fetchExisting(Class<T> type, String name) {
|
||||
return client
|
||||
.resources(type)
|
||||
.inNamespace(getNamespace())
|
||||
.withName(name)
|
||||
.get();
|
||||
}
|
||||
|
||||
private Job buildJob(PodTemplateSpec keycloakPodTemplate) {
|
||||
keycloakPodTemplate.getSpec().setRestartPolicy("Never");
|
||||
|
||||
return new JobBuilder()
|
||||
.withNewMetadata()
|
||||
.withName(getName())
|
||||
.withNamespace(getNamespace())
|
||||
.endMetadata()
|
||||
.withNewSpec()
|
||||
.withTemplate(keycloakPodTemplate)
|
||||
.endSpec()
|
||||
.build();
|
||||
}
|
||||
|
||||
private Volume buildSecretVolume() {
|
||||
return new VolumeBuilder()
|
||||
.withName(volumeName)
|
||||
.withSecret(new SecretVolumeSourceBuilder()
|
||||
.withSecretName(secretName)
|
||||
.build())
|
||||
.build();
|
||||
}
|
||||
|
||||
private Job createImportJob() {
|
||||
var keycloakPodTemplate = this
|
||||
.existingDeployment
|
||||
.getSpec()
|
||||
.getTemplate();
|
||||
|
||||
buildKeycloakJobContainer(keycloakPodTemplate.getSpec().getContainers().get(0));
|
||||
keycloakPodTemplate.getSpec().getVolumes().add(buildSecretVolume());
|
||||
|
||||
var labels = keycloakPodTemplate.getMetadata().getLabels();
|
||||
|
||||
// The Job should not be selected with app=keycloak
|
||||
labels.put("app", "keycloak-realm-import");
|
||||
|
||||
var envvars = keycloakPodTemplate
|
||||
.getSpec()
|
||||
.getContainers()
|
||||
.get(0)
|
||||
.getEnv();
|
||||
|
||||
var cacheEnvVarName = getKeycloakOptionEnvVarName("cache");
|
||||
var healthEnvVarName = getKeycloakOptionEnvVarName("health-enabled");
|
||||
envvars.removeIf(e -> e.getName().equals(cacheEnvVarName) || e.getName().equals(healthEnvVarName));
|
||||
|
||||
// The Job should not connect to the cache
|
||||
envvars.add(new EnvVarBuilder().withName(cacheEnvVarName).withValue("local").build());
|
||||
// The Job doesn't need health to be enabled
|
||||
envvars.add(new EnvVarBuilder().withName(healthEnvVarName).withValue("false").build());
|
||||
|
||||
return buildJob(keycloakPodTemplate);
|
||||
}
|
||||
|
||||
private void buildKeycloakJobContainer(Container keycloakContainer) {
|
||||
var importMntPath = "/mnt/realm-import/";
|
||||
|
||||
var command = List.of("/bin/bash");
|
||||
|
||||
var override = "--override=false";
|
||||
|
||||
var runBuild = (keycloak.getSpec().getImage() == null) ? "/opt/keycloak/bin/kc.sh --verbose build && " : "";
|
||||
|
||||
var commandArgs = List.of("-c",
|
||||
runBuild + "/opt/keycloak/bin/kc.sh --verbose import --optimized --file='" + importMntPath + getRealmName() + "-realm.json' " + override);
|
||||
|
||||
keycloakContainer
|
||||
.setCommand(command);
|
||||
keycloakContainer
|
||||
.setArgs(commandArgs);
|
||||
var volumeMount = new VolumeMountBuilder()
|
||||
.withName(volumeName)
|
||||
.withReadOnly(true)
|
||||
.withMountPath(importMntPath)
|
||||
.build();
|
||||
|
||||
keycloakContainer.getVolumeMounts().add(volumeMount);
|
||||
|
||||
// Disable probes since we are not really starting the server
|
||||
keycloakContainer.setReadinessProbe(null);
|
||||
keycloakContainer.setLivenessProbe(null);
|
||||
}
|
||||
|
||||
|
||||
public void updateStatus(KeycloakRealmImportStatusBuilder status) {
|
||||
if (existingDeployment == null) {
|
||||
status.addErrorMessage("No existing Deployment found, waiting for it to be created");
|
||||
return;
|
||||
}
|
||||
|
||||
if (existingJob == null) {
|
||||
Log.info("Job about to start");
|
||||
status.addStartedMessage("Import Job will start soon");
|
||||
} else {
|
||||
Log.info("Job already executed - not recreating");
|
||||
var oldStatus = existingJob.getStatus();
|
||||
var lastReportedStatus = realmCR.getStatus();
|
||||
|
||||
if (oldStatus == null) {
|
||||
Log.info("Job started");
|
||||
status.addStartedMessage("Import Job started");
|
||||
} else if (oldStatus.getSucceeded() != null && oldStatus.getSucceeded() > 0) {
|
||||
if (!lastReportedStatus.isDone()) {
|
||||
Log.info("Job finished performing a rolling restart of the deployment");
|
||||
rollingRestart(); // could be based upon a hash annotation on the deployment instead
|
||||
}
|
||||
status.addDone();
|
||||
} else if (oldStatus.getFailed() != null && oldStatus.getFailed() > 0) {
|
||||
Log.info("Job Failed");
|
||||
status.addErrorMessage("Import Job failed");
|
||||
} else {
|
||||
Log.info("Job running");
|
||||
status.addStartedMessage("Import Job running");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getName() {
|
||||
return realmCR.getMetadata().getName();
|
||||
}
|
||||
|
||||
private String getKeycloakName() { return realmCR.getSpec().getKeycloakCRName(); }
|
||||
|
||||
private String getRealmName() { return realmCR.getSpec().getRealm().getRealm(); }
|
||||
|
||||
private void rollingRestart() {
|
||||
client.apps().statefulSets()
|
||||
.inNamespace(getNamespace())
|
||||
.withName(getKeycloakName())
|
||||
.rolling().restart();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,139 @@
|
|||
/*
|
||||
* Copyright 2021 Red Hat, Inc. and/or its affiliates
|
||||
* and other contributors as indicated by the @author tags.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.keycloak.operator.controllers;
|
||||
|
||||
import io.fabric8.kubernetes.api.model.Container;
|
||||
import io.fabric8.kubernetes.api.model.EnvVarBuilder;
|
||||
import io.fabric8.kubernetes.api.model.PodTemplateSpec;
|
||||
import io.fabric8.kubernetes.api.model.SecretVolumeSourceBuilder;
|
||||
import io.fabric8.kubernetes.api.model.Volume;
|
||||
import io.fabric8.kubernetes.api.model.VolumeBuilder;
|
||||
import io.fabric8.kubernetes.api.model.VolumeMountBuilder;
|
||||
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.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.KubernetesDependentResource;
|
||||
import io.javaoperatorsdk.operator.processing.dependent.kubernetes.KubernetesDependentResourceConfig;
|
||||
|
||||
import org.keycloak.operator.Constants;
|
||||
import org.keycloak.operator.crds.v2alpha1.realmimport.KeycloakRealmImport;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import static org.keycloak.operator.controllers.KeycloakDistConfigurator.getKeycloakOptionEnvVarName;
|
||||
|
||||
public class KeycloakRealmImportJobDependentResource extends KubernetesDependentResource<Job, KeycloakRealmImport> implements Creator<Job, KeycloakRealmImport>, GarbageCollected<KeycloakRealmImport> {
|
||||
|
||||
KeycloakRealmImportJobDependentResource() {
|
||||
super(Job.class);
|
||||
this.configureWith(new KubernetesDependentResourceConfig<Job>()
|
||||
.setLabelSelector(Constants.DEFAULT_LABELS_AS_STRING));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Job desired(KeycloakRealmImport primary, Context<KeycloakRealmImport> context) {
|
||||
StatefulSet existingDeployment = context.managedDependentResourceContext().get(StatefulSet.class, StatefulSet.class).orElseThrow();
|
||||
|
||||
var keycloakPodTemplate = existingDeployment
|
||||
.getSpec()
|
||||
.getTemplate();
|
||||
|
||||
String secretName = KeycloakRealmImportSecretDependentResource.getSecretName(primary);
|
||||
String volumeName = KubernetesResourceUtil.sanitizeName(secretName + "-volume");
|
||||
|
||||
buildKeycloakJobContainer(keycloakPodTemplate.getSpec().getContainers().get(0), volumeName, primary.getRealmName());
|
||||
keycloakPodTemplate.getSpec().getVolumes().add(buildSecretVolume(volumeName, secretName));
|
||||
|
||||
var labels = keycloakPodTemplate.getMetadata().getLabels();
|
||||
|
||||
// The Job should not be selected with app=keycloak
|
||||
labels.put("app", "keycloak-realm-import");
|
||||
|
||||
var envvars = keycloakPodTemplate
|
||||
.getSpec()
|
||||
.getContainers()
|
||||
.get(0)
|
||||
.getEnv();
|
||||
|
||||
var cacheEnvVarName = getKeycloakOptionEnvVarName("cache");
|
||||
var healthEnvVarName = getKeycloakOptionEnvVarName("health-enabled");
|
||||
envvars.removeIf(e -> e.getName().equals(cacheEnvVarName) || e.getName().equals(healthEnvVarName));
|
||||
|
||||
// The Job should not connect to the cache
|
||||
envvars.add(new EnvVarBuilder().withName(cacheEnvVarName).withValue("local").build());
|
||||
// The Job doesn't need health to be enabled
|
||||
envvars.add(new EnvVarBuilder().withName(healthEnvVarName).withValue("false").build());
|
||||
|
||||
return buildJob(keycloakPodTemplate, primary);
|
||||
}
|
||||
|
||||
private Job buildJob(PodTemplateSpec keycloakPodTemplate, KeycloakRealmImport primary) {
|
||||
keycloakPodTemplate.getSpec().setRestartPolicy("Never");
|
||||
|
||||
return new JobBuilder()
|
||||
.withNewMetadata()
|
||||
.withName(primary.getMetadata().getName())
|
||||
.withNamespace(primary.getMetadata().getNamespace())
|
||||
// this is labeling the instance as the realm import, not the keycloak
|
||||
.withLabels(OperatorManagedResource.allInstanceLabels(primary))
|
||||
.endMetadata()
|
||||
.withNewSpec()
|
||||
.withTemplate(keycloakPodTemplate)
|
||||
.endSpec()
|
||||
.build();
|
||||
}
|
||||
|
||||
private Volume buildSecretVolume(String volumeName, String secretName) {
|
||||
return new VolumeBuilder()
|
||||
.withName(volumeName)
|
||||
.withSecret(new SecretVolumeSourceBuilder()
|
||||
.withSecretName(secretName)
|
||||
.build())
|
||||
.build();
|
||||
}
|
||||
|
||||
private void buildKeycloakJobContainer(Container keycloakContainer, String volumeName, String realmName) {
|
||||
var importMntPath = "/mnt/realm-import/";
|
||||
|
||||
var command = List.of("/bin/bash");
|
||||
|
||||
var override = "--override=false";
|
||||
|
||||
var runBuild = !keycloakContainer.getArgs().contains(KeycloakDeployment.OPTIMIZED_ARG) ? "/opt/keycloak/bin/kc.sh --verbose build && " : "";
|
||||
|
||||
var commandArgs = List.of("-c",
|
||||
runBuild + "/opt/keycloak/bin/kc.sh --verbose import --optimized --file='" + importMntPath + realmName + "-realm.json' " + override);
|
||||
|
||||
keycloakContainer.setCommand(command);
|
||||
keycloakContainer.setArgs(commandArgs);
|
||||
var volumeMount = new VolumeMountBuilder()
|
||||
.withName(volumeName)
|
||||
.withReadOnly(true)
|
||||
.withMountPath(importMntPath)
|
||||
.build();
|
||||
|
||||
keycloakContainer.getVolumeMounts().add(volumeMount);
|
||||
|
||||
// Disable probes since we are not really starting the server
|
||||
keycloakContainer.setReadinessProbe(null);
|
||||
keycloakContainer.setLivenessProbe(null);
|
||||
}
|
||||
}
|
|
@ -1,60 +0,0 @@
|
|||
package org.keycloak.operator.controllers;
|
||||
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import io.fabric8.kubernetes.api.model.HasMetadata;
|
||||
import io.fabric8.kubernetes.api.model.Secret;
|
||||
import io.fabric8.kubernetes.api.model.SecretBuilder;
|
||||
import io.fabric8.kubernetes.client.KubernetesClient;
|
||||
import io.fabric8.kubernetes.client.utils.KubernetesResourceUtil;
|
||||
import org.keycloak.operator.crds.v2alpha1.realmimport.KeycloakRealmImport;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
public class KeycloakRealmImportSecret extends OperatorManagedResource {
|
||||
|
||||
private final KeycloakRealmImport realmCR;
|
||||
private final String secretName;
|
||||
private final ObjectMapper jsonMapper;
|
||||
|
||||
public KeycloakRealmImportSecret(KubernetesClient client, KeycloakRealmImport realmCR, ObjectMapper jsonMapper) {
|
||||
super(client, realmCR);
|
||||
this.realmCR = realmCR;
|
||||
this.jsonMapper = jsonMapper;
|
||||
this.secretName = KubernetesResourceUtil.sanitizeName(getName() + "-" + realmCR.getSpec().getRealm().getRealm() + "-realm");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Optional<HasMetadata> getReconciledResource() {
|
||||
return Optional.of(createSecret());
|
||||
}
|
||||
|
||||
private Secret createSecret() {
|
||||
var fileName = getRealmName() + "-realm.json";
|
||||
var content = "";
|
||||
try {
|
||||
content = jsonMapper.writeValueAsString(this.realmCR.getSpec().getRealm());
|
||||
} catch (JsonProcessingException cause) {
|
||||
throw new RuntimeException("Failed to read the Realm Representation", cause);
|
||||
}
|
||||
|
||||
return new SecretBuilder()
|
||||
.withNewMetadata()
|
||||
.withName(secretName)
|
||||
.withNamespace(getNamespace())
|
||||
.endMetadata()
|
||||
.addToStringData(fileName, content)
|
||||
.build();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getName() {
|
||||
return realmCR.getMetadata().getName();
|
||||
}
|
||||
|
||||
private String getRealmName() { return realmCR.getSpec().getRealm().getRealm(); }
|
||||
|
||||
public String getSecretName() {
|
||||
return secretName;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,41 @@
|
|||
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.reconciler.Context;
|
||||
import io.javaoperatorsdk.operator.processing.dependent.kubernetes.CRUDKubernetesDependentResource;
|
||||
import io.javaoperatorsdk.operator.processing.dependent.kubernetes.KubernetesDependent;
|
||||
|
||||
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)
|
||||
public class KeycloakRealmImportSecretDependentResource extends CRUDKubernetesDependentResource<Secret, KeycloakRealmImport> {
|
||||
|
||||
public KeycloakRealmImportSecretDependentResource() {
|
||||
super(Secret.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Secret desired(KeycloakRealmImport primary, Context<KeycloakRealmImport> context) {
|
||||
var fileName = primary.getRealmName() + "-realm.json";
|
||||
var content = context.getClient().getKubernetesSerialization().asJson(primary.getSpec().getRealm());
|
||||
|
||||
return new SecretBuilder()
|
||||
.withNewMetadata()
|
||||
.withName(getSecretName(primary))
|
||||
.withNamespace(primary.getMetadata().getNamespace())
|
||||
// this is labeling the instance as the realm import, not the keycloak
|
||||
.addToLabels(OperatorManagedResource.allInstanceLabels(primary))
|
||||
.endMetadata()
|
||||
.addToData(fileName, Utils.asBase64(content))
|
||||
.build();
|
||||
}
|
||||
|
||||
public static String getSecretName(KeycloakRealmImport realmCR) {
|
||||
return KubernetesResourceUtil.sanitizeName(realmCR.getSpec().getKeycloakCRName() + "-" + realmCR.getRealmName() + "-realm");
|
||||
}
|
||||
|
||||
}
|
|
@ -27,7 +27,6 @@ import io.fabric8.kubernetes.client.dsl.base.PatchType;
|
|||
import io.quarkus.logging.Log;
|
||||
|
||||
import org.keycloak.operator.Constants;
|
||||
import org.keycloak.operator.crds.v2alpha1.deployment.Keycloak;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedHashMap;
|
||||
|
@ -104,9 +103,9 @@ public abstract class OperatorManagedResource<T extends HasMetadata> {
|
|||
return labels;
|
||||
}
|
||||
|
||||
public static Map<String, String> allInstanceLabels(Keycloak keycloak) {
|
||||
public static Map<String, String> allInstanceLabels(HasMetadata primary) {
|
||||
var labels = new LinkedHashMap<>(Constants.DEFAULT_LABELS);
|
||||
labels.put(Constants.INSTANCE_LABEL, keycloak.getMetadata().getName());
|
||||
labels.put(Constants.INSTANCE_LABEL, primary.getMetadata().getName());
|
||||
return labels;
|
||||
}
|
||||
|
||||
|
|
|
@ -21,14 +21,17 @@ import io.fabric8.kubernetes.api.model.Namespaced;
|
|||
import io.fabric8.kubernetes.client.CustomResource;
|
||||
import io.fabric8.kubernetes.model.annotation.Group;
|
||||
import io.fabric8.kubernetes.model.annotation.Version;
|
||||
import io.quarkiverse.operatorsdk.bundle.runtime.CSVMetadata;
|
||||
import io.sundr.builder.annotations.Buildable;
|
||||
import io.sundr.builder.annotations.BuildableReference;
|
||||
import io.quarkiverse.operatorsdk.bundle.runtime.CSVMetadata;
|
||||
|
||||
import org.keycloak.operator.Constants;
|
||||
import org.keycloak.representations.idm.ComponentExportRepresentation;
|
||||
import org.keycloak.representations.idm.GroupRepresentation;
|
||||
import org.keycloak.representations.idm.authorization.ScopeRepresentation;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||
|
||||
@CSVMetadata(
|
||||
description="Represents a Keycloak Realm Import",
|
||||
displayName="KeycloakRealmImport"
|
||||
|
@ -47,4 +50,9 @@ import org.keycloak.representations.idm.authorization.ScopeRepresentation;
|
|||
@SchemaSwap(originalType = ScopeRepresentation.class, fieldName = "resources")
|
||||
public class KeycloakRealmImport extends CustomResource<KeycloakRealmImportSpec, KeycloakRealmImportStatus> implements Namespaced {
|
||||
|
||||
@JsonIgnore
|
||||
public String getRealmName() {
|
||||
return this.getSpec().getRealm().getRealm();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue