Refactor KeycloakDeploymentConfig
(#14880)
Co-authored-by: Martin Bartoš <mabartos@redhat.com>
This commit is contained in:
parent
97c4495c4f
commit
0afc4a8af7
4 changed files with 118 additions and 61 deletions
|
@ -49,7 +49,7 @@ import static io.smallrye.config.common.utils.StringUtil.replaceNonAlphanumericB
|
|||
public class KeycloakDeployment extends OperatorManagedResource implements StatusUpdater<KeycloakStatusBuilder> {
|
||||
|
||||
private final Config operatorConfig;
|
||||
private final KeycloakDeploymentConfig deploymentConfig;
|
||||
private final KeycloakDistConfigurator distConfigurator;
|
||||
|
||||
private final Keycloak keycloakCR;
|
||||
private final StatefulSet existingDeployment;
|
||||
|
@ -75,7 +75,8 @@ public class KeycloakDeployment extends OperatorManagedResource implements Statu
|
|||
}
|
||||
|
||||
this.baseDeployment = createBaseDeployment();
|
||||
this.deploymentConfig = createDeploymentConfig();
|
||||
this.distConfigurator = configureDist();
|
||||
mergePodTemplate(this.baseDeployment.getSpec().getTemplate());
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -394,10 +395,9 @@ public class KeycloakDeployment extends OperatorManagedResource implements Statu
|
|||
return baseDeployment;
|
||||
}
|
||||
|
||||
private KeycloakDeploymentConfig createDeploymentConfig() {
|
||||
final KeycloakDeploymentConfig config = new KeycloakDeploymentConfig(keycloakCR, baseDeployment, client);
|
||||
config.configureProperties();
|
||||
mergePodTemplate(baseDeployment.getSpec().getTemplate());
|
||||
private KeycloakDistConfigurator configureDist() {
|
||||
final KeycloakDistConfigurator config = new KeycloakDistConfigurator(keycloakCR, baseDeployment, client);
|
||||
config.configureDistOptions();
|
||||
return config;
|
||||
}
|
||||
|
||||
|
@ -485,7 +485,7 @@ public class KeycloakDeployment extends OperatorManagedResource implements Statu
|
|||
status.addRollingUpdateMessage("Rolling out deployment update");
|
||||
}
|
||||
|
||||
deploymentConfig.validateProperties(status);
|
||||
distConfigurator.validateOptions(status);
|
||||
}
|
||||
|
||||
public Set<String> getConfigSecretsNames() {
|
||||
|
|
|
@ -17,34 +17,44 @@
|
|||
|
||||
package org.keycloak.operator.controllers;
|
||||
|
||||
import io.fabric8.kubernetes.api.model.EnvVar;
|
||||
import io.fabric8.kubernetes.api.model.EnvVarBuilder;
|
||||
import io.fabric8.kubernetes.api.model.ExecActionBuilder;
|
||||
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.client.KubernetesClient;
|
||||
import io.quarkus.logging.Log;
|
||||
import org.keycloak.common.util.CollectionUtil;
|
||||
import org.keycloak.operator.Constants;
|
||||
import org.keycloak.operator.crds.v2alpha1.deployment.Keycloak;
|
||||
import org.keycloak.operator.crds.v2alpha1.deployment.KeycloakStatusBuilder;
|
||||
import org.keycloak.operator.crds.v2alpha1.deployment.ValueOrSecret;
|
||||
import org.keycloak.operator.crds.v2alpha1.deployment.spec.FeatureSpec;
|
||||
import org.keycloak.operator.crds.v2alpha1.deployment.spec.TransactionsSpec;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Base64;
|
||||
import java.util.Collection;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static org.keycloak.operator.controllers.KeycloakDeployment.getEnvVarName;
|
||||
|
||||
/**
|
||||
* Configuration for the KeycloakDeployment
|
||||
*/
|
||||
public class KeycloakDeploymentConfig {
|
||||
public class KeycloakDistConfigurator {
|
||||
private final Keycloak keycloakCR;
|
||||
private final StatefulSet deployment;
|
||||
private final KubernetesClient client;
|
||||
|
||||
public KeycloakDeploymentConfig(Keycloak keycloakCR, StatefulSet deployment, KubernetesClient client) {
|
||||
public KeycloakDistConfigurator(Keycloak keycloakCR, StatefulSet deployment, KubernetesClient client) {
|
||||
this.keycloakCR = keycloakCR;
|
||||
this.deployment = deployment;
|
||||
this.client = client;
|
||||
|
@ -53,18 +63,12 @@ public class KeycloakDeploymentConfig {
|
|||
/**
|
||||
* Specify first-class citizens fields which should not be added as general server configuration property
|
||||
*/
|
||||
private final static List<String> FIRST_CLASS_FIELDS = List.of(
|
||||
"hostname",
|
||||
"tlsSecret",
|
||||
"features",
|
||||
"features-disabled",
|
||||
"transaction-xa-enabled"
|
||||
);
|
||||
private final Set<String> firstClassConfigOptions = new HashSet<>();
|
||||
|
||||
/**
|
||||
* Configure configuration properties for the KeycloakDeployment
|
||||
*/
|
||||
protected void configureProperties() {
|
||||
public void configureDistOptions() {
|
||||
configureHostname();
|
||||
configureTLS();
|
||||
configureFeatures();
|
||||
|
@ -76,7 +80,7 @@ public class KeycloakDeploymentConfig {
|
|||
*
|
||||
* @param status Keycloak Status builder
|
||||
*/
|
||||
protected void validateProperties(KeycloakStatusBuilder status) {
|
||||
public void validateOptions(KeycloakStatusBuilder status) {
|
||||
assumeFirstClassCitizens(status);
|
||||
}
|
||||
|
||||
|
@ -189,42 +193,14 @@ public class KeycloakDeploymentConfig {
|
|||
}
|
||||
|
||||
public void configureFeatures() {
|
||||
var featureSpec = keycloakCR.getSpec().getFeatureSpec();
|
||||
if (featureSpec == null) return;
|
||||
|
||||
var kcContainer = deployment.getSpec().getTemplate().getSpec().getContainers().get(0);
|
||||
var envVars = kcContainer.getEnv();
|
||||
var enabledFeatures = featureSpec.getEnabledFeatures();
|
||||
var disabledFeatures = featureSpec.getDisabledFeatures();
|
||||
|
||||
if (CollectionUtil.isNotEmpty(enabledFeatures)) {
|
||||
envVars.add(new EnvVarBuilder()
|
||||
.withName("KC_FEATURES")
|
||||
.withValue(CollectionUtil.join(enabledFeatures, ","))
|
||||
.build());
|
||||
}
|
||||
|
||||
if (CollectionUtil.isNotEmpty(disabledFeatures)) {
|
||||
envVars.add(new EnvVarBuilder()
|
||||
.withName("KC_FEATURES_DISABLED")
|
||||
.withValue(CollectionUtil.join(disabledFeatures, ","))
|
||||
.build());
|
||||
}
|
||||
optionMapper(keycloakCR.getSpec().getFeatureSpec())
|
||||
.mapOptionFromCollection("features", FeatureSpec::getEnabledFeatures)
|
||||
.mapOptionFromCollection("features-disabled", FeatureSpec::getDisabledFeatures);
|
||||
}
|
||||
|
||||
public void configureTransactions() {
|
||||
var transactionsSpec = keycloakCR.getSpec().getTransactionsSpec();
|
||||
if (transactionsSpec == null) return;
|
||||
|
||||
var kcContainer = deployment.getSpec().getTemplate().getSpec().getContainers().get(0);
|
||||
var envVars = kcContainer.getEnv();
|
||||
|
||||
if (transactionsSpec.isXaEnabled() != null) {
|
||||
envVars.add(new EnvVarBuilder()
|
||||
.withName("KC_TRANSACTION_XA_ENABLED")
|
||||
.withValue(String.valueOf(transactionsSpec.isXaEnabled()))
|
||||
.build());
|
||||
}
|
||||
optionMapper(keycloakCR.getSpec().getTransactionsSpec())
|
||||
.mapOption("transaction-xa-enabled", TransactionsSpec::isXaEnabled);
|
||||
}
|
||||
|
||||
/* ---------- END of configuration of first-class citizen fields ---------- */
|
||||
|
@ -280,10 +256,69 @@ public class KeycloakDeploymentConfig {
|
|||
.map(ValueOrSecret::getName)
|
||||
.collect(Collectors.toSet());
|
||||
|
||||
final var sameItems = CollectionUtil.intersection(serverConfigNames, FIRST_CLASS_FIELDS);
|
||||
final var sameItems = CollectionUtil.intersection(serverConfigNames, firstClassConfigOptions);
|
||||
if (CollectionUtil.isNotEmpty(sameItems)) {
|
||||
status.addWarningMessage("You need to specify these fields as the first-class citizen of the CR: "
|
||||
+ CollectionUtil.join(sameItems, ","));
|
||||
}
|
||||
}
|
||||
|
||||
private <T> OptionMapper<T> optionMapper(T optionSpec) {
|
||||
return new OptionMapper<>(optionSpec);
|
||||
}
|
||||
|
||||
private class OptionMapper<T> {
|
||||
private final T categorySpec;
|
||||
private final List<EnvVar> envVars;
|
||||
|
||||
public OptionMapper(T optionSpec) {
|
||||
this.categorySpec = optionSpec;
|
||||
|
||||
var kcContainer = deployment.getSpec().getTemplate().getSpec().getContainers().get(0);
|
||||
var envVars = kcContainer.getEnv();
|
||||
if (envVars == null) {
|
||||
envVars = new ArrayList<>();
|
||||
kcContainer.setEnv(envVars);
|
||||
}
|
||||
this.envVars = envVars;
|
||||
}
|
||||
|
||||
public <R> OptionMapper<T> mapOption(String optionName, Function<T, R> optionValueSupplier) {
|
||||
firstClassConfigOptions.add(optionName);
|
||||
|
||||
if (categorySpec == null) {
|
||||
Log.debugf("No category spec provided for %s", optionName);
|
||||
return this;
|
||||
}
|
||||
|
||||
R value = optionValueSupplier.apply(categorySpec);
|
||||
String valueStr = String.valueOf(value);
|
||||
|
||||
if (value == null || valueStr.trim().isEmpty()) {
|
||||
Log.debugf("No value provided for %s", optionName);
|
||||
return this;
|
||||
}
|
||||
|
||||
EnvVar envVar = new EnvVarBuilder()
|
||||
.withName(getEnvVarName(optionName))
|
||||
.withValue(valueStr)
|
||||
.build();
|
||||
|
||||
envVars.add(envVar);
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public <R> OptionMapper<T> mapOption(String optionName, R optionValue) {
|
||||
return mapOption(optionName, s -> optionValue);
|
||||
}
|
||||
|
||||
protected <R extends Collection<?>> OptionMapper<T> mapOptionFromCollection(String optionName, Function<T, R> optionValueSupplier) {
|
||||
return mapOption(optionName, s -> {
|
||||
var value = optionValueSupplier.apply(s);
|
||||
if (value == null) return null;
|
||||
return value.stream().filter(Objects::nonNull).map(String::valueOf).collect(Collectors.joining(","));
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
|
@ -25,7 +25,7 @@ import io.quarkus.test.junit.QuarkusTest;
|
|||
import org.junit.jupiter.api.Test;
|
||||
import org.keycloak.common.util.CollectionUtil;
|
||||
import org.keycloak.common.util.ObjectUtil;
|
||||
import org.keycloak.operator.controllers.KeycloakDeploymentConfig;
|
||||
import org.keycloak.operator.controllers.KeycloakDistConfigurator;
|
||||
import org.keycloak.operator.crds.v2alpha1.deployment.Keycloak;
|
||||
import org.keycloak.operator.testsuite.utils.K8sUtils;
|
||||
|
||||
|
@ -36,39 +36,51 @@ import java.util.function.Consumer;
|
|||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
@QuarkusTest
|
||||
public class KeycloakDeploymentConfigTest {
|
||||
public class KeycloakDistConfiguratorTest {
|
||||
|
||||
@Test
|
||||
public void enabledFeatures() {
|
||||
testFirstClassCitizenEnvVars("KC_FEATURES", KeycloakDeploymentConfig::configureFeatures, "docker", "authorization");
|
||||
testFirstClassCitizenEnvVars("KC_FEATURES", KeycloakDistConfigurator::configureFeatures, "docker", "authorization");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void disabledFeatures() {
|
||||
testFirstClassCitizenEnvVars("KC_FEATURES_DISABLED", KeycloakDeploymentConfig::configureFeatures, "admin", "step-up-authentication");
|
||||
testFirstClassCitizenEnvVars("KC_FEATURES_DISABLED", KeycloakDistConfigurator::configureFeatures, "admin", "step-up-authentication");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void transactions() {
|
||||
testFirstClassCitizenEnvVars("KC_TRANSACTION_XA_ENABLED", KeycloakDeploymentConfig::configureTransactions, "false");
|
||||
testFirstClassCitizenEnvVars("KC_TRANSACTION_XA_ENABLED", KeycloakDistConfigurator::configureTransactions, "false");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEmptyLists() {
|
||||
final Keycloak keycloak = K8sUtils.getResourceFromFile("test-serialization-keycloak-cr-with-empty-list.yml", Keycloak.class);
|
||||
final StatefulSet deployment = getBasicKcDeployment();
|
||||
final KeycloakDistConfigurator distConfig = new KeycloakDistConfigurator(keycloak, deployment, null);
|
||||
|
||||
final List<EnvVar> envVars = deployment.getSpec().getTemplate().getSpec().getContainers().get(0).getEnv();
|
||||
distConfig.configureFeatures();
|
||||
assertEnvVarNotPresent(envVars, "KC_FEATURES");
|
||||
assertEnvVarNotPresent(envVars, "KC_FEATURES_DISABLED");
|
||||
}
|
||||
|
||||
/* UTILS */
|
||||
private void testFirstClassCitizenEnvVars(String varName, Consumer<KeycloakDeploymentConfig> config, String... expectedValues) {
|
||||
private void testFirstClassCitizenEnvVars(String varName, Consumer<KeycloakDistConfigurator> config, String... expectedValues) {
|
||||
testFirstClassCitizenEnvVars("/test-serialization-keycloak-cr.yml", varName, config, expectedValues);
|
||||
}
|
||||
|
||||
private void testFirstClassCitizenEnvVars(String crName, String varName, Consumer<KeycloakDeploymentConfig> config, String... expectedValues) {
|
||||
private void testFirstClassCitizenEnvVars(String crName, String varName, Consumer<KeycloakDistConfigurator> config, String... expectedValues) {
|
||||
final Keycloak keycloak = K8sUtils.getResourceFromFile(crName, Keycloak.class);
|
||||
final StatefulSet deployment = getBasicKcDeployment();
|
||||
final KeycloakDeploymentConfig deploymentConfig = new KeycloakDeploymentConfig(keycloak, deployment, null);
|
||||
final KeycloakDistConfigurator distConfig = new KeycloakDistConfigurator(keycloak, deployment, null);
|
||||
|
||||
final Container container = deployment.getSpec().getTemplate().getSpec().getContainers().get(0);
|
||||
assertThat(container).isNotNull();
|
||||
|
||||
assertEnvVarNotPresent(container.getEnv(), varName);
|
||||
|
||||
config.accept(deploymentConfig);
|
||||
config.accept(distConfig);
|
||||
|
||||
assertContainerEnvVar(container.getEnv(), varName, expectedValues);
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
apiVersion: k8s.keycloak.org/v2alpha1
|
||||
kind: Keycloak
|
||||
metadata:
|
||||
name: test-serialization-kc
|
||||
spec:
|
||||
features:
|
||||
enabled:
|
||||
-
|
||||
hostname: my-hostname
|
||||
tlsSecret: my-tls-secret
|
Loading…
Reference in a new issue