Add extensions as init container
This commit is contained in:
parent
59d9e3e3ee
commit
6bce8b80b9
8 changed files with 127 additions and 4 deletions
4
.github/workflows/operator-ci.yml
vendored
4
.github/workflows/operator-ci.yml
vendored
|
@ -65,5 +65,7 @@ jobs:
|
||||||
mvn clean verify \
|
mvn clean verify \
|
||||||
-Dquarkus.container-image.build=true -Dquarkus.container-image.tag=test \
|
-Dquarkus.container-image.build=true -Dquarkus.container-image.tag=test \
|
||||||
-Dquarkus.kubernetes.deployment-target=kubernetes \
|
-Dquarkus.kubernetes.deployment-target=kubernetes \
|
||||||
-Dquarkus.jib.jvm-arguments="-Djava.util.logging.manager=org.jboss.logmanager.LogManager","-Doperator.keycloak.image=keycloak:${GITHUB_SHA}","-Doperator.keycloak.image-pull-policy=Never" \
|
-Dquarkus.jib.jvm-arguments="-Djava.util.logging.manager=org.jboss.logmanager.LogManager",\
|
||||||
|
"-Doperator.keycloak.image=keycloak:${GITHUB_SHA}", \
|
||||||
|
"-Doperator.keycloak.image-pull-policy=Never" \
|
||||||
--no-transfer-progress -Dtest.operator.deployment=remote
|
--no-transfer-progress -Dtest.operator.deployment=remote
|
||||||
|
|
|
@ -29,5 +29,8 @@ public interface Config {
|
||||||
interface Keycloak {
|
interface Keycloak {
|
||||||
String image();
|
String image();
|
||||||
String imagePullPolicy();
|
String imagePullPolicy();
|
||||||
|
|
||||||
|
String initContainerImage();
|
||||||
|
String initContainerImagePullPolicy();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -35,4 +35,11 @@ public final class Constants {
|
||||||
public static final Map<String, String> DEFAULT_DIST_CONFIG = Map.of(
|
public static final Map<String, String> DEFAULT_DIST_CONFIG = Map.of(
|
||||||
"KC_HEALTH_ENABLED", "true"
|
"KC_HEALTH_ENABLED", "true"
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Init container
|
||||||
|
public static final String EXTENSIONS_VOLUME_NAME = "extensions";
|
||||||
|
public static final String KEYCLOAK_PROVIDERS_FOLDER = "/opt/keycloak/providers";
|
||||||
|
public static final String INIT_CONTAINER_NAME = "keycloak-extensions";
|
||||||
|
public static final String INIT_CONTAINER_EXTENSIONS_FOLDER = "/opt/extensions";
|
||||||
|
public static final String INIT_CONTAINER_EXTENSIONS_ENV_VAR = "KEYCLOAK_EXTENSIONS";
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,8 +17,11 @@
|
||||||
package org.keycloak.operator.v2alpha1;
|
package org.keycloak.operator.v2alpha1;
|
||||||
|
|
||||||
import io.fabric8.kubernetes.api.model.Container;
|
import io.fabric8.kubernetes.api.model.Container;
|
||||||
|
import io.fabric8.kubernetes.api.model.ContainerBuilder;
|
||||||
import io.fabric8.kubernetes.api.model.EnvVarBuilder;
|
import io.fabric8.kubernetes.api.model.EnvVarBuilder;
|
||||||
import io.fabric8.kubernetes.api.model.HasMetadata;
|
import io.fabric8.kubernetes.api.model.HasMetadata;
|
||||||
|
import io.fabric8.kubernetes.api.model.VolumeBuilder;
|
||||||
|
import io.fabric8.kubernetes.api.model.VolumeMountBuilder;
|
||||||
import io.fabric8.kubernetes.api.model.apps.Deployment;
|
import io.fabric8.kubernetes.api.model.apps.Deployment;
|
||||||
import io.fabric8.kubernetes.api.model.apps.DeploymentBuilder;
|
import io.fabric8.kubernetes.api.model.apps.DeploymentBuilder;
|
||||||
import io.fabric8.kubernetes.client.KubernetesClient;
|
import io.fabric8.kubernetes.client.KubernetesClient;
|
||||||
|
@ -30,7 +33,9 @@ import org.keycloak.operator.v2alpha1.crds.Keycloak;
|
||||||
import org.keycloak.operator.v2alpha1.crds.KeycloakStatusBuilder;
|
import org.keycloak.operator.v2alpha1.crds.KeycloakStatusBuilder;
|
||||||
|
|
||||||
import java.net.URL;
|
import java.net.URL;
|
||||||
|
import java.util.Collections;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
@ -87,6 +92,60 @@ public class KeycloakDeployment extends OperatorManagedResource {
|
||||||
.get();
|
.get();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void addInitContainer(Deployment baseDeployment, List<String> extensions) {
|
||||||
|
var skipExtensions = Optional
|
||||||
|
.ofNullable(extensions)
|
||||||
|
.map(e -> e.isEmpty())
|
||||||
|
.orElse(true);
|
||||||
|
|
||||||
|
if (skipExtensions) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add emptyDir Volume
|
||||||
|
var volumes = baseDeployment.getSpec().getTemplate().getSpec().getVolumes();
|
||||||
|
|
||||||
|
var extensionVolume = new VolumeBuilder()
|
||||||
|
.withName(Constants.EXTENSIONS_VOLUME_NAME)
|
||||||
|
.withNewEmptyDir()
|
||||||
|
.endEmptyDir()
|
||||||
|
.build();
|
||||||
|
|
||||||
|
volumes.add(extensionVolume);
|
||||||
|
baseDeployment.getSpec().getTemplate().getSpec().setVolumes(volumes);
|
||||||
|
|
||||||
|
// Add the main deployment Volume Mount
|
||||||
|
var container = baseDeployment.getSpec().getTemplate().getSpec().getContainers().get(0);
|
||||||
|
var containerVolumeMounts = container.getVolumeMounts();
|
||||||
|
|
||||||
|
var extensionVM = new VolumeMountBuilder()
|
||||||
|
.withName(Constants.EXTENSIONS_VOLUME_NAME)
|
||||||
|
.withMountPath(Constants.KEYCLOAK_PROVIDERS_FOLDER)
|
||||||
|
.withReadOnly(true)
|
||||||
|
.build();
|
||||||
|
containerVolumeMounts.add(extensionVM);
|
||||||
|
|
||||||
|
container.setVolumeMounts(containerVolumeMounts);
|
||||||
|
|
||||||
|
// Add the Extensions downloader init container
|
||||||
|
var extensionsValue = extensions.stream().collect(Collectors.joining(","));
|
||||||
|
var initContainer = new ContainerBuilder()
|
||||||
|
.withName(Constants.INIT_CONTAINER_NAME)
|
||||||
|
.withImage(config.keycloak().initContainerImage())
|
||||||
|
.withImagePullPolicy(config.keycloak().initContainerImagePullPolicy())
|
||||||
|
.addNewVolumeMount()
|
||||||
|
.withName(Constants.EXTENSIONS_VOLUME_NAME)
|
||||||
|
.withMountPath(Constants.INIT_CONTAINER_EXTENSIONS_FOLDER)
|
||||||
|
.endVolumeMount()
|
||||||
|
.addNewEnv()
|
||||||
|
.withName(Constants.INIT_CONTAINER_EXTENSIONS_ENV_VAR)
|
||||||
|
.withValue(extensionsValue)
|
||||||
|
.endEnv()
|
||||||
|
.build();
|
||||||
|
|
||||||
|
baseDeployment.getSpec().getTemplate().getSpec().setInitContainers(Collections.singletonList(initContainer));
|
||||||
|
}
|
||||||
|
|
||||||
private Deployment createBaseDeployment() {
|
private Deployment createBaseDeployment() {
|
||||||
URL url = this.getClass().getResource("/base-keycloak-deployment.yaml");
|
URL url = this.getClass().getResource("/base-keycloak-deployment.yaml");
|
||||||
Deployment baseDeployment = client.apps().deployments().load(url).get();
|
Deployment baseDeployment = client.apps().deployments().load(url).get();
|
||||||
|
@ -111,6 +170,8 @@ public class KeycloakDeployment extends OperatorManagedResource {
|
||||||
.map(e -> new EnvVarBuilder().withName(e.getKey()).withValue(e.getValue()).build())
|
.map(e -> new EnvVarBuilder().withName(e.getKey()).withValue(e.getValue()).build())
|
||||||
.collect(Collectors.toList()));
|
.collect(Collectors.toList()));
|
||||||
|
|
||||||
|
addInitContainer(baseDeployment, keycloakCR.getSpec().getExtensions());
|
||||||
|
|
||||||
// Set<String> configSecretsNames = new HashSet<>();
|
// Set<String> configSecretsNames = new HashSet<>();
|
||||||
// List<EnvVar> configEnvVars = serverConfig.entrySet().stream()
|
// List<EnvVar> configEnvVars = serverConfig.entrySet().stream()
|
||||||
// .map(e -> {
|
// .map(e -> {
|
||||||
|
|
|
@ -16,6 +16,9 @@
|
||||||
*/
|
*/
|
||||||
package org.keycloak.operator.v2alpha1.crds;
|
package org.keycloak.operator.v2alpha1.crds;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonPropertyDescription;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
public class KeycloakSpec {
|
public class KeycloakSpec {
|
||||||
|
@ -24,6 +27,17 @@ public class KeycloakSpec {
|
||||||
private String image;
|
private String image;
|
||||||
private Map<String, String> serverConfiguration;
|
private Map<String, String> serverConfiguration;
|
||||||
|
|
||||||
|
@JsonPropertyDescription("List of URLs to download Keycloak extensions.")
|
||||||
|
private List<String> extensions;
|
||||||
|
|
||||||
|
public List<String> getExtensions() {
|
||||||
|
return extensions;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setExtensions(List<String> extensions) {
|
||||||
|
this.extensions = extensions;
|
||||||
|
}
|
||||||
|
|
||||||
public int getInstances() {
|
public int getInstances() {
|
||||||
return instances;
|
return instances;
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,5 +5,8 @@ quarkus.container-image.builder=jib
|
||||||
quarkus.operator-sdk.crd.validate=false
|
quarkus.operator-sdk.crd.validate=false
|
||||||
|
|
||||||
# Operator config
|
# Operator config
|
||||||
operator.keycloak.image=quay.io/keycloak/keycloak-x:latest
|
operator.keycloak.image=quay.io/keycloak/keycloak:latest
|
||||||
operator.keycloak.image-pull-policy=Always
|
operator.keycloak.image-pull-policy=Always
|
||||||
|
|
||||||
|
operator.keycloak.init-container-image=quay.io/keycloak/keycloak-init-container:legacy
|
||||||
|
operator.keycloak.init-container-image-pull-policy=Always
|
||||||
|
|
|
@ -30,14 +30,14 @@ spec:
|
||||||
httpGet:
|
httpGet:
|
||||||
path: /health/live
|
path: /health/live
|
||||||
port: 8080
|
port: 8080
|
||||||
initialDelaySeconds: 15
|
initialDelaySeconds: 20
|
||||||
periodSeconds: 2
|
periodSeconds: 2
|
||||||
failureThreshold: 100
|
failureThreshold: 100
|
||||||
readinessProbe:
|
readinessProbe:
|
||||||
httpGet:
|
httpGet:
|
||||||
path: /health/ready
|
path: /health/ready
|
||||||
port: 8080
|
port: 8080
|
||||||
initialDelaySeconds: 15
|
initialDelaySeconds: 20
|
||||||
periodSeconds: 2
|
periodSeconds: 2
|
||||||
failureThreshold: 200
|
failureThreshold: 200
|
||||||
dnsPolicy: ClusterFirst
|
dnsPolicy: ClusterFirst
|
||||||
|
|
|
@ -9,10 +9,13 @@ import org.junit.jupiter.api.Test;
|
||||||
import org.keycloak.operator.v2alpha1.crds.Keycloak;
|
import org.keycloak.operator.v2alpha1.crds.Keycloak;
|
||||||
|
|
||||||
import java.time.Duration;
|
import java.time.Duration;
|
||||||
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
import static org.assertj.core.api.Assertions.assertThat;
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||||
|
import static org.keycloak.operator.Constants.DEFAULT_LABELS;
|
||||||
import static org.keycloak.operator.utils.K8sUtils.deployKeycloak;
|
import static org.keycloak.operator.utils.K8sUtils.deployKeycloak;
|
||||||
import static org.keycloak.operator.utils.K8sUtils.getDefaultKeycloakDeployment;
|
import static org.keycloak.operator.utils.K8sUtils.getDefaultKeycloakDeployment;
|
||||||
import static org.keycloak.operator.utils.K8sUtils.waitForKeycloakToBeReady;
|
import static org.keycloak.operator.utils.K8sUtils.waitForKeycloakToBeReady;
|
||||||
|
@ -112,4 +115,34 @@ public class KeycloakDeploymentE2EIT extends ClusterOperatorTest {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testExtensions() {
|
||||||
|
try {
|
||||||
|
var kc = getDefaultKeycloakDeployment();
|
||||||
|
kc.getSpec().setExtensions(
|
||||||
|
Collections.singletonList(
|
||||||
|
"https://github.com/aerogear/keycloak-metrics-spi/releases/download/2.5.3/keycloak-metrics-spi-2.5.3.jar"));
|
||||||
|
deployKeycloak(k8sclient, kc, true);
|
||||||
|
|
||||||
|
var kcPod = k8sclient
|
||||||
|
.pods()
|
||||||
|
.inNamespace(namespace)
|
||||||
|
.withLabels(DEFAULT_LABELS)
|
||||||
|
.list()
|
||||||
|
.getItems()
|
||||||
|
.get(0);
|
||||||
|
|
||||||
|
Awaitility.await()
|
||||||
|
.ignoreExceptions()
|
||||||
|
.untilAsserted(() -> {
|
||||||
|
var logs = k8sclient.pods().inNamespace(namespace).withName(kcPod.getMetadata().getName()).getLog();
|
||||||
|
|
||||||
|
assertTrue(logs.contains("metrics-listener (org.jboss.aerogear.keycloak.metrics.MetricsEventListenerFactory) is implementing the internal SPI"));
|
||||||
|
});
|
||||||
|
} catch (Exception e) {
|
||||||
|
savePodLogs();
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue