8000 Added field to the RealmImport spec to replace environment variables within the realm import by stustison · Pull Request #26471 · keycloak/keycloak · GitHub
[go: up one dir, main page]

Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,10 @@
import org.keycloak.operator.Constants;
import org.keycloak.operator.Utils;
import org.keycloak.o 8000 perator.crds.v2alpha1.realmimport.KeycloakRealmImport;
import org.keycloak.operator.crds.v2alpha1.realmimport.PlaceholderSecret;

import java.util.List;
import java.util.Map;
import java.util.Set;

import static org.keycloak.operator.Utils.addResources;
Expand All @@ -60,6 +62,8 @@ public class KeycloakRealmImportJobDependentResource extends KubernetesDependent
@Override
protected Job desired(KeycloakRealmImport primary, Context<KeycloakRealmImport> context) {
StatefulSet existingDeployment = context.managedDependentResourceContext().get(StatefulSet.class, StatefulSet.class).orElseThrow();
Map<String, PlaceholderSecret> placeholders = primary.getSpec().getPlaceholders();
boolean replacePlaceholders = (placeholders != null && !placeholders.isEmpty());

var keycloakPodTemplate = existingDeployment
.getSpec()
Expand All @@ -68,7 +72,7 @@ protected Job desired(KeycloakRealmImport primary, Context<KeycloakRealmImport>
String secretName = KeycloakRealmImportSecretDependentResource.getSecretName(primary);
String volumeName = KubernetesResourceUtil.sanitizeName(secretName + "-volume");

buildKeycloakJobContainer(keycloakPodTemplate.getSpec().getContainers().get(0), primary, volumeName);
buildKeycloakJobContainer(keycloakPodTemplate.getSpec().getContainers().get(0), primary, volumeName, replacePlaceholders);
keycloakPodTemplate.getSpec().getVolumes().add(buildSecretVolume(volumeName, secretName));

var labels = keycloakPodTemplate.getMetadata().getLabels();
Expand All @@ -93,6 +97,22 @@ protected Job desired(KeycloakRealmImport primary, Context<KeycloakRealmImport>
// The Job doesn't need health to be enabled
envvars.add(new EnvVarBuilder().withName(healthEnvVarName).withValue("false").build());

if (replacePlaceholders) {
for (Map.Entry<String, PlaceholderSecret> secret : primary.getSpec().getPlaceholders().entrySet()) {
envvars.add(
new EnvVarBuilder()
.withName(secret.getKey())
.withNewValueFrom()
.withNewSecretKeyRef()
.withName(secret.getValue().getSecret().getName())
.withKey(secret.getValue().getSecret().getKey())
.withOptional(false)
.endSecretKeyRef()
.endValueFrom()
.build());
}
}

return buildJob(keycloakPodTemplate, primary);
}

Expand Down Expand Up @@ -121,7 +141,7 @@ private Volume buildSecretVolume(String volumeName, String secretName) {
.build();
}

private void buildKeycloakJobContainer(Container keycloakContainer, KeycloakRealmImport keycloakRealmImport, String volumeName) {
private void buildKeycloakJobContainer(Container keycloakContainer, KeycloakRealmImport keycloakRealmImport, String volumeName, boolean replacePlaceholders) {
var importMntPath = "/mnt/realm-import/";

var command = List.of("/bin/bash");
Expand All @@ -130,8 +150,10 @@ private void buildKeycloakJobContainer(Container keycloakContainer, KeycloakReal

var runBuild = !keycloakContainer.getArgs().contains(KeycloakDeploymentDependentResource.OPTIMIZED_ARG) ? "/opt/keycloak/bin/kc.sh --verbose build && " : "";

var replaceOption = (replacePlaceholders) ? " -Dkeycloak.migration.replace-placeholders=true": "";

var commandArgs = List.of("-c",
runBuild + "/opt/keycloak/bin/kc.sh --verbose import --optimized --file='" + importMntPath + keycloakRealmImport.getRealmName() + "-realm.json' " + override);
runBuild + "/opt/keycloak/bin/kc.sh" + replaceOption + " --verbose import --optimized --file='" + importMntPath + keycloakRealmImport.getRealmName() + "-realm.json' " + override);

keycloakContainer.setCommand(command);
keycloakContainer.setArgs(commandArgs);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,9 @@
import io.fabric8.kubernetes.api.model.ResourceRequirements;
import org.keycloak.representations.idm.RealmRepresentation;

import java.util.List;
import java.util.Map;

public class KeycloakRealmImportSpec {

@Required
Expand All @@ -35,6 +38,9 @@ public class KeycloakRealmImportSpec {
@JsonPropertyDescription("Compute Resources required by Keycloak container. If not specified, the value is inherited from the Keycloak CR.")
private ResourceRequirements resourceRequirements;

@JsonPropertyDescription("Optionally set to replace ENV variable placeholders in the realm import.")
private Map<String, PlaceholderSecret> placeholders;

public String getKeycloakCRName() {
return keycloakCRName;
}
Expand All @@ -58,4 +64,12 @@ public ResourceRequirements getResourceRequirements() {
public void setResourceRequirements(ResourceRequirements resourceRequirements) {
this.resourceRequirements = resourceRequirements;
}

public Map<String, PlaceholderSecret> getPlaceholders() {
return placeholders;
}

public void setPlaceholders(Map<String, PlaceholderSecret> placeholders) {
this.placeholders = placeholders;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
/*
* Copyright 2022 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.crds.v2alpha1.realmimport;

import com.fasterxml.jackson.annotation.JsonInclude;
import io.fabric8.kubernetes.api.model.SecretKeySelector;
import io.sundr.builder.annotations.Buildable;

import java.util.Objects;

/**
* @author Scott Tustison
*/
@JsonInclude(JsonInclude.Include.NON_NULL)
Comment thread
stustison marked this conversation as resolved.
Resolved
@Buildable(editableEnabled = false, builderPackage = "io.fabric8.kubernetes.api.builder")
public class PlaceholderSecret {
private SecretKeySelector secret;

public PlaceholderSecret() {
}

public PlaceholderSecret(SecretKeySelector secret) {
this.secret = secret;
}

public SecretKeySelector getSecret() {
return secret;
}

public void setSecret(SecretKeySelector secret) {
this.secret = secret;
}

@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
PlaceholderSecret that = (PlaceholderSecret) o;
return getSecret().equals(that.getSecret());
}

@Override
public int hashCode() {
return Objects.hash(getSecret());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
import io.fabric8.kubernetes.api.model.LocalObjectReferenceBuilder;
import io.fabric8.kubernetes.api.model.Quantity;
import io.fabric8.kubernetes.api.model.ResourceRequirements;
import io.fabric8.kubernetes.api.model.Secret;
import io.quarkus.logging.Log;
import io.quarkus.test.junit.QuarkusTest;

Expand All @@ -34,10 +35,12 @@
import org.keycloak.operator.controllers.KeycloakServiceDependentResource;
import org.keycloak.operator.crds.v2alpha1.deployment.Keycloak;
import org.keycloak.operator.crds.v2alpha1.realmimport.KeycloakRealmImport;
import org.keycloak.operator.crds.v2alpha1.realmimport.KeycloakRealmImportBuilder;
import org.keycloak.operator.testsuite.utils.CRAssert;
import org.keycloak.operator.testsuite.utils.K8sUtils;

import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import java.util.stream.Collectors;

Expand All @@ -50,6 +53,7 @@
import static org.keycloak.operator.crds.v2alpha1.realmimport.KeycloakRealmImportStatusCondition.HAS_ERRORS;
import static org.keycloak.operator.crds.v2alpha1.realmimport.KeycloakRealmImportStatusCondition.STARTED;
import static org.keycloak.operator.testsuite.utils.K8sUtils.deployKeycloak;
import static org.keycloak.operator.testsuite.utils.K8sUtils.getResourceFromFile;
import static org.keycloak.operator.testsuite.utils.K8sUtils.inClusterCurl;

@QuarkusTest
Expand Down Expand Up @@ -87,10 +91,15 @@ private String getJobArgs() {
.collect(Collectors.joining());
}

protected static void deploySmtpSecret() {
K8sUtils.set(k8sclient, getResourceFromFile("example-smtp-secret.yaml", Secret.class));
}

@Test
public void testWorkingRealmImport() {
// Arrange
var kc = getTestKeycloakDeployment(false);

kc.getSpec().setImage(null); // checks the job args for the base, not custom image
kc.getSpec().setImagePullSecrets(Arrays.asList(new LocalObjectReferenceBuilder().withName("my-empty-secret").build()));
deployKeycloak(k8sclient, kc, false);
Expand All @@ -99,6 +108,28 @@ public void testWorkingRealmImport() {
K8sUtils.set(k8sclient, getClass().getResourceAsStream("/example-realm.yaml"));

// Assert
assertWorkingRealmImport(kc, false);
}

@Test
public void testWorkingRealmImportWithReplacement() {
// Arrange
var kc = getTestKeycloakDeployment(false);

deploySmtpSecret();

kc.getSpec().setImage(null); // checks the job args for the base, not custom image
kc.getSpec().setImagePullSecrets(Arrays.asList(new LocalObjectReferenceBuilder().withName("my-empty-secret").build()));
deployKeycloak(k8sclient, kc, false);

// Act
K8sUtils.set(k8sclient, getClass().getResourceAsStream("/example-realm-with-placeholders.yaml"));

// Assert
assertWorkingRealmImport(kc, true);
}

private void assertWorkingRealmImport(Keycloak kc, boolean checkPlaceholders) {
var crSelector = k8sclient
.resources(KeycloakRealmImport.class)
.inNamespace(namespace)
Expand Down Expand Up @@ -131,6 +162,16 @@ public void testWorkingRealmImport() {
var envvars = container.getEnv();
assertThat(envvars.stream().filter(e -> e.getName().equals(getKeycloakOptionEnvVarName("cache"))).findAny().get().getValue()).isEqualTo("local");
assertThat(envvars.stream().filter(e -> e.getName().equals(getKeycloakOptionEnvVarName("health-enabled"))).findAny().get().getValue()).isEqualTo("false");

if (checkPlaceholders) {
assertThat(envvars.stream().filter(e -> e.getName().equals(getKeycloakOptionEnvVarName("SMTP_PORT"))).findAny().get().getName()).isEqualTo("KC_SMTP_PORT");
assertThat(envvars.stream().filter(e -> e.getName().equals(getKeycloakOptionEnvVarName("SMTP_SERVER"))).findAny().get().getName()).isEqualTo("KC_SMTP_SERVER");
assertThat(envvars.stream().filter(e -> e.getName().equals(getKeycloakOptionEnvVarName("SMTP_FROM"))).findAny().get().getName()).isEqualTo("KC_SMTP_FROM");
} else {
assertThat(envvars.stream().filter(e -> e.getName().equals(getKeycloakOptionEnvVarName("SMTP_PORT"))).findAny().isEmpty()).isEqualTo(true);
assertThat(envvars.stream().filter(e -> e.getName().equals(getKeycloakOptionEnvVarName("SMTP_SERVER"))).findAny().isEmpty()).isEqualTo(true);
assertThat(envvars.stream().filter(e -> e.getName().equals(getKeycloakOptionEnvVarName("SMTP_FROM"))).findAny().isEmpty()).isEqualTo(true);
}
assertThat(job.getSpec().getTemplate().getSpec().getImagePullSecrets().size()).isEqualTo(1);
assertThat(job.getSpec().getTemplate().getSpec().getImagePullSecrets().get(0).getName()).isEqualTo("my-empty-secret");

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,12 @@ spec:
value: postgres
- name: db-password
value: testpassword
- name: SMTP_SERVER
value: "test-smtp-server"
- name: SMTP_PORT
value: "567"
- name: SMTP_FROM
value: "test-smtp-from@example.com"
hostname:
hostname: example.com
unsupported:
Expand Down
Loading
0