Converts realm import to dependent resources (#22299)

Closes #22223
This commit is contained in:
Steven Hawkins 2023-08-21 12:18:56 -04:00 committed by GitHub
parent 835ad30c83
commit e516d27f24
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 257 additions and 306 deletions

View file

@ -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

View file

@ -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();
}
}

View file

@ -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();
}
}

View file

@ -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);
}
}

View file

@ -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;
}
}

View file

@ -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");
}
}

View file

@ -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;
}

View file

@ -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();
}
}