From f9c6ea84ad83f800c6dad382c52cb532d5dc8b10 Mon Sep 17 00:00:00 2001 From: andreaTP Date: Mon, 30 May 2022 11:49:01 +0100 Subject: [PATCH] Respect `http-relative-path` with probes --- .../java/org/keycloak/operator/Constants.java | 2 + .../controllers/KeycloakDeployment.java | 69 +++++++++++++++++-- .../resources/base-keycloak-deployment.yaml | 16 ----- .../operator/KeycloakDeploymentE2EIT.java | 60 ++++++++++++++++ 4 files changed, 126 insertions(+), 21 deletions(-) diff --git a/operator/app/src/main/java/org/keycloak/operator/Constants.java b/operator/app/src/main/java/org/keycloak/operator/Constants.java index b56fe2419f..f22ae89922 100644 --- a/operator/app/src/main/java/org/keycloak/operator/Constants.java +++ b/operator/app/src/main/java/org/keycloak/operator/Constants.java @@ -51,4 +51,6 @@ public final class Constants { public static final String INSECURE_DISABLE = "INSECURE-DISABLE"; public static final String CERTIFICATES_FOLDER = "/mnt/certificates"; + + public static String KEYCLOAK_HTTP_RELATIVE_PATH_KEY = "http-relative-path"; } diff --git a/operator/app/src/main/java/org/keycloak/operator/controllers/KeycloakDeployment.java b/operator/app/src/main/java/org/keycloak/operator/controllers/KeycloakDeployment.java index d971fe7790..284f202b04 100644 --- a/operator/app/src/main/java/org/keycloak/operator/controllers/KeycloakDeployment.java +++ b/operator/app/src/main/java/org/keycloak/operator/controllers/KeycloakDeployment.java @@ -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; @@ -36,7 +37,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; @@ -347,6 +350,7 @@ public class KeycloakDeployment extends OperatorManagedResource implements Statu var kcContainer = deployment.getSpec().getTemplate().getSpec().getContainers().get(0); var tlsSecret = this.keycloakCR.getSpec().getTlsSecret(); var envVars = kcContainer.getEnv(); + if (this.keycloakCR.getSpec().isHttp()) { var disableTls = List.of( new EnvVarBuilder() @@ -363,11 +367,6 @@ public class KeycloakDeployment extends OperatorManagedResource implements Statu .build()); envVars.addAll(disableTls); - - kcContainer.getReadinessProbe().getExec().setCommand( - List.of("curl", "--head", "--fail", "--silent", "http://127.0.0.1:" + Constants.KEYCLOAK_HTTP_PORT + "/health/ready")); - kcContainer.getLivenessProbe().getExec().setCommand( - List.of("curl", "--head", "--fail", "--silent", "http://127.0.0.1:" + Constants.KEYCLOAK_HTTP_PORT + "/health/live")); } else { var enabledTls = List.of( new EnvVarBuilder() @@ -401,6 +400,66 @@ public class KeycloakDeployment extends OperatorManagedResource implements Statu 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 = (this.keycloakCR.getSpec().isHttp()) ? "http" : "https"; + var kcPort = (this.keycloakCR.getSpec().isHttp()) ? Constants.KEYCLOAK_HTTP_PORT : Constants.KEYCLOAK_HTTPS_PORT; + + var baseProbe = new ArrayList<>(List.of("curl", "--head", "--fail", "--silent")); + + if (!this.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 String readConfigurationValue(String key) { + if (this.keycloakCR != null && + this.keycloakCR.getSpec() != null && + this.keycloakCR.getSpec().getServerConfiguration() != null + ) { + var serverConfigValue = this.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(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; + } } private Deployment createBaseDeployment() { diff --git a/operator/app/src/main/resources/base-keycloak-deployment.yaml b/operator/app/src/main/resources/base-keycloak-deployment.yaml index 3f18dc593f..e681777196 100644 --- a/operator/app/src/main/resources/base-keycloak-deployment.yaml +++ b/operator/app/src/main/resources/base-keycloak-deployment.yaml @@ -27,26 +27,10 @@ spec: - containerPort: 8080 protocol: TCP livenessProbe: - exec: - command: - - curl - - --insecure - - --head - - --fail - - --silent - - https://127.0.0.1:8443/health/live initialDelaySeconds: 20 periodSeconds: 2 failureThreshold: 150 readinessProbe: - exec: - command: - - curl - - --insecure - - --head - - --fail - - --silent - - https://127.0.0.1:8443/health/ready initialDelaySeconds: 20 periodSeconds: 2 failureThreshold: 250 diff --git a/operator/app/src/test/java/org/keycloak/operator/KeycloakDeploymentE2EIT.java b/operator/app/src/test/java/org/keycloak/operator/KeycloakDeploymentE2EIT.java index 16a56f594e..9a80a079fd 100644 --- a/operator/app/src/test/java/org/keycloak/operator/KeycloakDeploymentE2EIT.java +++ b/operator/app/src/test/java/org/keycloak/operator/KeycloakDeploymentE2EIT.java @@ -1,6 +1,8 @@ package org.keycloak.operator; import io.fabric8.kubernetes.api.model.EnvVarBuilder; +import io.fabric8.kubernetes.api.model.SecretBuilder; +import io.fabric8.kubernetes.api.model.SecretKeySelectorBuilder; import io.fabric8.kubernetes.api.model.apps.DeploymentSpecBuilder; import io.quarkus.logging.Log; import io.quarkus.test.junit.QuarkusTest; @@ -20,6 +22,7 @@ import java.util.Base64; import java.util.List; import java.util.Map; import java.util.concurrent.atomic.AtomicReference; +import java.util.stream.Collectors; import static java.util.concurrent.TimeUnit.MINUTES; import static java.util.concurrent.TimeUnit.SECONDS; @@ -354,4 +357,61 @@ public class KeycloakDeploymentE2EIT extends ClusterOperatorTest { } } + @Test + public void testHttpRelativePathWithPlainValue() { + try { + var kc = getDefaultKeycloakDeployment(); + kc.getSpec().getServerConfiguration().add(new ValueOrSecret(Constants.KEYCLOAK_HTTP_RELATIVE_PATH_KEY, "/foobar")); + deployKeycloak(k8sclient, kc, true); + + var pods = k8sclient + .pods() + .inNamespace(namespace) + .withLabels(Constants.DEFAULT_LABELS) + .list() + .getItems(); + + assertTrue(pods.get(0).getSpec().getContainers().get(0).getReadinessProbe().getExec().getCommand().stream().collect(Collectors.joining()).contains("foobar")); + } catch (Exception e) { + savePodLogs(); + throw e; + } + } + + @Test + public void testHttpRelativePathWithSecretValue() { + try { + var kc = getDefaultKeycloakDeployment(); + var secretName = "my-http-relative-path"; + var keyName = "rel-path"; + var httpRelativePathSecret = new SecretBuilder() + .withNewMetadata() + .withName(secretName) + .withNamespace(namespace) + .endMetadata() + .addToStringData(keyName, "/barfoo") + .build(); + k8sclient.secrets().inNamespace(namespace).createOrReplace(httpRelativePathSecret); + + kc.getSpec().getServerConfiguration().add(new ValueOrSecret(Constants.KEYCLOAK_HTTP_RELATIVE_PATH_KEY, + new SecretKeySelectorBuilder() + .withName(secretName) + .withKey(keyName) + .build())); + deployKeycloak(k8sclient, kc, true); + + var pods = k8sclient + .pods() + .inNamespace(namespace) + .withLabels(Constants.DEFAULT_LABELS) + .list() + .getItems(); + + assertTrue(pods.get(0).getSpec().getContainers().get(0).getReadinessProbe().getExec().getCommand().stream().collect(Collectors.joining()).contains("barfoo")); + } catch (Exception e) { + savePodLogs(); + throw e; + } + } + }