Add HTTP options to Keycloak CR
This commit is contained in:
parent
f7490b7f7c
commit
19ee00ff54
24 changed files with 483 additions and 213 deletions
|
@ -42,7 +42,8 @@ public final class Constants {
|
|||
public static final Map<String, String> DEFAULT_DIST_CONFIG = Map.of(
|
||||
"health-enabled","true",
|
||||
"cache", "ispn",
|
||||
"cache-stack", "kubernetes"
|
||||
"cache-stack", "kubernetes",
|
||||
"proxy", "passthrough"
|
||||
);
|
||||
|
||||
public static final Integer KEYCLOAK_HTTP_PORT = 8080;
|
||||
|
|
|
@ -20,6 +20,7 @@ import io.fabric8.kubernetes.api.model.Container;
|
|||
import io.fabric8.kubernetes.api.model.EnvVar;
|
||||
import io.fabric8.kubernetes.api.model.EnvVarBuilder;
|
||||
import io.fabric8.kubernetes.api.model.EnvVarSourceBuilder;
|
||||
import io.fabric8.kubernetes.api.model.ExecActionBuilder;
|
||||
import io.fabric8.kubernetes.api.model.HasMetadata;
|
||||
import io.fabric8.kubernetes.api.model.PodTemplateSpec;
|
||||
import io.fabric8.kubernetes.api.model.ResourceRequirements;
|
||||
|
@ -34,7 +35,9 @@ 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 java.nio.charset.StandardCharsets;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Base64;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
|
@ -44,7 +47,7 @@ import java.util.Set;
|
|||
import java.util.function.Consumer;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static io.smallrye.config.common.utils.StringUtil.replaceNonAlphanumericByUnderscores;
|
||||
import static org.keycloak.operator.crds.v2alpha1.CRDUtils.isTlsConfigured;
|
||||
|
||||
public class KeycloakDeployment extends OperatorManagedResource implements StatusUpdater<KeycloakStatusBuilder> {
|
||||
|
||||
|
@ -392,6 +395,31 @@ public class KeycloakDeployment extends OperatorManagedResource implements Statu
|
|||
|
||||
container.setEnv(getEnvVars());
|
||||
|
||||
// probes
|
||||
var tlsConfigured = isTlsConfigured(keycloakCR);
|
||||
var userRelativePath = readConfigurationValue(Constants.KEYCLOAK_HTTP_RELATIVE_PATH_KEY);
|
||||
var kcRelativePath = (userRelativePath == null) ? "" : userRelativePath;
|
||||
var protocol = !tlsConfigured ? "http" : "https";
|
||||
var kcPort = KeycloakService.getServicePort(keycloakCR);
|
||||
|
||||
var baseProbe = new ArrayList<>(List.of("curl", "--head", "--fail", "--silent"));
|
||||
|
||||
if (tlsConfigured) {
|
||||
baseProbe.add("--insecure");
|
||||
}
|
||||
|
||||
var readyProbe = new ArrayList<>(baseProbe);
|
||||
readyProbe.add(protocol + "://127.0.0.1:" + kcPort + kcRelativePath + "/health/ready");
|
||||
var liveProbe = new ArrayList<>(baseProbe);
|
||||
liveProbe.add(protocol + "://127.0.0.1:" + kcPort + kcRelativePath + "/health/live");
|
||||
|
||||
container
|
||||
.getReadinessProbe()
|
||||
.setExec(new ExecActionBuilder().withCommand(readyProbe).build());
|
||||
container
|
||||
.getLivenessProbe()
|
||||
.setExec(new ExecActionBuilder().withCommand(liveProbe).build());
|
||||
|
||||
return baseDeployment;
|
||||
}
|
||||
|
||||
|
@ -417,7 +445,7 @@ public class KeycloakDeployment extends OperatorManagedResource implements Statu
|
|||
serverConfigSecretsNames = new HashSet<>();
|
||||
List<EnvVar> envVars = serverConfig.stream()
|
||||
.map(v -> {
|
||||
var envBuilder = new EnvVarBuilder().withName(getEnvVarName(v.getName()));
|
||||
var envBuilder = new EnvVarBuilder().withName(KeycloakDistConfigurator.getKeycloakOptionEnvVarName(v.getName()));
|
||||
var secret = v.getSecret();
|
||||
if (secret != null) {
|
||||
envBuilder.withValueFrom(
|
||||
|
@ -490,8 +518,8 @@ public class KeycloakDeployment extends OperatorManagedResource implements Statu
|
|||
|
||||
public Set<String> getConfigSecretsNames() {
|
||||
Set<String> ret = new HashSet<>(serverConfigSecretsNames);
|
||||
if (!keycloakCR.getSpec().isHttp()) {
|
||||
ret.add(keycloakCR.getSpec().getTlsSecret());
|
||||
if (isTlsConfigured(keycloakCR)) {
|
||||
ret.add(keycloakCR.getSpec().getHttpSpec().getTlsSecret());
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
@ -535,8 +563,41 @@ public class KeycloakDeployment extends OperatorManagedResource implements Statu
|
|||
}
|
||||
}
|
||||
|
||||
public static String getEnvVarName(String kcConfigName) {
|
||||
// TODO make this use impl from Quarkus dist (Configuration.toEnvVarFormat)
|
||||
return "KC_" + replaceNonAlphanumericByUnderscores(kcConfigName).toUpperCase();
|
||||
protected String readConfigurationValue(String key) {
|
||||
if (keycloakCR != null &&
|
||||
keycloakCR.getSpec() != null &&
|
||||
keycloakCR.getSpec().getServerConfiguration() != null
|
||||
) {
|
||||
|
||||
var serverConfigValue = keycloakCR
|
||||
.getSpec()
|
||||
.getServerConfiguration()
|
||||
.stream()
|
||||
.filter(sc -> sc.getName().equals(key))
|
||||
.findFirst();
|
||||
if (serverConfigValue.isPresent()) {
|
||||
if (serverConfigValue.get().getValue() != null) {
|
||||
return serverConfigValue.get().getValue();
|
||||
} else {
|
||||
var secretSelector = serverConfigValue.get().getSecret();
|
||||
if (secretSelector == null) {
|
||||
throw new IllegalStateException("Secret " + serverConfigValue.get().getName() + " not defined");
|
||||
}
|
||||
var secret = client.secrets().inNamespace(keycloakCR.getMetadata().getNamespace()).withName(secretSelector.getName()).get();
|
||||
if (secret == null) {
|
||||
throw new IllegalStateException("Secret " + secretSelector.getName() + " not found in cluster");
|
||||
}
|
||||
if (secret.getData().containsKey(secretSelector.getKey())) {
|
||||
return new String(Base64.getDecoder().decode(secret.getData().get(secretSelector.getKey())), StandardCharsets.UTF_8);
|
||||
} else {
|
||||
throw new IllegalStateException("Secret " + secretSelector.getName() + " doesn't contain the expected key " + secretSelector.getKey());
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -19,7 +19,6 @@ 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;
|
||||
|
@ -31,11 +30,10 @@ 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.HttpSpec;
|
||||
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;
|
||||
|
@ -44,7 +42,8 @@ import java.util.Set;
|
|||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static org.keycloak.operator.controllers.KeycloakDeployment.getEnvVarName;
|
||||
import static io.smallrye.config.common.utils.StringUtil.replaceNonAlphanumericByUnderscores;
|
||||
import static org.keycloak.operator.crds.v2alpha1.CRDUtils.isTlsConfigured;
|
||||
|
||||
/**
|
||||
* Configuration for the KeycloakDeployment
|
||||
|
@ -70,9 +69,9 @@ public class KeycloakDistConfigurator {
|
|||
*/
|
||||
public void configureDistOptions() {
|
||||
configureHostname();
|
||||
configureTLS();
|
||||
configureFeatures();
|
||||
configureTransactions();
|
||||
configureHttp();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -113,85 +112,6 @@ public class KeycloakDistConfigurator {
|
|||
}
|
||||
}
|
||||
|
||||
public void configureTLS() {
|
||||
var kcContainer = deployment.getSpec().getTemplate().getSpec().getContainers().get(0);
|
||||
var tlsSecret = keycloakCR.getSpec().getTlsSecret();
|
||||
var envVars = kcContainer.getEnv();
|
||||
|
||||
if (keycloakCR.getSpec().isHttp()) {
|
||||
var disableTls = List.of(
|
||||
new EnvVarBuilder()
|
||||
.withName("KC_HTTP_ENABLED")
|
||||
.withValue("true")
|
||||
.build(),
|
||||
new EnvVarBuilder()
|
||||
.withName("KC_HOSTNAME_STRICT_HTTPS")
|
||||
.withValue("false")
|
||||
.build(),
|
||||
new EnvVarBuilder()
|
||||
.withName("KC_PROXY")
|
||||
.withValue("edge")
|
||||
.build());
|
||||
|
||||
envVars.addAll(disableTls);
|
||||
} else {
|
||||
var enabledTls = List.of(
|
||||
new EnvVarBuilder()
|
||||
.withName("KC_HTTPS_CERTIFICATE_FILE")
|
||||
.withValue(Constants.CERTIFICATES_FOLDER + "/tls.crt")
|
||||
.build(),
|
||||
new EnvVarBuilder()
|
||||
.withName("KC_HTTPS_CERTIFICATE_KEY_FILE")
|
||||
.withValue(Constants.CERTIFICATES_FOLDER + "/tls.key")
|
||||
.build(),
|
||||
new EnvVarBuilder()
|
||||
.withName("KC_PROXY")
|
||||
.withValue("passthrough")
|
||||
.build());
|
||||
|
||||
envVars.addAll(enabledTls);
|
||||
|
||||
var volume = new VolumeBuilder()
|
||||
.withName("keycloak-tls-certificates")
|
||||
.withNewSecret()
|
||||
.withSecretName(tlsSecret)
|
||||
.withOptional(false)
|
||||
.endSecret()
|
||||
.build();
|
||||
|
||||
var volumeMount = new VolumeMountBuilder()
|
||||
.withName(volume.getName())
|
||||
.withMountPath(Constants.CERTIFICATES_FOLDER)
|
||||
.build();
|
||||
|
||||
deployment.getSpec().getTemplate().getSpec().getVolumes().add(volume);
|
||||
kcContainer.getVolumeMounts().add(volumeMount);
|
||||
}
|
||||
|
||||
var userRelativePath = readConfigurationValue(Constants.KEYCLOAK_HTTP_RELATIVE_PATH_KEY);
|
||||
var kcRelativePath = (userRelativePath == null) ? "" : userRelativePath;
|
||||
var protocol = (keycloakCR.getSpec().isHttp()) ? "http" : "https";
|
||||
var kcPort = (keycloakCR.getSpec().isHttp()) ? Constants.KEYCLOAK_HTTP_PORT : Constants.KEYCLOAK_HTTPS_PORT;
|
||||
|
||||
var baseProbe = new ArrayList<>(List.of("curl", "--head", "--fail", "--silent"));
|
||||
|
||||
if (!keycloakCR.getSpec().isHttp()) {
|
||||
baseProbe.add("--insecure");
|
||||
}
|
||||
|
||||
var readyProbe = new ArrayList<>(baseProbe);
|
||||
readyProbe.add(protocol + "://127.0.0.1:" + kcPort + kcRelativePath + "/health/ready");
|
||||
var liveProbe = new ArrayList<>(baseProbe);
|
||||
liveProbe.add(protocol + "://127.0.0.1:" + kcPort + kcRelativePath + "/health/live");
|
||||
|
||||
kcContainer
|
||||
.getReadinessProbe()
|
||||
.setExec(new ExecActionBuilder().withCommand(readyProbe).build());
|
||||
kcContainer
|
||||
.getLivenessProbe()
|
||||
.setExec(new ExecActionBuilder().withCommand(liveProbe).build());
|
||||
}
|
||||
|
||||
public void configureFeatures() {
|
||||
optionMapper(keycloakCR.getSpec().getFeatureSpec())
|
||||
.mapOptionFromCollection("features", FeatureSpec::getEnabledFeatures)
|
||||
|
@ -203,46 +123,50 @@ public class KeycloakDistConfigurator {
|
|||
.mapOption("transaction-xa-enabled", TransactionsSpec::isXaEnabled);
|
||||
}
|
||||
|
||||
/* ---------- END of configuration of first-class citizen fields ---------- */
|
||||
public void configureHttp() {
|
||||
var optionMapper = optionMapper(keycloakCR.getSpec().getHttpSpec())
|
||||
.mapOption("http-enabled", HttpSpec::getHttpEnabled)
|
||||
.mapOption("http-port", HttpSpec::getHttpPort)
|
||||
.mapOption("https-port", HttpSpec::getHttpsPort);
|
||||
|
||||
protected String readConfigurationValue(String key) {
|
||||
if (keycloakCR != null &&
|
||||
keycloakCR.getSpec() != null &&
|
||||
keycloakCR.getSpec().getServerConfiguration() != null
|
||||
) {
|
||||
|
||||
var serverConfigValue = keycloakCR
|
||||
.getSpec()
|
||||
.getServerConfiguration()
|
||||
.stream()
|
||||
.filter(sc -> sc.getName().equals(key))
|
||||
.findFirst();
|
||||
if (serverConfigValue.isPresent()) {
|
||||
if (serverConfigValue.get().getValue() != null) {
|
||||
return serverConfigValue.get().getValue();
|
||||
} else {
|
||||
var secretSelector = serverConfigValue.get().getSecret();
|
||||
if (secretSelector == null) {
|
||||
throw new IllegalStateException("Secret " + serverConfigValue.get().getName() + " not defined");
|
||||
}
|
||||
var secret = client.secrets().inNamespace(keycloakCR.getMetadata().getNamespace()).withName(secretSelector.getName()).get();
|
||||
if (secret == null) {
|
||||
throw new IllegalStateException("Secret " + secretSelector.getName() + " not found in cluster");
|
||||
}
|
||||
if (secret.getData().containsKey(secretSelector.getKey())) {
|
||||
return new String(Base64.getDecoder().decode(secret.getData().get(secretSelector.getKey())), StandardCharsets.UTF_8);
|
||||
} else {
|
||||
throw new IllegalStateException("Secret " + secretSelector.getName() + " doesn't contain the expected key " + secretSelector.getKey());
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
configureTLS(optionMapper);
|
||||
}
|
||||
|
||||
public void configureTLS(OptionMapper<HttpSpec> optionMapper) {
|
||||
final String certFileOptionName = "https-certificate-file";
|
||||
final String keyFileOptionName = "https-certificate-key-file";
|
||||
|
||||
if (!isTlsConfigured(keycloakCR)) {
|
||||
// for mapping and triggering warning in status if someone uses the fields directly
|
||||
optionMapper.mapOption(certFileOptionName);
|
||||
optionMapper.mapOption(keyFileOptionName);
|
||||
return;
|
||||
}
|
||||
|
||||
optionMapper.mapOption(certFileOptionName, Constants.CERTIFICATES_FOLDER + "/tls.crt");
|
||||
optionMapper.mapOption(keyFileOptionName, Constants.CERTIFICATES_FOLDER + "/tls.key");
|
||||
|
||||
var kcContainer = deployment.getSpec().getTemplate().getSpec().getContainers().get(0);
|
||||
|
||||
var volume = new VolumeBuilder()
|
||||
.withName("keycloak-tls-certificates")
|
||||
.withNewSecret()
|
||||
.withSecretName(keycloakCR.getSpec().getHttpSpec().getTlsSecret())
|
||||
.withOptional(false)
|
||||
.endSecret()
|
||||
.build();
|
||||
|
||||
var volumeMount = new VolumeMountBuilder()
|
||||
.withName(volume.getName())
|
||||
.withMountPath(Constants.CERTIFICATES_FOLDER)
|
||||
.build();
|
||||
|
||||
deployment.getSpec().getTemplate().getSpec().getVolumes().add(volume);
|
||||
kcContainer.getVolumeMounts().add(volumeMount);
|
||||
}
|
||||
|
||||
/* ---------- END of configuration of first-class citizen fields ---------- */
|
||||
|
||||
/**
|
||||
* Assume the specified first-class citizens are not included in the general server configuration
|
||||
*
|
||||
|
@ -263,6 +187,11 @@ public class KeycloakDistConfigurator {
|
|||
}
|
||||
}
|
||||
|
||||
public static String getKeycloakOptionEnvVarName(String kcConfigName) {
|
||||
// TODO make this use impl from Quarkus dist (Configuration.toEnvVarFormat)
|
||||
return "KC_" + replaceNonAlphanumericByUnderscores(kcConfigName).toUpperCase();
|
||||
}
|
||||
|
||||
private <T> OptionMapper<T> optionMapper(T optionSpec) {
|
||||
return new OptionMapper<>(optionSpec);
|
||||
}
|
||||
|
@ -300,7 +229,7 @@ public class KeycloakDistConfigurator {
|
|||
}
|
||||
|
||||
EnvVar envVar = new EnvVarBuilder()
|
||||
.withName(getEnvVarName(optionName))
|
||||
.withName(getKeycloakOptionEnvVarName(optionName))
|
||||
.withValue(valueStr)
|
||||
.build();
|
||||
|
||||
|
@ -309,6 +238,10 @@ public class KeycloakDistConfigurator {
|
|||
return this;
|
||||
}
|
||||
|
||||
public <R> OptionMapper<T> mapOption(String optionName) {
|
||||
return mapOption(optionName, s -> null);
|
||||
}
|
||||
|
||||
public <R> OptionMapper<T> mapOption(String optionName, R optionValue) {
|
||||
return mapOption(optionName, s -> optionValue);
|
||||
}
|
||||
|
|
|
@ -27,6 +27,8 @@ import org.keycloak.operator.crds.v2alpha1.deployment.KeycloakStatusBuilder;
|
|||
import java.util.HashMap;
|
||||
import java.util.Optional;
|
||||
|
||||
import static org.keycloak.operator.crds.v2alpha1.CRDUtils.isTlsConfigured;
|
||||
|
||||
public class KeycloakIngress extends OperatorManagedResource implements StatusUpdater<KeycloakStatusBuilder> {
|
||||
|
||||
private final Ingress existingIngress;
|
||||
|
@ -59,8 +61,8 @@ public class KeycloakIngress extends OperatorManagedResource implements StatusUp
|
|||
}
|
||||
|
||||
private Ingress newIngress() {
|
||||
var port = (keycloak.getSpec().isHttp()) ? Constants.KEYCLOAK_HTTP_PORT : Constants.KEYCLOAK_HTTPS_PORT;
|
||||
var backendProtocol = (keycloak.getSpec().isHttp()) ? "HTTP" : "HTTPS";
|
||||
var port = KeycloakService.getServicePort(keycloak);
|
||||
var backendProtocol = (!isTlsConfigured(keycloak)) ? "HTTP" : "HTTPS";
|
||||
|
||||
Ingress ingress = new IngressBuilder()
|
||||
.withNewMetadata()
|
||||
|
|
|
@ -38,8 +38,7 @@ import org.keycloak.operator.crds.v2alpha1.realmimport.KeycloakRealmImportStatus
|
|||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
import static org.keycloak.operator.Constants.DEFAULT_DIST_CONFIG;
|
||||
import static org.keycloak.operator.controllers.KeycloakDeployment.getEnvVarName;
|
||||
import static org.keycloak.operator.controllers.KeycloakDistConfigurator.getKeycloakOptionEnvVarName;
|
||||
|
||||
public class KeycloakRealmImportJob extends OperatorManagedResource {
|
||||
|
||||
|
@ -144,8 +143,8 @@ public class KeycloakRealmImportJob extends OperatorManagedResource {
|
|||
.get(0)
|
||||
.getEnv();
|
||||
|
||||
var cacheEnvVarName = getEnvVarName("cache");
|
||||
var healthEnvVarName = getEnvVarName("health-enabled");
|
||||
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
|
||||
|
|
|
@ -25,9 +25,13 @@ import io.fabric8.kubernetes.client.KubernetesClient;
|
|||
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.spec.HttpSpec;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
import static org.keycloak.operator.crds.v2alpha1.CRDUtils.getValueFromSubSpec;
|
||||
import static org.keycloak.operator.crds.v2alpha1.CRDUtils.isTlsConfigured;
|
||||
|
||||
public class KeycloakService extends OperatorManagedResource implements StatusUpdater<KeycloakStatusBuilder> {
|
||||
|
||||
private Service existingService;
|
||||
|
@ -40,10 +44,9 @@ public class KeycloakService extends OperatorManagedResource implements StatusUp
|
|||
}
|
||||
|
||||
private ServiceSpec getServiceSpec() {
|
||||
var port = (this.keycloak.getSpec().isHttp()) ? Constants.KEYCLOAK_HTTP_PORT : Constants.KEYCLOAK_HTTPS_PORT;
|
||||
return new ServiceSpecBuilder()
|
||||
return new ServiceSpecBuilder()
|
||||
.addNewPort()
|
||||
.withPort(port)
|
||||
.withPort(getServicePort(keycloak))
|
||||
.withProtocol(Constants.KEYCLOAK_SERVICE_PROTOCOL)
|
||||
.endPort()
|
||||
.withSelector(Constants.DEFAULT_LABELS)
|
||||
|
@ -91,4 +94,13 @@ public class KeycloakService extends OperatorManagedResource implements StatusUp
|
|||
public String getName() {
|
||||
return cr.getMetadata().getName() + Constants.KEYCLOAK_SERVICE_SUFFIX;
|
||||
}
|
||||
|
||||
public static int getServicePort(Keycloak keycloak) {
|
||||
// we assume HTTP when TLS is not configureed
|
||||
if (!isTlsConfigured(keycloak)) {
|
||||
return getValueFromSubSpec(keycloak.getSpec().getHttpSpec(), HttpSpec::getHttpPort).orElse(Constants.KEYCLOAK_HTTP_PORT);
|
||||
} else {
|
||||
return getValueFromSubSpec(keycloak.getSpec().getHttpSpec(), HttpSpec::getHttpsPort).orElse(Constants.KEYCLOAK_HTTPS_PORT);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,42 @@
|
|||
/*
|
||||
* 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;
|
||||
|
||||
import org.keycloak.operator.crds.v2alpha1.deployment.Keycloak;
|
||||
import org.keycloak.operator.crds.v2alpha1.deployment.spec.HttpSpec;
|
||||
|
||||
import java.util.Optional;
|
||||
import java.util.function.Function;
|
||||
|
||||
/**
|
||||
* @author Vaclav Muzikar <vmuzikar@redhat.com>
|
||||
*/
|
||||
public final class CRDUtils {
|
||||
public static boolean isTlsConfigured(Keycloak keycloakCR) {
|
||||
var tlsSecret = getValueFromSubSpec(keycloakCR.getSpec().getHttpSpec(), HttpSpec::getTlsSecret);
|
||||
return tlsSecret.isPresent() && !tlsSecret.get().trim().isEmpty();
|
||||
}
|
||||
|
||||
public static <T, R> Optional<R> getValueFromSubSpec(T subSpec, Function<T, R> valueSupplier) {
|
||||
if (subSpec != null) {
|
||||
return Optional.ofNullable(valueSupplier.apply(subSpec));
|
||||
} else {
|
||||
return Optional.empty();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -23,6 +23,8 @@ import com.fasterxml.jackson.annotation.JsonPropertyDescription;
|
|||
import io.fabric8.kubernetes.api.model.LocalObjectReference;
|
||||
import org.keycloak.operator.Constants;
|
||||
import org.keycloak.operator.crds.v2alpha1.deployment.spec.FeatureSpec;
|
||||
import org.keycloak.operator.crds.v2alpha1.deployment.spec.HttpSpec;
|
||||
import org.keycloak.operator.crds.v2alpha1.deployment.spec.UnsupportedSpec;
|
||||
import org.keycloak.operator.crds.v2alpha1.deployment.spec.TransactionsSpec;
|
||||
|
||||
import javax.validation.constraints.NotNull;
|
||||
|
@ -43,18 +45,14 @@ public class KeycloakSpec {
|
|||
"expressed as a keys (reference: https://www.keycloak.org/server/all-config) and values that can be either direct values or references to secrets.")
|
||||
private List<ValueOrSecret> serverConfiguration; // can't use Set due to a bug in Sundrio https://github.com/sundrio/sundrio/issues/316
|
||||
|
||||
// TODO: switch to this serverConfig when all the options are ported
|
||||
// private ServerConfig serverConfig;
|
||||
|
||||
@NotNull
|
||||
@JsonPropertyDescription("Hostname for the Keycloak server.\n" +
|
||||
"The special value `" + Constants.INSECURE_DISABLE + "` disables the hostname strict resolution.")
|
||||
private String hostname;
|
||||
|
||||
@NotNull
|
||||
@JsonPropertyDescription("A secret containing the TLS configuration for HTTPS. Reference: https://kubernetes.io/docs/concepts/configuration/secret/#tls-secrets.\n" +
|
||||
"The special value `" + Constants.INSECURE_DISABLE + "` disables https.")
|
||||
private String tlsSecret;
|
||||
@JsonProperty("http")
|
||||
@JsonPropertyDescription("In this section you can configure Keycloak features related to HTTP and HTTPS")
|
||||
private HttpSpec httpSpec;
|
||||
|
||||
@JsonPropertyDescription("Disable the default ingress.")
|
||||
private boolean disableDefaultIngress;
|
||||
|
@ -62,7 +60,7 @@ public class KeycloakSpec {
|
|||
@JsonPropertyDescription(
|
||||
"In this section you can configure podTemplate advanced features, not production-ready, and not supported settings.\n" +
|
||||
"Use at your own risk and open an issue with your use-case if you don't find an alternative way.")
|
||||
private KeycloakSpecUnsupported unsupported;
|
||||
private UnsupportedSpec unsupported;
|
||||
|
||||
@JsonProperty("features")
|
||||
@JsonPropertyDescription("In this section you can configure Keycloak features, which should be enabled/disabled.")
|
||||
|
@ -85,6 +83,14 @@ public class KeycloakSpec {
|
|||
return this.hostname.equals(Constants.INSECURE_DISABLE);
|
||||
}
|
||||
|
||||
public HttpSpec getHttpSpec() {
|
||||
return httpSpec;
|
||||
}
|
||||
|
||||
public void setHttpSpec(HttpSpec httpSpec) {
|
||||
this.httpSpec = httpSpec;
|
||||
}
|
||||
|
||||
public void setDisableDefaultIngress(boolean value) {
|
||||
this.disableDefaultIngress = value;
|
||||
}
|
||||
|
@ -93,24 +99,11 @@ public class KeycloakSpec {
|
|||
return this.disableDefaultIngress;
|
||||
}
|
||||
|
||||
public String getTlsSecret() {
|
||||
return tlsSecret;
|
||||
}
|
||||
|
||||
public void setTlsSecret(String tlsSecret) {
|
||||
this.tlsSecret = tlsSecret;
|
||||
}
|
||||
|
||||
@JsonIgnore
|
||||
public boolean isHttp() {
|
||||
return this.tlsSecret.equals(Constants.INSECURE_DISABLE);
|
||||
}
|
||||
|
||||
public KeycloakSpecUnsupported getUnsupported() {
|
||||
public UnsupportedSpec getUnsupported() {
|
||||
return unsupported;
|
||||
}
|
||||
|
||||
public void setUnsupported(KeycloakSpecUnsupported unsupported) {
|
||||
public void setUnsupported(UnsupportedSpec unsupported) {
|
||||
this.unsupported = unsupported;
|
||||
}
|
||||
|
||||
|
|
|
@ -68,4 +68,13 @@ public class KeycloakStatusCondition {
|
|||
public int hashCode() {
|
||||
return Objects.hash(getType(), getStatus(), getMessage());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "KeycloakStatusCondition{" +
|
||||
"type='" + type + '\'' +
|
||||
", status=" + status +
|
||||
", message='" + message + '\'' +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,72 @@
|
|||
/*
|
||||
* 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.deployment.spec;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonPropertyDescription;
|
||||
import io.sundr.builder.annotations.Buildable;
|
||||
import org.keycloak.operator.Constants;
|
||||
|
||||
/**
|
||||
* @author Vaclav Muzikar <vmuzikar@redhat.com>
|
||||
*/
|
||||
@Buildable(editableEnabled = false, builderPackage = "io.fabric8.kubernetes.api.builder")
|
||||
public class HttpSpec {
|
||||
@JsonPropertyDescription("A secret containing the TLS configuration for HTTPS. Reference: https://kubernetes.io/docs/concepts/configuration/secret/#tls-secrets.")
|
||||
private String tlsSecret;
|
||||
|
||||
@JsonPropertyDescription("Enables the HTTP listener.")
|
||||
private Boolean httpEnabled;
|
||||
|
||||
@JsonPropertyDescription("The used HTTP port.")
|
||||
private Integer httpPort = Constants.KEYCLOAK_HTTP_PORT;
|
||||
|
||||
@JsonPropertyDescription("The used HTTPS port.")
|
||||
private Integer httpsPort = Constants.KEYCLOAK_HTTPS_PORT;
|
||||
|
||||
public String getTlsSecret() {
|
||||
return tlsSecret;
|
||||
}
|
||||
|
||||
public void setTlsSecret(String tlsSecret) {
|
||||
this.tlsSecret = tlsSecret;
|
||||
}
|
||||
|
||||
public Boolean getHttpEnabled() {
|
||||
return httpEnabled;
|
||||
}
|
||||
|
||||
public void setHttpEnabled(Boolean httpEnabled) {
|
||||
this.httpEnabled = httpEnabled;
|
||||
}
|
||||
|
||||
public Integer getHttpPort() {
|
||||
return httpPort;
|
||||
}
|
||||
|
||||
public void setHttpPort(Integer httpPort) {
|
||||
this.httpPort = httpPort;
|
||||
}
|
||||
|
||||
public Integer getHttpsPort() {
|
||||
return httpsPort;
|
||||
}
|
||||
|
||||
public void setHttpsPort(Integer httpsPort) {
|
||||
this.httpsPort = httpsPort;
|
||||
}
|
||||
}
|
|
@ -1,4 +1,21 @@
|
|||
package org.keycloak.operator.crds.v2alpha1.deployment;
|
||||
/*
|
||||
* 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.deployment.spec;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonPropertyDescription;
|
||||
import io.fabric8.kubernetes.api.model.PodTemplateSpec;
|
||||
|
@ -10,16 +27,16 @@ import io.sundr.builder.annotations.BuildableReference;
|
|||
@BuildableReference(io.fabric8.kubernetes.api.model.ObjectMeta.class),
|
||||
@BuildableReference(io.fabric8.kubernetes.api.model.PodTemplateSpec.class)
|
||||
})
|
||||
public class KeycloakSpecUnsupported {
|
||||
public class UnsupportedSpec {
|
||||
|
||||
@JsonPropertyDescription("You can configure that will be merged with the one configured by default by the operator.\n" +
|
||||
"Use at your own risk, we reserve the possibility to remove/change the way any field gets merged in future releases without notice.\n" +
|
||||
"Reference: https://kubernetes.io/docs/concepts/workloads/pods/#pod-templates")
|
||||
private PodTemplateSpec podTemplate;
|
||||
|
||||
public KeycloakSpecUnsupported() {}
|
||||
public UnsupportedSpec() {}
|
||||
|
||||
public KeycloakSpecUnsupported(PodTemplateSpec podTemplate) {
|
||||
public UnsupportedSpec(PodTemplateSpec podTemplate) {
|
||||
this.podTemplate = podTemplate;
|
||||
}
|
||||
|
|
@ -65,4 +65,13 @@ public class KeycloakRealmImportStatusCondition {
|
|||
public int hashCode() {
|
||||
return Objects.hash(getType(), getStatus(), getMessage());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "KeycloakRealmImportStatusCondition{" +
|
||||
"type='" + type + '\'' +
|
||||
", status=" + status +
|
||||
", message='" + message + '\'' +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
|
|
|
@ -18,4 +18,5 @@ spec:
|
|||
name: keycloak-db-secret
|
||||
key: password
|
||||
hostname: example.com
|
||||
tlsSecret: example-tls-secret
|
||||
http:
|
||||
tlsSecret: example-tls-secret
|
|
@ -30,10 +30,10 @@ import org.awaitility.Awaitility;
|
|||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.condition.EnabledIfSystemProperty;
|
||||
import org.keycloak.operator.Constants;
|
||||
import org.keycloak.operator.controllers.KeycloakDistConfigurator;
|
||||
import org.keycloak.operator.crds.v2alpha1.deployment.KeycloakStatusCondition;
|
||||
import org.keycloak.operator.testsuite.utils.K8sUtils;
|
||||
import org.keycloak.operator.controllers.KeycloakAdminSecret;
|
||||
import org.keycloak.operator.controllers.KeycloakDeployment;
|
||||
import org.keycloak.operator.controllers.KeycloakService;
|
||||
import org.keycloak.operator.crds.v2alpha1.deployment.Keycloak;
|
||||
import org.keycloak.operator.crds.v2alpha1.deployment.ValueOrSecret;
|
||||
|
@ -110,7 +110,7 @@ public class KeycloakDeploymentTest extends BaseOperatorTest {
|
|||
.getSpec().getTemplate().getSpec().getContainers().get(0);
|
||||
assertThat(c.getImage()).isEqualTo("quay.io/keycloak/non-existing-keycloak");
|
||||
assertThat(c.getEnv().stream()
|
||||
.anyMatch(e -> e.getName().equals(KeycloakDeployment.getEnvVarName(dbConf.getName()))
|
||||
.anyMatch(e -> e.getName().equals(KeycloakDistConfigurator.getKeycloakOptionEnvVarName(dbConf.getName()))
|
||||
&& e.getValue().equals(dbConf.getValue())))
|
||||
.isTrue();
|
||||
});
|
||||
|
@ -127,7 +127,7 @@ public class KeycloakDeploymentTest extends BaseOperatorTest {
|
|||
var kc = getDefaultKeycloakDeployment();
|
||||
var health = new ValueOrSecret("health-enabled", "false");
|
||||
var e = new EnvVarBuilder()
|
||||
.withName(KeycloakDeployment.getEnvVarName(health.getName()))
|
||||
.withName(KeycloakDistConfigurator.getKeycloakOptionEnvVarName(health.getName()))
|
||||
.withValue(health.getValue())
|
||||
.build();
|
||||
kc.getSpec().getServerConfiguration().add(health);
|
||||
|
@ -218,21 +218,11 @@ public class KeycloakDeploymentTest extends BaseOperatorTest {
|
|||
public void testTlsDisabled() {
|
||||
try {
|
||||
var kc = getDefaultKeycloakDeployment();
|
||||
kc.getSpec().setTlsSecret(Constants.INSECURE_DISABLE);
|
||||
kc.getSpec().getHttpSpec().setTlsSecret(null);
|
||||
kc.getSpec().getHttpSpec().setHttpEnabled(true);
|
||||
deployKeycloak(k8sclient, kc, true);
|
||||
|
||||
var service = new KeycloakService(k8sclient, kc);
|
||||
Awaitility.await()
|
||||
.ignoreExceptions()
|
||||
.untilAsserted(() -> {
|
||||
String url = "http://" + service.getName() + "." + namespace + ":" + Constants.KEYCLOAK_HTTP_PORT;
|
||||
Log.info("Checking url: " + url);
|
||||
|
||||
var curlOutput = K8sUtils.inClusterCurl(k8sclient, namespace, url);
|
||||
Log.info("Curl Output: " + curlOutput);
|
||||
|
||||
assertEquals("200", curlOutput);
|
||||
});
|
||||
assertKeycloakAccessibleViaService(kc, false, Constants.KEYCLOAK_HTTP_PORT);
|
||||
} catch (Exception e) {
|
||||
savePodLogs();
|
||||
throw e;
|
||||
|
@ -288,6 +278,44 @@ public class KeycloakDeploymentTest extends BaseOperatorTest {
|
|||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testHttpsPort() {
|
||||
try {
|
||||
final int httpsPort = 8543;
|
||||
final int httpPort = 8180;
|
||||
var kc = getDefaultKeycloakDeployment();
|
||||
kc.getSpec().setHostname(Constants.INSECURE_DISABLE);
|
||||
kc.getSpec().getHttpSpec().setHttpsPort(httpsPort);
|
||||
kc.getSpec().getHttpSpec().setHttpPort(httpPort);
|
||||
deployKeycloak(k8sclient, kc, true);
|
||||
|
||||
assertKeycloakAccessibleViaService(kc, true, httpsPort);
|
||||
} catch (Exception e) {
|
||||
savePodLogs();
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testHttpPort() {
|
||||
try {
|
||||
final int httpsPort = 8543;
|
||||
final int httpPort = 8180;
|
||||
var kc = getDefaultKeycloakDeployment();
|
||||
kc.getSpec().setHostname(Constants.INSECURE_DISABLE);
|
||||
kc.getSpec().getHttpSpec().setHttpsPort(httpsPort);
|
||||
kc.getSpec().getHttpSpec().setHttpPort(httpPort);
|
||||
kc.getSpec().getHttpSpec().setTlsSecret(null);
|
||||
kc.getSpec().getHttpSpec().setHttpEnabled(true);
|
||||
deployKeycloak(k8sclient, kc, true);
|
||||
|
||||
assertKeycloakAccessibleViaService(kc, false, httpPort);
|
||||
} catch (Exception e) {
|
||||
savePodLogs();
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
// Reference curl command:
|
||||
// curl --insecure --data "grant_type=password&client_id=admin-cli&username=admin&password=adminPassword" https://localhost:8443/realms/master/protocol/openid-connect/token
|
||||
@Test
|
||||
|
@ -542,4 +570,20 @@ public class KeycloakDeploymentTest extends BaseOperatorTest {
|
|||
LocalObjectReference localObjRefAsSecretTmp = new LocalObjectReferenceBuilder().withName(imagePullSecret.getMetadata().getName()).build();
|
||||
keycloakCR.getSpec().setImagePullSecrets(Collections.singletonList(localObjRefAsSecretTmp));
|
||||
}
|
||||
|
||||
private void assertKeycloakAccessibleViaService(Keycloak kc, boolean https, int port) {
|
||||
var service = new KeycloakService(k8sclient, kc);
|
||||
Awaitility.await()
|
||||
.ignoreExceptions()
|
||||
.untilAsserted(() -> {
|
||||
String protocol = https ? "https" : "http";
|
||||
String url = protocol + "://" + service.getName() + "." + namespace + ":" + port;
|
||||
Log.info("Checking url: " + url);
|
||||
|
||||
var curlOutput = K8sUtils.inClusterCurl(k8sclient, namespace, url);
|
||||
Log.info("Curl Output: " + curlOutput);
|
||||
|
||||
assertEquals("200", curlOutput);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -39,7 +39,8 @@ public class KeycloakIngressTest extends BaseOperatorTest {
|
|||
public void testIngressOnHTTP() {
|
||||
var kc = K8sUtils.getDefaultKeycloakDeployment();
|
||||
kc.getSpec().setHostname(Constants.INSECURE_DISABLE);
|
||||
kc.getSpec().setTlsSecret(Constants.INSECURE_DISABLE);
|
||||
kc.getSpec().getHttpSpec().setTlsSecret(null);
|
||||
kc.getSpec().getHttpSpec().setHttpEnabled(true);
|
||||
K8sUtils.deployKeycloak(k8sclient, kc, true);
|
||||
|
||||
Awaitility.await()
|
||||
|
|
|
@ -28,7 +28,7 @@ import org.junit.jupiter.api.condition.EnabledIfSystemProperty;
|
|||
import org.keycloak.operator.testsuite.utils.CRAssert;
|
||||
import org.keycloak.operator.controllers.KeycloakService;
|
||||
import org.keycloak.operator.crds.v2alpha1.realmimport.KeycloakRealmImport;
|
||||
import org.keycloak.operator.crds.v2alpha1.deployment.KeycloakSpecUnsupported;
|
||||
import org.keycloak.operator.crds.v2alpha1.deployment.spec.UnsupportedSpec;
|
||||
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
|
@ -36,7 +36,7 @@ import static java.util.concurrent.TimeUnit.MINUTES;
|
|||
import static java.util.concurrent.TimeUnit.SECONDS;
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.keycloak.operator.Constants.KEYCLOAK_HTTPS_PORT;
|
||||
import static org.keycloak.operator.controllers.KeycloakDeployment.getEnvVarName;
|
||||
import static org.keycloak.operator.controllers.KeycloakDistConfigurator.getKeycloakOptionEnvVarName;
|
||||
import static org.keycloak.operator.testsuite.utils.K8sUtils.deployKeycloak;
|
||||
import static org.keycloak.operator.testsuite.utils.K8sUtils.getDefaultKeycloakDeployment;
|
||||
import static org.keycloak.operator.testsuite.utils.K8sUtils.inClusterCurl;
|
||||
|
@ -85,7 +85,7 @@ public class RealmImportTest extends BaseOperatorTest {
|
|||
.withImagePullSecrets(new LocalObjectReferenceBuilder().withName("my-empty-secret").build())
|
||||
.endSpec()
|
||||
.build();
|
||||
kc.getSpec().setUnsupported(new KeycloakSpecUnsupported(podTemplate));
|
||||
kc.getSpec().setUnsupported(new UnsupportedSpec(podTemplate));
|
||||
deployKeycloak(k8sclient, kc, false);
|
||||
|
||||
// Act
|
||||
|
@ -118,8 +118,8 @@ public class RealmImportTest extends BaseOperatorTest {
|
|||
var job = k8sclient.batch().v1().jobs().inNamespace(namespace).withName("example-count0-kc").get();
|
||||
assertThat(job.getSpec().getTemplate().getMetadata().getLabels().get("app")).isEqualTo("keycloak-realm-import");
|
||||
var envvars = job.getSpec().getTemplate().getSpec().getContainers().get(0).getEnv();
|
||||
assertThat(envvars.stream().filter(e -> e.getName().equals(getEnvVarName("cache"))).findAny().get().getValue()).isEqualTo("local");
|
||||
assertThat(envvars.stream().filter(e -> e.getName().equals(getEnvVarName("health-enabled"))).findAny().get().getValue()).isEqualTo("false");
|
||||
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");
|
||||
assertThat(job.getSpec().getTemplate().getSpec().getImagePullSecrets().size()).isEqualTo(1);
|
||||
assertThat(job.getSpec().getTemplate().getSpec().getImagePullSecrets().get(0).getName()).isEqualTo("my-empty-secret");
|
||||
|
||||
|
|
|
@ -39,7 +39,7 @@ public class CRSerializationTest {
|
|||
|
||||
assertEquals("my-hostname", keycloak.getSpec().getHostname());
|
||||
assertEquals("my-image", keycloak.getSpec().getImage());
|
||||
assertEquals("my-tls-secret", keycloak.getSpec().getTlsSecret());
|
||||
assertEquals("my-tls-secret", keycloak.getSpec().getHttpSpec().getTlsSecret());
|
||||
assertTrue(keycloak.getSpec().isDisableDefaultIngress());
|
||||
|
||||
final TransactionsSpec transactionsSpec = keycloak.getSpec().getTransactionsSpec();
|
||||
|
|
|
@ -25,8 +25,13 @@ 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.Constants;
|
||||
import org.keycloak.operator.controllers.KeycloakDistConfigurator;
|
||||
import org.keycloak.operator.crds.v2alpha1.deployment.Keycloak;
|
||||
import org.keycloak.operator.crds.v2alpha1.deployment.KeycloakStatus;
|
||||
import org.keycloak.operator.crds.v2alpha1.deployment.KeycloakStatusBuilder;
|
||||
import org.keycloak.operator.crds.v2alpha1.deployment.KeycloakStatusCondition;
|
||||
import org.keycloak.operator.crds.v2alpha1.deployment.ValueOrSecret;
|
||||
import org.keycloak.operator.testsuite.utils.K8sUtils;
|
||||
|
||||
import java.util.Collections;
|
||||
|
@ -34,23 +39,54 @@ import java.util.List;
|
|||
import java.util.function.Consumer;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.keycloak.operator.testsuite.utils.CRAssert.assertKeycloakStatusCondition;
|
||||
import static org.keycloak.operator.testsuite.utils.CRAssert.assertKeycloakStatusDoesNotContainMessage;
|
||||
|
||||
@QuarkusTest
|
||||
public class KeycloakDistConfiguratorTest {
|
||||
|
||||
@Test
|
||||
public void enabledFeatures() {
|
||||
testFirstClassCitizenEnvVars("KC_FEATURES", KeycloakDistConfigurator::configureFeatures, "docker", "authorization");
|
||||
testFirstClassCitizen("KC_FEATURES", "features",
|
||||
KeycloakDistConfigurator::configureFeatures, "docker", "authorization");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void disabledFeatures() {
|
||||
testFirstClassCitizenEnvVars("KC_FEATURES_DISABLED", KeycloakDistConfigurator::configureFeatures, "admin", "step-up-authentication");
|
||||
testFirstClassCitizen("KC_FEATURES_DISABLED", "features-disabled",
|
||||
KeycloakDistConfigurator::configureFeatures, "admin", "step-up-authentication");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void transactions() {
|
||||
testFirstClassCitizenEnvVars("KC_TRANSACTION_XA_ENABLED", KeycloakDistConfigurator::configureTransactions, "false");
|
||||
testFirstClassCitizen("KC_TRANSACTION_XA_ENABLED", "transaction-xa-enabled",
|
||||
KeycloakDistConfigurator::configureTransactions, "false");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void httpEnabled() {
|
||||
testFirstClassCitizen("KC_HTTP_ENABLED", "http-enabled",
|
||||
KeycloakDistConfigurator::configureHttp, "true");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void httpPort() {
|
||||
testFirstClassCitizen("KC_HTTP_PORT", "http-port",
|
||||
KeycloakDistConfigurator::configureHttp, "123");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void httpsPort() {
|
||||
testFirstClassCitizen("KC_HTTPS_PORT", "https-port",
|
||||
KeycloakDistConfigurator::configureHttp, "456");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void tlsSecret() {
|
||||
testFirstClassCitizen("KC_HTTPS_CERTIFICATE_FILE", "https-certificate-file",
|
||||
KeycloakDistConfigurator::configureHttp, Constants.CERTIFICATES_FOLDER + "/tls.crt");
|
||||
testFirstClassCitizen("KC_HTTPS_CERTIFICATE_KEY_FILE", "https-certificate-key-file",
|
||||
KeycloakDistConfigurator::configureHttp, Constants.CERTIFICATES_FOLDER + "/tls.key");
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -66,11 +102,11 @@ public class KeycloakDistConfiguratorTest {
|
|||
}
|
||||
|
||||
/* UTILS */
|
||||
private void testFirstClassCitizenEnvVars(String varName, Consumer<KeycloakDistConfigurator> config, String... expectedValues) {
|
||||
testFirstClassCitizenEnvVars("/test-serialization-keycloak-cr.yml", varName, config, expectedValues);
|
||||
private void testFirstClassCitizen(String envVarName, String optionName, Consumer<KeycloakDistConfigurator> config, String... expectedValues) {
|
||||
testFirstClassCitizen("/test-serialization-keycloak-cr.yml", envVarName, optionName, config, expectedValues);
|
||||
}
|
||||
|
||||
private void testFirstClassCitizenEnvVars(String crName, String varName, Consumer<KeycloakDistConfigurator> config, String... expectedValues) {
|
||||
private void testFirstClassCitizen(String crName, String envVarName, String optionName, Consumer<KeycloakDistConfigurator> config, String... expectedValues) {
|
||||
final Keycloak keycloak = K8sUtils.getResourceFromFile(crName, Keycloak.class);
|
||||
final StatefulSet deployment = getBasicKcDeployment();
|
||||
final KeycloakDistConfigurator distConfig = new KeycloakDistConfigurator(keycloak, deployment, null);
|
||||
|
@ -78,11 +114,15 @@ public class KeycloakDistConfiguratorTest {
|
|||
final Container container = deployment.getSpec().getTemplate().getSpec().getContainers().get(0);
|
||||
assertThat(container).isNotNull();
|
||||
|
||||
assertEnvVarNotPresent(container.getEnv(), varName);
|
||||
assertEnvVarNotPresent(container.getEnv(), envVarName);
|
||||
assertWarningStatus(distConfig, optionName, false);
|
||||
|
||||
config.accept(distConfig);
|
||||
|
||||
assertContainerEnvVar(container.getEnv(), varName, expectedValues);
|
||||
assertContainerEnvVar(container.getEnv(), envVarName, expectedValues);
|
||||
|
||||
keycloak.getSpec().setServerConfiguration(List.of(new ValueOrSecret(optionName, "foo")));
|
||||
assertWarningStatus(distConfig, optionName, true);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -109,6 +149,19 @@ public class KeycloakDistConfiguratorTest {
|
|||
assertThat(containsEnvironmentVariable(envVars, varName)).isFalse();
|
||||
}
|
||||
|
||||
private void assertWarningStatus(KeycloakDistConfigurator distConfig, String optionName, boolean expectWarning) {
|
||||
final String message = "warning: You need to specify these fields as the first-class citizen of the CR: " + optionName;
|
||||
final KeycloakStatusBuilder statusBuilder = new KeycloakStatusBuilder();
|
||||
distConfig.validateOptions(statusBuilder);
|
||||
final KeycloakStatus status = statusBuilder.build();
|
||||
|
||||
if (expectWarning) {
|
||||
assertKeycloakStatusCondition(status, KeycloakStatusCondition.HAS_ERRORS, false, message);
|
||||
} else {
|
||||
assertKeycloakStatusDoesNotContainMessage(status, message);
|
||||
}
|
||||
}
|
||||
|
||||
private StatefulSet getBasicKcDeployment() {
|
||||
return new StatefulSetBuilder()
|
||||
.withNewSpec()
|
||||
|
|
|
@ -29,7 +29,8 @@ import org.keycloak.operator.Config;
|
|||
import org.keycloak.operator.controllers.KeycloakDeployment;
|
||||
import org.keycloak.operator.crds.v2alpha1.deployment.Keycloak;
|
||||
import org.keycloak.operator.crds.v2alpha1.deployment.KeycloakSpec;
|
||||
import org.keycloak.operator.crds.v2alpha1.deployment.KeycloakSpecUnsupported;
|
||||
import org.keycloak.operator.crds.v2alpha1.deployment.spec.HttpSpec;
|
||||
import org.keycloak.operator.crds.v2alpha1.deployment.spec.UnsupportedSpec;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
|
@ -56,9 +57,13 @@ public class PodTemplateTest {
|
|||
};
|
||||
var kc = new Keycloak();
|
||||
var spec = new KeycloakSpec();
|
||||
spec.setUnsupported(new KeycloakSpecUnsupported(podTemplate));
|
||||
spec.setUnsupported(new UnsupportedSpec(podTemplate));
|
||||
spec.setHostname("example.com");
|
||||
spec.setTlsSecret("example-tls-secret");
|
||||
|
||||
var httpSpec = new HttpSpec();
|
||||
httpSpec.setTlsSecret("example-tls-secret");
|
||||
spec.setHttpSpec(httpSpec);
|
||||
|
||||
kc.setSpec(spec);
|
||||
var deployment = new KeycloakDeployment(null, config, kc, existingDeployment, "dummy-admin");
|
||||
return (StatefulSet) deployment.getReconciledResource().get();
|
||||
|
|
|
@ -20,6 +20,7 @@ package org.keycloak.operator.testsuite.utils;
|
|||
import io.fabric8.kubernetes.client.utils.Serialization;
|
||||
import io.quarkus.logging.Log;
|
||||
import org.keycloak.operator.crds.v2alpha1.deployment.Keycloak;
|
||||
import org.keycloak.operator.crds.v2alpha1.deployment.KeycloakStatus;
|
||||
import org.keycloak.operator.crds.v2alpha1.realmimport.KeycloakRealmImport;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
@ -35,20 +36,32 @@ public final class CRAssert {
|
|||
public static void assertKeycloakStatusCondition(Keycloak kc, String condition, boolean status, String containedMessage) {
|
||||
Log.debugf("Asserting CR: %s, condition: %s, status: %s, message: %s", kc.getMetadata().getName(), condition, status, containedMessage);
|
||||
try {
|
||||
assertThat(kc.getStatus().getConditions().stream()
|
||||
.anyMatch(c ->
|
||||
c.getType().equals(condition) &&
|
||||
c.getStatus() == status &&
|
||||
(containedMessage == null || c.getMessage().contains(containedMessage)))
|
||||
).isTrue();
|
||||
assertKeycloakStatusCondition(kc.getStatus(), condition, status, containedMessage);
|
||||
} catch (Exception e) {
|
||||
Log.infof("Asserting CR: %s with status:\n%s", kc.getMetadata().getName(), Serialization.asYaml(kc.getStatus()));
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
public static void assertKeycloakStatusCondition(KeycloakStatus kcStatus, String condition, boolean status) {
|
||||
assertKeycloakStatusCondition(kcStatus, condition, status, null);
|
||||
}
|
||||
public static void assertKeycloakStatusCondition(KeycloakStatus kcStatus, String condition, boolean status, String containedMessage) {
|
||||
assertThat(kcStatus.getConditions())
|
||||
.anyMatch(c ->
|
||||
c.getType().equals(condition) &&
|
||||
c.getStatus() == status &&
|
||||
(containedMessage == null || c.getMessage().contains(containedMessage))
|
||||
);
|
||||
}
|
||||
|
||||
public static void assertKeycloakStatusDoesNotContainMessage(KeycloakStatus kcStatus, String message) {
|
||||
assertThat(kcStatus.getConditions())
|
||||
.noneMatch(c -> c.getMessage().contains(message));
|
||||
}
|
||||
|
||||
public static void assertKeycloakRealmImportStatusCondition(KeycloakRealmImport kri, String condition, boolean status) {
|
||||
assertThat(kri.getStatus().getConditions().stream()
|
||||
.anyMatch(c -> c.getType().equals(condition) && c.getStatus() == status)).isTrue();
|
||||
assertThat(kri.getStatus().getConditions())
|
||||
.anyMatch(c -> c.getType().equals(condition) && c.getStatus() == status);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -14,7 +14,6 @@ spec:
|
|||
- name: db-password
|
||||
value: testpassword
|
||||
hostname: example.com
|
||||
tlsSecret: INSECURE-DISABLE
|
||||
unsupported:
|
||||
podTemplate:
|
||||
metadata:
|
||||
|
|
|
@ -14,6 +14,5 @@ spec:
|
|||
- name: db-password
|
||||
value: testpassword
|
||||
hostname: example.com
|
||||
tlsSecret: INSECURE-DISABLE
|
||||
unsupported:
|
||||
podTemplate:
|
||||
|
|
|
@ -7,4 +7,5 @@ spec:
|
|||
enabled:
|
||||
-
|
||||
hostname: my-hostname
|
||||
tlsSecret: my-tls-secret
|
||||
http:
|
||||
tlsSecret: my-tls-secret
|
|
@ -11,7 +11,11 @@ spec:
|
|||
- name: features
|
||||
value: docker
|
||||
hostname: my-hostname
|
||||
tlsSecret: my-tls-secret
|
||||
http:
|
||||
httpEnabled: true
|
||||
httpPort: 123
|
||||
httpsPort: 456
|
||||
tlsSecret: my-tls-secret
|
||||
features:
|
||||
enabled:
|
||||
- docker
|
||||
|
|
Loading…
Reference in a new issue