Support management port for health and metrics (#27629)
* Support management port for health and metrics Closes #19334 Signed-off-by: Martin Bartoš <mabartos@redhat.com> * Deprecate option Signed-off-by: Martin Bartoš <mabartos@redhat.com> * Remove relativePath first-class citizen, rename ManagementSpec Signed-off-by: Martin Bartoš <mabartos@redhat.com> * Fix KeycloakDistConfiguratorTest Signed-off-by: Martin Bartoš <mabartos@redhat.com> --------- Signed-off-by: Martin Bartoš <mabartos@redhat.com>
This commit is contained in:
parent
8ef3423f4a
commit
7f048300fe
53 changed files with 1634 additions and 165 deletions
|
@ -60,6 +60,8 @@ public final class Constants {
|
|||
public static final Integer KEYCLOAK_DISCOVERY_SERVICE_PORT = 7800;
|
||||
public static final String KEYCLOAK_DISCOVERY_TCP_PORT_NAME = "tcp";
|
||||
public static final String KEYCLOAK_DISCOVERY_SERVICE_SUFFIX = "-discovery";
|
||||
public static final Integer KEYCLOAK_MANAGEMENT_PORT = 9000;
|
||||
public static final String KEYCLOAK_MANAGEMENT_PORT_NAME = "management";
|
||||
|
||||
public static final String KEYCLOAK_INGRESS_SUFFIX = "-ingress";
|
||||
|
||||
|
@ -72,4 +74,5 @@ public final class Constants {
|
|||
public static final String CACHE_CONFIG_FOLDER = CONFIG_FOLDER + "/" + CACHE_CONFIG_SUBFOLDER;
|
||||
|
||||
public static final String KEYCLOAK_HTTP_RELATIVE_PATH_KEY = "http-relative-path";
|
||||
public static final String KEYCLOAK_HTTP_MANAGEMENT_RELATIVE_PATH_KEY = "http-management-relative-path";
|
||||
}
|
||||
|
|
|
@ -22,7 +22,6 @@ import io.fabric8.kubernetes.api.model.EnvVar;
|
|||
import io.fabric8.kubernetes.api.model.EnvVarBuilder;
|
||||
import io.fabric8.kubernetes.api.model.EnvVarSource;
|
||||
import io.fabric8.kubernetes.api.model.EnvVarSourceBuilder;
|
||||
import io.fabric8.kubernetes.api.model.PodResourceClaim;
|
||||
import io.fabric8.kubernetes.api.model.PodSpec;
|
||||
import io.fabric8.kubernetes.api.model.PodTemplateSpec;
|
||||
import io.fabric8.kubernetes.api.model.Secret;
|
||||
|
@ -45,6 +44,7 @@ import org.keycloak.operator.crds.v2alpha1.deployment.Keycloak;
|
|||
import org.keycloak.operator.crds.v2alpha1.deployment.KeycloakSpec;
|
||||
import org.keycloak.operator.crds.v2alpha1.deployment.ValueOrSecret;
|
||||
import org.keycloak.operator.crds.v2alpha1.deployment.spec.CacheSpec;
|
||||
import org.keycloak.operator.crds.v2alpha1.deployment.spec.HttpManagementSpec;
|
||||
import org.keycloak.operator.crds.v2alpha1.deployment.spec.Truststore;
|
||||
import org.keycloak.operator.crds.v2alpha1.deployment.spec.TruststoreSource;
|
||||
import org.keycloak.operator.crds.v2alpha1.deployment.spec.UnsupportedSpec;
|
||||
|
@ -284,12 +284,13 @@ public class KeycloakDeploymentDependentResource extends CRUDKubernetesDependent
|
|||
containerBuilder.addToArgs(0, getJGroupsParameter(keycloakCR));
|
||||
|
||||
// probes
|
||||
var tlsConfigured = isTlsConfigured(keycloakCR);
|
||||
var protocol = !tlsConfigured ? "HTTP" : "HTTPS";
|
||||
var kcPort = KeycloakServiceDependentResource.getServicePort(tlsConfigured, keycloakCR);
|
||||
|
||||
// Relative path ends with '/'
|
||||
var kcRelativePath = readConfigurationValue(Constants.KEYCLOAK_HTTP_RELATIVE_PATH_KEY, keycloakCR, context)
|
||||
var protocol = isTlsConfigured(keycloakCR) ? "HTTPS" : "HTTP";
|
||||
var port = Optional.ofNullable(keycloakCR.getSpec())
|
||||
.map(KeycloakSpec::getHttpManagementSpec)
|
||||
.map(HttpManagementSpec::getPort)
|
||||
.orElse(Constants.KEYCLOAK_MANAGEMENT_PORT);
|
||||
var relativePath = readConfigurationValue(Constants.KEYCLOAK_HTTP_MANAGEMENT_RELATIVE_PATH_KEY, keycloakCR, context)
|
||||
.or(() -> readConfigurationValue(Constants.KEYCLOAK_HTTP_RELATIVE_PATH_KEY, keycloakCR, context))
|
||||
.map(path -> !path.endsWith("/") ? path + "/" : path)
|
||||
.orElse("/");
|
||||
|
||||
|
@ -299,8 +300,8 @@ public class KeycloakDeploymentDependentResource extends CRUDKubernetesDependent
|
|||
.withFailureThreshold(3)
|
||||
.withNewHttpGet()
|
||||
.withScheme(protocol)
|
||||
.withNewPort(kcPort)
|
||||
.withPath(kcRelativePath + "health/ready")
|
||||
.withNewPort(port)
|
||||
.withPath(relativePath + "health/ready")
|
||||
.endHttpGet()
|
||||
.endReadinessProbe();
|
||||
}
|
||||
|
@ -310,8 +311,8 @@ public class KeycloakDeploymentDependentResource extends CRUDKubernetesDependent
|
|||
.withFailureThreshold(3)
|
||||
.withNewHttpGet()
|
||||
.withScheme(protocol)
|
||||
.withNewPort(kcPort)
|
||||
.withPath(kcRelativePath + "health/live")
|
||||
.withNewPort(port)
|
||||
.withPath(relativePath + "health/live")
|
||||
.endHttpGet()
|
||||
.endLivenessProbe();
|
||||
}
|
||||
|
@ -321,14 +322,14 @@ public class KeycloakDeploymentDependentResource extends CRUDKubernetesDependent
|
|||
.withFailureThreshold(600)
|
||||
.withNewHttpGet()
|
||||
.withScheme(protocol)
|
||||
.withNewPort(kcPort)
|
||||
.withPath(kcRelativePath + "health/started")
|
||||
.withNewPort(port)
|
||||
.withPath(relativePath + "health/started")
|
||||
.endHttpGet()
|
||||
.endStartupProbe();
|
||||
}
|
||||
|
||||
// add in ports - there's no merging being done here
|
||||
StatefulSet baseDeployment = containerBuilder
|
||||
final StatefulSet baseDeployment = containerBuilder
|
||||
.addNewPort()
|
||||
.withName(Constants.KEYCLOAK_HTTPS_PORT_NAME)
|
||||
.withContainerPort(Constants.KEYCLOAK_HTTPS_PORT)
|
||||
|
@ -339,6 +340,11 @@ public class KeycloakDeploymentDependentResource extends CRUDKubernetesDependent
|
|||
.withContainerPort(Constants.KEYCLOAK_HTTP_PORT)
|
||||
.withProtocol(Constants.KEYCLOAK_SERVICE_PROTOCOL)
|
||||
.endPort()
|
||||
.addNewPort()
|
||||
.withName(Constants.KEYCLOAK_MANAGEMENT_PORT_NAME)
|
||||
.withContainerPort(Constants.KEYCLOAK_MANAGEMENT_PORT)
|
||||
.withProtocol(Constants.KEYCLOAK_SERVICE_PROTOCOL)
|
||||
.endPort()
|
||||
.endContainer().endSpec().endTemplate().endSpec().build();
|
||||
|
||||
return baseDeployment;
|
||||
|
|
|
@ -32,6 +32,7 @@ import org.keycloak.operator.crds.v2alpha1.deployment.spec.DatabaseSpec;
|
|||
import org.keycloak.operator.crds.v2alpha1.deployment.spec.FeatureSpec;
|
||||
import org.keycloak.operator.crds.v2alpha1.deployment.spec.HostnameSpec;
|
||||
import org.keycloak.operator.crds.v2alpha1.deployment.spec.HttpSpec;
|
||||
import org.keycloak.operator.crds.v2alpha1.deployment.spec.HttpManagementSpec;
|
||||
import org.keycloak.operator.crds.v2alpha1.deployment.spec.ProxySpec;
|
||||
import org.keycloak.operator.crds.v2alpha1.deployment.spec.TransactionsSpec;
|
||||
|
||||
|
@ -70,6 +71,7 @@ public class KeycloakDistConfigurator {
|
|||
configureDatabase();
|
||||
configureCache();
|
||||
configureProxy();
|
||||
configureManagement();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -137,6 +139,11 @@ public class KeycloakDistConfigurator {
|
|||
.mapOption("proxy-headers", ProxySpec::getHeaders);
|
||||
}
|
||||
|
||||
void configureManagement() {
|
||||
optionMapper(keycloakCR -> keycloakCR.getSpec().getHttpManagementSpec())
|
||||
.mapOption("http-management-port", HttpManagementSpec::getPort);
|
||||
}
|
||||
|
||||
/* ---------- END of configuration of first-class citizen fields ---------- */
|
||||
|
||||
/**
|
||||
|
|
|
@ -29,7 +29,9 @@ import io.javaoperatorsdk.operator.processing.dependent.kubernetes.KubernetesDep
|
|||
import org.keycloak.operator.Constants;
|
||||
import org.keycloak.operator.Utils;
|
||||
import org.keycloak.operator.crds.v2alpha1.deployment.Keycloak;
|
||||
import org.keycloak.operator.crds.v2alpha1.deployment.KeycloakSpec;
|
||||
import org.keycloak.operator.crds.v2alpha1.deployment.spec.HttpSpec;
|
||||
import org.keycloak.operator.crds.v2alpha1.deployment.spec.HttpManagementSpec;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
|
@ -56,13 +58,31 @@ public class KeycloakServiceDependentResource extends CRUDKubernetesDependentRes
|
|||
Optional<HttpSpec> httpSpec = Optional.ofNullable(keycloak.getSpec().getHttpSpec());
|
||||
boolean httpEnabled = httpSpec.map(HttpSpec::getHttpEnabled).orElse(false);
|
||||
if (!tlsConfigured || httpEnabled) {
|
||||
builder.addNewPort().withPort(getServicePort(false, keycloak)).withName(Constants.KEYCLOAK_HTTP_PORT_NAME)
|
||||
.withProtocol(Constants.KEYCLOAK_SERVICE_PROTOCOL).endPort();
|
||||
builder.addNewPort()
|
||||
.withPort(getServicePort(false, keycloak))
|
||||
.withName(Constants.KEYCLOAK_HTTP_PORT_NAME)
|
||||
.withProtocol(Constants.KEYCLOAK_SERVICE_PROTOCOL)
|
||||
.endPort();
|
||||
}
|
||||
if (tlsConfigured) {
|
||||
builder.addNewPort().withPort(getServicePort(true, keycloak)).withName(Constants.KEYCLOAK_HTTPS_PORT_NAME)
|
||||
.withProtocol(Constants.KEYCLOAK_SERVICE_PROTOCOL).endPort();
|
||||
builder.addNewPort()
|
||||
.withPort(getServicePort(true, keycloak))
|
||||
.withName(Constants.KEYCLOAK_HTTPS_PORT_NAME)
|
||||
.withProtocol(Constants.KEYCLOAK_SERVICE_PROTOCOL)
|
||||
.endPort();
|
||||
}
|
||||
|
||||
var managementPort = Optional.ofNullable(keycloak.getSpec())
|
||||
.map(KeycloakSpec::getHttpManagementSpec)
|
||||
.map(HttpManagementSpec::getPort)
|
||||
.orElse(Constants.KEYCLOAK_MANAGEMENT_PORT);
|
||||
|
||||
builder.addNewPort()
|
||||
.withPort(managementPort)
|
||||
.withName(Constants.KEYCLOAK_MANAGEMENT_PORT_NAME)
|
||||
.withProtocol(Constants.KEYCLOAK_SERVICE_PROTOCOL)
|
||||
.endPort();
|
||||
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
|
|
|
@ -24,6 +24,7 @@ import org.keycloak.operator.crds.v2alpha1.deployment.spec.CacheSpec;
|
|||
import org.keycloak.operator.crds.v2alpha1.deployment.spec.DatabaseSpec;
|
||||
import org.keycloak.operator.crds.v2alpha1.deployment.spec.FeatureSpec;
|
||||
import org.keycloak.operator.crds.v2alpha1.deployment.spec.HostnameSpec;
|
||||
import org.keycloak.operator.crds.v2alpha1.deployment.spec.HttpManagementSpec;
|
||||
import org.keycloak.operator.crds.v2alpha1.deployment.spec.HttpSpec;
|
||||
import org.keycloak.operator.crds.v2alpha1.deployment.spec.IngressSpec;
|
||||
import org.keycloak.operator.crds.v2alpha1.deployment.spec.ProxySpec;
|
||||
|
@ -105,6 +106,10 @@ public class KeycloakSpec {
|
|||
@JsonPropertyDescription("In this section you can configure Keycloak's reverse proxy setting")
|
||||
private ProxySpec proxySpec;
|
||||
|
||||
@JsonProperty("httpManagement")
|
||||
@JsonPropertyDescription("In this section you can configure Keycloak's management interface setting.")
|
||||
private HttpManagementSpec httpManagementSpec;
|
||||
|
||||
public HttpSpec getHttpSpec() {
|
||||
return httpSpec;
|
||||
}
|
||||
|
@ -185,6 +190,14 @@ public class KeycloakSpec {
|
|||
this.imagePullSecrets = imagePullSecrets;
|
||||
}
|
||||
|
||||
public HttpManagementSpec getHttpManagementSpec() {
|
||||
return httpManagementSpec;
|
||||
}
|
||||
|
||||
public void setHttpManagementSpec(HttpManagementSpec httpManagementSpec) {
|
||||
this.httpManagementSpec = httpManagementSpec;
|
||||
}
|
||||
|
||||
public List<ValueOrSecret> getAdditionalOptions() {
|
||||
if (this.additionalOptions == null) {
|
||||
this.additionalOptions = new ArrayList<>();
|
||||
|
|
|
@ -0,0 +1,38 @@
|
|||
/*
|
||||
* Copyright 2024 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.JsonInclude;
|
||||
import com.fasterxml.jackson.annotation.JsonPropertyDescription;
|
||||
import io.sundr.builder.annotations.Buildable;
|
||||
import org.keycloak.operator.Constants;
|
||||
|
||||
@JsonInclude(JsonInclude.Include.NON_NULL)
|
||||
@Buildable(editableEnabled = false, builderPackage = "io.fabric8.kubernetes.api.builder")
|
||||
public class HttpManagementSpec {
|
||||
|
||||
@JsonPropertyDescription("Port of the management interface.")
|
||||
private Integer port = Constants.KEYCLOAK_MANAGEMENT_PORT;
|
||||
|
||||
public Integer getPort() {
|
||||
return port;
|
||||
}
|
||||
|
||||
public void setPort(Integer port) {
|
||||
this.port = port;
|
||||
}
|
||||
}
|
|
@ -287,6 +287,7 @@ public class KeycloakDeploymentTest extends BaseOperatorTest {
|
|||
deployKeycloak(k8sclient, kc, true);
|
||||
|
||||
assertKeycloakAccessibleViaService(kc, false, Constants.KEYCLOAK_HTTP_PORT);
|
||||
assertManagementInterfaceAccessibleViaService(kc, false);
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -296,6 +297,9 @@ public class KeycloakDeploymentTest extends BaseOperatorTest {
|
|||
deployKeycloak(k8sclient, kc, true);
|
||||
|
||||
assertKeycloakAccessibleViaService(kc, false, Constants.KEYCLOAK_HTTP_PORT);
|
||||
|
||||
// if TLS is enabled, management interface should use https
|
||||
assertManagementInterfaceAccessibleViaService(kc, true);
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -723,7 +727,7 @@ public class KeycloakDeploymentTest extends BaseOperatorTest {
|
|||
|
||||
String serviceName = KeycloakServiceDependentResource.getServiceName(kc);
|
||||
assertThat(k8sclient.resources(Service.class).withName(serviceName).require().getSpec().getPorts()
|
||||
.stream().map(ServicePort::getName).anyMatch(protocol::equals));
|
||||
.stream().map(ServicePort::getName).anyMatch(protocol::equals)).isTrue();
|
||||
|
||||
String url = protocol + "://" + serviceName + "." + namespace + ":" + port + "/admin/master/console/";
|
||||
Log.info("Checking url: " + url);
|
||||
|
@ -734,4 +738,23 @@ public class KeycloakDeploymentTest extends BaseOperatorTest {
|
|||
assertEquals("200", curlOutput);
|
||||
});
|
||||
}
|
||||
|
||||
private void assertManagementInterfaceAccessibleViaService(Keycloak kc, boolean https) {
|
||||
Awaitility.await()
|
||||
.ignoreExceptions()
|
||||
.untilAsserted(() -> {
|
||||
String serviceName = KeycloakServiceDependentResource.getServiceName(kc);
|
||||
assertThat(k8sclient.resources(Service.class).withName(serviceName).require().getSpec().getPorts()
|
||||
.stream().map(ServicePort::getName).anyMatch(Constants.KEYCLOAK_MANAGEMENT_PORT_NAME::equals)).isTrue();
|
||||
|
||||
String protocol = https ? "https" : "http";
|
||||
String url = protocol + "://" + serviceName + "." + namespace + ":" + Constants.KEYCLOAK_MANAGEMENT_PORT;
|
||||
Log.info("Checking url: " + url);
|
||||
|
||||
var curlOutput = K8sUtils.inClusterCurl(k8sclient, namespace, url);
|
||||
Log.info("Curl Output: " + curlOutput);
|
||||
|
||||
assertEquals("200", curlOutput);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -61,9 +61,6 @@ public class KeycloakServicesTest extends BaseOperatorTest {
|
|||
currentService.getSpec().setSessionAffinity("ClientIP");
|
||||
var origSpecs = new ServiceSpecBuilder(currentService.getSpec()).build(); // deep copy
|
||||
|
||||
// a managed change
|
||||
currentService.getSpec().getPorts().get(0).setName(null);
|
||||
|
||||
currentService.getMetadata().getLabels().putAll(labels);
|
||||
|
||||
currentService.getMetadata().setResourceVersion(null);
|
||||
|
|
|
@ -26,6 +26,7 @@ import org.keycloak.operator.crds.v2alpha1.deployment.ValueOrSecret;
|
|||
import org.keycloak.operator.crds.v2alpha1.deployment.spec.DatabaseSpec;
|
||||
import org.keycloak.operator.crds.v2alpha1.deployment.spec.FeatureSpec;
|
||||
import org.keycloak.operator.crds.v2alpha1.deployment.spec.HostnameSpec;
|
||||
import org.keycloak.operator.crds.v2alpha1.deployment.spec.HttpManagementSpec;
|
||||
import org.keycloak.operator.crds.v2alpha1.deployment.spec.TransactionsSpec;
|
||||
import org.keycloak.operator.crds.v2alpha1.realmimport.KeycloakRealmImport;
|
||||
import org.keycloak.operator.testsuite.utils.K8sUtils;
|
||||
|
@ -89,6 +90,10 @@ public class CRSerializationTest {
|
|||
assertEquals("usernameSecretKey", databaseSpec.getUsernameSecret().getKey());
|
||||
assertEquals("passwordSecret", databaseSpec.getPasswordSecret().getName());
|
||||
assertEquals("passwordSecretKey", databaseSpec.getPasswordSecret().getKey());
|
||||
|
||||
HttpManagementSpec managementSpec = keycloak.getSpec().getHttpManagementSpec();
|
||||
assertNotNull(managementSpec);
|
||||
assertEquals(9003, managementSpec.getPort());
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
|
@ -144,6 +144,15 @@ public class KeycloakDistConfiguratorTest {
|
|||
testFirstClassCitizen(expectedValues);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void management() {
|
||||
final Map<String, String> expectedValues = new HashMap<>(Map.of(
|
||||
"http-management-port", "9003"
|
||||
));
|
||||
|
||||
testFirstClassCitizen(expectedValues);
|
||||
}
|
||||
|
||||
/* UTILS */
|
||||
|
||||
private void testFirstClassCitizen(Map<String, String> expectedValues) {
|
||||
|
|
|
@ -55,6 +55,7 @@ import jakarta.inject.Inject;
|
|||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
@QuarkusTest
|
||||
|
@ -341,7 +342,7 @@ public class PodTemplateTest {
|
|||
@Test
|
||||
public void testRelativePathHealthProbes() {
|
||||
final Function<String, Container> setUpRelativePath = (path) -> getDeployment(null, new StatefulSet(),
|
||||
spec -> spec.withAdditionalOptions(new ValueOrSecret("http-relative-path", path)))
|
||||
spec -> spec.withAdditionalOptions(new ValueOrSecret("http-management-relative-path", path)))
|
||||
.getSpec()
|
||||
.getTemplate()
|
||||
.getSpec()
|
||||
|
@ -351,18 +352,22 @@ public class PodTemplateTest {
|
|||
var first = setUpRelativePath.apply("/");
|
||||
assertEquals("/health/ready", first.getReadinessProbe().getHttpGet().getPath());
|
||||
assertEquals("/health/live", first.getLivenessProbe().getHttpGet().getPath());
|
||||
assertEquals("/health/started", first.getStartupProbe().getHttpGet().getPath());
|
||||
|
||||
var second = setUpRelativePath.apply("some");
|
||||
assertEquals("some/health/ready", second.getReadinessProbe().getHttpGet().getPath());
|
||||
assertEquals("some/health/live", second.getLivenessProbe().getHttpGet().getPath());
|
||||
assertEquals("some/health/started", second.getStartupProbe().getHttpGet().getPath());
|
||||
|
||||
var third = setUpRelativePath.apply("");
|
||||
assertEquals("/health/ready", third.getReadinessProbe().getHttpGet().getPath());
|
||||
assertEquals("/health/live", third.getLivenessProbe().getHttpGet().getPath());
|
||||
assertEquals("/health/started", third.getStartupProbe().getHttpGet().getPath());
|
||||
|
||||
var fourth = setUpRelativePath.apply("/some/");
|
||||
assertEquals("/some/health/ready", fourth.getReadinessProbe().getHttpGet().getPath());
|
||||
assertEquals("/some/health/live", fourth.getLivenessProbe().getHttpGet().getPath());
|
||||
assertEquals("/some/health/started", fourth.getStartupProbe().getHttpGet().getPath());
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -372,10 +377,27 @@ public class PodTemplateTest {
|
|||
|
||||
// Act
|
||||
var podTemplate = getDeployment(additionalPodTemplate).getSpec().getTemplate();
|
||||
var container = podTemplate.getSpec().getContainers().get(0);
|
||||
|
||||
// Assert
|
||||
assertThat(podTemplate.getSpec().getContainers().get(0).getArgs()).doesNotContain(KeycloakDeploymentDependentResource.OPTIMIZED_ARG);
|
||||
assertThat(podTemplate.getSpec().getContainers().get(0).getEnv().stream().anyMatch(envVar -> envVar.getName().equals(KeycloakDeploymentDependentResource.KC_TRUSTSTORE_PATHS)));
|
||||
assertNotNull(container);
|
||||
assertThat(container.getArgs()).doesNotContain(KeycloakDeploymentDependentResource.OPTIMIZED_ARG);
|
||||
assertThat(container.getEnv().stream()).anyMatch(envVar -> envVar.getName().equals(KeycloakDeploymentDependentResource.KC_TRUSTSTORE_PATHS));
|
||||
|
||||
var readiness = container.getReadinessProbe().getHttpGet();
|
||||
assertNotNull(readiness);
|
||||
assertThat(readiness.getPath()).isEqualTo("/health/ready");
|
||||
assertThat(readiness.getPort().getIntVal()).isEqualTo(Constants.KEYCLOAK_MANAGEMENT_PORT);
|
||||
|
||||
var liveness = container.getLivenessProbe().getHttpGet();
|
||||
assertNotNull(liveness);
|
||||
assertThat(liveness.getPath()).isEqualTo("/health/live");
|
||||
assertThat(liveness.getPort().getIntVal()).isEqualTo(Constants.KEYCLOAK_MANAGEMENT_PORT);
|
||||
|
||||
var startup = container.getStartupProbe().getHttpGet();
|
||||
assertNotNull(startup);
|
||||
assertThat(startup.getPath()).isEqualTo("/health/started");
|
||||
assertThat(startup.getPort().getIntVal()).isEqualTo(Constants.KEYCLOAK_MANAGEMENT_PORT);
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
|
@ -69,6 +69,8 @@ spec:
|
|||
x:
|
||||
secret:
|
||||
name: my-secret
|
||||
httpManagement:
|
||||
port: 9003
|
||||
unsupported:
|
||||
podTemplate:
|
||||
metadata:
|
||||
|
|
|
@ -0,0 +1,104 @@
|
|||
/*
|
||||
* Copyright 2024 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.config;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Options for the management interface that handles management endpoints (f.e. health and metrics endpoints)
|
||||
*/
|
||||
public class ManagementOptions {
|
||||
|
||||
public static final Option<Boolean> LEGACY_OBSERVABILITY_INTERFACE = new OptionBuilder<>("legacy-observability-interface", Boolean.class)
|
||||
.category(OptionCategory.MANAGEMENT)
|
||||
.deprecated()
|
||||
.description("If metrics/health endpoints should be exposed on the main HTTP server (not recommended). If set to true, the management interface is disabled.")
|
||||
.defaultValue(Boolean.FALSE)
|
||||
.buildTime(true)
|
||||
.build();
|
||||
|
||||
public static final Option<String> HTTP_MANAGEMENT_RELATIVE_PATH = new OptionBuilder<>("http-management-relative-path", String.class)
|
||||
.category(OptionCategory.MANAGEMENT)
|
||||
.description("Set the path relative to '/' for serving resources from management interface. The path must start with a '/'. If not given, the value is inherited from HTTP options.")
|
||||
.defaultValue("/")
|
||||
.buildTime(true)
|
||||
.build();
|
||||
|
||||
public static final Option<Integer> HTTP_MANAGEMENT_PORT = new OptionBuilder<>("http-management-port", Integer.class)
|
||||
.category(OptionCategory.MANAGEMENT)
|
||||
.description("Port of the management interface.")
|
||||
.defaultValue(9000)
|
||||
.build();
|
||||
|
||||
public static final Option<String> HTTP_MANAGEMENT_HOST = new OptionBuilder<>("http-management-host", String.class)
|
||||
.hidden()
|
||||
.category(OptionCategory.MANAGEMENT)
|
||||
.description("Host of the management interface. If not given, the value is inherited from HTTP options.")
|
||||
.defaultValue("0.0.0.0")
|
||||
.build();
|
||||
|
||||
//HTTPS
|
||||
public static final Option<HttpOptions.ClientAuth> HTTPS_MANAGEMENT_CLIENT_AUTH = new OptionBuilder<>("https-management-client-auth", HttpOptions.ClientAuth.class)
|
||||
.category(OptionCategory.MANAGEMENT)
|
||||
.description("Configures the management interface to require/request client authentication. If not given, the value is inherited from HTTP options.")
|
||||
.defaultValue(HttpOptions.ClientAuth.none)
|
||||
.buildTime(true)
|
||||
.build();
|
||||
|
||||
public static final Option<String> HTTPS_MANAGEMENT_CIPHER_SUITES = new OptionBuilder<>("https-management-cipher-suites", String.class)
|
||||
.hidden()
|
||||
.category(OptionCategory.MANAGEMENT)
|
||||
.description("The cipher suites to use for the management server. If not given, the value is inherited from HTTP options.")
|
||||
.hidden()
|
||||
.build();
|
||||
|
||||
public static final Option<List<String>> HTTPS_MANAGEMENT_PROTOCOLS = OptionBuilder.listOptionBuilder("https-management-protocols", String.class)
|
||||
.hidden()
|
||||
.category(OptionCategory.MANAGEMENT)
|
||||
.description("The list of protocols to explicitly enable for the management server. If not given, the value is inherited from HTTP options.")
|
||||
.defaultValue(List.of("TLSv1.3,TLSv1.2"))
|
||||
.hidden()
|
||||
.build();
|
||||
|
||||
public static final Option<File> HTTPS_MANAGEMENT_CERTIFICATE_FILE = new OptionBuilder<>("https-management-certificate-file", File.class)
|
||||
.category(OptionCategory.MANAGEMENT)
|
||||
.description("The file path to a server certificate or certificate chain in PEM format for the management server. If not given, the value is inherited from HTTP options.")
|
||||
.build();
|
||||
|
||||
public static final Option<File> HTTPS_MANAGEMENT_CERTIFICATE_KEY_FILE = new OptionBuilder<>("https-management-certificate-key-file", File.class)
|
||||
.category(OptionCategory.MANAGEMENT)
|
||||
.description("The file path to a private key in PEM format for the management server. If not given, the value is inherited from HTTP options.")
|
||||
.build();
|
||||
|
||||
public static final Option<File> HTTPS_MANAGEMENT_KEY_STORE_FILE = new OptionBuilder<>("https-management-key-store-file", File.class)
|
||||
.category(OptionCategory.MANAGEMENT)
|
||||
.description("The key store which holds the certificate information instead of specifying separate files for the management server. If not given, the value is inherited from HTTP options.")
|
||||
.build();
|
||||
|
||||
public static final Option<String> HTTPS_MANAGEMENT_KEY_STORE_PASSWORD = new OptionBuilder<>("https-management-key-store-password", String.class)
|
||||
.category(OptionCategory.MANAGEMENT)
|
||||
.description("The password of the key store file for the management server. If not given, the value is inherited from HTTP options.")
|
||||
.defaultValue("password")
|
||||
.build();
|
||||
|
||||
public static final Option<String> HTTPS_MANAGEMENT_KEY_STORE_TYPE = new OptionBuilder<>("https-management-key-store-type", String.class)
|
||||
.hidden()
|
||||
.category(OptionCategory.MANAGEMENT)
|
||||
.description("The type of the key store file for the management server. If not given, the value is inherited from HTTP options.")
|
||||
.build();
|
||||
}
|
|
@ -3,13 +3,14 @@ package org.keycloak.config;
|
|||
public enum OptionCategory {
|
||||
// ordered by name asc
|
||||
CACHE("Cache", 10, ConfigSupportLevel.SUPPORTED),
|
||||
CONFIG("Config", 15, ConfigSupportLevel.SUPPORTED),
|
||||
DATABASE("Database", 20, ConfigSupportLevel.SUPPORTED),
|
||||
TRANSACTION("Transaction",30, ConfigSupportLevel.SUPPORTED),
|
||||
FEATURE("Feature", 40, ConfigSupportLevel.SUPPORTED),
|
||||
HOSTNAME("Hostname", 50, ConfigSupportLevel.SUPPORTED),
|
||||
HTTP("HTTP(S)", 60, ConfigSupportLevel.SUPPORTED),
|
||||
HEALTH("Health", 70, ConfigSupportLevel.SUPPORTED),
|
||||
CONFIG("Config", 75, ConfigSupportLevel.SUPPORTED),
|
||||
MANAGEMENT("Management", 75, ConfigSupportLevel.SUPPORTED),
|
||||
METRICS("Metrics", 80, ConfigSupportLevel.SUPPORTED),
|
||||
PROXY("Proxy", 90, ConfigSupportLevel.SUPPORTED),
|
||||
VAULT("Vault", 100, ConfigSupportLevel.SUPPORTED),
|
||||
|
|
|
@ -35,5 +35,6 @@ USER 1000
|
|||
|
||||
EXPOSE 8080
|
||||
EXPOSE 8443
|
||||
EXPOSE 9000
|
||||
|
||||
ENTRYPOINT [ "/opt/keycloak/bin/kc.sh" ]
|
||||
|
|
|
@ -0,0 +1,28 @@
|
|||
/*
|
||||
* Copyright 2024 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.quarkus.deployment;
|
||||
|
||||
import org.keycloak.quarkus.runtime.configuration.mappers.ManagementPropertyMappers;
|
||||
|
||||
import java.util.function.BooleanSupplier;
|
||||
|
||||
public class IsManagementEnabled implements BooleanSupplier {
|
||||
@Override
|
||||
public boolean getAsBoolean() {
|
||||
return ManagementPropertyMappers.isManagementEnabled();
|
||||
}
|
||||
}
|
|
@ -45,6 +45,8 @@ import io.quarkus.hibernate.orm.deployment.spi.AdditionalJpaModelBuildItem;
|
|||
import io.quarkus.resteasy.reactive.server.spi.MethodScannerBuildItem;
|
||||
import io.quarkus.runtime.configuration.ConfigurationException;
|
||||
import io.quarkus.runtime.configuration.ProfileManager;
|
||||
import io.quarkus.vertx.http.deployment.NonApplicationRootPathBuildItem;
|
||||
import io.quarkus.vertx.http.deployment.RouteBuildItem;
|
||||
import io.smallrye.config.ConfigValue;
|
||||
import org.eclipse.microprofile.health.Readiness;
|
||||
import org.hibernate.cfg.AvailableSettings;
|
||||
|
@ -69,6 +71,7 @@ import org.keycloak.common.crypto.FipsMode;
|
|||
import org.keycloak.common.util.StreamUtil;
|
||||
import org.keycloak.config.DatabaseOptions;
|
||||
import org.keycloak.config.HealthOptions;
|
||||
import org.keycloak.config.ManagementOptions;
|
||||
import org.keycloak.config.MetricsOptions;
|
||||
import org.keycloak.config.SecurityOptions;
|
||||
import org.keycloak.connections.jpa.DefaultJpaConnectionProviderFactory;
|
||||
|
@ -131,7 +134,6 @@ import java.nio.charset.StandardCharsets;
|
|||
import java.nio.file.Path;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Comparator;
|
||||
import java.util.Enumeration;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
|
@ -232,6 +234,21 @@ class KeycloakProcessor {
|
|||
recorder.configureProfile(profile.getName(), profile.getFeatures());
|
||||
}
|
||||
|
||||
@Record(ExecutionTime.STATIC_INIT)
|
||||
@BuildStep(onlyIf = IsManagementEnabled.class)
|
||||
@Consume(ConfigBuildItem.class)
|
||||
void configureManagementInterface(BuildProducer<RouteBuildItem> routes,
|
||||
NonApplicationRootPathBuildItem nonApplicationRootPathBuildItem,
|
||||
KeycloakRecorder recorder) {
|
||||
final var path = Configuration.getOptionalKcValue(ManagementOptions.HTTP_MANAGEMENT_RELATIVE_PATH.getKey()).orElse("/");
|
||||
|
||||
routes.produce(nonApplicationRootPathBuildItem.routeBuilder()
|
||||
.management()
|
||||
.route(path)
|
||||
.handler(recorder.getManagementHandler())
|
||||
.build());
|
||||
}
|
||||
|
||||
@Record(ExecutionTime.STATIC_INIT)
|
||||
@BuildStep
|
||||
@Consume(ConfigBuildItem.class)
|
||||
|
|
|
@ -0,0 +1,59 @@
|
|||
/*
|
||||
* Copyright 2020 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 test.org.keycloak.quarkus.services.health;
|
||||
|
||||
import io.quarkus.test.QuarkusUnitTest;
|
||||
import io.restassured.RestAssured;
|
||||
import org.jboss.shrinkwrap.api.ShrinkWrap;
|
||||
import org.jboss.shrinkwrap.api.spec.JavaArchive;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.RegisterExtension;
|
||||
|
||||
import static io.restassured.RestAssured.given;
|
||||
|
||||
class KeycloakMetricsConfigurationTest {
|
||||
|
||||
@BeforeEach
|
||||
void setUpPort() {
|
||||
RestAssured.port = 9001;
|
||||
}
|
||||
|
||||
@RegisterExtension
|
||||
static final QuarkusUnitTest test = new QuarkusUnitTest()
|
||||
.setArchiveProducer(() -> ShrinkWrap.create(JavaArchive.class)
|
||||
.addAsResource("keycloak.conf", "META-INF/keycloak.conf"))
|
||||
.overrideConfigKey("quarkus.micrometer.export.prometheus.path", "/prom/metrics");
|
||||
|
||||
@Test
|
||||
void testMetrics() {
|
||||
given().basePath("/")
|
||||
.when().get("prom/metrics")
|
||||
.then()
|
||||
.statusCode(200);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testWrongMetricsEndpoints() {
|
||||
given().basePath("/")
|
||||
.when().get("metrics")
|
||||
.then()
|
||||
// Metrics are available under `/prom/metrics` (see quarkus.micrometer.export.prometheus.path)
|
||||
// so /metrics should return 404.
|
||||
.statusCode(404);
|
||||
}
|
||||
}
|
|
@ -18,6 +18,7 @@ package test.org.keycloak.quarkus.services.health;
|
|||
|
||||
import io.agroal.api.AgroalDataSource;
|
||||
import io.quarkus.test.QuarkusUnitTest;
|
||||
import io.restassured.RestAssured;
|
||||
import org.hamcrest.Matchers;
|
||||
import org.jboss.shrinkwrap.api.ShrinkWrap;
|
||||
import org.jboss.shrinkwrap.api.spec.JavaArchive;
|
||||
|
@ -41,6 +42,8 @@ public class KeycloakNegativeHealthCheckTest {
|
|||
@Test
|
||||
public void testReadinessDown() {
|
||||
agroalDataSource.close();
|
||||
|
||||
RestAssured.port = 9001;
|
||||
given()
|
||||
.when().get("/health/ready")
|
||||
.then()
|
||||
|
|
|
@ -17,8 +17,10 @@
|
|||
package test.org.keycloak.quarkus.services.health;
|
||||
|
||||
import io.quarkus.test.QuarkusUnitTest;
|
||||
import io.restassured.RestAssured;
|
||||
import org.jboss.shrinkwrap.api.ShrinkWrap;
|
||||
import org.jboss.shrinkwrap.api.spec.JavaArchive;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.RegisterExtension;
|
||||
|
||||
|
@ -26,40 +28,18 @@ import static io.restassured.RestAssured.given;
|
|||
|
||||
class KeycloakPathConfigurationTest {
|
||||
|
||||
@BeforeEach
|
||||
void setUpPort() {
|
||||
RestAssured.port = 9001;
|
||||
}
|
||||
|
||||
@RegisterExtension
|
||||
static final QuarkusUnitTest test = new QuarkusUnitTest()
|
||||
.setArchiveProducer(() -> ShrinkWrap.create(JavaArchive.class)
|
||||
.addAsResource("keycloak.conf", "META-INF/keycloak.conf"))
|
||||
.overrideConfigKey("kc.http-relative-path","/auth")
|
||||
.overrideConfigKey("quarkus.http.non-application-root-path", "/q")
|
||||
.overrideConfigKey("quarkus.micrometer.export.prometheus.path", "/prom/metrics");
|
||||
|
||||
|
||||
@Test
|
||||
void testHealth() {
|
||||
given().basePath("/")
|
||||
.when().get("q/health")
|
||||
.then()
|
||||
.statusCode(200);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testWrongHealthEndpoints() {
|
||||
given().basePath("/")
|
||||
.when().get("health")
|
||||
.then()
|
||||
// Health is available under `/q/health` (see non-application-root-path),
|
||||
// so /health should return 404.
|
||||
.statusCode(404);
|
||||
|
||||
given().basePath("/")
|
||||
.when().get("auth/health")
|
||||
.then()
|
||||
// Health is available under `/q/health` (see non-application-root-path),
|
||||
// so /auth/health one should return 404.
|
||||
.statusCode(404);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testMetrics() {
|
||||
given().basePath("/")
|
||||
|
@ -68,6 +48,20 @@ class KeycloakPathConfigurationTest {
|
|||
.statusCode(200);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testHealth() {
|
||||
given().basePath("/")
|
||||
.when().get("health")
|
||||
.then()
|
||||
// Health is available under `/auth/health` (see http-relative-path),
|
||||
.statusCode(404);
|
||||
|
||||
given().basePath("/")
|
||||
.when().get("auth/health")
|
||||
.then()
|
||||
.statusCode(200);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testWrongMetricsEndpoints() {
|
||||
given().basePath("/")
|
||||
|
|
|
@ -17,19 +17,23 @@
|
|||
package test.org.keycloak.quarkus.services.health;
|
||||
|
||||
import io.quarkus.test.QuarkusUnitTest;
|
||||
import io.restassured.RestAssured;
|
||||
import org.hamcrest.Matchers;
|
||||
import org.jboss.shrinkwrap.api.ShrinkWrap;
|
||||
import org.jboss.shrinkwrap.api.spec.JavaArchive;
|
||||
import org.junit.jupiter.api.Disabled;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.RegisterExtension;
|
||||
|
||||
import java.sql.SQLException;
|
||||
|
||||
import static io.restassured.RestAssured.given;
|
||||
|
||||
public class KeycloakReadyHealthCheckTest {
|
||||
|
||||
@BeforeEach
|
||||
void setUpPort() {
|
||||
RestAssured.port = 9001;
|
||||
}
|
||||
|
||||
@RegisterExtension
|
||||
static final QuarkusUnitTest test = new QuarkusUnitTest()
|
||||
.setArchiveProducer(() -> ShrinkWrap.create(JavaArchive.class)
|
||||
|
@ -45,7 +49,7 @@ public class KeycloakReadyHealthCheckTest {
|
|||
}
|
||||
|
||||
@Test
|
||||
public void testReadinessUp() throws SQLException {
|
||||
public void testReadinessUp() {
|
||||
given()
|
||||
.when().get("/health/ready")
|
||||
.then()
|
||||
|
|
|
@ -30,6 +30,8 @@ import io.quarkus.agroal.DataSource;
|
|||
import io.quarkus.arc.Arc;
|
||||
import io.quarkus.arc.InstanceHandle;
|
||||
import io.quarkus.hibernate.orm.runtime.integration.HibernateOrmIntegrationRuntimeInitListener;
|
||||
import io.vertx.core.Handler;
|
||||
import io.vertx.ext.web.RoutingContext;
|
||||
import liquibase.Scope;
|
||||
|
||||
import org.hibernate.cfg.AvailableSettings;
|
||||
|
@ -73,6 +75,11 @@ public class KeycloakRecorder {
|
|||
Profile.init(profileName, features);
|
||||
}
|
||||
|
||||
// default handler for the management interface
|
||||
public Handler<RoutingContext> getManagementHandler() {
|
||||
return routingContext -> routingContext.response().end("Keycloak Management Interface");
|
||||
}
|
||||
|
||||
public void configureTruststore() {
|
||||
String[] truststores = Configuration.getOptionalKcValue(TruststoreOptions.TRUSTSTORE_PATHS.getKey())
|
||||
.map(s -> s.split(",")).orElse(new String[0]);
|
||||
|
|
|
@ -52,6 +52,10 @@ public final class Configuration {
|
|||
return getOptionalBooleanValue(NS_KEYCLOAK_PREFIX + option.getKey()).orElse(false);
|
||||
}
|
||||
|
||||
public static boolean isTrue(String propertyName) {
|
||||
return getOptionalBooleanValue(propertyName).orElse(false);
|
||||
}
|
||||
|
||||
public static boolean contains(Option<?> option, String value) {
|
||||
return getOptionalValue(NS_KEYCLOAK_PREFIX + option.getKey())
|
||||
.filter(f -> f.contains(value))
|
||||
|
|
|
@ -0,0 +1,146 @@
|
|||
/*
|
||||
* Copyright 2024 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.quarkus.runtime.configuration.mappers;
|
||||
|
||||
import io.smallrye.config.ConfigSourceInterceptorContext;
|
||||
import org.keycloak.config.HttpOptions;
|
||||
import org.keycloak.config.ManagementOptions;
|
||||
import org.keycloak.quarkus.runtime.Messages;
|
||||
import org.keycloak.quarkus.runtime.cli.PropertyException;
|
||||
import org.keycloak.quarkus.runtime.configuration.Configuration;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
import static org.keycloak.quarkus.runtime.configuration.Configuration.isTrue;
|
||||
import static org.keycloak.quarkus.runtime.configuration.mappers.PropertyMapper.fromOption;
|
||||
|
||||
public class ManagementPropertyMappers {
|
||||
private static final String MANAGEMENT_ENABLED_MSG = "Management interface is enabled";
|
||||
|
||||
private ManagementPropertyMappers() {
|
||||
}
|
||||
|
||||
public static PropertyMapper<?>[] getManagementPropertyMappers() {
|
||||
return new PropertyMapper[]{
|
||||
fromOption(ManagementOptions.LEGACY_OBSERVABILITY_INTERFACE)
|
||||
.to("quarkus.management.enabled") // ATM, the management interface state is only based on the legacy-observability-interface property
|
||||
.paramLabel(Boolean.TRUE + "|" + Boolean.FALSE)
|
||||
.transformer(ManagementPropertyMappers::managementEnabledTransformer)
|
||||
.build(),
|
||||
fromOption(ManagementOptions.HTTP_MANAGEMENT_RELATIVE_PATH)
|
||||
.isEnabled(ManagementPropertyMappers::isManagementEnabled, MANAGEMENT_ENABLED_MSG)
|
||||
.mapFrom(HttpOptions.HTTP_RELATIVE_PATH.getKey())
|
||||
.to("quarkus.management.root-path")
|
||||
.paramLabel("path")
|
||||
.build(),
|
||||
fromOption(ManagementOptions.HTTP_MANAGEMENT_PORT)
|
||||
.isEnabled(ManagementPropertyMappers::isManagementEnabled, MANAGEMENT_ENABLED_MSG)
|
||||
.to("quarkus.management.port")
|
||||
.paramLabel("port")
|
||||
.build(),
|
||||
fromOption(ManagementOptions.HTTP_MANAGEMENT_HOST)
|
||||
.isEnabled(ManagementPropertyMappers::isManagementEnabled, MANAGEMENT_ENABLED_MSG)
|
||||
.mapFrom(HttpOptions.HTTP_HOST.getKey())
|
||||
.to("quarkus.management.host")
|
||||
.paramLabel("host")
|
||||
.build(),
|
||||
// HTTPS
|
||||
fromOption(ManagementOptions.HTTPS_MANAGEMENT_CLIENT_AUTH)
|
||||
.isEnabled(ManagementPropertyMappers::isManagementEnabled, MANAGEMENT_ENABLED_MSG)
|
||||
.mapFrom(HttpOptions.HTTPS_CLIENT_AUTH.getKey())
|
||||
.to("quarkus.management.ssl.client-auth")
|
||||
.paramLabel("auth")
|
||||
.build(),
|
||||
fromOption(ManagementOptions.HTTPS_MANAGEMENT_CIPHER_SUITES)
|
||||
.isEnabled(ManagementPropertyMappers::isManagementEnabled, MANAGEMENT_ENABLED_MSG)
|
||||
.mapFrom(HttpOptions.HTTPS_CIPHER_SUITES.getKey())
|
||||
.to("quarkus.management.ssl.cipher-suites")
|
||||
.paramLabel("ciphers")
|
||||
.build(),
|
||||
fromOption(ManagementOptions.HTTPS_MANAGEMENT_PROTOCOLS)
|
||||
.isEnabled(ManagementPropertyMappers::isManagementEnabled, MANAGEMENT_ENABLED_MSG)
|
||||
.mapFrom(HttpOptions.HTTPS_PROTOCOLS.getKey())
|
||||
.to("quarkus.management.ssl.protocols")
|
||||
.paramLabel("protocols")
|
||||
.build(),
|
||||
fromOption(ManagementOptions.HTTPS_MANAGEMENT_CERTIFICATE_FILE)
|
||||
.isEnabled(ManagementPropertyMappers::isManagementEnabled, MANAGEMENT_ENABLED_MSG)
|
||||
.mapFrom(HttpOptions.HTTPS_CERTIFICATE_FILE.getKey())
|
||||
.to("quarkus.management.ssl.certificate.files")
|
||||
.validator((mapper, value) -> validateTlsProperties())
|
||||
.paramLabel("file")
|
||||
.build(),
|
||||
fromOption(ManagementOptions.HTTPS_MANAGEMENT_CERTIFICATE_KEY_FILE)
|
||||
.isEnabled(ManagementPropertyMappers::isManagementEnabled, MANAGEMENT_ENABLED_MSG)
|
||||
.mapFrom(HttpOptions.HTTPS_CERTIFICATE_KEY_FILE.getKey())
|
||||
.to("quarkus.management.ssl.certificate.key-files")
|
||||
.validator((mapper, value) -> validateTlsProperties())
|
||||
.paramLabel("file")
|
||||
.build(),
|
||||
fromOption(ManagementOptions.HTTPS_MANAGEMENT_KEY_STORE_FILE)
|
||||
.isEnabled(ManagementPropertyMappers::isManagementEnabled, MANAGEMENT_ENABLED_MSG)
|
||||
.mapFrom(HttpOptions.HTTPS_KEY_STORE_FILE.getKey())
|
||||
.to("quarkus.management.ssl.certificate.key-store-file")
|
||||
.validator((mapper, value) -> validateTlsProperties())
|
||||
.paramLabel("file")
|
||||
.build(),
|
||||
fromOption(ManagementOptions.HTTPS_MANAGEMENT_KEY_STORE_PASSWORD)
|
||||
.isEnabled(ManagementPropertyMappers::isManagementEnabled, MANAGEMENT_ENABLED_MSG)
|
||||
.mapFrom(HttpOptions.HTTPS_KEY_STORE_PASSWORD.getKey())
|
||||
.to("quarkus.management.ssl.certificate.key-store-password")
|
||||
.validator((mapper, value) -> validateTlsProperties())
|
||||
.paramLabel("password")
|
||||
.isMasked(true)
|
||||
.build(),
|
||||
fromOption(ManagementOptions.HTTPS_MANAGEMENT_KEY_STORE_TYPE)
|
||||
.isEnabled(ManagementPropertyMappers::isManagementEnabled, MANAGEMENT_ENABLED_MSG)
|
||||
.mapFrom(HttpOptions.HTTPS_KEY_STORE_TYPE.getKey())
|
||||
.to("quarkus.management.ssl.certificate.key-store-file-type")
|
||||
.transformer((value, config) -> value.or(() -> Configuration.getOptionalKcValue(HttpOptions.HTTPS_KEY_STORE_TYPE.getKey())))
|
||||
.paramLabel("type")
|
||||
.build(),
|
||||
};
|
||||
}
|
||||
|
||||
public static boolean isManagementEnabled() {
|
||||
return isTrue("quarkus.management.enabled");
|
||||
}
|
||||
|
||||
public static boolean isManagementTlsEnabled() {
|
||||
var key = Configuration.getOptionalKcValue(ManagementOptions.HTTPS_MANAGEMENT_CERTIFICATE_KEY_FILE.getKey());
|
||||
var cert = Configuration.getOptionalKcValue(ManagementOptions.HTTPS_MANAGEMENT_CERTIFICATE_FILE.getKey());
|
||||
if (key.isPresent() && cert.isPresent()) return true;
|
||||
|
||||
var keystore = Configuration.getOptionalKcValue(ManagementOptions.HTTPS_MANAGEMENT_KEY_STORE_FILE.getKey());
|
||||
return keystore.isPresent();
|
||||
}
|
||||
|
||||
private static void validateTlsProperties() {
|
||||
var isHttpEnabled = Configuration.isTrue(HttpOptions.HTTP_ENABLED);
|
||||
if (!isHttpEnabled && !isManagementTlsEnabled()) {
|
||||
throw new PropertyException(Messages.httpsConfigurationNotSet());
|
||||
}
|
||||
}
|
||||
|
||||
private static Optional<String> managementEnabledTransformer(Optional<String> value, ConfigSourceInterceptorContext ctx) {
|
||||
if (value.isPresent()) {
|
||||
var b = Boolean.parseBoolean(value.get());
|
||||
return Optional.of(Boolean.toString(!b)); // negate the output
|
||||
}
|
||||
return Optional.of(Boolean.TRUE.toString());
|
||||
}
|
||||
}
|
|
@ -52,6 +52,7 @@ public final class PropertyMappers {
|
|||
MAPPERS.addAll(HttpPropertyMappers.getHttpPropertyMappers());
|
||||
MAPPERS.addAll(HealthPropertyMappers.getHealthPropertyMappers());
|
||||
MAPPERS.addAll(ConfigKeystorePropertyMappers.getConfigKeystorePropertyMappers());
|
||||
MAPPERS.addAll(ManagementPropertyMappers.getManagementPropertyMappers());
|
||||
MAPPERS.addAll(MetricsPropertyMappers.getMetricsPropertyMappers());
|
||||
MAPPERS.addAll(ProxyPropertyMappers.getProxyPropertyMappers());
|
||||
MAPPERS.addAll(VaultPropertyMappers.getVaultPropertyMappers());
|
||||
|
|
|
@ -77,6 +77,10 @@ public class ConfigurationTest {
|
|||
}
|
||||
}
|
||||
|
||||
public static void putEnvVars(Map<String, String> map) {
|
||||
map.forEach(ConfigurationTest::putEnvVar);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public static void removeEnvVar(String name) {
|
||||
Map<String, String> env = System.getenv();
|
||||
|
@ -573,7 +577,7 @@ public class ConfigurationTest {
|
|||
assertEquals("secret", secret.getValue());
|
||||
}
|
||||
|
||||
private Config.Scope initConfig(String... scope) {
|
||||
protected Config.Scope initConfig(String... scope) {
|
||||
Config.init(new MicroProfileConfigProvider(createConfig()));
|
||||
return Config.scope(scope);
|
||||
}
|
||||
|
|
|
@ -0,0 +1,277 @@
|
|||
/*
|
||||
* Copyright 2024 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.quarkus.runtime.configuration.test;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.keycloak.quarkus.runtime.configuration.Configuration;
|
||||
import org.keycloak.quarkus.runtime.configuration.mappers.ManagementPropertyMappers;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import static org.hamcrest.CoreMatchers.is;
|
||||
import static org.hamcrest.CoreMatchers.notNullValue;
|
||||
import static org.hamcrest.MatcherAssert.assertThat;
|
||||
|
||||
public class ManagementConfigurationTest extends ConfigurationTest {
|
||||
|
||||
@Test
|
||||
public void managementDefaults() {
|
||||
initConfig();
|
||||
|
||||
assertConfig(Map.of(
|
||||
"http-management-port", "9000",
|
||||
"http-management-relative-path", "/",
|
||||
"http-management-host", "0.0.0.0"
|
||||
));
|
||||
|
||||
assertManagementEnabled(true);
|
||||
assertManagementHttpsEnabled(false);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void managementBasicChanges() {
|
||||
putEnvVars(Map.of(
|
||||
"KC_HTTP_MANAGEMENT_PORT", "9999",
|
||||
"KC_HTTP_MANAGEMENT_RELATIVE_PATH", "/management2",
|
||||
"KC_HTTP_MANAGEMENT_HOST", "somehost"
|
||||
));
|
||||
|
||||
initConfig();
|
||||
|
||||
assertConfig(Map.of(
|
||||
"http-management-port", "9999",
|
||||
"http-management-relative-path", "/management2",
|
||||
"http-relative-path", "/",
|
||||
"http-management-host", "somehost"
|
||||
));
|
||||
assertManagementEnabled(true);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void managementRelativePath() {
|
||||
putEnvVar("KC_HTTP_RELATIVE_PATH", "/management3");
|
||||
|
||||
initConfig();
|
||||
|
||||
assertConfig(Map.of(
|
||||
"http-management-relative-path", "/management3",
|
||||
"http-relative-path", "/management3"
|
||||
));
|
||||
assertManagementEnabled(true);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void managementHttpsValues() {
|
||||
putEnvVars(Map.of(
|
||||
"KC_HTTP_MANAGEMENT_HOST", "host1",
|
||||
"KC_HTTPS_MANAGEMENT_CLIENT_AUTH", "requested",
|
||||
"KC_HTTPS_MANAGEMENT_CIPHER_SUITES", "some-cipher-suite1",
|
||||
"KC_HTTPS_MANAGEMENT_PROTOCOLS", "TLSv1.3",
|
||||
"KC_HTTPS_MANAGEMENT_CERTIFICATE_FILE", "/some/path/s.crt.pem",
|
||||
"KC_HTTPS_MANAGEMENT_CERTIFICATE_KEY_FILE", "/some/path/s.key.pem",
|
||||
"KC_HTTPS_MANAGEMENT_KEY_STORE_FILE", "keystore123.p12",
|
||||
"KC_HTTPS_MANAGEMENT_KEY_STORE_PASSWORD", "ultra-password123",
|
||||
"KC_HTTPS_MANAGEMENT_KEY_STORE_TYPE", "BCFKS-0.1"
|
||||
));
|
||||
|
||||
initConfig();
|
||||
|
||||
assertConfig(Map.of(
|
||||
"http-management-host", "host1",
|
||||
"https-management-client-auth", "requested",
|
||||
"https-management-cipher-suites", "some-cipher-suite1",
|
||||
"https-management-protocols", "TLSv1.3",
|
||||
"https-management-certificate-file", "/some/path/s.crt.pem",
|
||||
"https-management-certificate-key-file", "/some/path/s.key.pem",
|
||||
"https-management-key-store-file", "keystore123.p12",
|
||||
"https-management-key-store-password", "ultra-password123",
|
||||
"https-management-key-store-type", "BCFKS-0.1"
|
||||
));
|
||||
assertManagementEnabled(true);
|
||||
assertManagementHttpsEnabled(true);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void managementMappedValues() {
|
||||
putEnvVars(Map.of(
|
||||
"KC_HTTP_HOST", "host123",
|
||||
"KC_HTTPS_CLIENT_AUTH", "required",
|
||||
"KC_HTTPS_CIPHER_SUITES", "some-cipher-suite",
|
||||
"KC_HTTPS_PROTOCOLS", "TLSv1.2",
|
||||
"KC_HTTPS_CERTIFICATE_FILE", "/some/path/srv.crt.pem",
|
||||
"KC_HTTPS_CERTIFICATE_KEY_FILE", "/some/path/srv.key.pem",
|
||||
"KC_HTTPS_KEY_STORE_FILE", "keystore.p12",
|
||||
"KC_HTTPS_KEY_STORE_PASSWORD", "ultra-password",
|
||||
"KC_HTTPS_KEY_STORE_TYPE", "BCFKS"
|
||||
));
|
||||
|
||||
initConfig();
|
||||
|
||||
assertConfig(Map.of(
|
||||
"http-management-host", "host123",
|
||||
"https-management-client-auth", "required",
|
||||
"https-management-cipher-suites", "some-cipher-suite",
|
||||
"https-management-protocols", "TLSv1.2",
|
||||
"https-management-certificate-file", "/some/path/srv.crt.pem",
|
||||
"https-management-certificate-key-file", "/some/path/srv.key.pem",
|
||||
"https-management-key-store-file", "keystore.p12",
|
||||
"https-management-key-store-password", "ultra-password",
|
||||
"https-management-key-store-type", "BCFKS"
|
||||
));
|
||||
assertManagementEnabled(true);
|
||||
assertManagementHttpsEnabled(true);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void managementDefaultHttps() {
|
||||
putEnvVars(Map.of(
|
||||
"KC_HTTPS_CERTIFICATE_FILE", "/some/path/srv.crt.pem",
|
||||
"KC_HTTPS_CERTIFICATE_KEY_FILE", "/some/path/srv.key.pem"
|
||||
));
|
||||
|
||||
initConfig();
|
||||
|
||||
assertConfig(Map.of(
|
||||
"https-certificate-file", "/some/path/srv.crt.pem",
|
||||
"https-certificate-key-file", "/some/path/srv.key.pem",
|
||||
"https-management-certificate-file", "/some/path/srv.crt.pem",
|
||||
"https-management-certificate-key-file", "/some/path/srv.key.pem"
|
||||
));
|
||||
assertManagementEnabled(true);
|
||||
assertManagementHttpsEnabled(true);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void managementDefaultHttpsManagementProps() {
|
||||
putEnvVars(Map.of(
|
||||
"KC_HTTPS_MANAGEMENT_CERTIFICATE_FILE", "/some/path/srv.crt.pem",
|
||||
"KC_HTTPS_MANAGEMENT_CERTIFICATE_KEY_FILE", "/some/path/srv.key.pem"
|
||||
));
|
||||
|
||||
initConfig();
|
||||
|
||||
assertConfig(Map.of(
|
||||
"https-management-certificate-file", "/some/path/srv.crt.pem",
|
||||
"https-management-certificate-key-file", "/some/path/srv.key.pem"
|
||||
));
|
||||
assertManagementEnabled(true);
|
||||
assertManagementHttpsEnabled(true);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void managementDefaultHttpsCertDisabled() {
|
||||
putEnvVar("KC_HTTPS_CERTIFICATE_FILE", "/some/path/srv.crt.pem");
|
||||
|
||||
initConfig();
|
||||
|
||||
assertConfig("https-management-certificate-file", "/some/path/srv.crt.pem");
|
||||
assertManagementEnabled(true);
|
||||
assertManagementHttpsEnabled(false);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void managementDefaultHttpsKeyDisabled() {
|
||||
putEnvVar("KC_HTTPS_CERTIFICATE_KEY_FILE", "/some/path/srv.key.pem");
|
||||
|
||||
initConfig();
|
||||
|
||||
assertConfig("https-management-certificate-key-file", "/some/path/srv.key.pem");
|
||||
assertManagementEnabled(true);
|
||||
assertManagementHttpsEnabled(false);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void managementEnabledDefaultHttpsKeystore(){
|
||||
putEnvVar("KC_HTTPS_KEY_STORE_FILE", "keystore.p12");
|
||||
|
||||
initConfig();
|
||||
|
||||
assertConfig(Map.of(
|
||||
"https-key-store-file", "keystore.p12",
|
||||
"https-management-key-store-file", "keystore.p12"
|
||||
));
|
||||
assertManagementEnabled(true);
|
||||
assertManagementHttpsEnabled(true);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void fipsKeystoreType(){
|
||||
putEnvVar("KC_FIPS_MODE", "strict");
|
||||
|
||||
initConfig();
|
||||
|
||||
assertConfig(Map.of(
|
||||
"https-key-store-type", "BCFKS",
|
||||
"https-management-key-store-type", "BCFKS"
|
||||
));
|
||||
assertManagementEnabled(true);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void keystoreType(){
|
||||
putEnvVars(Map.of(
|
||||
"KC_HTTPS_KEY_STORE_TYPE", "pkcs12",
|
||||
"KC_HTTPS_MANAGEMENT_KEY_STORE_TYPE", "BCFKS"
|
||||
));
|
||||
|
||||
initConfig();
|
||||
|
||||
assertConfig(Map.of(
|
||||
"https-key-store-type", "pkcs12",
|
||||
"https-management-key-store-type", "BCFKS"
|
||||
));
|
||||
assertManagementEnabled(true);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void legacyObservabilityInterface() {
|
||||
putEnvVar("KC_LEGACY_OBSERVABILITY_INTERFACE", "true");
|
||||
|
||||
initConfig();
|
||||
|
||||
assertConfig("legacy-observability-interface", "true");
|
||||
assertManagementEnabled(false);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void legacyObservabilityInterfaceFalse() {
|
||||
putEnvVar("KC_LEGACY_OBSERVABILITY_INTERFACE", "false");
|
||||
|
||||
initConfig();
|
||||
|
||||
assertConfig("legacy-observability-interface", "false");
|
||||
assertManagementEnabled(true);
|
||||
}
|
||||
|
||||
private void assertManagementEnabled(boolean expected) {
|
||||
assertThat("Expected value for Management interface state is different", ManagementPropertyMappers.isManagementEnabled(), is(expected));
|
||||
}
|
||||
|
||||
private void assertManagementHttpsEnabled(boolean expected) {
|
||||
assertThat("Expected value for Management HTTPS is different", ManagementPropertyMappers.isManagementTlsEnabled(), is(expected));
|
||||
}
|
||||
|
||||
private void assertConfig(String key, String expectedValue) {
|
||||
var value = Configuration.getKcConfigValue(key).getValue();
|
||||
assertThat(String.format("Value is null for key '%s'", key), value, notNullValue());
|
||||
assertThat(String.format("Different value for key '%s'", key), value, is(expectedValue));
|
||||
}
|
||||
|
||||
private void assertConfig(Map<String, String> expectedValues) {
|
||||
expectedValues.forEach(this::assertConfig);
|
||||
}
|
||||
}
|
|
@ -29,7 +29,9 @@ import java.util.List;
|
|||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
@DistributionTest(keepAlive =true)
|
||||
@DistributionTest(keepAlive = true,
|
||||
requestPort = 9000,
|
||||
containerExposedPorts = {8080, 9000})
|
||||
public class HealthDistTest {
|
||||
|
||||
@Test
|
||||
|
@ -83,7 +85,7 @@ public class HealthDistTest {
|
|||
@Test
|
||||
void testUsingRelativePath(KeycloakDistribution distribution) {
|
||||
for (String relativePath : List.of("/auth", "/auth/", "auth")) {
|
||||
distribution.run("start-dev", "--health-enabled=true", "--http-relative-path=" + relativePath);
|
||||
distribution.run("start-dev", "--health-enabled=true", "--http-management-relative-path=" + relativePath);
|
||||
if (!relativePath.endsWith("/")) {
|
||||
relativePath = relativePath + "/";
|
||||
}
|
||||
|
@ -95,7 +97,7 @@ public class HealthDistTest {
|
|||
@Test
|
||||
void testMultipleRequests(KeycloakDistribution distribution) throws Exception {
|
||||
for (String relativePath : List.of("/", "/auth/", "auth")) {
|
||||
distribution.run("start-dev", "--health-enabled=true", "--http-relative-path=" + relativePath);
|
||||
distribution.run("start-dev", "--health-enabled=true", "--http-management-relative-path=" + relativePath);
|
||||
CompletableFuture future = CompletableFuture.completedFuture(null);
|
||||
|
||||
for (int i = 0; i < 3; i++) {
|
||||
|
@ -123,7 +125,9 @@ public class HealthDistTest {
|
|||
|
||||
@Test
|
||||
@Launch({ "start-dev", "--features=multi-site" })
|
||||
void testLoadBalancerCheck() {
|
||||
void testLoadBalancerCheck(KeycloakDistribution distribution) {
|
||||
distribution.setRequestPort(8080);
|
||||
|
||||
when().get("/lb-check").then()
|
||||
.statusCode(200);
|
||||
}
|
||||
|
|
167
quarkus/tests/integration/src/test/java/org/keycloak/it/cli/dist/ManagementDistTest.java
vendored
Normal file
167
quarkus/tests/integration/src/test/java/org/keycloak/it/cli/dist/ManagementDistTest.java
vendored
Normal file
|
@ -0,0 +1,167 @@
|
|||
/*
|
||||
* Copyright 2024 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.it.cli.dist;
|
||||
|
||||
import io.quarkus.test.junit.main.Launch;
|
||||
import io.quarkus.test.junit.main.LaunchResult;
|
||||
import org.junit.jupiter.api.MethodOrderer;
|
||||
import org.junit.jupiter.api.Order;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.TestMethodOrder;
|
||||
import org.keycloak.it.junit5.extension.CLIResult;
|
||||
import org.keycloak.it.junit5.extension.DistributionTest;
|
||||
import org.keycloak.it.junit5.extension.DistributionType;
|
||||
import org.keycloak.it.utils.KeycloakDistribution;
|
||||
|
||||
import java.net.SocketException;
|
||||
|
||||
import static io.restassured.RestAssured.when;
|
||||
import static org.hamcrest.CoreMatchers.is;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
|
||||
@DistributionTest(keepAlive = true,
|
||||
defaultOptions = {"--health-enabled=true", "--metrics-enabled=true"},
|
||||
requestPort = 9000,
|
||||
containerExposedPorts = {9000, 8080, 9005})
|
||||
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
|
||||
public class ManagementDistTest {
|
||||
|
||||
@Test
|
||||
@Order(1)
|
||||
@Launch({"start", "--hostname=hostname", "--http-enabled=false"})
|
||||
void testManagementNoHttps(LaunchResult result) {
|
||||
CLIResult cliResult = (CLIResult) result;
|
||||
cliResult.assertNoMessage("Management interface listening on");
|
||||
cliResult.assertError("Key material not provided to setup HTTPS. Please configure your keys/certificates or start the server in development mode.");
|
||||
}
|
||||
|
||||
@Test
|
||||
@Order(2)
|
||||
@Launch({"start-dev", "--legacy-observability-interface=true"})
|
||||
void testManagementDisabled(LaunchResult result, KeycloakDistribution distribution) {
|
||||
CLIResult cliResult = (CLIResult) result;
|
||||
cliResult.assertNoMessage("Management interface listening on");
|
||||
|
||||
assertThrows(SocketException.class, () -> when().get("/"), "Connection refused must be thrown");
|
||||
assertThrows(SocketException.class, () -> when().get("/health"), "Connection refused must be thrown");
|
||||
|
||||
distribution.setRequestPort(8080);
|
||||
|
||||
when().get("/health").then()
|
||||
.statusCode(200);
|
||||
}
|
||||
|
||||
@Test
|
||||
@Order(3)
|
||||
@Launch({"start-dev"})
|
||||
void testManagementEnabled(LaunchResult result) {
|
||||
CLIResult cliResult = (CLIResult) result;
|
||||
cliResult.assertMessage("Management interface listening on http://0.0.0.0:9000");
|
||||
|
||||
when().get("/").then()
|
||||
.statusCode(200)
|
||||
.and()
|
||||
.body(is("Keycloak Management Interface"));
|
||||
when().get("/health").then()
|
||||
.statusCode(200);
|
||||
when().get("/health/live").then()
|
||||
.statusCode(200);
|
||||
when().get("/health/ready").then()
|
||||
.statusCode(200);
|
||||
when().get("/metrics").then()
|
||||
.statusCode(200);
|
||||
}
|
||||
|
||||
@Test
|
||||
@Launch({"start-dev", "--http-management-port=9005"})
|
||||
void testManagementDifferentPort(LaunchResult result, KeycloakDistribution distribution) {
|
||||
CLIResult cliResult = (CLIResult) result;
|
||||
cliResult.assertMessage("Management interface listening on http://0.0.0.0:9005");
|
||||
|
||||
distribution.setRequestPort(9005);
|
||||
|
||||
when().get("/").then()
|
||||
.statusCode(200)
|
||||
.and()
|
||||
.body(is("Keycloak Management Interface"));
|
||||
when().get("/health").then()
|
||||
.statusCode(200);
|
||||
when().get("/health/live").then()
|
||||
.statusCode(200);
|
||||
when().get("/health/ready").then()
|
||||
.statusCode(200);
|
||||
when().get("/metrics").then()
|
||||
.statusCode(200);
|
||||
}
|
||||
|
||||
@Test
|
||||
@Launch({"start-dev", "--http-relative-path=/management2"})
|
||||
void testManagementInheritedRelativePath(LaunchResult result) {
|
||||
assertRelativePath(result, "/management2");
|
||||
}
|
||||
|
||||
@Test
|
||||
@Launch({"start-dev", "--http-management-relative-path=/management"})
|
||||
void testManagementDifferentRelativePath(LaunchResult result) {
|
||||
assertRelativePath(result, "/management");
|
||||
}
|
||||
|
||||
private void assertRelativePath(LaunchResult result, String relativePath) {
|
||||
CLIResult cliResult = (CLIResult) result;
|
||||
cliResult.assertMessage("Management interface listening on http://0.0.0.0:9000");
|
||||
|
||||
when().get(relativePath).then()
|
||||
.statusCode(200)
|
||||
.and()
|
||||
.body(is("Keycloak Management Interface"));
|
||||
when().get(relativePath + "/health").then()
|
||||
.statusCode(200);
|
||||
when().get("/health").then()
|
||||
.statusCode(404);
|
||||
when().get(relativePath + "/health/live").then()
|
||||
.statusCode(200);
|
||||
when().get(relativePath + "/health/ready").then()
|
||||
.statusCode(200);
|
||||
when().get(relativePath + "/metrics").then()
|
||||
.statusCode(200);
|
||||
when().get("/metrics").then()
|
||||
.statusCode(404);
|
||||
}
|
||||
|
||||
@Test
|
||||
@Launch({"start-dev", "--http-management-host=localhost"})
|
||||
void testManagementDifferentHost(LaunchResult result) {
|
||||
CLIResult cliResult = (CLIResult) result;
|
||||
cliResult.assertMessage("Management interface listening on http://localhost:9000");
|
||||
|
||||
// If running in container, we cannot access the localhost due to network host settings
|
||||
if (DistributionType.isContainerDist()) return;
|
||||
|
||||
when().get("/").then()
|
||||
.statusCode(200)
|
||||
.and()
|
||||
.body(is("Keycloak Management Interface"));
|
||||
when().get("/health").then()
|
||||
.statusCode(200);
|
||||
when().get("/health/live").then()
|
||||
.statusCode(200);
|
||||
when().get("/health/ready").then()
|
||||
.statusCode(200);
|
||||
when().get("/metrics").then()
|
||||
.statusCode(200);
|
||||
}
|
||||
}
|
65
quarkus/tests/integration/src/test/java/org/keycloak/it/cli/dist/ManagementHttpsDistTest.java
vendored
Normal file
65
quarkus/tests/integration/src/test/java/org/keycloak/it/cli/dist/ManagementHttpsDistTest.java
vendored
Normal file
|
@ -0,0 +1,65 @@
|
|||
/*
|
||||
* Copyright 2024 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.it.cli.dist;
|
||||
|
||||
import io.quarkus.test.junit.main.Launch;
|
||||
import io.quarkus.test.junit.main.LaunchResult;
|
||||
import io.restassured.RestAssured;
|
||||
import io.restassured.config.RedirectConfig;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.keycloak.it.junit5.extension.CLIResult;
|
||||
import org.keycloak.it.junit5.extension.DistributionTest;
|
||||
import org.keycloak.it.junit5.extension.RawDistOnly;
|
||||
|
||||
import static io.restassured.RestAssured.when;
|
||||
import static org.hamcrest.CoreMatchers.is;
|
||||
|
||||
@DistributionTest(keepAlive = true,
|
||||
enableTls = true,
|
||||
defaultOptions = {"--health-enabled=true", "--metrics-enabled=true"},
|
||||
requestPort = 9000)
|
||||
@RawDistOnly(reason = "We do not test TLS in containers")
|
||||
public class ManagementHttpsDistTest {
|
||||
|
||||
@BeforeEach
|
||||
public void setRestAssuredHttps() {
|
||||
RestAssured.useRelaxedHTTPSValidation();
|
||||
RestAssured.config = RestAssured.config.redirect(RedirectConfig.redirectConfig().followRedirects(false));
|
||||
}
|
||||
|
||||
@Test
|
||||
@Launch({"start-dev"})
|
||||
public void simpleHttpsStartDev(LaunchResult result) {
|
||||
CLIResult cliResult = (CLIResult) result;
|
||||
var url = "https://localhost:9000";
|
||||
cliResult.assertMessage("Management interface listening on https://0.0.0.0:9000");
|
||||
|
||||
when().get(url).then()
|
||||
.statusCode(200)
|
||||
.and()
|
||||
.body(is("Keycloak Management Interface"));
|
||||
when().get(url + "/health").then()
|
||||
.statusCode(200);
|
||||
when().get(url + "/health/live").then()
|
||||
.statusCode(200);
|
||||
when().get(url + "/health/ready").then()
|
||||
.statusCode(200);
|
||||
when().get(url + "/metrics").then()
|
||||
.statusCode(200);
|
||||
}
|
||||
}
|
|
@ -30,7 +30,9 @@ import org.keycloak.it.utils.KeycloakDistribution;
|
|||
|
||||
import io.quarkus.test.junit.main.Launch;
|
||||
|
||||
@DistributionTest(keepAlive =true)
|
||||
@DistributionTest(keepAlive = true,
|
||||
requestPort = 9000,
|
||||
containerExposedPorts = {8080, 9000})
|
||||
public class MetricsDistTest {
|
||||
|
||||
@Test
|
||||
|
@ -67,7 +69,7 @@ public class MetricsDistTest {
|
|||
@Test
|
||||
void testUsingRelativePath(KeycloakDistribution distribution) {
|
||||
for (String relativePath : List.of("/auth", "/auth/", "auth")) {
|
||||
distribution.run("start-dev", "--metrics-enabled=true", "--http-relative-path=" + relativePath);
|
||||
distribution.run("start-dev", "--metrics-enabled=true", "--http-management-relative-path=" + relativePath);
|
||||
if (!relativePath.endsWith("/")) {
|
||||
relativePath = relativePath + "/";
|
||||
}
|
||||
|
@ -79,8 +81,8 @@ public class MetricsDistTest {
|
|||
@Test
|
||||
void testMultipleRequests(KeycloakDistribution distribution) throws Exception {
|
||||
for (String relativePath : List.of("/", "/auth/", "auth")) {
|
||||
distribution.run("start-dev", "--metrics-enabled=true", "--http-relative-path=" + relativePath);
|
||||
CompletableFuture future = CompletableFuture.completedFuture(null);
|
||||
distribution.run("start-dev", "--metrics-enabled=true", "--http-management-relative-path=" + relativePath);
|
||||
CompletableFuture<Void> future = CompletableFuture.completedFuture(null);
|
||||
|
||||
for (int i = 0; i < 3; i++) {
|
||||
future = CompletableFuture.allOf(CompletableFuture.runAsync(new Runnable() {
|
||||
|
|
|
@ -26,6 +26,7 @@ import static org.keycloak.quarkus.runtime.cli.command.AbstractStartCommand.OPTI
|
|||
import java.util.Optional;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import io.restassured.RestAssured;
|
||||
import org.junit.jupiter.api.Disabled;
|
||||
import org.junit.jupiter.api.MethodOrderer.OrderAnnotation;
|
||||
import org.junit.jupiter.api.Order;
|
||||
|
@ -121,6 +122,7 @@ public class QuarkusPropertiesDistTest {
|
|||
void testUnknownQuarkusBuildTimePropertyApplied(LaunchResult result) {
|
||||
CLIResult cliResult = (CLIResult) result;
|
||||
cliResult.assertNoBuild();
|
||||
RestAssured.port = 9000;
|
||||
when().get("/metrics").then().statusCode(200)
|
||||
.body(containsString("jvm_gc_"));
|
||||
}
|
||||
|
|
|
@ -66,6 +66,23 @@ Health:
|
|||
are available at the '/health', '/health/ready' and '/health/live'
|
||||
endpoints. Default: false.
|
||||
|
||||
Management:
|
||||
|
||||
--http-management-relative-path <path>
|
||||
Set the path relative to '/' for serving resources from management interface.
|
||||
The path must start with a '/'. If not given, the value is inherited from
|
||||
HTTP options. Default: /. Available only when Management interface is
|
||||
enabled.
|
||||
--https-management-client-auth <auth>
|
||||
Configures the management interface to require/request client authentication.
|
||||
If not given, the value is inherited from HTTP options. Possible values are:
|
||||
none, request, required. Default: none. Available only when Management
|
||||
interface is enabled.
|
||||
--legacy-observability-interface <true|false>
|
||||
DEPRECATED. If metrics/health endpoints should be exposed on the main HTTP
|
||||
server (not recommended). If set to true, the management interface is
|
||||
disabled. Default: false.
|
||||
|
||||
Metrics:
|
||||
|
||||
--metrics-enabled <true|false>
|
||||
|
|
|
@ -14,6 +14,15 @@ Options:
|
|||
built a server image using the 'build' command.
|
||||
-v, --verbose Print out error details when running this command.
|
||||
|
||||
Config:
|
||||
|
||||
--config-keystore <config-keystore>
|
||||
Specifies a path to the KeyStore Configuration Source.
|
||||
--config-keystore-password <config-keystore-password>
|
||||
Specifies a password to the KeyStore Configuration Source.
|
||||
--config-keystore-type <config-keystore-type>
|
||||
Specifies a type of the KeyStore Configuration Source. Default: PKCS12.
|
||||
|
||||
Database:
|
||||
|
||||
--db <vendor> The database vendor. Possible values are: dev-file, dev-mem, mariadb, mssql,
|
||||
|
@ -59,14 +68,42 @@ Feature:
|
|||
--features-disabled <feature>
|
||||
Disables a set of one or more features. Possible values are: <...>.
|
||||
|
||||
Config:
|
||||
Management:
|
||||
|
||||
--config-keystore <config-keystore>
|
||||
Specifies a path to the KeyStore Configuration Source.
|
||||
--config-keystore-password <config-keystore-password>
|
||||
Specifies a password to the KeyStore Configuration Source.
|
||||
--config-keystore-type <config-keystore-type>
|
||||
Specifies a type of the KeyStore Configuration Source. Default: PKCS12.
|
||||
--http-management-port <port>
|
||||
Port of the management interface. Default: 9000. Available only when
|
||||
Management interface is enabled.
|
||||
--http-management-relative-path <path>
|
||||
Set the path relative to '/' for serving resources from management interface.
|
||||
The path must start with a '/'. If not given, the value is inherited from
|
||||
HTTP options. Default: /. Available only when Management interface is
|
||||
enabled.
|
||||
--https-management-certificate-file <file>
|
||||
The file path to a server certificate or certificate chain in PEM format for
|
||||
the management server. If not given, the value is inherited from HTTP
|
||||
options. Available only when Management interface is enabled.
|
||||
--https-management-certificate-key-file <file>
|
||||
The file path to a private key in PEM format for the management server. If not
|
||||
given, the value is inherited from HTTP options. Available only when
|
||||
Management interface is enabled.
|
||||
--https-management-client-auth <auth>
|
||||
Configures the management interface to require/request client authentication.
|
||||
If not given, the value is inherited from HTTP options. Possible values are:
|
||||
none, request, required. Default: none. Available only when Management
|
||||
interface is enabled.
|
||||
--https-management-key-store-file <file>
|
||||
The key store which holds the certificate information instead of specifying
|
||||
separate files for the management server. If not given, the value is
|
||||
inherited from HTTP options. Available only when Management interface is
|
||||
enabled.
|
||||
--https-management-key-store-password <password>
|
||||
The password of the key store file for the management server. If not given,
|
||||
the value is inherited from HTTP options. Default: password. Available only
|
||||
when Management interface is enabled.
|
||||
--legacy-observability-interface <true|false>
|
||||
DEPRECATED. If metrics/health endpoints should be exposed on the main HTTP
|
||||
server (not recommended). If set to true, the management interface is
|
||||
disabled. Default: false.
|
||||
|
||||
Logging:
|
||||
|
||||
|
|
|
@ -14,6 +14,15 @@ Options:
|
|||
built a server image using the 'build' command.
|
||||
-v, --verbose Print out error details when running this command.
|
||||
|
||||
Config:
|
||||
|
||||
--config-keystore <config-keystore>
|
||||
Specifies a path to the KeyStore Configuration Source.
|
||||
--config-keystore-password <config-keystore-password>
|
||||
Specifies a password to the KeyStore Configuration Source.
|
||||
--config-keystore-type <config-keystore-type>
|
||||
Specifies a type of the KeyStore Configuration Source. Default: PKCS12.
|
||||
|
||||
Database:
|
||||
|
||||
--db <vendor> The database vendor. Possible values are: dev-file, dev-mem, mariadb, mssql,
|
||||
|
@ -59,14 +68,42 @@ Feature:
|
|||
--features-disabled <feature>
|
||||
Disables a set of one or more features. Possible values are: <...>.
|
||||
|
||||
Config:
|
||||
Management:
|
||||
|
||||
--config-keystore <config-keystore>
|
||||
Specifies a path to the KeyStore Configuration Source.
|
||||
--config-keystore-password <config-keystore-password>
|
||||
Specifies a password to the KeyStore Configuration Source.
|
||||
--config-keystore-type <config-keystore-type>
|
||||
Specifies a type of the KeyStore Configuration Source. Default: PKCS12.
|
||||
--http-management-port <port>
|
||||
Port of the management interface. Default: 9000. Available only when
|
||||
Management interface is enabled.
|
||||
--http-management-relative-path <path>
|
||||
Set the path relative to '/' for serving resources from management interface.
|
||||
The path must start with a '/'. If not given, the value is inherited from
|
||||
HTTP options. Default: /. Available only when Management interface is
|
||||
enabled.
|
||||
--https-management-certificate-file <file>
|
||||
The file path to a server certificate or certificate chain in PEM format for
|
||||
the management server. If not given, the value is inherited from HTTP
|
||||
options. Available only when Management interface is enabled.
|
||||
--https-management-certificate-key-file <file>
|
||||
The file path to a private key in PEM format for the management server. If not
|
||||
given, the value is inherited from HTTP options. Available only when
|
||||
Management interface is enabled.
|
||||
--https-management-client-auth <auth>
|
||||
Configures the management interface to require/request client authentication.
|
||||
If not given, the value is inherited from HTTP options. Possible values are:
|
||||
none, request, required. Default: none. Available only when Management
|
||||
interface is enabled.
|
||||
--https-management-key-store-file <file>
|
||||
The key store which holds the certificate information instead of specifying
|
||||
separate files for the management server. If not given, the value is
|
||||
inherited from HTTP options. Available only when Management interface is
|
||||
enabled.
|
||||
--https-management-key-store-password <password>
|
||||
The password of the key store file for the management server. If not given,
|
||||
the value is inherited from HTTP options. Default: password. Available only
|
||||
when Management interface is enabled.
|
||||
--legacy-observability-interface <true|false>
|
||||
DEPRECATED. If metrics/health endpoints should be exposed on the main HTTP
|
||||
server (not recommended). If set to true, the management interface is
|
||||
disabled. Default: false.
|
||||
|
||||
Logging:
|
||||
|
||||
|
|
|
@ -14,6 +14,15 @@ Options:
|
|||
built a server image using the 'build' command.
|
||||
-v, --verbose Print out error details when running this command.
|
||||
|
||||
Config:
|
||||
|
||||
--config-keystore <config-keystore>
|
||||
Specifies a path to the KeyStore Configuration Source.
|
||||
--config-keystore-password <config-keystore-password>
|
||||
Specifies a password to the KeyStore Configuration Source.
|
||||
--config-keystore-type <config-keystore-type>
|
||||
Specifies a type of the KeyStore Configuration Source. Default: PKCS12.
|
||||
|
||||
Database:
|
||||
|
||||
--db <vendor> The database vendor. Possible values are: dev-file, dev-mem, mariadb, mssql,
|
||||
|
@ -59,14 +68,42 @@ Feature:
|
|||
--features-disabled <feature>
|
||||
Disables a set of one or more features. Possible values are: <...>.
|
||||
|
||||
Config:
|
||||
Management:
|
||||
|
||||
--config-keystore <config-keystore>
|
||||
Specifies a path to the KeyStore Configuration Source.
|
||||
--config-keystore-password <config-keystore-password>
|
||||
Specifies a password to the KeyStore Configuration Source.
|
||||
--config-keystore-type <config-keystore-type>
|
||||
Specifies a type of the KeyStore Configuration Source. Default: PKCS12.
|
||||
--http-management-port <port>
|
||||
Port of the management interface. Default: 9000. Available only when
|
||||
Management interface is enabled.
|
||||
--http-management-relative-path <path>
|
||||
Set the path relative to '/' for serving resources from management interface.
|
||||
The path must start with a '/'. If not given, the value is inherited from
|
||||
HTTP options. Default: /. Available only when Management interface is
|
||||
enabled.
|
||||
--https-management-certificate-file <file>
|
||||
The file path to a server certificate or certificate chain in PEM format for
|
||||
the management server. If not given, the value is inherited from HTTP
|
||||
options. Available only when Management interface is enabled.
|
||||
--https-management-certificate-key-file <file>
|
||||
The file path to a private key in PEM format for the management server. If not
|
||||
given, the value is inherited from HTTP options. Available only when
|
||||
Management interface is enabled.
|
||||
--https-management-client-auth <auth>
|
||||
Configures the management interface to require/request client authentication.
|
||||
If not given, the value is inherited from HTTP options. Possible values are:
|
||||
none, request, required. Default: none. Available only when Management
|
||||
interface is enabled.
|
||||
--https-management-key-store-file <file>
|
||||
The key store which holds the certificate information instead of specifying
|
||||
separate files for the management server. If not given, the value is
|
||||
inherited from HTTP options. Available only when Management interface is
|
||||
enabled.
|
||||
--https-management-key-store-password <password>
|
||||
The password of the key store file for the management server. If not given,
|
||||
the value is inherited from HTTP options. Default: password. Available only
|
||||
when Management interface is enabled.
|
||||
--legacy-observability-interface <true|false>
|
||||
DEPRECATED. If metrics/health endpoints should be exposed on the main HTTP
|
||||
server (not recommended). If set to true, the management interface is
|
||||
disabled. Default: false.
|
||||
|
||||
Logging:
|
||||
|
||||
|
|
|
@ -14,6 +14,15 @@ Options:
|
|||
built a server image using the 'build' command.
|
||||
-v, --verbose Print out error details when running this command.
|
||||
|
||||
Config:
|
||||
|
||||
--config-keystore <config-keystore>
|
||||
Specifies a path to the KeyStore Configuration Source.
|
||||
--config-keystore-password <config-keystore-password>
|
||||
Specifies a password to the KeyStore Configuration Source.
|
||||
--config-keystore-type <config-keystore-type>
|
||||
Specifies a type of the KeyStore Configuration Source. Default: PKCS12.
|
||||
|
||||
Database:
|
||||
|
||||
--db <vendor> The database vendor. Possible values are: dev-file, dev-mem, mariadb, mssql,
|
||||
|
@ -59,14 +68,42 @@ Feature:
|
|||
--features-disabled <feature>
|
||||
Disables a set of one or more features. Possible values are: <...>.
|
||||
|
||||
Config:
|
||||
Management:
|
||||
|
||||
--config-keystore <config-keystore>
|
||||
Specifies a path to the KeyStore Configuration Source.
|
||||
--config-keystore-password <config-keystore-password>
|
||||
Specifies a password to the KeyStore Configuration Source.
|
||||
--config-keystore-type <config-keystore-type>
|
||||
Specifies a type of the KeyStore Configuration Source. Default: PKCS12.
|
||||
--http-management-port <port>
|
||||
Port of the management interface. Default: 9000. Available only when
|
||||
Management interface is enabled.
|
||||
--http-management-relative-path <path>
|
||||
Set the path relative to '/' for serving resources from management interface.
|
||||
The path must start with a '/'. If not given, the value is inherited from
|
||||
HTTP options. Default: /. Available only when Management interface is
|
||||
enabled.
|
||||
--https-management-certificate-file <file>
|
||||
The file path to a server certificate or certificate chain in PEM format for
|
||||
the management server. If not given, the value is inherited from HTTP
|
||||
options. Available only when Management interface is enabled.
|
||||
--https-management-certificate-key-file <file>
|
||||
The file path to a private key in PEM format for the management server. If not
|
||||
given, the value is inherited from HTTP options. Available only when
|
||||
Management interface is enabled.
|
||||
--https-management-client-auth <auth>
|
||||
Configures the management interface to require/request client authentication.
|
||||
If not given, the value is inherited from HTTP options. Possible values are:
|
||||
none, request, required. Default: none. Available only when Management
|
||||
interface is enabled.
|
||||
--https-management-key-store-file <file>
|
||||
The key store which holds the certificate information instead of specifying
|
||||
separate files for the management server. If not given, the value is
|
||||
inherited from HTTP options. Available only when Management interface is
|
||||
enabled.
|
||||
--https-management-key-store-password <password>
|
||||
The password of the key store file for the management server. If not given,
|
||||
the value is inherited from HTTP options. Default: password. Available only
|
||||
when Management interface is enabled.
|
||||
--legacy-observability-interface <true|false>
|
||||
DEPRECATED. If metrics/health endpoints should be exposed on the main HTTP
|
||||
server (not recommended). If set to true, the management interface is
|
||||
disabled. Default: false.
|
||||
|
||||
Logging:
|
||||
|
||||
|
|
|
@ -66,6 +66,15 @@ Cache:
|
|||
This option only takes effect if 'cache' is set to 'ispn'. Default: udp.
|
||||
Possible values are: tcp, udp, kubernetes, ec2, azure, google.
|
||||
|
||||
Config:
|
||||
|
||||
--config-keystore <config-keystore>
|
||||
Specifies a path to the KeyStore Configuration Source.
|
||||
--config-keystore-password <config-keystore-password>
|
||||
Specifies a password to the KeyStore Configuration Source.
|
||||
--config-keystore-type <config-keystore-type>
|
||||
Specifies a type of the KeyStore Configuration Source. Default: PKCS12.
|
||||
|
||||
Database:
|
||||
|
||||
--db <vendor> The database vendor. Possible values are: dev-file, dev-mem, mariadb, mssql,
|
||||
|
@ -198,14 +207,42 @@ Health:
|
|||
are available at the '/health', '/health/ready' and '/health/live'
|
||||
endpoints. Default: false.
|
||||
|
||||
Config:
|
||||
Management:
|
||||
|
||||
--config-keystore <config-keystore>
|
||||
Specifies a path to the KeyStore Configuration Source.
|
||||
--config-keystore-password <config-keystore-password>
|
||||
Specifies a password to the KeyStore Configuration Source.
|
||||
--config-keystore-type <config-keystore-type>
|
||||
Specifies a type of the KeyStore Configuration Source. Default: PKCS12.
|
||||
--http-management-port <port>
|
||||
Port of the management interface. Default: 9000. Available only when
|
||||
Management interface is enabled.
|
||||
--http-management-relative-path <path>
|
||||
Set the path relative to '/' for serving resources from management interface.
|
||||
The path must start with a '/'. If not given, the value is inherited from
|
||||
HTTP options. Default: /. Available only when Management interface is
|
||||
enabled.
|
||||
--https-management-certificate-file <file>
|
||||
The file path to a server certificate or certificate chain in PEM format for
|
||||
the management server. If not given, the value is inherited from HTTP
|
||||
options. Available only when Management interface is enabled.
|
||||
--https-management-certificate-key-file <file>
|
||||
The file path to a private key in PEM format for the management server. If not
|
||||
given, the value is inherited from HTTP options. Available only when
|
||||
Management interface is enabled.
|
||||
--https-management-client-auth <auth>
|
||||
Configures the management interface to require/request client authentication.
|
||||
If not given, the value is inherited from HTTP options. Possible values are:
|
||||
none, request, required. Default: none. Available only when Management
|
||||
interface is enabled.
|
||||
--https-management-key-store-file <file>
|
||||
The key store which holds the certificate information instead of specifying
|
||||
separate files for the management server. If not given, the value is
|
||||
inherited from HTTP options. Available only when Management interface is
|
||||
enabled.
|
||||
--https-management-key-store-password <password>
|
||||
The password of the key store file for the management server. If not given,
|
||||
the value is inherited from HTTP options. Default: password. Available only
|
||||
when Management interface is enabled.
|
||||
--legacy-observability-interface <true|false>
|
||||
DEPRECATED. If metrics/health endpoints should be exposed on the main HTTP
|
||||
server (not recommended). If set to true, the management interface is
|
||||
disabled. Default: false.
|
||||
|
||||
Metrics:
|
||||
|
||||
|
|
|
@ -69,6 +69,15 @@ Cache:
|
|||
This option only takes effect if 'cache' is set to 'ispn'. Default: udp.
|
||||
Possible values are: tcp, udp, kubernetes, ec2, azure, google.
|
||||
|
||||
Config:
|
||||
|
||||
--config-keystore <config-keystore>
|
||||
Specifies a path to the KeyStore Configuration Source.
|
||||
--config-keystore-password <config-keystore-password>
|
||||
Specifies a password to the KeyStore Configuration Source.
|
||||
--config-keystore-type <config-keystore-type>
|
||||
Specifies a type of the KeyStore Configuration Source. Default: PKCS12.
|
||||
|
||||
Database:
|
||||
|
||||
--db <vendor> The database vendor. Possible values are: dev-file, dev-mem, mariadb, mssql,
|
||||
|
@ -201,14 +210,42 @@ Health:
|
|||
are available at the '/health', '/health/ready' and '/health/live'
|
||||
endpoints. Default: false.
|
||||
|
||||
Config:
|
||||
Management:
|
||||
|
||||
--config-keystore <config-keystore>
|
||||
Specifies a path to the KeyStore Configuration Source.
|
||||
--config-keystore-password <config-keystore-password>
|
||||
Specifies a password to the KeyStore Configuration Source.
|
||||
--config-keystore-type <config-keystore-type>
|
||||
Specifies a type of the KeyStore Configuration Source. Default: PKCS12.
|
||||
--http-management-port <port>
|
||||
Port of the management interface. Default: 9000. Available only when
|
||||
Management interface is enabled.
|
||||
--http-management-relative-path <path>
|
||||
Set the path relative to '/' for serving resources from management interface.
|
||||
The path must start with a '/'. If not given, the value is inherited from
|
||||
HTTP options. Default: /. Available only when Management interface is
|
||||
enabled.
|
||||
--https-management-certificate-file <file>
|
||||
The file path to a server certificate or certificate chain in PEM format for
|
||||
the management server. If not given, the value is inherited from HTTP
|
||||
options. Available only when Management interface is enabled.
|
||||
--https-management-certificate-key-file <file>
|
||||
The file path to a private key in PEM format for the management server. If not
|
||||
given, the value is inherited from HTTP options. Available only when
|
||||
Management interface is enabled.
|
||||
--https-management-client-auth <auth>
|
||||
Configures the management interface to require/request client authentication.
|
||||
If not given, the value is inherited from HTTP options. Possible values are:
|
||||
none, request, required. Default: none. Available only when Management
|
||||
interface is enabled.
|
||||
--https-management-key-store-file <file>
|
||||
The key store which holds the certificate information instead of specifying
|
||||
separate files for the management server. If not given, the value is
|
||||
inherited from HTTP options. Available only when Management interface is
|
||||
enabled.
|
||||
--https-management-key-store-password <password>
|
||||
The password of the key store file for the management server. If not given,
|
||||
the value is inherited from HTTP options. Default: password. Available only
|
||||
when Management interface is enabled.
|
||||
--legacy-observability-interface <true|false>
|
||||
DEPRECATED. If metrics/health endpoints should be exposed on the main HTTP
|
||||
server (not recommended). If set to true, the management interface is
|
||||
disabled. Default: false.
|
||||
|
||||
Metrics:
|
||||
|
||||
|
|
|
@ -67,6 +67,15 @@ Cache:
|
|||
This option only takes effect if 'cache' is set to 'ispn'. Default: udp.
|
||||
Possible values are: tcp, udp, kubernetes, ec2, azure, google.
|
||||
|
||||
Config:
|
||||
|
||||
--config-keystore <config-keystore>
|
||||
Specifies a path to the KeyStore Configuration Source.
|
||||
--config-keystore-password <config-keystore-password>
|
||||
Specifies a password to the KeyStore Configuration Source.
|
||||
--config-keystore-type <config-keystore-type>
|
||||
Specifies a type of the KeyStore Configuration Source. Default: PKCS12.
|
||||
|
||||
Database:
|
||||
|
||||
--db <vendor> The database vendor. Possible values are: dev-file, dev-mem, mariadb, mssql,
|
||||
|
@ -199,14 +208,42 @@ Health:
|
|||
are available at the '/health', '/health/ready' and '/health/live'
|
||||
endpoints. Default: false.
|
||||
|
||||
Config:
|
||||
Management:
|
||||
|
||||
--config-keystore <config-keystore>
|
||||
Specifies a path to the KeyStore Configuration Source.
|
||||
--config-keystore-password <config-keystore-password>
|
||||
Specifies a password to the KeyStore Configuration Source.
|
||||
--config-keystore-type <config-keystore-type>
|
||||
Specifies a type of the KeyStore Configuration Source. Default: PKCS12.
|
||||
--http-management-port <port>
|
||||
Port of the management interface. Default: 9000. Available only when
|
||||
Management interface is enabled.
|
||||
--http-management-relative-path <path>
|
||||
Set the path relative to '/' for serving resources from management interface.
|
||||
The path must start with a '/'. If not given, the value is inherited from
|
||||
HTTP options. Default: /. Available only when Management interface is
|
||||
enabled.
|
||||
--https-management-certificate-file <file>
|
||||
The file path to a server certificate or certificate chain in PEM format for
|
||||
the management server. If not given, the value is inherited from HTTP
|
||||
options. Available only when Management interface is enabled.
|
||||
--https-management-certificate-key-file <file>
|
||||
The file path to a private key in PEM format for the management server. If not
|
||||
given, the value is inherited from HTTP options. Available only when
|
||||
Management interface is enabled.
|
||||
--https-management-client-auth <auth>
|
||||
Configures the management interface to require/request client authentication.
|
||||
If not given, the value is inherited from HTTP options. Possible values are:
|
||||
none, request, required. Default: none. Available only when Management
|
||||
interface is enabled.
|
||||
--https-management-key-store-file <file>
|
||||
The key store which holds the certificate information instead of specifying
|
||||
separate files for the management server. If not given, the value is
|
||||
inherited from HTTP options. Available only when Management interface is
|
||||
enabled.
|
||||
--https-management-key-store-password <password>
|
||||
The password of the key store file for the management server. If not given,
|
||||
the value is inherited from HTTP options. Default: password. Available only
|
||||
when Management interface is enabled.
|
||||
--legacy-observability-interface <true|false>
|
||||
DEPRECATED. If metrics/health endpoints should be exposed on the main HTTP
|
||||
server (not recommended). If set to true, the management interface is
|
||||
disabled. Default: false.
|
||||
|
||||
Metrics:
|
||||
|
||||
|
|
|
@ -70,6 +70,15 @@ Cache:
|
|||
This option only takes effect if 'cache' is set to 'ispn'. Default: udp.
|
||||
Possible values are: tcp, udp, kubernetes, ec2, azure, google.
|
||||
|
||||
Config:
|
||||
|
||||
--config-keystore <config-keystore>
|
||||
Specifies a path to the KeyStore Configuration Source.
|
||||
--config-keystore-password <config-keystore-password>
|
||||
Specifies a password to the KeyStore Configuration Source.
|
||||
--config-keystore-type <config-keystore-type>
|
||||
Specifies a type of the KeyStore Configuration Source. Default: PKCS12.
|
||||
|
||||
Database:
|
||||
|
||||
--db <vendor> The database vendor. Possible values are: dev-file, dev-mem, mariadb, mssql,
|
||||
|
@ -202,14 +211,42 @@ Health:
|
|||
are available at the '/health', '/health/ready' and '/health/live'
|
||||
endpoints. Default: false.
|
||||
|
||||
Config:
|
||||
Management:
|
||||
|
||||
--config-keystore <config-keystore>
|
||||
Specifies a path to the KeyStore Configuration Source.
|
||||
--config-keystore-password <config-keystore-password>
|
||||
Specifies a password to the KeyStore Configuration Source.
|
||||
--config-keystore-type <config-keystore-type>
|
||||
Specifies a type of the KeyStore Configuration Source. Default: PKCS12.
|
||||
--http-management-port <port>
|
||||
Port of the management interface. Default: 9000. Available only when
|
||||
Management interface is enabled.
|
||||
--http-management-relative-path <path>
|
||||
Set the path relative to '/' for serving resources from management interface.
|
||||
The path must start with a '/'. If not given, the value is inherited from
|
||||
HTTP options. Default: /. Available only when Management interface is
|
||||
enabled.
|
||||
--https-management-certificate-file <file>
|
||||
The file path to a server certificate or certificate chain in PEM format for
|
||||
the management server. If not given, the value is inherited from HTTP
|
||||
options. Available only when Management interface is enabled.
|
||||
--https-management-certificate-key-file <file>
|
||||
The file path to a private key in PEM format for the management server. If not
|
||||
given, the value is inherited from HTTP options. Available only when
|
||||
Management interface is enabled.
|
||||
--https-management-client-auth <auth>
|
||||
Configures the management interface to require/request client authentication.
|
||||
If not given, the value is inherited from HTTP options. Possible values are:
|
||||
none, request, required. Default: none. Available only when Management
|
||||
interface is enabled.
|
||||
--https-management-key-store-file <file>
|
||||
The key store which holds the certificate information instead of specifying
|
||||
separate files for the management server. If not given, the value is
|
||||
inherited from HTTP options. Available only when Management interface is
|
||||
enabled.
|
||||
--https-management-key-store-password <password>
|
||||
The password of the key store file for the management server. If not given,
|
||||
the value is inherited from HTTP options. Default: password. Available only
|
||||
when Management interface is enabled.
|
||||
--legacy-observability-interface <true|false>
|
||||
DEPRECATED. If metrics/health endpoints should be exposed on the main HTTP
|
||||
server (not recommended). If set to true, the management interface is
|
||||
disabled. Default: false.
|
||||
|
||||
Metrics:
|
||||
|
||||
|
|
|
@ -55,6 +55,15 @@ Cache:
|
|||
specified, 'cache-remote-host' and 'cache-remote-password' are required as
|
||||
well and the related configuration in XML file should not be present.
|
||||
|
||||
Config:
|
||||
|
||||
--config-keystore <config-keystore>
|
||||
Specifies a path to the KeyStore Configuration Source.
|
||||
--config-keystore-password <config-keystore-password>
|
||||
Specifies a password to the KeyStore Configuration Source.
|
||||
--config-keystore-type <config-keystore-type>
|
||||
Specifies a type of the KeyStore Configuration Source. Default: PKCS12.
|
||||
|
||||
Database:
|
||||
|
||||
--db-password <password>
|
||||
|
@ -159,14 +168,28 @@ HTTP(S):
|
|||
'strict' and no value is set, it defaults to 'BCFKS'. Use the System
|
||||
Truststore instead, see the docs for details.
|
||||
|
||||
Config:
|
||||
Management:
|
||||
|
||||
--config-keystore <config-keystore>
|
||||
Specifies a path to the KeyStore Configuration Source.
|
||||
--config-keystore-password <config-keystore-password>
|
||||
Specifies a password to the KeyStore Configuration Source.
|
||||
--config-keystore-type <config-keystore-type>
|
||||
Specifies a type of the KeyStore Configuration Source. Default: PKCS12.
|
||||
--http-management-port <port>
|
||||
Port of the management interface. Default: 9000. Available only when
|
||||
Management interface is enabled.
|
||||
--https-management-certificate-file <file>
|
||||
The file path to a server certificate or certificate chain in PEM format for
|
||||
the management server. If not given, the value is inherited from HTTP
|
||||
options. Available only when Management interface is enabled.
|
||||
--https-management-certificate-key-file <file>
|
||||
The file path to a private key in PEM format for the management server. If not
|
||||
given, the value is inherited from HTTP options. Available only when
|
||||
Management interface is enabled.
|
||||
--https-management-key-store-file <file>
|
||||
The key store which holds the certificate information instead of specifying
|
||||
separate files for the management server. If not given, the value is
|
||||
inherited from HTTP options. Available only when Management interface is
|
||||
enabled.
|
||||
--https-management-key-store-password <password>
|
||||
The password of the key store file for the management server. If not given,
|
||||
the value is inherited from HTTP options. Default: password. Available only
|
||||
when Management interface is enabled.
|
||||
|
||||
Proxy:
|
||||
|
||||
|
|
|
@ -58,6 +58,15 @@ Cache:
|
|||
specified, 'cache-remote-host' and 'cache-remote-password' are required as
|
||||
well and the related configuration in XML file should not be present.
|
||||
|
||||
Config:
|
||||
|
||||
--config-keystore <config-keystore>
|
||||
Specifies a path to the KeyStore Configuration Source.
|
||||
--config-keystore-password <config-keystore-password>
|
||||
Specifies a password to the KeyStore Configuration Source.
|
||||
--config-keystore-type <config-keystore-type>
|
||||
Specifies a type of the KeyStore Configuration Source. Default: PKCS12.
|
||||
|
||||
Database:
|
||||
|
||||
--db-password <password>
|
||||
|
@ -162,14 +171,28 @@ HTTP(S):
|
|||
'strict' and no value is set, it defaults to 'BCFKS'. Use the System
|
||||
Truststore instead, see the docs for details.
|
||||
|
||||
Config:
|
||||
Management:
|
||||
|
||||
--config-keystore <config-keystore>
|
||||
Specifies a path to the KeyStore Configuration Source.
|
||||
--config-keystore-password <config-keystore-password>
|
||||
Specifies a password to the KeyStore Configuration Source.
|
||||
--config-keystore-type <config-keystore-type>
|
||||
Specifies a type of the KeyStore Configuration Source. Default: PKCS12.
|
||||
--http-management-port <port>
|
||||
Port of the management interface. Default: 9000. Available only when
|
||||
Management interface is enabled.
|
||||
--https-management-certificate-file <file>
|
||||
The file path to a server certificate or certificate chain in PEM format for
|
||||
the management server. If not given, the value is inherited from HTTP
|
||||
options. Available only when Management interface is enabled.
|
||||
--https-management-certificate-key-file <file>
|
||||
The file path to a private key in PEM format for the management server. If not
|
||||
given, the value is inherited from HTTP options. Available only when
|
||||
Management interface is enabled.
|
||||
--https-management-key-store-file <file>
|
||||
The key store which holds the certificate information instead of specifying
|
||||
separate files for the management server. If not given, the value is
|
||||
inherited from HTTP options. Available only when Management interface is
|
||||
enabled.
|
||||
--https-management-key-store-password <password>
|
||||
The password of the key store file for the management server. If not given,
|
||||
the value is inherited from HTTP options. Default: password. Available only
|
||||
when Management interface is enabled.
|
||||
|
||||
Proxy:
|
||||
|
||||
|
|
|
@ -60,6 +60,10 @@
|
|||
<version>${junit.version}</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.rest-assured</groupId>
|
||||
<artifactId>rest-assured</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.quarkus</groupId>
|
||||
<artifactId>quarkus-junit5-internal</artifactId>
|
||||
|
|
|
@ -64,5 +64,15 @@ public @interface DistributionTest {
|
|||
* If any option must be set when starting the server.
|
||||
*/
|
||||
String[] defaultOptions() default {};
|
||||
|
||||
/**
|
||||
* Exposed ports when container is used
|
||||
*/
|
||||
int[] containerExposedPorts() default {8080};
|
||||
|
||||
/**
|
||||
* Default port for making HTTP requests with RestAssured
|
||||
*/
|
||||
int requestPort() default 8080;
|
||||
}
|
||||
|
||||
|
|
|
@ -33,7 +33,8 @@ public enum DistributionType {
|
|||
return new DockerKeycloakDistribution(
|
||||
config.debug(),
|
||||
config.keepAlive(),
|
||||
!DistributionTest.ReInstall.NEVER.equals(config.reInstall()));
|
||||
config.requestPort(),
|
||||
config.containerExposedPorts());
|
||||
}
|
||||
|
||||
private static KeycloakDistribution createRawDistribution(DistributionTest config) {
|
||||
|
@ -42,7 +43,8 @@ public enum DistributionType {
|
|||
config.keepAlive(),
|
||||
config.enableTls(),
|
||||
!DistributionTest.ReInstall.NEVER.equals(config.reInstall()),
|
||||
config.removeBuildOptionsAfterBuild());
|
||||
config.removeBuildOptionsAfterBuild(),
|
||||
config.requestPort());
|
||||
}
|
||||
|
||||
private final Function<DistributionTest, KeycloakDistribution> factory;
|
||||
|
@ -65,6 +67,14 @@ public enum DistributionType {
|
|||
}
|
||||
}
|
||||
|
||||
public static boolean isContainerDist() {
|
||||
return DistributionType.getCurrent().map(f -> f.equals(DistributionType.DOCKER)).orElse(false);
|
||||
}
|
||||
|
||||
public static boolean isRawDist() {
|
||||
return DistributionType.getCurrent().map(f -> f.equals(DistributionType.RAW)).orElse(false);
|
||||
}
|
||||
|
||||
public KeycloakDistribution newInstance(DistributionTest config) {
|
||||
return factory.apply(config);
|
||||
}
|
||||
|
|
|
@ -126,6 +126,16 @@ public class KeycloakDistributionDecorator implements KeycloakDistribution {
|
|||
delegate.assertStopped();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setRequestPort() {
|
||||
delegate.setRequestPort();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setRequestPort(int port) {
|
||||
delegate.setRequestPort(port);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <D extends KeycloakDistribution> D unwrap(Class<D> type) {
|
||||
if (!KeycloakDistribution.class.isAssignableFrom(type)) {
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
package org.keycloak.it.utils;
|
||||
|
||||
import com.github.dockerjava.api.DockerClient;
|
||||
import com.github.dockerjava.api.exception.NotFoundException;
|
||||
import io.restassured.RestAssured;
|
||||
import org.jboss.logging.Logger;
|
||||
import org.keycloak.common.Version;
|
||||
import org.keycloak.it.junit5.extension.CLIResult;
|
||||
|
@ -15,36 +17,41 @@ import org.testcontainers.utility.DockerImageName;
|
|||
import org.testcontainers.utility.LazyFuture;
|
||||
|
||||
import java.io.File;
|
||||
import java.lang.reflect.Field;
|
||||
import java.time.Duration;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.stream.IntStream;
|
||||
|
||||
public final class DockerKeycloakDistribution implements KeycloakDistribution {
|
||||
|
||||
private static final Logger LOGGER = Logger.getLogger(DockerKeycloakDistribution.class);
|
||||
|
||||
private boolean debug;
|
||||
private boolean manualStop;
|
||||
private final boolean debug;
|
||||
private final boolean manualStop;
|
||||
private final int requestPort;
|
||||
private final Integer[] exposedPorts;
|
||||
|
||||
private int exitCode = -1;
|
||||
|
||||
private String stdout = "";
|
||||
private String stderr = "";
|
||||
private ToStringConsumer backupConsumer = new ToStringConsumer();
|
||||
private File dockerScriptFile = new File("../../container/ubi-null.sh");
|
||||
private final File dockerScriptFile = new File("../../container/ubi-null.sh");
|
||||
|
||||
private GenericContainer<?> keycloakContainer = null;
|
||||
private String containerId = null;
|
||||
|
||||
private Executor parallelReaperExecutor = Executors.newSingleThreadExecutor();
|
||||
private Map<String, String> envVars = new HashMap<>();
|
||||
private final Executor parallelReaperExecutor = Executors.newSingleThreadExecutor();
|
||||
private final Map<String, String> envVars = new HashMap<>();
|
||||
|
||||
public DockerKeycloakDistribution(boolean debug, boolean manualStop, boolean reCreate) {
|
||||
public DockerKeycloakDistribution(boolean debug, boolean manualStop, int requestPort, int[] exposedPorts) {
|
||||
this.debug = debug;
|
||||
this.manualStop = manualStop;
|
||||
this.requestPort = requestPort;
|
||||
this.exposedPorts = IntStream.of(exposedPorts).boxed().toArray(Integer[]::new);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -79,10 +86,10 @@ public final class DockerKeycloakDistribution implements KeycloakDistribution {
|
|||
|
||||
return new GenericContainer<>(image)
|
||||
.withEnv(envVars)
|
||||
.withExposedPorts(8080)
|
||||
.withExposedPorts(exposedPorts)
|
||||
.withStartupAttempts(1)
|
||||
.withStartupTimeout(Duration.ofSeconds(120))
|
||||
.waitingFor(Wait.forListeningPort());
|
||||
.waitingFor(Wait.forListeningPorts(8080));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -118,20 +125,20 @@ public final class DockerKeycloakDistribution implements KeycloakDistribution {
|
|||
}
|
||||
}
|
||||
|
||||
trySetRestAssuredPort();
|
||||
setRequestPort();
|
||||
|
||||
return CLIResult.create(getOutputStream(), getErrorStream(), getExitCode());
|
||||
}
|
||||
|
||||
private void trySetRestAssuredPort() {
|
||||
try {
|
||||
ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
|
||||
Class<?> restAssured = classLoader.loadClass("io.restassured.RestAssured");
|
||||
Field port = restAssured.getDeclaredField("port");
|
||||
port.set(null, keycloakContainer.getMappedPort(8080));
|
||||
} catch (Exception ignore) {
|
||||
// keeping the workaround to set the container port to restassured
|
||||
// TODO: better way to expose the port to tests
|
||||
@Override
|
||||
public void setRequestPort() {
|
||||
setRequestPort(requestPort);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setRequestPort(int port) {
|
||||
if (keycloakContainer != null) {
|
||||
RestAssured.port = keycloakContainer.getMappedPort(port);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -186,9 +193,12 @@ public final class DockerKeycloakDistribution implements KeycloakDistribution {
|
|||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
if (containerId == null) return;
|
||||
DockerClient dockerClient = DockerClientFactory.lazyClient();
|
||||
dockerClient.killContainerCmd(containerId).exec();
|
||||
dockerClient.removeContainerCmd(containerId).withRemoveVolumes(true).withForce(true).exec();
|
||||
} catch (NotFoundException notFound) {
|
||||
LOGGER.debug("Container is already cleaned up, no additional cleanup required");
|
||||
} catch (Exception cause) {
|
||||
throw new RuntimeException("Failed to stop and remove container", cause);
|
||||
}
|
||||
|
|
|
@ -29,6 +29,10 @@ public interface KeycloakDistribution {
|
|||
|
||||
void assertStopped();
|
||||
|
||||
void setRequestPort();
|
||||
|
||||
void setRequestPort(int port);
|
||||
|
||||
default String[] getCliArgs(List<String> arguments) {
|
||||
throw new RuntimeException("Not implemented");
|
||||
}
|
||||
|
|
|
@ -60,6 +60,7 @@ import javax.net.ssl.X509TrustManager;
|
|||
import io.quarkus.deployment.util.FileUtil;
|
||||
import io.quarkus.fs.util.ZipUtils;
|
||||
|
||||
import io.restassured.RestAssured;
|
||||
import org.awaitility.Awaitility;
|
||||
import org.jboss.logging.Logger;
|
||||
import org.jboss.shrinkwrap.api.ShrinkWrap;
|
||||
|
@ -91,20 +92,22 @@ public final class RawKeycloakDistribution implements KeycloakDistribution {
|
|||
private String relativePath;
|
||||
private int httpPort;
|
||||
private int httpsPort;
|
||||
private boolean debug;
|
||||
private boolean enableTls;
|
||||
private boolean reCreate;
|
||||
private boolean removeBuildOptionsAfterBuild;
|
||||
private final boolean debug;
|
||||
private final boolean enableTls;
|
||||
private final boolean reCreate;
|
||||
private final boolean removeBuildOptionsAfterBuild;
|
||||
private final int requestPort;
|
||||
private ExecutorService outputExecutor;
|
||||
private boolean inited = false;
|
||||
private Map<String, String> envVars = new HashMap<>();
|
||||
private final Map<String, String> envVars = new HashMap<>();
|
||||
|
||||
public RawKeycloakDistribution(boolean debug, boolean manualStop, boolean enableTls, boolean reCreate, boolean removeBuildOptionsAfterBuild) {
|
||||
public RawKeycloakDistribution(boolean debug, boolean manualStop, boolean enableTls, boolean reCreate, boolean removeBuildOptionsAfterBuild, int requestPort) {
|
||||
this.debug = debug;
|
||||
this.manualStop = manualStop;
|
||||
this.enableTls = enableTls;
|
||||
this.reCreate = reCreate;
|
||||
this.removeBuildOptionsAfterBuild = removeBuildOptionsAfterBuild;
|
||||
this.requestPort = requestPort;
|
||||
this.distPath = prepareDistribution();
|
||||
}
|
||||
|
||||
|
@ -145,6 +148,8 @@ public final class RawKeycloakDistribution implements KeycloakDistribution {
|
|||
}
|
||||
}
|
||||
|
||||
setRequestPort();
|
||||
|
||||
return CLIResult.create(getOutputStream(), getErrorStream(), getExitCode());
|
||||
}
|
||||
|
||||
|
@ -276,6 +281,16 @@ public final class RawKeycloakDistribution implements KeycloakDistribution {
|
|||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setRequestPort() {
|
||||
setRequestPort(requestPort);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setRequestPort(int port) {
|
||||
RestAssured.port = port;
|
||||
}
|
||||
|
||||
private void waitForReadiness() throws MalformedURLException {
|
||||
waitForReadiness("http", httpPort);
|
||||
|
||||
|
|
|
@ -164,6 +164,8 @@ public abstract class AbstractQuarkusDeployableContainer implements DeployableCo
|
|||
if (suiteContext.get().isAuthServerMigrationEnabled()) {
|
||||
commands.add("--hostname-strict=false");
|
||||
commands.add("--hostname-strict-https=false");
|
||||
} else { // Do not set management port for older versions of Keycloak for migration tests - available since Keycloak ~22
|
||||
commands.add("--http-management-port=" + configuration.getManagementPort());
|
||||
}
|
||||
|
||||
if (configuration.getRoute() != null) {
|
||||
|
|
|
@ -24,6 +24,7 @@ public class KeycloakQuarkusConfiguration implements ContainerConfiguration {
|
|||
private int bindHttpPort = 8080;
|
||||
private int bindHttpsPortOffset = 0;
|
||||
private int bindHttpsPort = Integer.getInteger("auth.server.https.port", 8543);
|
||||
private int managementPort = 9000;
|
||||
|
||||
private String keystoreFile = System.getProperty("auth.server.keystore");
|
||||
|
||||
|
@ -59,7 +60,7 @@ public class KeycloakQuarkusConfiguration implements ContainerConfiguration {
|
|||
int newHttpsPort = baseHttpsPort + bindHttpsPortOffset;
|
||||
setBindHttpsPort(newHttpsPort);
|
||||
|
||||
log.info("Keycloak will listen for http on port: " + newPort + " and for https on port: " + newHttpsPort);
|
||||
log.infof("Keycloak will listen for http on port: %d, for https on port: %d, and for management on port: %d\n", newPort, newHttpsPort, managementPort);
|
||||
|
||||
if (this.keycloakConfigPropertyOverrides != null) {
|
||||
try {
|
||||
|
@ -103,6 +104,14 @@ public class KeycloakQuarkusConfiguration implements ContainerConfiguration {
|
|||
this.bindHttpPort = bindHttpPort;
|
||||
}
|
||||
|
||||
public int getManagementPort() {
|
||||
return managementPort;
|
||||
}
|
||||
|
||||
public void setManagementPort(int managementPort) {
|
||||
this.managementPort = managementPort;
|
||||
}
|
||||
|
||||
public String getKeystoreFile() {
|
||||
return keystoreFile;
|
||||
}
|
||||
|
|
|
@ -693,6 +693,7 @@
|
|||
<property name="bindHttpsPort">${auth.server.https.port}</property>
|
||||
<property name="bindHttpPortOffset">2</property>
|
||||
<property name="bindHttpsPortOffset">2</property>
|
||||
<property name="managementPort">9001</property>
|
||||
<property name="route">node2</property>
|
||||
<property name="remoteMode">${quarkus.remote}</property>
|
||||
<property name="profile">ha</property>
|
||||
|
|
Loading…
Reference in a new issue