From 28673ed4d893dc9ac3bc187dbea6dbdc8ab080cb Mon Sep 17 00:00:00 2001 From: Dominik Guhr Date: Thu, 2 Dec 2021 11:49:40 +0100 Subject: [PATCH 001/144] KEYCLOAK-19832 Update to Quarkus 2.5.1 --- distribution/server-x-dist/src/main/content/bin/kc.sh | 2 +- quarkus/pom.xml | 6 +++--- .../org/keycloak/quarkus/runtime/cli/command/Build.java | 2 +- .../java/org/keycloak/it/junit5/extension/CLIResult.java | 1 + 4 files changed, 6 insertions(+), 5 deletions(-) diff --git a/distribution/server-x-dist/src/main/content/bin/kc.sh b/distribution/server-x-dist/src/main/content/bin/kc.sh index d8960c8bab09..64e78155e3a2 100644 --- a/distribution/server-x-dist/src/main/content/bin/kc.sh +++ b/distribution/server-x-dist/src/main/content/bin/kc.sh @@ -63,7 +63,7 @@ done # Specify options to pass to the Java VM. # if [ "x$JAVA_OPTS" = "x" ]; then - JAVA_OPTS="-Xms64m -Xmx512m -XX:MetaspaceSize=96M -XX:MaxMetaspaceSize=256m -Djava.net.preferIPv4Stack=true -Dquarkus-log-max-startup-records=10000" + JAVA_OPTS="-Xms64m -Xmx512m -XX:MetaspaceSize=96M -XX:MaxMetaspaceSize=256m -Djava.net.preferIPv4Stack=true" else echo "JAVA_OPTS already set in environment; overriding default settings with values: $JAVA_OPTS" fi diff --git a/quarkus/pom.xml b/quarkus/pom.xml index 513738977abf..887da8b80981 100644 --- a/quarkus/pom.xml +++ b/quarkus/pom.xml @@ -32,7 +32,7 @@ - 2.4.2.Final + 2.5.1.Final - https://keycloak-packages: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@maven.pkg.github.com/keycloak/keycloak-admin-ui + https://keycloak-packages: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@maven.pkg.github.com/keycloak/keycloak-admin-ui false From 48835576daa158443f69917ac309e1a7c951bc87 Mon Sep 17 00:00:00 2001 From: Pedro Igor Date: Thu, 9 Dec 2021 08:32:40 -0300 Subject: [PATCH 016/144] [KEYCLOAK-19885] - Parallelize cache manager init --- .../deployment/CLusteringBuildSteps.java | 11 +- .../quarkus/deployment/KeycloakProcessor.java | 7 +- ...eycloakSessionFactoryPreInitBuildItem.java | 27 +++++ .../quarkus/runtime/KeycloakRecorder.java | 57 +++++++++- .../keycloak/quarkus/runtime/cli/Picocli.java | 2 + .../QuarkusKeycloakSessionFactory.java | 18 ++- .../storage/infinispan/CacheInitializer.java | 61 ---------- .../infinispan/CacheManagerFactory.java | 104 ++++++++++++++++++ .../QuarkusCacheManagerProvider.java | 2 +- .../org/keycloak/it/cli/StartCommandTest.java | 7 -- .../it/cli/dist/ClusterConfigDistTest.java | 56 ++++++++++ .../it/cli/dist/StartCommandDistTest.java | 7 ++ 12 files changed, 271 insertions(+), 88 deletions(-) create mode 100644 quarkus/deployment/src/main/java/org/keycloak/quarkus/deployment/KeycloakSessionFactoryPreInitBuildItem.java delete mode 100644 quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/storage/infinispan/CacheInitializer.java create mode 100644 quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/storage/infinispan/CacheManagerFactory.java create mode 100644 quarkus/tests/integration/src/test/java/org/keycloak/it/cli/dist/ClusterConfigDistTest.java diff --git a/quarkus/deployment/src/main/java/org/keycloak/quarkus/deployment/CLusteringBuildSteps.java b/quarkus/deployment/src/main/java/org/keycloak/quarkus/deployment/CLusteringBuildSteps.java index c63ad1284533..6c1da3551430 100644 --- a/quarkus/deployment/src/main/java/org/keycloak/quarkus/deployment/CLusteringBuildSteps.java +++ b/quarkus/deployment/src/main/java/org/keycloak/quarkus/deployment/CLusteringBuildSteps.java @@ -29,19 +29,22 @@ import org.infinispan.commons.util.FileLookupFactory; import org.keycloak.quarkus.runtime.Environment; import org.keycloak.quarkus.runtime.KeycloakRecorder; -import org.keycloak.quarkus.runtime.storage.infinispan.CacheInitializer; +import org.keycloak.quarkus.runtime.storage.infinispan.CacheManagerFactory; import io.quarkus.arc.deployment.SyntheticBeanBuildItem; import io.quarkus.deployment.annotations.BuildProducer; import io.quarkus.deployment.annotations.BuildStep; +import io.quarkus.deployment.annotations.Consume; import io.quarkus.deployment.annotations.ExecutionTime; import io.quarkus.deployment.annotations.Record; +import io.quarkus.deployment.builditem.ShutdownContextBuildItem; public class CLusteringBuildSteps { + @Consume(KeycloakSessionFactoryPreInitBuildItem.class) @Record(ExecutionTime.RUNTIME_INIT) @BuildStep - void configureInfinispan(KeycloakRecorder recorder, BuildProducer syntheticBeanBuildItems) { + void configureInfinispan(KeycloakRecorder recorder, BuildProducer syntheticBeanBuildItems, ShutdownContextBuildItem shutdownContext) { String pathPrefix; String homeDir = Environment.getHomeDir(); @@ -72,11 +75,11 @@ void configureInfinispan(KeycloakRecorder recorder, BuildProducer, Map>>> factories = new HashMap<>(); Map, String> defaultProviders = new HashMap<>(); @@ -252,6 +255,8 @@ void configureProviders(KeycloakRecorder recorder) { } recorder.configSessionFactory(factories, defaultProviders, preConfiguredProviders, Environment.isRebuild()); + + return new KeycloakSessionFactoryPreInitBuildItem(); } /** diff --git a/quarkus/deployment/src/main/java/org/keycloak/quarkus/deployment/KeycloakSessionFactoryPreInitBuildItem.java b/quarkus/deployment/src/main/java/org/keycloak/quarkus/deployment/KeycloakSessionFactoryPreInitBuildItem.java new file mode 100644 index 000000000000..d7e157446f0f --- /dev/null +++ b/quarkus/deployment/src/main/java/org/keycloak/quarkus/deployment/KeycloakSessionFactoryPreInitBuildItem.java @@ -0,0 +1,27 @@ +/* + * Copyright 2021 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 io.quarkus.builder.item.SimpleBuildItem; + +/** + * A symbolic build item that can be consumed by other build steps when the {@link org.keycloak.quarkus.runtime.integration.QuarkusKeycloakSessionFactory} + * is pre-initialized. + */ +public final class KeycloakSessionFactoryPreInitBuildItem extends SimpleBuildItem { +} diff --git a/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/KeycloakRecorder.java b/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/KeycloakRecorder.java index 397da1a596e7..aceb2a20299a 100644 --- a/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/KeycloakRecorder.java +++ b/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/KeycloakRecorder.java @@ -23,7 +23,10 @@ import java.util.Map; import java.util.Optional; -import org.jboss.logging.Logger; +import org.infinispan.configuration.parsing.ConfigurationBuilderHolder; +import org.infinispan.configuration.parsing.ParserRegistry; +import org.infinispan.jboss.marshalling.core.JBossUserMarshaller; +import org.infinispan.manager.DefaultCacheManager; import org.keycloak.common.Profile; import org.keycloak.quarkus.runtime.configuration.Configuration; import org.keycloak.quarkus.runtime.integration.QuarkusKeycloakSessionFactory; @@ -32,9 +35,10 @@ import org.keycloak.provider.Provider; import org.keycloak.provider.ProviderFactory; import org.keycloak.provider.Spi; -import org.keycloak.quarkus.runtime.storage.infinispan.CacheInitializer; +import org.keycloak.quarkus.runtime.storage.infinispan.CacheManagerFactory; import io.quarkus.runtime.RuntimeValue; +import io.quarkus.runtime.ShutdownContext; import io.quarkus.runtime.annotations.Recorder; import liquibase.logging.LogFactory; import liquibase.servicelocator.ServiceLocator; @@ -100,7 +104,52 @@ public String resolve(String feature) { }); } - public RuntimeValue createCacheInitializer(String config) { - return new RuntimeValue<>(new CacheInitializer(config)); + public RuntimeValue createCacheInitializer(String config, ShutdownContext shutdownContext) { + try { + ConfigurationBuilderHolder builder = new ParserRegistry().parse(config); + + if (builder.getNamedConfigurationBuilders().get("sessions").clustering().cacheMode().isClustered()) { + configureTransportStack(builder); + } + + // For Infinispan 10, we go with the JBoss marshalling. + // TODO: This should be replaced later with the marshalling recommended by infinispan. Probably protostream. + // See https://infinispan.org/docs/stable/titles/developing/developing.html#marshalling for the details + builder.getGlobalConfigurationBuilder().serialization().marshaller(new JBossUserMarshaller()); + CacheManagerFactory cacheManagerFactory = new CacheManagerFactory(builder); + + shutdownContext.addShutdownTask(new Runnable() { + @Override + public void run() { + DefaultCacheManager cacheManager = cacheManagerFactory.getOrCreate(); + + if (cacheManager != null) { + cacheManager.stop(); + } + } + }); + + return new RuntimeValue<>(cacheManagerFactory); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + private void configureTransportStack(ConfigurationBuilderHolder builder) { + String transportStack = Configuration.getRawValue("kc.cluster-stack"); + + if (transportStack != null) { + builder.getGlobalConfigurationBuilder().transport().defaultTransport() + .addProperty("configurationFile", "default-configs/default-jgroups-" + transportStack + ".xml"); + } + } + + public void registerShutdownHook(ShutdownContext shutdownContext) { + shutdownContext.addShutdownTask(new Runnable() { + @Override + public void run() { + QuarkusKeycloakSessionFactory.getInstance().close(); + } + }); } } diff --git a/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/cli/Picocli.java b/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/cli/Picocli.java index 992629a22d66..26ae98edab0c 100644 --- a/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/cli/Picocli.java +++ b/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/cli/Picocli.java @@ -33,6 +33,7 @@ import static picocli.CommandLine.Model.UsageMessageSpec.SECTION_KEY_COMMAND_LIST; import java.io.File; +import java.io.PrintWriter; import java.util.ArrayList; import java.util.Arrays; import java.util.Iterator; @@ -281,6 +282,7 @@ public static CommandLine createCommandLine(List cliArgs) { cmd.setHelpFactory(new HelpFactory()); cmd.getHelpSectionMap().put(SECTION_KEY_COMMAND_LIST, new SubCommandListRenderer()); + cmd.setErr(new PrintWriter(System.err, true)); return cmd; } diff --git a/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/integration/QuarkusKeycloakSessionFactory.java b/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/integration/QuarkusKeycloakSessionFactory.java index 1b877e0f752f..4aeaa76ff1a8 100644 --- a/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/integration/QuarkusKeycloakSessionFactory.java +++ b/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/integration/QuarkusKeycloakSessionFactory.java @@ -21,7 +21,6 @@ import java.util.HashMap; import java.util.Map; -import org.jboss.logging.Logger; import org.keycloak.Config; import org.keycloak.provider.Provider; import org.keycloak.provider.ProviderFactory; @@ -58,15 +57,6 @@ public QuarkusKeycloakSessionFactory( this.factories = factories; this.preConfiguredProviders = preConfiguredProviders; this.reaugmented = reaugmented; - } - - private QuarkusKeycloakSessionFactory() { - reaugmented = false; - factories = Collections.emptyMap(); - } - - @Override - public void init() { serverStartupTimestamp = System.currentTimeMillis(); spis = factories.keySet(); @@ -86,7 +76,15 @@ public void init() { } } } + } + + private QuarkusKeycloakSessionFactory() { + reaugmented = false; + factories = Collections.emptyMap(); + } + @Override + public void init() { // Component factory must be initialized first, so that postInit in other factories can use component factories updateComponentFactoryProviderFactory(); if (componentFactoryPF != null) { diff --git a/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/storage/infinispan/CacheInitializer.java b/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/storage/infinispan/CacheInitializer.java deleted file mode 100644 index 44dffc29e51d..000000000000 --- a/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/storage/infinispan/CacheInitializer.java +++ /dev/null @@ -1,61 +0,0 @@ -/* - * Copyright 2021 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.storage.infinispan; - -import org.infinispan.configuration.parsing.ConfigurationBuilderHolder; -import org.infinispan.configuration.parsing.ParserRegistry; -import org.infinispan.jboss.marshalling.core.JBossUserMarshaller; -import org.infinispan.manager.DefaultCacheManager; -import org.keycloak.Config; - -public class CacheInitializer { - - private final String config; - - public CacheInitializer(String config) { - this.config = config; - } - - public DefaultCacheManager getCacheManager(Config.Scope config) { - try { - ConfigurationBuilderHolder builder = new ParserRegistry().parse(this.config); - - if (builder.getNamedConfigurationBuilders().get("sessions").clustering().cacheMode().isClustered()) { - configureTransportStack(config, builder); - } - - // For Infinispan 10, we go with the JBoss marshalling. - // TODO: This should be replaced later with the marshalling recommended by infinispan. Probably protostream. - // See https://infinispan.org/docs/stable/titles/developing/developing.html#marshalling for the details - builder.getGlobalConfigurationBuilder().serialization().marshaller(new JBossUserMarshaller()); - - return new DefaultCacheManager(builder, false); - } catch (Exception e) { - throw new RuntimeException(e); - } - } - - private void configureTransportStack(Config.Scope config, ConfigurationBuilderHolder builder) { - String transportStack = config.get("stack"); - - if (transportStack != null) { - builder.getGlobalConfigurationBuilder().transport().defaultTransport() - .addProperty("configurationFile", "default-configs/default-jgroups-" + transportStack + ".xml"); - } - } -} diff --git a/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/storage/infinispan/CacheManagerFactory.java b/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/storage/infinispan/CacheManagerFactory.java new file mode 100644 index 000000000000..c36a25094314 --- /dev/null +++ b/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/storage/infinispan/CacheManagerFactory.java @@ -0,0 +1,104 @@ +/* + * Copyright 2021 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.storage.infinispan; + +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; +import java.util.concurrent.ThreadFactory; +import java.util.concurrent.TimeUnit; +import org.infinispan.configuration.parsing.ConfigurationBuilderHolder; +import org.infinispan.manager.DefaultCacheManager; +import org.jboss.logging.Logger; + +public class CacheManagerFactory { + + private ConfigurationBuilderHolder config; + private DefaultCacheManager cacheManager; + private Future cacheManagerFuture; + private ExecutorService executor; + private boolean initialized; + + public CacheManagerFactory(ConfigurationBuilderHolder config) { + this.config = config; + this.executor = createThreadPool(); + this.cacheManagerFuture = executor.submit(this::startCacheManager); + } + + public DefaultCacheManager getOrCreate() { + if (cacheManager == null) { + if (initialized) { + return null; + } + + try { + // for now, we don't have any explicit property for setting the cache start timeout + return cacheManager = cacheManagerFuture.get(getStartTimeout(), TimeUnit.SECONDS); + } catch (Exception e) { + throw new RuntimeException("Failed to start caches", e); + } finally { + shutdownThreadPool(); + } + } + + return cacheManager; + } + + private ExecutorService createThreadPool() { + return Executors.newSingleThreadExecutor(new ThreadFactory() { + @Override + public Thread newThread(Runnable r) { + return new Thread(r, "keycloak-cache-init"); + } + }); + } + + private DefaultCacheManager startCacheManager() { + return new DefaultCacheManager(config, isStartEagerly()); + } + + private boolean isStartEagerly() { + // eagerly starts caches by default + return Boolean.parseBoolean(System.getProperty("kc.cache.ispn.start-eagerly", Boolean.TRUE.toString())); + } + + private Integer getStartTimeout() { + return Integer.getInteger("kc.cache.ispn.start-timeout", 120); + } + + private void shutdownThreadPool() { + if (executor != null) { + executor.shutdown(); + try { + if (!executor.awaitTermination(60, TimeUnit.SECONDS)) { + executor.shutdownNow(); + if (!executor.awaitTermination(60, TimeUnit.SECONDS)) { + Logger.getLogger(CacheManagerFactory.class).warn("Cache init thread pool not terminated"); + } + } + } catch (Exception cause) { + executor.shutdownNow(); + } finally { + executor = null; + cacheManagerFuture = null; + config = null; + initialized = true; + } + } + } +} diff --git a/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/storage/infinispan/QuarkusCacheManagerProvider.java b/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/storage/infinispan/QuarkusCacheManagerProvider.java index 95b549bf81b1..ae46deca05b3 100644 --- a/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/storage/infinispan/QuarkusCacheManagerProvider.java +++ b/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/storage/infinispan/QuarkusCacheManagerProvider.java @@ -29,6 +29,6 @@ public final class QuarkusCacheManagerProvider implements ManagedCacheManagerPro @Override public C getCacheManager(Config.Scope config) { - return (C) Arc.container().instance(CacheInitializer.class).get().getCacheManager(config); + return (C) Arc.container().instance(CacheManagerFactory.class).get().getOrCreate(); } } diff --git a/quarkus/tests/integration/src/test/java/org/keycloak/it/cli/StartCommandTest.java b/quarkus/tests/integration/src/test/java/org/keycloak/it/cli/StartCommandTest.java index 02f2361b99ad..fbc4b084bfeb 100644 --- a/quarkus/tests/integration/src/test/java/org/keycloak/it/cli/StartCommandTest.java +++ b/quarkus/tests/integration/src/test/java/org/keycloak/it/cli/StartCommandTest.java @@ -36,13 +36,6 @@ void failNoTls(LaunchResult result) { () -> "The Output:\n" + result.getOutput() + "doesn't contains the expected string."); } - @Test - @Launch({ "start", "--http-enabled=true" }) - void failNoHostnameNotSet(LaunchResult result) { - assertTrue(result.getOutput().contains("ERROR: Strict hostname resolution configured but no hostname was set"), - () -> "The Output:\n" + result.getOutput() + "doesn't contains the expected string."); - } - @Test @Launch({ "--profile=dev", "start" }) void failUsingDevProfile(LaunchResult result) { diff --git a/quarkus/tests/integration/src/test/java/org/keycloak/it/cli/dist/ClusterConfigDistTest.java b/quarkus/tests/integration/src/test/java/org/keycloak/it/cli/dist/ClusterConfigDistTest.java new file mode 100644 index 000000000000..e8d61bdd8555 --- /dev/null +++ b/quarkus/tests/integration/src/test/java/org/keycloak/it/cli/dist/ClusterConfigDistTest.java @@ -0,0 +1,56 @@ +/* + * Copyright 2021 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 static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import org.junit.jupiter.api.Test; +import org.keycloak.it.cli.StartCommandTest; +import org.keycloak.it.junit5.extension.DistributionTest; + +import io.quarkus.test.junit.main.Launch; +import io.quarkus.test.junit.main.LaunchResult; + +@DistributionTest +public class ClusterConfigDistTest { + + @Test + @Launch({ "start-dev", "--cluster=default" }) + void changeClusterSetting(LaunchResult result) { + assertTrue(result.getOutput().contains("Received new cluster view")); + } + + @Test + @Launch({ "start-dev", "--cluster=invalid" }) + void failInvalidClusterConfig(LaunchResult result) { + assertTrue(result.getErrorOutput().contains("ERROR: Could not load cluster configuration file")); + } + + @Test + @Launch({ "start-dev", "--cluster=default", "--cluster-stack=kubernetes" }) + void failMisConfiguredClusterStack(LaunchResult result) { + assertTrue(result.getOutput().contains("ERROR: dns_query can not be null or empty")); + } + + @Test + @Launch({ "start-dev", "--cluster-stack=invalid" }) + void failInvalidClusterStack(LaunchResult result) { + assertTrue(result.getErrorOutput().contains("Invalid value for option '--cluster-stack': invalid. Expected values are: tcp, udp, kubernetes, ec2, azure, google")); + } +} diff --git a/quarkus/tests/integration/src/test/java/org/keycloak/it/cli/dist/StartCommandDistTest.java b/quarkus/tests/integration/src/test/java/org/keycloak/it/cli/dist/StartCommandDistTest.java index 0d0114598cf6..0ac3df5ea073 100644 --- a/quarkus/tests/integration/src/test/java/org/keycloak/it/cli/dist/StartCommandDistTest.java +++ b/quarkus/tests/integration/src/test/java/org/keycloak/it/cli/dist/StartCommandDistTest.java @@ -37,4 +37,11 @@ void failIfAutoBuildUsingDevProfile(LaunchResult result) { () -> "The Output:\n" + result.getErrorOutput() + "doesn't contains the expected string."); assertEquals(4, result.getErrorStream().size()); } + + @Test + @Launch({ "start", "--http-enabled=true" }) + void failNoHostnameNotSet(LaunchResult result) { + assertTrue(result.getErrorOutput().contains("ERROR: Strict hostname resolution configured but no hostname was set"), + () -> "The Output:\n" + result.getOutput() + "doesn't contains the expected string."); + } } From 50060775f6643dc3b3eb3592c0e7771c44d39cf5 Mon Sep 17 00:00:00 2001 From: Thomas Darimont Date: Mon, 22 Nov 2021 21:46:28 +0100 Subject: [PATCH 017/144] 8877 Make KeycloakMetricsHandler aware of context-path Adapt KeycloakMetricsHandler to consider custom relative paths via `kc.http.relative-path` or --http-relative-path=/auth. Previously only the fixed path /metrics was allowed which did not work with custom context-paths. - Adapted KeycloakProcessor to Pedros proposal from https://github.com/pedroigor/keycloak/commit/0b28d71dd0aec131a9c21d7f730a44b5ee2d7cbf - Removed unnecessary KeycloakMetricsHandler - Added MetricsDistTest Fixes #8877 --- .../quarkus/deployment/KeycloakProcessor.java | 11 ++++--- .../quarkus/runtime/KeycloakRecorder.java | 9 ++++++ .../health/KeycloakMetricsHandler.java | 29 ------------------- .../keycloak/it/cli/dist/MetricsDistTest.java | 2 -- 4 files changed, 16 insertions(+), 35 deletions(-) delete mode 100644 quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/services/health/KeycloakMetricsHandler.java diff --git a/quarkus/deployment/src/main/java/org/keycloak/quarkus/deployment/KeycloakProcessor.java b/quarkus/deployment/src/main/java/org/keycloak/quarkus/deployment/KeycloakProcessor.java index 0657991121ae..bf31b3436e6b 100644 --- a/quarkus/deployment/src/main/java/org/keycloak/quarkus/deployment/KeycloakProcessor.java +++ b/quarkus/deployment/src/main/java/org/keycloak/quarkus/deployment/KeycloakProcessor.java @@ -67,6 +67,7 @@ import io.quarkus.resteasy.server.common.deployment.ResteasyDeploymentCustomizerBuildItem; import io.quarkus.runtime.LaunchMode; import io.quarkus.smallrye.health.runtime.SmallRyeHealthHandler; +import io.quarkus.vertx.http.deployment.NonApplicationRootPathBuildItem; import io.quarkus.vertx.http.deployment.RouteBuildItem; import io.vertx.core.Handler; import io.vertx.ext.web.RoutingContext; @@ -117,7 +118,6 @@ import org.keycloak.representations.provider.ScriptProviderMetadata; import org.keycloak.quarkus.runtime.integration.web.NotFoundHandler; import org.keycloak.services.ServicesLogger; -import org.keycloak.quarkus.runtime.services.health.KeycloakMetricsHandler; import org.keycloak.theme.FolderThemeProviderFactory; import org.keycloak.transaction.JBossJtaTransactionManagerLookup; import org.keycloak.quarkus.runtime.Environment; @@ -132,6 +132,7 @@ class KeycloakProcessor { private static final String JAR_FILE_SEPARATOR = "!/"; private static final String DEFAULT_HEALTH_ENDPOINT = "/health"; + private static final String DEFAULT_METRICS_ENDPOINT = "/metrics"; private static final Map> DEPLOYEABLE_SCRIPT_PROVIDERS = new HashMap<>(); private static final String KEYCLOAK_SCRIPTS_JSON_PATH = "META-INF/keycloak-scripts.json"; @@ -344,14 +345,16 @@ void initializeFilter(BuildProducer filters, LaunchModeBuildIte * * @param routes */ + @Record(ExecutionTime.STATIC_INIT) @BuildStep - void initializeMetrics(BuildProducer routes) { + void initializeMetrics(KeycloakRecorder recorder, BuildProducer routes, NonApplicationRootPathBuildItem nonAppRootPath) { Handler healthHandler; Handler metricsHandler; if (isMetricsEnabled()) { healthHandler = new SmallRyeHealthHandler(); - metricsHandler = new KeycloakMetricsHandler(); + String rootPath = nonAppRootPath.getNormalizedHttpRootPath(); + metricsHandler = recorder.createMetricsHandler(rootPath.concat(DEFAULT_METRICS_ENDPOINT).replace("//", "/")); } else { healthHandler = new NotFoundHandler(); metricsHandler = new NotFoundHandler(); @@ -360,7 +363,7 @@ void initializeMetrics(BuildProducer routes) { routes.produce(RouteBuildItem.builder().route(DEFAULT_HEALTH_ENDPOINT).handler(healthHandler).build()); routes.produce(RouteBuildItem.builder().route(DEFAULT_HEALTH_ENDPOINT.concat("/live")).handler(healthHandler).build()); routes.produce(RouteBuildItem.builder().route(DEFAULT_HEALTH_ENDPOINT.concat("/ready")).handler(healthHandler).build()); - routes.produce(RouteBuildItem.builder().route(KeycloakMetricsHandler.DEFAULT_METRICS_ENDPOINT).handler(metricsHandler).build()); + routes.produce(RouteBuildItem.builder().route(DEFAULT_METRICS_ENDPOINT).handler(metricsHandler).build()); } @BuildStep diff --git a/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/KeycloakRecorder.java b/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/KeycloakRecorder.java index aceb2a20299a..8309b49187b1 100644 --- a/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/KeycloakRecorder.java +++ b/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/KeycloakRecorder.java @@ -27,6 +27,9 @@ import org.infinispan.configuration.parsing.ParserRegistry; import org.infinispan.jboss.marshalling.core.JBossUserMarshaller; import org.infinispan.manager.DefaultCacheManager; +import io.quarkus.smallrye.metrics.runtime.SmallRyeMetricsHandler; +import io.vertx.core.Handler; +import io.vertx.ext.web.RoutingContext; import org.keycloak.common.Profile; import org.keycloak.quarkus.runtime.configuration.Configuration; import org.keycloak.quarkus.runtime.integration.QuarkusKeycloakSessionFactory; @@ -152,4 +155,10 @@ public void run() { } }); } + + public Handler createMetricsHandler(String path) { + SmallRyeMetricsHandler metricsHandler = new SmallRyeMetricsHandler(); + metricsHandler.setMetricsPath(path); + return metricsHandler; + } } diff --git a/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/services/health/KeycloakMetricsHandler.java b/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/services/health/KeycloakMetricsHandler.java deleted file mode 100644 index 3d65e17b5018..000000000000 --- a/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/services/health/KeycloakMetricsHandler.java +++ /dev/null @@ -1,29 +0,0 @@ -/* - * 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 org.keycloak.quarkus.runtime.services.health; - -import io.quarkus.smallrye.metrics.runtime.SmallRyeMetricsHandler; - -public class KeycloakMetricsHandler extends SmallRyeMetricsHandler { - - public static final String DEFAULT_METRICS_ENDPOINT = "/metrics"; - - public KeycloakMetricsHandler() { - setMetricsPath(DEFAULT_METRICS_ENDPOINT); - } -} diff --git a/quarkus/tests/integration/src/test/java/org/keycloak/it/cli/dist/MetricsDistTest.java b/quarkus/tests/integration/src/test/java/org/keycloak/it/cli/dist/MetricsDistTest.java index 4bc71aca55f9..9723ad69d9fe 100644 --- a/quarkus/tests/integration/src/test/java/org/keycloak/it/cli/dist/MetricsDistTest.java +++ b/quarkus/tests/integration/src/test/java/org/keycloak/it/cli/dist/MetricsDistTest.java @@ -20,7 +20,6 @@ import static io.restassured.RestAssured.when; import static org.hamcrest.Matchers.containsString; -import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import org.keycloak.it.junit5.extension.DistributionTest; @@ -37,7 +36,6 @@ void testMetricsEndpoint() { .body(containsString("base_gc_total")); } - @Disabled("https://github.com/keycloak/keycloak/pull/8878") @Test @Launch({ "start-dev", "--http-relative-path=/auth", "--metrics-enabled=true" }) void testMetricsEndpointUsingRelativePath() { From bf0f3d605c9ad73f0c0a5efb0534164e0fde24de Mon Sep 17 00:00:00 2001 From: Pedro Igor Date: Thu, 9 Dec 2021 12:19:14 -0300 Subject: [PATCH 018/144] [fixes #9052] - Renaming cluster options to cache --- distribution/server-x-dist/assembly.xml | 6 +- .../deployment/CLusteringBuildSteps.java | 11 +-- .../quarkus/runtime/KeycloakRecorder.java | 2 +- .../mappers/ClusteringPropertyMappers.java | 54 +++++++++++--- .../configuration/mappers/PropertyMapper.java | 9 +++ .../resources/META-INF/keycloak.properties | 2 +- .../{cluster-default.xml => cache-ispn.xml} | 0 .../{cluster-local.xml => cache-local.xml} | 0 .../provider/quarkus/ConfigurationTest.java | 6 +- .../it/cli/dist/BuildAndStartDistTest.java | 2 +- .../it/cli/dist/ClusterConfigDistTest.java | 42 +++++++++-- .../servers/auth-server/quarkus/assembly.xml | 4 +- .../src/main/content/conf/cluster-local.xml | 74 ------------------- ...cloakQuarkusServerDeployableContainer.java | 8 +- 14 files changed, 105 insertions(+), 115 deletions(-) rename quarkus/runtime/src/main/resources/{cluster-default.xml => cache-ispn.xml} (100%) rename quarkus/runtime/src/main/resources/{cluster-local.xml => cache-local.xml} (100%) delete mode 100644 testsuite/integration-arquillian/servers/auth-server/quarkus/src/main/content/conf/cluster-local.xml diff --git a/distribution/server-x-dist/assembly.xml b/distribution/server-x-dist/assembly.xml index 0658b162e00d..783183bd75a6 100755 --- a/distribution/server-x-dist/assembly.xml +++ b/distribution/server-x-dist/assembly.xml @@ -94,11 +94,7 @@ true - target/keycloak-quarkus-server/cluster-local.xml - conf - - - target/keycloak-quarkus-server/cluster-default.xml + target/keycloak-quarkus-server/cache-ispn.xml conf diff --git a/quarkus/deployment/src/main/java/org/keycloak/quarkus/deployment/CLusteringBuildSteps.java b/quarkus/deployment/src/main/java/org/keycloak/quarkus/deployment/CLusteringBuildSteps.java index 6c1da3551430..03472e3ba91d 100644 --- a/quarkus/deployment/src/main/java/org/keycloak/quarkus/deployment/CLusteringBuildSteps.java +++ b/quarkus/deployment/src/main/java/org/keycloak/quarkus/deployment/CLusteringBuildSteps.java @@ -45,19 +45,10 @@ public class CLusteringBuildSteps { @Record(ExecutionTime.RUNTIME_INIT) @BuildStep void configureInfinispan(KeycloakRecorder recorder, BuildProducer syntheticBeanBuildItems, ShutdownContextBuildItem shutdownContext) { - String pathPrefix; - String homeDir = Environment.getHomeDir(); - - if (homeDir == null) { - pathPrefix = ""; - } else { - pathPrefix = homeDir + "/conf/"; - } - String configFile = getConfigValue("kc.spi.connections-infinispan.quarkus.config-file").getValue(); if (configFile != null) { - Path configPath = Paths.get(pathPrefix + configFile); + Path configPath = Paths.get(configFile); String path; if (configPath.toFile().exists()) { diff --git a/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/KeycloakRecorder.java b/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/KeycloakRecorder.java index 8309b49187b1..ed6ac77e59e7 100644 --- a/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/KeycloakRecorder.java +++ b/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/KeycloakRecorder.java @@ -139,7 +139,7 @@ public void run() { } private void configureTransportStack(ConfigurationBuilderHolder builder) { - String transportStack = Configuration.getRawValue("kc.cluster-stack"); + String transportStack = Configuration.getRawValue("kc.cache-stack"); if (transportStack != null) { builder.getGlobalConfigurationBuilder().transport().defaultTransport() diff --git a/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/configuration/mappers/ClusteringPropertyMappers.java b/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/configuration/mappers/ClusteringPropertyMappers.java index 93181b9ed843..101a5e7c0581 100644 --- a/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/configuration/mappers/ClusteringPropertyMappers.java +++ b/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/configuration/mappers/ClusteringPropertyMappers.java @@ -1,6 +1,11 @@ package org.keycloak.quarkus.runtime.configuration.mappers; import java.util.Arrays; +import java.util.function.BiFunction; + +import org.keycloak.quarkus.runtime.Environment; + +import io.smallrye.config.ConfigSourceInterceptorContext; final class ClusteringPropertyMappers { @@ -9,24 +14,51 @@ private ClusteringPropertyMappers() { public static PropertyMapper[] getClusteringPropertyMappers() { return new PropertyMapper[] { - builder().from("cluster") - .to("kc.spi.connections-infinispan.quarkus.config-file") - .defaultValue("default") - .transformer((value, context) -> "cluster-" + value + ".xml") - .description("Specifies clustering configuration. The specified value points to the infinispan " + - "configuration file prefixed with the 'cluster-` inside the distribution configuration directory. " + - "Value 'local' effectively disables clustering and use infinispan caches in the local mode. " + - "Value 'default' enables clustering for infinispan caches.") - .paramLabel("mode") + builder().from("cache") + .defaultValue("ispn") + .description("Defines the cache mechanism for high-availability. " + + "By default, a 'ispn' cache is used to create a cluster between multiple server nodes. " + + "A 'local' cache disables clustering and is intended for development and testing purposes.") + .paramLabel("type") .isBuildTimeProperty(true) + .expectedValues("local", "ispn") .build(), - builder().from("cluster-stack") + builder().from("cache-stack") .to("kc.spi.connections-infinispan.quarkus.stack") - .description("Define the default stack to use for cluster communication and node discovery.") + .description("Define the default stack to use for cluster communication and node discovery. This option only takes effect " + + "if 'cache' is set to 'ispn'.") .defaultValue("udp") .paramLabel("stack") .isBuildTimeProperty(true) .expectedValues(Arrays.asList("tcp", "udp", "kubernetes", "ec2", "azure", "google")) + .build(), + builder().from("cache.config-file") + .mapFrom("cache") + .to("kc.spi.connections-infinispan.quarkus.config-file") + .description("Defines the file from which cache configuration should be loaded from.") + .transformer(new BiFunction() { + @Override + public String apply(String value, ConfigSourceInterceptorContext context) { + if ("local".equals(value)) { + return "cache-local.xml"; + } else if ("ispn".equals(value)) { + return "cache-ispn.xml"; + } + + String pathPrefix; + String homeDir = Environment.getHomeDir(); + + if (homeDir == null) { + pathPrefix = ""; + } else { + pathPrefix = homeDir + "/conf/"; + } + + return pathPrefix + value; + } + }) + .paramLabel("file") + .isBuildTimeProperty(true) .build() }; } diff --git a/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/configuration/mappers/PropertyMapper.java b/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/configuration/mappers/PropertyMapper.java index 4f6c47c65f3f..ece3d2462904 100644 --- a/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/configuration/mappers/PropertyMapper.java +++ b/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/configuration/mappers/PropertyMapper.java @@ -101,6 +101,15 @@ ConfigValue getConfigValue(String name, ConfigSourceInterceptorContext context) String parentKey = MicroProfileConfigProvider.NS_KEYCLOAK + "." + mapFrom; ConfigValue parentValue = context.proceed(parentKey); + if (parentValue == null) { + // parent value not explicitly set, try to resolve the default value set to the parent property + PropertyMapper parentMapper = PropertyMappers.getMapper(parentKey); + + if (parentMapper != null) { + parentValue = ConfigValue.builder().withValue(parentMapper.getDefaultValue()).build(); + } + } + if (parentValue != null) { ConfigValue value = transformValue(parentValue.getValue(), context); diff --git a/quarkus/runtime/src/main/resources/META-INF/keycloak.properties b/quarkus/runtime/src/main/resources/META-INF/keycloak.properties index c7d137e04eb8..6bebacccf686 100644 --- a/quarkus/runtime/src/main/resources/META-INF/keycloak.properties +++ b/quarkus/runtime/src/main/resources/META-INF/keycloak.properties @@ -28,7 +28,7 @@ metrics.enabled=false %dev.http.enabled=true %dev.hostname.strict=false %dev.hostname.strict-https=false -%dev.cluster=local +%dev.cache=local %dev.spi.theme.cache-themes=false %dev.spi.theme.cache-templates=false %dev.spi.theme.static-max-age=-1 diff --git a/quarkus/runtime/src/main/resources/cluster-default.xml b/quarkus/runtime/src/main/resources/cache-ispn.xml similarity index 100% rename from quarkus/runtime/src/main/resources/cluster-default.xml rename to quarkus/runtime/src/main/resources/cache-ispn.xml diff --git a/quarkus/runtime/src/main/resources/cluster-local.xml b/quarkus/runtime/src/main/resources/cache-local.xml similarity index 100% rename from quarkus/runtime/src/main/resources/cluster-local.xml rename to quarkus/runtime/src/main/resources/cache-local.xml diff --git a/quarkus/runtime/src/test/java/org/keycloak/provider/quarkus/ConfigurationTest.java b/quarkus/runtime/src/test/java/org/keycloak/provider/quarkus/ConfigurationTest.java index fa8b8ea3c665..eb28a890b66c 100644 --- a/quarkus/runtime/src/test/java/org/keycloak/provider/quarkus/ConfigurationTest.java +++ b/quarkus/runtime/src/test/java/org/keycloak/provider/quarkus/ConfigurationTest.java @@ -313,17 +313,17 @@ public void testNestedDatabaseProperties() { @Test public void testClusterConfig() { // Cluster enabled by default, but disabled for the "dev" profile - Assert.assertEquals("cluster-default.xml", initConfig("connectionsInfinispan", "quarkus").get("configFile")); + Assert.assertEquals("cache-ispn.xml", initConfig("connectionsInfinispan", "quarkus").get("configFile")); // If explicitly set, then it is always used regardless of the profile System.clearProperty(Environment.PROFILE); - System.setProperty(CLI_ARGS, "--cluster=foo"); + System.setProperty(CLI_ARGS, "--cache=cluster-foo.xml"); Assert.assertEquals("cluster-foo.xml", initConfig("connectionsInfinispan", "quarkus").get("configFile")); System.setProperty(Environment.PROFILE, "dev"); Assert.assertEquals("cluster-foo.xml", initConfig("connectionsInfinispan", "quarkus").get("configFile")); - System.setProperty(CLI_ARGS, "--cluster-stack=foo"); + System.setProperty(CLI_ARGS, "--cache-stack=foo"); Assert.assertEquals("foo", initConfig("connectionsInfinispan", "quarkus").get("stack")); } diff --git a/quarkus/tests/integration/src/test/java/org/keycloak/it/cli/dist/BuildAndStartDistTest.java b/quarkus/tests/integration/src/test/java/org/keycloak/it/cli/dist/BuildAndStartDistTest.java index ab9ce018816f..2dc2cbcc7d95 100644 --- a/quarkus/tests/integration/src/test/java/org/keycloak/it/cli/dist/BuildAndStartDistTest.java +++ b/quarkus/tests/integration/src/test/java/org/keycloak/it/cli/dist/BuildAndStartDistTest.java @@ -39,7 +39,7 @@ public class BuildAndStartDistTest { @Test - @Launch({ "build", "--http-enabled=true", "--hostname-strict=false", "--cluster=local" }) + @Launch({ "build", "--http-enabled=true", "--hostname-strict=false", "--cache=local" }) @Order(1) void firstYouBuild(LaunchResult result) { } diff --git a/quarkus/tests/integration/src/test/java/org/keycloak/it/cli/dist/ClusterConfigDistTest.java b/quarkus/tests/integration/src/test/java/org/keycloak/it/cli/dist/ClusterConfigDistTest.java index e8d61bdd8555..ea735030a19e 100644 --- a/quarkus/tests/integration/src/test/java/org/keycloak/it/cli/dist/ClusterConfigDistTest.java +++ b/quarkus/tests/integration/src/test/java/org/keycloak/it/cli/dist/ClusterConfigDistTest.java @@ -18,10 +18,12 @@ package org.keycloak.it.cli.dist; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; import org.junit.jupiter.api.Test; import org.keycloak.it.cli.StartCommandTest; +import org.keycloak.it.junit5.extension.CLIResult; import org.keycloak.it.junit5.extension.DistributionTest; import io.quarkus.test.junit.main.Launch; @@ -31,26 +33,54 @@ public class ClusterConfigDistTest { @Test - @Launch({ "start-dev", "--cluster=default" }) + @Launch({ "start-dev", "--cache=ispn" }) void changeClusterSetting(LaunchResult result) { - assertTrue(result.getOutput().contains("Received new cluster view")); + assertTrue(isClustered(result)); } @Test - @Launch({ "start-dev", "--cluster=invalid" }) + @Launch({ "build", "--cache-config-file=invalid" }) void failInvalidClusterConfig(LaunchResult result) { assertTrue(result.getErrorOutput().contains("ERROR: Could not load cluster configuration file")); } @Test - @Launch({ "start-dev", "--cluster=default", "--cluster-stack=kubernetes" }) + @Launch({ "start-dev", "--cache=ispn", "--cache-stack=kubernetes" }) void failMisConfiguredClusterStack(LaunchResult result) { assertTrue(result.getOutput().contains("ERROR: dns_query can not be null or empty")); } @Test - @Launch({ "start-dev", "--cluster-stack=invalid" }) + @Launch({ "build", "--cache-stack=invalid" }) void failInvalidClusterStack(LaunchResult result) { - assertTrue(result.getErrorOutput().contains("Invalid value for option '--cluster-stack': invalid. Expected values are: tcp, udp, kubernetes, ec2, azure, google")); + assertTrue(result.getErrorOutput().contains("Invalid value for option '--cache-stack': invalid. Expected values are: tcp, udp, kubernetes, ec2, azure, google")); + } + + @Test + @Launch({ "start-dev", "--cache-config-file=cache-ispn.xml" }) + void testExplicitCacheConfigFile(LaunchResult result) { + CLIResult cliResult = (CLIResult) result; + cliResult.assertStartedDevMode(); + assertTrue(isClustered(cliResult)); + } + + @Test + @Launch({ "start", "--http-enabled=true", "--hostname-strict false" }) + void testStartDefaultsToClustering(LaunchResult result) { + CLIResult cliResult = (CLIResult) result; + cliResult.assertStarted(); + assertTrue(isClustered(result)); + } + + @Test + @Launch({ "start-dev" }) + void testStartDevDefaultsToLocalCaches(LaunchResult result) { + CLIResult cliResult = (CLIResult) result; + cliResult.assertStartedDevMode(); + assertFalse(isClustered(result)); + } + + private boolean isClustered(LaunchResult result) { + return result.getOutput().contains("Received new cluster view"); } } diff --git a/testsuite/integration-arquillian/servers/auth-server/quarkus/assembly.xml b/testsuite/integration-arquillian/servers/auth-server/quarkus/assembly.xml index 338c6be7238a..6f05cdf95681 100644 --- a/testsuite/integration-arquillian/servers/auth-server/quarkus/assembly.xml +++ b/testsuite/integration-arquillian/servers/auth-server/quarkus/assembly.xml @@ -44,9 +44,9 @@ - src/main/content/conf/cluster-${auth.server.quarkus.cluster.config}.xml + src/main/content/conf/cluster-ha.xml /auth-server-quarkus/conf - cluster-${auth.server.quarkus.cluster.config}.xml + cluster-ha.xml true diff --git a/testsuite/integration-arquillian/servers/auth-server/quarkus/src/main/content/conf/cluster-local.xml b/testsuite/integration-arquillian/servers/auth-server/quarkus/src/main/content/conf/cluster-local.xml deleted file mode 100644 index acbceb0dbdee..000000000000 --- a/testsuite/integration-arquillian/servers/auth-server/quarkus/src/main/content/conf/cluster-local.xml +++ /dev/null @@ -1,74 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/arquillian/containers/KeycloakQuarkusServerDeployableContainer.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/arquillian/containers/KeycloakQuarkusServerDeployableContainer.java index 08e5f41a18c2..3374f9c047e9 100644 --- a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/arquillian/containers/KeycloakQuarkusServerDeployableContainer.java +++ b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/arquillian/containers/KeycloakQuarkusServerDeployableContainer.java @@ -172,7 +172,13 @@ private String[] getProcessCommands() { commands.add("-Djboss.node.name=" + configuration.getRoute()); } - commands.add("--cluster=" + System.getProperty("auth.server.quarkus.cluster.config", "local")); + String cacheMode = System.getProperty("auth.server.quarkus.cluster.config", "local"); + + if ("local".equals(cacheMode)) { + commands.add("--cache=local"); + } else { + commands.add("--cache-config-file=cluster-" + cacheMode + ".xml"); + } commands.addAll(getAdditionalBuildArgs()); return commands.toArray(new String[0]); From fc237a8b63b3432dd60f2004ee31de351d769545 Mon Sep 17 00:00:00 2001 From: Michal Hajas Date: Wed, 8 Dec 2021 12:56:55 +0100 Subject: [PATCH 019/144] Introduce ancestor interface for entities with attributes --- ...enerateEntityImplementationsProcessor.java | 21 ++++++++++--- .../hotRod/common/HotRodTypesUtils.java | 4 +++ .../adapter/MapResourceAdapter.java | 3 +- .../entity/MapResourceEntity.java | 19 ++++++++---- .../models/map/client/MapClientEntity.java | 9 ++---- .../map/clientscope/MapClientScopeEntity.java | 14 ++++++++- .../map/common/EntityWithAttributes.java | 30 +++++++++++++++++++ .../models/map/group/MapGroupEntity.java | 11 ++----- .../models/map/realm/MapRealmEntity.java | 14 ++++++++- .../models/map/role/MapRoleEntity.java | 9 ++---- .../models/map/user/MapUserEntity.java | 10 +++++-- 11 files changed, 105 insertions(+), 39 deletions(-) create mode 100644 model/map/src/main/java/org/keycloak/models/map/common/EntityWithAttributes.java diff --git a/model/build-processor/src/main/java/org/keycloak/models/map/processor/AbstractGenerateEntityImplementationsProcessor.java b/model/build-processor/src/main/java/org/keycloak/models/map/processor/AbstractGenerateEntityImplementationsProcessor.java index 1dca4f21de75..3e5fa013eeb6 100644 --- a/model/build-processor/src/main/java/org/keycloak/models/map/processor/AbstractGenerateEntityImplementationsProcessor.java +++ b/model/build-processor/src/main/java/org/keycloak/models/map/processor/AbstractGenerateEntityImplementationsProcessor.java @@ -47,6 +47,7 @@ import static org.keycloak.models.map.processor.FieldAccessorType.GETTER; import static org.keycloak.models.map.processor.Util.getGenericsDeclaration; +import static org.keycloak.models.map.processor.Util.isMapType; public abstract class AbstractGenerateEntityImplementationsProcessor extends AbstractProcessor { @@ -157,7 +158,7 @@ protected boolean isImmutableFinalType(TypeMirror fieldType) { protected boolean isKnownCollectionOfImmutableFinalTypes(TypeMirror fieldType) { List res = getGenericsDeclaration(fieldType); - return isCollection(fieldType) && res.stream().allMatch(tm -> isImmutableFinalType(tm) || isKnownCollectionOfImmutableFinalTypes(tm)); + return isCollection(fieldType) && res.stream().allMatch(this::isImmutableFinalType); } protected boolean isCollection(TypeMirror fieldType) { @@ -175,12 +176,24 @@ protected boolean isCollection(TypeMirror fieldType) { } protected String deepClone(TypeMirror fieldType, String parameterName) { + TypeElement typeElement = elements.getTypeElement(types.erasure(fieldType).toString()); if (isKnownCollectionOfImmutableFinalTypes(fieldType)) { - TypeElement typeElement = elements.getTypeElement(types.erasure(fieldType).toString()); return parameterName + " == null ? null : " + interfaceToImplementation(typeElement, parameterName); - } else { - return "deepClone(" + parameterName + ")"; + } else if (isMapType(typeElement)) { + List mapTypes = getGenericsDeclaration(fieldType); + boolean isKeyImmutable = isImmutableFinalType(mapTypes.get(0)); + boolean isValueImmutable = isImmutableFinalType(mapTypes.get(1)); + + return parameterName + " == null ? null : " + parameterName + ".entrySet().stream().collect(" + + "java.util.stream.Collectors.toMap(" + + (isKeyImmutable ? "java.util.Map.Entry::getKey" : "entry -> " + deepClone(mapTypes.get(0), "entry.getKey()")) + + ", " + + (isValueImmutable ? "java.util.Map.Entry::getValue" : "entry -> " + deepClone(mapTypes.get(1), "entry.getValue()")) + + ", (o1, o2) -> o1" + + ", java.util.HashMap::new" + + "))"; } + return "deepClone(" + parameterName + ")"; } protected boolean isPrimitiveType(TypeMirror fieldType) { diff --git a/model/map-hot-rod/src/main/java/org/keycloak/models/map/storage/hotRod/common/HotRodTypesUtils.java b/model/map-hot-rod/src/main/java/org/keycloak/models/map/storage/hotRod/common/HotRodTypesUtils.java index 0ff392761f83..0a852eaa0bc0 100644 --- a/model/map-hot-rod/src/main/java/org/keycloak/models/map/storage/hotRod/common/HotRodTypesUtils.java +++ b/model/map-hot-rod/src/main/java/org/keycloak/models/map/storage/hotRod/common/HotRodTypesUtils.java @@ -44,6 +44,10 @@ public static HotRodPair createHotRodPairFromMapEntry(Map.Entry(entry.getKey(), entry.getValue()); } + public static HotRodAttributeEntity createHotRodAttributeEntityFromMapEntry(Map.Entry> entry) { + return new HotRodAttributeEntity(entry.getKey(), entry.getValue()); + } + public static boolean removeFromSetByMapKey(Set set, KeyType key, Function keyGetter) { if (set == null || set.isEmpty()) { return false; } return set.stream() diff --git a/model/map/src/main/java/org/keycloak/models/map/authorization/adapter/MapResourceAdapter.java b/model/map/src/main/java/org/keycloak/models/map/authorization/adapter/MapResourceAdapter.java index f40cb36e7f91..73e2c3eed892 100644 --- a/model/map/src/main/java/org/keycloak/models/map/authorization/adapter/MapResourceAdapter.java +++ b/model/map/src/main/java/org/keycloak/models/map/authorization/adapter/MapResourceAdapter.java @@ -136,7 +136,8 @@ public Map> getAttributes() { @Override public String getSingleAttribute(String name) { - return entity.getSingleAttribute(name); + List attributeValues = entity.getAttribute(name); + return attributeValues == null || attributeValues.isEmpty() ? null : attributeValues.get(0); } @Override diff --git a/model/map/src/main/java/org/keycloak/models/map/authorization/entity/MapResourceEntity.java b/model/map/src/main/java/org/keycloak/models/map/authorization/entity/MapResourceEntity.java index 2be129707e04..6c9d449dd317 100644 --- a/model/map/src/main/java/org/keycloak/models/map/authorization/entity/MapResourceEntity.java +++ b/model/map/src/main/java/org/keycloak/models/map/authorization/entity/MapResourceEntity.java @@ -19,6 +19,7 @@ import org.keycloak.models.map.common.AbstractEntity; +import org.keycloak.models.map.common.EntityWithAttributes; import org.keycloak.models.map.common.UpdatableEntity; import java.util.HashMap; import java.util.HashSet; @@ -27,7 +28,7 @@ import java.util.Objects; import java.util.Set; -public class MapResourceEntity extends UpdatableEntity.Impl implements AbstractEntity { +public class MapResourceEntity extends UpdatableEntity.Impl implements AbstractEntity, EntityWithAttributes { private String id; private String name; @@ -156,23 +157,29 @@ public Set getPolicyIds() { return policyIds; } + @Override public Map> getAttributes() { return attributes; } - public List getAttribute(String name) { - return attributes.get(name); + @Override + public void setAttributes(Map> attributes) { + this.updated |= ! Objects.equals(this.attributes, attributes); + this.attributes.clear(); + this.attributes.putAll(attributes); } - public String getSingleAttribute(String name) { - List attributeValues = attributes.get(name); - return attributeValues == null || attributeValues.isEmpty() ? null : attributeValues.get(0); + @Override + public List getAttribute(String name) { + return attributes.get(name); } + @Override public void setAttribute(String name, List value) { this.updated |= !Objects.equals(this.attributes.put(name, value), value); } + @Override public void removeAttribute(String name) { this.updated |= this.attributes.remove(name) != null; } diff --git a/model/map/src/main/java/org/keycloak/models/map/client/MapClientEntity.java b/model/map/src/main/java/org/keycloak/models/map/client/MapClientEntity.java index d87243d280e0..87f7a512250d 100644 --- a/model/map/src/main/java/org/keycloak/models/map/client/MapClientEntity.java +++ b/model/map/src/main/java/org/keycloak/models/map/client/MapClientEntity.java @@ -17,9 +17,9 @@ package org.keycloak.models.map.client; import org.keycloak.models.map.common.AbstractEntity; +import org.keycloak.models.map.common.EntityWithAttributes; import org.keycloak.models.map.common.UpdatableEntity; import java.util.Collection; -import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Objects; @@ -36,7 +36,7 @@ inherits = "org.keycloak.models.map.client.MapClientEntity.AbstractClientEntity" ) @DeepCloner.Root -public interface MapClientEntity extends AbstractEntity, UpdatableEntity { +public interface MapClientEntity extends AbstractEntity, UpdatableEntity, EntityWithAttributes { public abstract class AbstractClientEntity extends UpdatableEntity.Impl implements MapClientEntity { @@ -87,11 +87,6 @@ public Stream getClientScopes(boolean defaultScope) { void removeWebOrigin(String webOrigin); void setWebOrigins(Set webOrigins); - default List getAttribute(String name) { return getAttributes() == null ? null : getAttributes().get(name); } - Map> getAttributes(); - void removeAttribute(String name); - void setAttribute(String name, List values); - String getAuthenticationFlowBindingOverride(String binding); Map getAuthenticationFlowBindingOverrides(); void removeAuthenticationFlowBindingOverride(String binding); diff --git a/model/map/src/main/java/org/keycloak/models/map/clientscope/MapClientScopeEntity.java b/model/map/src/main/java/org/keycloak/models/map/clientscope/MapClientScopeEntity.java index dd2d800512ae..2b818f7acb23 100644 --- a/model/map/src/main/java/org/keycloak/models/map/clientscope/MapClientScopeEntity.java +++ b/model/map/src/main/java/org/keycloak/models/map/clientscope/MapClientScopeEntity.java @@ -29,9 +29,10 @@ import java.util.stream.Stream; import org.keycloak.models.ProtocolMapperModel; import org.keycloak.models.map.common.AbstractEntity; +import org.keycloak.models.map.common.EntityWithAttributes; import org.keycloak.models.map.common.UpdatableEntity; -public class MapClientScopeEntity extends UpdatableEntity.Impl implements AbstractEntity { +public class MapClientScopeEntity extends UpdatableEntity.Impl implements AbstractEntity, EntityWithAttributes { private String id; private String realmId; @@ -94,10 +95,19 @@ public void setProtocol(String protocol) { this.protocol = protocol; } + @Override public Map> getAttributes() { return attributes; } + @Override + public void setAttributes(Map> attributes) { + this.attributes.clear(); + this.attributes.putAll(attributes); + this.updated = true; + } + + @Override public void setAttribute(String name, List values) { this.updated |= ! Objects.equals(this.attributes.put(name, values), values); } @@ -132,10 +142,12 @@ public ProtocolMapperModel getProtocolMapperById(String id) { return id == null ? null : protocolMappers.get(id); } + @Override public void removeAttribute(String name) { this.updated |= this.attributes.remove(name) != null; } + @Override public List getAttribute(String name) { return attributes.getOrDefault(name, Collections.EMPTY_LIST); } diff --git a/model/map/src/main/java/org/keycloak/models/map/common/EntityWithAttributes.java b/model/map/src/main/java/org/keycloak/models/map/common/EntityWithAttributes.java new file mode 100644 index 000000000000..f549ba1d41ed --- /dev/null +++ b/model/map/src/main/java/org/keycloak/models/map/common/EntityWithAttributes.java @@ -0,0 +1,30 @@ +/* + * Copyright 2021 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.models.map.common; + +import java.util.List; +import java.util.Map; + +public interface EntityWithAttributes { + Map> getAttributes(); + void setAttributes(Map> attributes); + List getAttribute(String name); + void setAttribute(String name, List value); + void removeAttribute(String name); + +} diff --git a/model/map/src/main/java/org/keycloak/models/map/group/MapGroupEntity.java b/model/map/src/main/java/org/keycloak/models/map/group/MapGroupEntity.java index 5f57c12727af..15a649dc52bf 100644 --- a/model/map/src/main/java/org/keycloak/models/map/group/MapGroupEntity.java +++ b/model/map/src/main/java/org/keycloak/models/map/group/MapGroupEntity.java @@ -20,17 +20,16 @@ import org.keycloak.models.map.annotations.GenerateEntityImplementations; import org.keycloak.models.map.common.AbstractEntity; import org.keycloak.models.map.common.DeepCloner; +import org.keycloak.models.map.common.EntityWithAttributes; import org.keycloak.models.map.common.UpdatableEntity; -import java.util.List; -import java.util.Map; import java.util.Set; @GenerateEntityImplementations( inherits = "org.keycloak.models.map.group.MapGroupEntity.AbstractGroupEntity" ) @DeepCloner.Root -public interface MapGroupEntity extends UpdatableEntity, AbstractEntity { +public interface MapGroupEntity extends UpdatableEntity, AbstractEntity, EntityWithAttributes { public abstract class AbstractGroupEntity extends UpdatableEntity.Impl implements MapGroupEntity { @@ -50,12 +49,6 @@ public void setId(String id) { } - Map> getAttributes(); - void setAttributes(Map> attributes); - List getAttribute(String name); - void setAttribute(String name, List value); - void removeAttribute(String name); - String getName(); void setName(String name); diff --git a/model/map/src/main/java/org/keycloak/models/map/realm/MapRealmEntity.java b/model/map/src/main/java/org/keycloak/models/map/realm/MapRealmEntity.java index 5755f699de67..bfa4066d1b9d 100644 --- a/model/map/src/main/java/org/keycloak/models/map/realm/MapRealmEntity.java +++ b/model/map/src/main/java/org/keycloak/models/map/realm/MapRealmEntity.java @@ -30,6 +30,7 @@ import org.keycloak.models.ModelDuplicateException; import org.keycloak.models.OTPPolicy; import org.keycloak.models.map.common.AbstractEntity; +import org.keycloak.models.map.common.EntityWithAttributes; import org.keycloak.models.map.common.UpdatableEntity; import org.keycloak.models.map.realm.entity.MapAuthenticationExecutionEntity; import org.keycloak.models.map.realm.entity.MapAuthenticationFlowEntity; @@ -43,7 +44,7 @@ import org.keycloak.models.map.realm.entity.MapRequiredCredentialEntity; import org.keycloak.models.map.realm.entity.MapWebAuthnPolicyEntity; -public class MapRealmEntity extends UpdatableEntity.Impl implements AbstractEntity { +public class MapRealmEntity extends UpdatableEntity.Impl implements AbstractEntity, EntityWithAttributes { private String id; private String name; @@ -669,22 +670,33 @@ public void setWebAuthnPolicyPasswordless(MapWebAuthnPolicyEntity webAuthnPolicy this.webAuthnPolicyPasswordless = webAuthnPolicyPasswordless; } + @Override public void setAttribute(String name, List values) { this.updated |= ! Objects.equals(this.attributes.put(name, values), values); } + @Override public void removeAttribute(String name) { this.updated |= attributes.remove(name) != null; } + @Override public List getAttribute(String name) { return attributes.getOrDefault(name, Collections.EMPTY_LIST); } + @Override public Map> getAttributes() { return attributes; } + @Override + public void setAttributes(Map> attributes) { + this.attributes.clear(); + this.attributes.putAll(attributes); + this.updated = true; + } + public void addDefaultClientScope(String scopeId) { this.updated |= this.defaultClientScopes.add(scopeId); } diff --git a/model/map/src/main/java/org/keycloak/models/map/role/MapRoleEntity.java b/model/map/src/main/java/org/keycloak/models/map/role/MapRoleEntity.java index ba5f7be4be16..5c96aafce6bc 100644 --- a/model/map/src/main/java/org/keycloak/models/map/role/MapRoleEntity.java +++ b/model/map/src/main/java/org/keycloak/models/map/role/MapRoleEntity.java @@ -22,13 +22,14 @@ import org.keycloak.models.map.annotations.GenerateEntityImplementations; import org.keycloak.models.map.common.AbstractEntity; import org.keycloak.models.map.common.DeepCloner; +import org.keycloak.models.map.common.EntityWithAttributes; import org.keycloak.models.map.common.UpdatableEntity; @GenerateEntityImplementations( inherits = "org.keycloak.models.map.role.MapRoleEntity.AbstractRoleEntity" ) @DeepCloner.Root -public interface MapRoleEntity extends AbstractEntity, UpdatableEntity { +public interface MapRoleEntity extends AbstractEntity, UpdatableEntity, EntityWithAttributes { public abstract class AbstractRoleEntity extends UpdatableEntity.Impl implements MapRoleEntity { @@ -76,10 +77,4 @@ default Boolean isComposite() { void setCompositeRoles(Set compositeRoles); void addCompositeRole(String roleId); void removeCompositeRole(String roleId); - - Map> getAttributes(); - void setAttributes(Map> attributes); - void setAttribute(String name, List values); - void removeAttribute(String name); - } diff --git a/model/map/src/main/java/org/keycloak/models/map/user/MapUserEntity.java b/model/map/src/main/java/org/keycloak/models/map/user/MapUserEntity.java index 2eab99a6aa1c..3d8227354581 100644 --- a/model/map/src/main/java/org/keycloak/models/map/user/MapUserEntity.java +++ b/model/map/src/main/java/org/keycloak/models/map/user/MapUserEntity.java @@ -19,12 +19,12 @@ import org.keycloak.models.ModelDuplicateException; import org.keycloak.models.map.common.AbstractEntity; +import org.keycloak.models.map.common.EntityWithAttributes; import org.keycloak.models.map.common.UpdatableEntity; import org.keycloak.models.utils.KeycloakModelUtils; import java.util.Collection; import java.util.Collections; -import java.util.Comparator; import java.util.HashMap; import java.util.HashSet; import java.util.LinkedList; @@ -40,7 +40,7 @@ * * @author mhajas */ -public class MapUserEntity extends UpdatableEntity.Impl implements AbstractEntity { +public class MapUserEntity extends UpdatableEntity.Impl implements AbstractEntity, EntityWithAttributes { private String id; private String realmId; @@ -183,19 +183,23 @@ public Map> getAttributes() { return attributes; } + @Override public List getAttribute(String name) { return attributes.getOrDefault(name, Collections.emptyList()); } + @Override public void setAttributes(Map> attributes) { this.updated |= !Objects.equals(this.attributes, attributes); this.attributes = attributes; } + @Override public void setAttribute(String name, List value) { this.updated |= !Objects.equals(this.attributes.put(name, value), value); } - + + @Override public void removeAttribute(String name) { this.updated |= this.attributes.remove(name) != null; } From c7134fd5390d7c650b3dfd4bd2a2855157042271 Mon Sep 17 00:00:00 2001 From: stianst Date: Thu, 18 Nov 2021 13:40:02 +0100 Subject: [PATCH 020/144] Initial toolchain for Quarkus distribution documentation Closes #9054 --- docs/guides/pom.xml | 88 ++++++++++++++++++ docs/guides/src/main/server/all-config.adoc | 27 ++++++ docs/guides/src/main/server/db.adoc | 23 +++++ docs/guides/src/main/server/index.adoc | 5 + docs/guides/src/main/templates/guide.adoc | 30 ++++++ docs/guides/src/main/templates/kc.adoc | 13 +++ docs/guides/src/main/templates/options.adoc | 5 + .../keycloak/guides/DocsBuildDebugUtil.java | 22 +++++ docs/maven-plugin/pom.xml | 86 ++++++++++++++++++ .../org/keycloak/guides/maven/Context.java | 29 ++++++ .../org/keycloak/guides/maven/FreeMarker.java | 44 +++++++++ .../keycloak/guides/maven/GuideBuilder.java | 43 +++++++++ .../org/keycloak/guides/maven/GuideMojo.java | 43 +++++++++ .../org/keycloak/guides/maven/Options.java | 91 +++++++++++++++++++ docs/pom.xml | 36 ++++++++ pom.xml | 2 + quarkus/pom.xml | 3 - 17 files changed, 587 insertions(+), 3 deletions(-) create mode 100644 docs/guides/pom.xml create mode 100644 docs/guides/src/main/server/all-config.adoc create mode 100644 docs/guides/src/main/server/db.adoc create mode 100644 docs/guides/src/main/server/index.adoc create mode 100644 docs/guides/src/main/templates/guide.adoc create mode 100644 docs/guides/src/main/templates/kc.adoc create mode 100644 docs/guides/src/main/templates/options.adoc create mode 100644 docs/guides/src/test/java/org/keycloak/guides/DocsBuildDebugUtil.java create mode 100644 docs/maven-plugin/pom.xml create mode 100644 docs/maven-plugin/src/main/java/org/keycloak/guides/maven/Context.java create mode 100644 docs/maven-plugin/src/main/java/org/keycloak/guides/maven/FreeMarker.java create mode 100644 docs/maven-plugin/src/main/java/org/keycloak/guides/maven/GuideBuilder.java create mode 100644 docs/maven-plugin/src/main/java/org/keycloak/guides/maven/GuideMojo.java create mode 100644 docs/maven-plugin/src/main/java/org/keycloak/guides/maven/Options.java create mode 100755 docs/pom.xml diff --git a/docs/guides/pom.xml b/docs/guides/pom.xml new file mode 100644 index 000000000000..2e43edfcee79 --- /dev/null +++ b/docs/guides/pom.xml @@ -0,0 +1,88 @@ + + + + keycloak-docs-parent + org.keycloak + 16.0.0-SNAPSHOT + ../pom.xml + + + 4.0.0 + + Keycloak Guides + keycloak-guides + Keycloak Guides + + + + org.keycloak + keycloak-guides-maven-plugin + 16.0.0-SNAPSHOT + + + + + + + org.keycloak + keycloak-guides-maven-plugin + + + generate-asciidoc + + keycloak-guide + + + + + + org.asciidoctor + asciidoctor-maven-plugin + 1.5.5 + + + asciidoc-to-html + generate-resources + + process-asciidoc + + + ${basedir}/target/generated-guides/server + index.adoc + html5 + coderay + + + ./ + left + left + font + true + + - + true + + + + + + + + + \ No newline at end of file diff --git a/docs/guides/src/main/server/all-config.adoc b/docs/guides/src/main/server/all-config.adoc new file mode 100644 index 000000000000..3420f9fa588a --- /dev/null +++ b/docs/guides/src/main/server/all-config.adoc @@ -0,0 +1,27 @@ +<#import "/templates/guide.adoc" as template> + +<@template.guide +title="All configuration" +summary="All the configuration you will ever need and want"> + +<#list ctx.options.categories as category> +<#assign categoryOptions=ctx.options.getValues(category)> +<#if categoryOptions?has_content> +== ${category.heading} +|=== +|Key|CLI|ENV|Description|Default|Values +<#list categoryOptions as option> +|${option.key} +|${option.keyCli} +|${option.keyEnv} +|${option.description} +|${option.defaultValue!} +|${option.expectedValues?join(", ")} +<#if option?has_next> + + + +|=== + + + diff --git a/docs/guides/src/main/server/db.adoc b/docs/guides/src/main/server/db.adoc new file mode 100644 index 000000000000..ccc60bfa0342 --- /dev/null +++ b/docs/guides/src/main/server/db.adoc @@ -0,0 +1,23 @@ +<#import "/templates/guide.adoc" as tmpl> +<#import "/templates/kc.adoc" as kc> +<#import "/templates/options.adoc" as opts> + +<@tmpl.guide + title="Relational database setup" + summary="Understand how to configure different relational databases for Keycloak" + includedOptions="db db.* hostname"> + +First step is to decide which database vendor you are going to use. Keycloak has support for a number of different vendors. + +Selecting the database vendor is done at build-time rather than at runtime. To select the database vendor run: + +<@kc.build parameters="--db "/> + +Valid options for database vendors include: + +<@opts.expectedValues option="db"/> + +Once configured you can easily connect to the database with: + +<@kc.start parameters="--db-host --db-schema --db-user --db-password "/> + \ No newline at end of file diff --git a/docs/guides/src/main/server/index.adoc b/docs/guides/src/main/server/index.adoc new file mode 100644 index 000000000000..cf8571a86150 --- /dev/null +++ b/docs/guides/src/main/server/index.adoc @@ -0,0 +1,5 @@ += Keycloak server guide + +<#list ctx.serverGuides as guide> +include::${guide}[leveloffset=+1] + diff --git a/docs/guides/src/main/templates/guide.adoc b/docs/guides/src/main/templates/guide.adoc new file mode 100644 index 000000000000..ced8c9e9aa9e --- /dev/null +++ b/docs/guides/src/main/templates/guide.adoc @@ -0,0 +1,30 @@ +<#macro guide title summary includedOptions=""> +:title: ${title} +:summary: ${summary} + +[[${ctx.getAnchor(title)}]] += {title} + +{summary} + +<#nested> + +<#if includedOptions?has_content> +== Relevant options + +|=== +|Key|CLI|ENV|Description|Default|Values +<#list ctx.options.getOptions(includedOptions) as option> +|${option.key} +|${option.keyCli} +|${option.keyEnv} +|${option.description} +|${option.defaultValue!} +|${option.expectedValues?join(", ")} +<#if option?has_next> + + + +|=== + + \ No newline at end of file diff --git a/docs/guides/src/main/templates/kc.adoc b/docs/guides/src/main/templates/kc.adoc new file mode 100644 index 000000000000..7b905d2d9282 --- /dev/null +++ b/docs/guides/src/main/templates/kc.adoc @@ -0,0 +1,13 @@ +<#macro build parameters> +[source,bash] +---- +bin/kc.[sh|bat] build ${parameters} +---- + + +<#macro start parameters> +[source,bash] +---- +bin/kc.[sh|bat] start ${parameters} +---- + \ No newline at end of file diff --git a/docs/guides/src/main/templates/options.adoc b/docs/guides/src/main/templates/options.adoc new file mode 100644 index 000000000000..e14a8f6eb8ba --- /dev/null +++ b/docs/guides/src/main/templates/options.adoc @@ -0,0 +1,5 @@ +<#macro expectedValues option> +<#list ctx.options.getOption(option).expectedValues as expectedValue> +* ${expectedValue} + + \ No newline at end of file diff --git a/docs/guides/src/test/java/org/keycloak/guides/DocsBuildDebugUtil.java b/docs/guides/src/test/java/org/keycloak/guides/DocsBuildDebugUtil.java new file mode 100644 index 000000000000..108c2a2c6521 --- /dev/null +++ b/docs/guides/src/test/java/org/keycloak/guides/DocsBuildDebugUtil.java @@ -0,0 +1,22 @@ +package org.keycloak.guides; + +import freemarker.template.TemplateException; +import org.keycloak.guides.maven.GuideBuilder; + +import java.io.File; +import java.io.IOException; + +public class DocsBuildDebugUtil { + + public static void main(String[] args) throws IOException, TemplateException { + String userDir = System.getProperty("user.dir"); + File usrDir = new File(System.getProperty("user.dir")); + File srcDir = usrDir.toPath().resolve("docs/guides/src/main").toFile(); + File targetDir = usrDir.toPath().resolve("target/generated-guides-tests").toFile(); + targetDir.mkdirs(); + GuideBuilder builder = new GuideBuilder(srcDir, targetDir, null); + builder.server(); + System.out.println("Guides generated to: " + targetDir.getAbsolutePath().toString()); + } + +} diff --git a/docs/maven-plugin/pom.xml b/docs/maven-plugin/pom.xml new file mode 100644 index 000000000000..1cc8897bbd70 --- /dev/null +++ b/docs/maven-plugin/pom.xml @@ -0,0 +1,86 @@ + + + + + keycloak-docs-parent + org.keycloak + 16.0.0-SNAPSHOT + ../pom.xml + + + 4.0.0 + + Keycloak Guides Maven Plugin + keycloak-guides-maven-plugin + Keycloak Guides Maven Plugin + maven-plugin + + + + + io.quarkus + quarkus-bom + ${quarkus.version} + pom + import + + + + + + org.apache.maven + maven-plugin-api + 3.6.3 + + + org.apache.maven.plugin-tools + maven-plugin-annotations + 3.6.0 + provided + + + org.apache.maven + maven-project + 2.2.1 + + + org.keycloak + keycloak-quarkus-server + + + * + * + + + + + io.quarkus + quarkus-core + + + io.smallrye.config + smallrye-config + + + org.freemarker + freemarker + + + + \ No newline at end of file diff --git a/docs/maven-plugin/src/main/java/org/keycloak/guides/maven/Context.java b/docs/maven-plugin/src/main/java/org/keycloak/guides/maven/Context.java new file mode 100644 index 000000000000..9b3b248ca35c --- /dev/null +++ b/docs/maven-plugin/src/main/java/org/keycloak/guides/maven/Context.java @@ -0,0 +1,29 @@ +package org.keycloak.guides.maven; + +import java.io.File; + +public class Context { + + private File srcDir; + private Options options; + private String[] serverGuides; + + public Context(File srcDir) { + this.srcDir = srcDir; + this.options = new Options(); + this.serverGuides = new File(srcDir, "server").list((dir, f) -> f.endsWith(".adoc") && !f.equals("index.adoc")); + } + + public String getAnchor(String title) { + return title.toLowerCase().replace(' ', '_'); + } + + public Options getOptions() { + return options; + } + + public String[] getServerGuides() { + return new File(srcDir, "server").list((dir, f) -> f.endsWith(".adoc") && !f.equals("index.adoc")); + } + +} diff --git a/docs/maven-plugin/src/main/java/org/keycloak/guides/maven/FreeMarker.java b/docs/maven-plugin/src/main/java/org/keycloak/guides/maven/FreeMarker.java new file mode 100644 index 000000000000..25e127a2988c --- /dev/null +++ b/docs/maven-plugin/src/main/java/org/keycloak/guides/maven/FreeMarker.java @@ -0,0 +1,44 @@ +package org.keycloak.guides.maven; + +import freemarker.template.Configuration; +import freemarker.template.Template; +import freemarker.template.TemplateException; +import freemarker.template.TemplateExceptionHandler; + +import java.io.File; +import java.io.FileWriter; +import java.io.IOException; +import java.io.Writer; +import java.util.Map; + +public class FreeMarker { + + private File targetDir; + private Map attributes; + private Configuration configuration; + + public FreeMarker(File srcDir, File targetDir, Map attributes) throws IOException { + this.targetDir = targetDir; + this.attributes = attributes; + + configuration = new Configuration(Configuration.VERSION_2_3_31); + configuration.setDirectoryForTemplateLoading(srcDir); + configuration.setDefaultEncoding("UTF-8"); + configuration.setTemplateExceptionHandler(TemplateExceptionHandler.RETHROW_HANDLER); + configuration.setLogTemplateExceptions(false); + } + + public void template(String template) throws IOException, TemplateException { + Template t = configuration.getTemplate(template); + File out = targetDir.toPath().resolve(template).toFile(); + + File parent = out.getParentFile(); + if (!parent.isDirectory()) { + parent.mkdir(); + } + + Writer w = new FileWriter(out); + t.process(attributes, w); + } + +} diff --git a/docs/maven-plugin/src/main/java/org/keycloak/guides/maven/GuideBuilder.java b/docs/maven-plugin/src/main/java/org/keycloak/guides/maven/GuideBuilder.java new file mode 100644 index 000000000000..3f11967dbf0e --- /dev/null +++ b/docs/maven-plugin/src/main/java/org/keycloak/guides/maven/GuideBuilder.java @@ -0,0 +1,43 @@ +package org.keycloak.guides.maven; + +import freemarker.template.TemplateException; +import org.apache.maven.plugin.logging.Log; + +import java.io.File; +import java.io.IOException; +import java.util.HashMap; +import java.util.Map; + +public class GuideBuilder { + + private final FreeMarker freeMarker; + private final File srcDir; + private final File targetDir; + private final Log log; + + public GuideBuilder(File srcDir, File targetDir, Log log) throws IOException { + this.srcDir = srcDir; + this.targetDir = targetDir; + this.log = log; + + Map globalAttributes = new HashMap<>(); + globalAttributes.put("ctx", new Context(srcDir)); + + this.freeMarker = new FreeMarker(srcDir, targetDir, globalAttributes); + } + + public void server() throws TemplateException, IOException { + File serverGuidesDir = new File(srcDir, "server"); + if (!serverGuidesDir.isDirectory()) { + serverGuidesDir.mkdir(); + } + + for (String t : serverGuidesDir.list((dir, name) -> name.endsWith(".adoc"))) { + freeMarker.template("server/" + t); + if (log != null) { + log.info("Templated: server/" + t); + } + } + } + +} diff --git a/docs/maven-plugin/src/main/java/org/keycloak/guides/maven/GuideMojo.java b/docs/maven-plugin/src/main/java/org/keycloak/guides/maven/GuideMojo.java new file mode 100644 index 000000000000..649fd6effccd --- /dev/null +++ b/docs/maven-plugin/src/main/java/org/keycloak/guides/maven/GuideMojo.java @@ -0,0 +1,43 @@ +package org.keycloak.guides.maven; + +import org.apache.maven.plugin.AbstractMojo; +import org.apache.maven.plugin.MojoExecutionException; +import org.apache.maven.plugin.MojoFailureException; +import org.apache.maven.plugin.logging.Log; +import org.apache.maven.plugins.annotations.LifecyclePhase; +import org.apache.maven.plugins.annotations.Mojo; +import org.apache.maven.plugins.annotations.Parameter; + +import java.io.File; + +@Mojo(name = "keycloak-guide", defaultPhase = LifecyclePhase.GENERATE_SOURCES) +public class GuideMojo extends AbstractMojo { + + @Parameter(property = "project.build.sourceDirectory") + private String sourceDir; + + @Parameter(property = "project.build.directory") + private String targetDir; + + @Override + public void execute() throws MojoFailureException { + try { + Log log = getLog(); + File srcDir = new File(sourceDir).getParentFile(); + File targetDir = new File(this.targetDir, "generated-guides"); + if (!targetDir.isDirectory()) { + targetDir.mkdirs(); + } + + log.info("Guide dir: " + srcDir.getAbsolutePath()); + log.info("Target dir: " + targetDir.getAbsolutePath()); + + GuideBuilder g = new GuideBuilder(srcDir, targetDir, log); + g.server(); + } catch (Exception e) { + e.printStackTrace(); + throw new MojoFailureException("Failed to generated asciidoc files", e); + } + } + +} diff --git a/docs/maven-plugin/src/main/java/org/keycloak/guides/maven/Options.java b/docs/maven-plugin/src/main/java/org/keycloak/guides/maven/Options.java new file mode 100644 index 000000000000..34d3524530ef --- /dev/null +++ b/docs/maven-plugin/src/main/java/org/keycloak/guides/maven/Options.java @@ -0,0 +1,91 @@ +package org.keycloak.guides.maven; + +import org.keycloak.quarkus.runtime.configuration.mappers.ConfigCategory; +import org.keycloak.quarkus.runtime.configuration.mappers.PropertyMappers; + +import java.util.Collection; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; +import java.util.stream.StreamSupport; + +public class Options { + + private final Map options; + + public Options() { + options = PropertyMappers.getMappers().stream() + .filter(m -> !m.isHidden()) + .map(m -> new Option(m.getFrom(), m.getCategory(), m.isBuildTime(), m.getDescription(), m.getDefaultValue(), m.getExpectedValues())) + .collect(Collectors.toMap(Option::getKey, o -> o, (o1, o2) -> o1)); // Need to ignore duplicate keys?? + } + + public ConfigCategory[] getCategories() { + return ConfigCategory.values(); + } + + public Collection