From 9e57f836f2393cf01a885e96936b475384ff8e77 Mon Sep 17 00:00:00 2001 From: Dominik Guhr Date: Fri, 25 Mar 2022 12:33:51 +0100 Subject: [PATCH] Make quarkus runtime properties also available as runtime properties in keycloak Changes behaviour to: - all raw quarkus config properties are handled as runtime config in keycloak, with the exception of raw properties we need for additional datasources, there we check for build- vs runtime - unknown quarkus buildtime properties require a build first or the usual quarkus warning is shown - wrapped quarkus properties still get ignored / overwritten by our configuration layer (no change in behaviour here) Closes #10968 --- .../guides/src/main/server/configuration.adoc | 9 +- .../keycloak/quarkus/runtime/cli/Picocli.java | 63 ++++++++++- .../PropertyMappingInterceptor.java | 20 ---- .../QuarkusPropertiesConfigSource.java | 21 ++-- .../mappers/PropertyMappers.java | 8 +- .../test/ConfigRegExPatternMatchingTest.java | 24 +++++ .../it/junit5/extension/CLIResult.java | 4 + .../it/junit5/extension/CLITestExtension.java | 12 +++ .../it/junit5/extension/KeepServerAlive.java | 37 +++++++ .../it/utils/KeycloakDistribution.java | 4 + .../it/utils/RawKeycloakDistribution.java | 5 + .../QuarkusPropertiesAutoBuildDistTest.java | 100 ++++++++++++++++-- .../cli/dist/QuarkusPropertiesDistTest.java | 75 +++++++++++-- 13 files changed, 326 insertions(+), 56 deletions(-) create mode 100644 quarkus/runtime/src/test/java/org/keycloak/quarkus/runtime/configuration/test/ConfigRegExPatternMatchingTest.java create mode 100644 quarkus/tests/integration/src/main/java/org/keycloak/it/junit5/extension/KeepServerAlive.java diff --git a/docs/guides/src/main/server/configuration.adoc b/docs/guides/src/main/server/configuration.adoc index 49275a91b1..0a4bbf0976 100644 --- a/docs/guides/src/main/server/configuration.adoc +++ b/docs/guides/src/main/server/configuration.adoc @@ -165,16 +165,19 @@ Once the first user with administrative rights exists, you can use the UI or the == Unsupported server options In most cases, the available options from the server configuration should suffice to configure the server. -However, you might need to use properties directly from Quarkus to enable a specific behavior or capability that is missing from the server configuration. +However, you might need to use properties directly from Quarkus to enable a specific behavior or capability that is missing in the keycloak configuration. As much as possible, avoid using properties directly from Quarkus. If your need is essential, consider opening an https://github.com/keycloak/keycloak/issues/new?assignees=&labels=kind%2Fenhancement%2Cstatus%2Ftriage&template=enhancement.yml[issue] first and help us to improve the server configuration. -To configure the server using Quarkus properties, perform the following steps: +If that's not possible for you, you can configure the server using Quarkus properties, perform the following steps: . Create a `conf/quarkus.properties` file and define any property you need. -. Run the `build` command to apply the settings to the server For a complete list of Quarkus properties, see the https://quarkus.io/guides/all-config[Quarkus documentation] . +When a raw quarkus property is a runtime property, it is also handled as runtime property for keycloak. When a quarkus property is a build time property, you have to invoke a new keycloak build first for the property to apply. + +Note that some quarkus properties are mapped to internally by the Keycloak configuration, for example `quarkus.http.port` and similar properties that are needed to configure Keycloak. If the property is used by Keycloak, and you define the same property key in the quarkus.properties file, the keycloak configuration value takes precedence over the raw quarkus configuration value, so the value you set in `quarkus.properties` will be ignored when there is a value in the actual Keycloak configuration. + 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 a0a193976f..e3182d860f 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 @@ -34,6 +34,7 @@ import static picocli.CommandLine.Model.UsageMessageSpec.SECTION_KEY_COMMAND_LIS import java.io.File; import java.io.PrintWriter; import java.util.ArrayList; +import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; @@ -42,8 +43,10 @@ import java.util.Set; import java.util.function.BiConsumer; import java.util.function.Predicate; import java.util.function.UnaryOperator; +import java.util.regex.Pattern; import java.util.stream.Collectors; +import org.eclipse.microprofile.config.spi.ConfigSource; import org.keycloak.quarkus.runtime.cli.command.Build; import org.keycloak.quarkus.runtime.cli.command.ImportRealmMixin; import org.keycloak.quarkus.runtime.cli.command.Main; @@ -51,6 +54,7 @@ import org.keycloak.quarkus.runtime.cli.command.Start; import org.keycloak.quarkus.runtime.cli.command.StartDev; import org.keycloak.quarkus.runtime.configuration.ConfigArgsConfigSource; import org.keycloak.quarkus.runtime.configuration.PersistedConfigSource; +import org.keycloak.quarkus.runtime.configuration.QuarkusPropertiesConfigSource; import org.keycloak.quarkus.runtime.configuration.mappers.ConfigCategory; import org.keycloak.quarkus.runtime.configuration.mappers.PropertyMappers; import org.keycloak.quarkus.runtime.configuration.mappers.PropertyMapper; @@ -65,9 +69,9 @@ import picocli.CommandLine.Model.ArgGroupSpec; public final class Picocli { public static final String ARG_PREFIX = "--"; - private static final String ARG_KEY_VALUE_SEPARATOR = "="; public static final String ARG_SHORT_PREFIX = "-"; public static final String NO_PARAM_LABEL = "none"; + private static final String ARG_KEY_VALUE_SEPARATOR = "="; private Picocli() { } @@ -94,6 +98,7 @@ public final class Picocli { private static int runReAugmentationIfNeeded(List cliArgs, CommandLine cmd) { int exitCode = 0; + if (hasAutoBuildOption(cliArgs) && !isHelpCommand(cliArgs)) { if (cliArgs.contains(StartDev.NAME)) { String profile = Environment.getProfile(); @@ -269,9 +274,65 @@ public final class Picocli { } } + //check for defined quarkus raw build properties for UserStorageProvider extensions + if (QuarkusPropertiesConfigSource.getConfigurationFile() != null) { + Optional quarkusPropertiesConfigSource = getConfig().getConfigSource(QuarkusPropertiesConfigSource.NAME); + + if (quarkusPropertiesConfigSource.isPresent()) { + Map foundQuarkusBuildProperties = findSupportedRawQuarkusBuildProperties(quarkusPropertiesConfigSource.get().getProperties().entrySet()); + + //only check if buildProps are found in quarkus properties file. + if (!foundQuarkusBuildProperties.isEmpty()) { + Optional persistedConfigSource = getConfig().getConfigSource(PersistedConfigSource.NAME); + + if(persistedConfigSource.isPresent()) { + for(String key : foundQuarkusBuildProperties.keySet()) { + if (notContainsKey(persistedConfigSource.get(), key)) { + //if persisted cs does not contain raw quarkus key from quarkus.properties, assume build is needed as the key is new. + return true; + } + } + + //if it contains the key, check if the value actually changed from the persisted one. + return hasAtLeastOneChangedBuildProperty(foundQuarkusBuildProperties, persistedConfigSource.get().getProperties().entrySet()); + } + } + } + } + return false; } + private static boolean hasAtLeastOneChangedBuildProperty(Map foundQuarkusBuildProperties, Set> persistedEntries) { + for(Map.Entry persistedEntry : persistedEntries) { + if (foundQuarkusBuildProperties.containsKey(persistedEntry.getKey())) { + return isChangedValue(foundQuarkusBuildProperties, persistedEntry); + } + } + + return false; + } + + private static boolean notContainsKey(ConfigSource persistedConfigSource, String key) { + return !persistedConfigSource.getProperties().containsKey(key); + } + + private static Map findSupportedRawQuarkusBuildProperties(Set> entries) { + Pattern buildTimePattern = Pattern.compile(QuarkusPropertiesConfigSource.QUARKUS_DATASOURCE_BUILDTIME_REGEX); + Map result = new HashMap<>(); + + for(Map.Entry entry : entries) { + if (buildTimePattern.matcher(entry.getKey()).matches()) { + result.put(entry.getKey(), entry.getValue()); + } + } + return result; + } + + private static boolean isChangedValue(Map foundQuarkusBuildProps, Map.Entry persistedEntry) { + return !foundQuarkusBuildProps.get(persistedEntry.getKey()).equals(persistedEntry.getValue()); + } + private static boolean isProviderKey(String key) { return key.startsWith("kc.provider.file"); } diff --git a/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/configuration/PropertyMappingInterceptor.java b/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/configuration/PropertyMappingInterceptor.java index b7203a129f..1c6d1cc47a 100644 --- a/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/configuration/PropertyMappingInterceptor.java +++ b/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/configuration/PropertyMappingInterceptor.java @@ -49,11 +49,6 @@ public class PropertyMappingInterceptor implements ConfigSourceInterceptor { return null; } - if (isPersistedOnlyProperty(value)) { - // quarkus properties values always resolved from persisted config source - return value.withValue(PersistedConfigSource.getInstance().getValue(name)); - } - if (value.getValue().indexOf("${") == -1) { return value; } @@ -70,19 +65,4 @@ public class PropertyMappingInterceptor implements ConfigSourceInterceptor { return prop.getValue(); })); } - - private boolean isPersistedOnlyProperty(ConfigValue value) { - if (isQuarkusPropertiesEnabled && value.getName().startsWith(NS_QUARKUS)) { - String configSourceName = value.getConfigSourceName(); - - return Environment.isRuntimeMode() - && configSourceName != null - && !configSourceName.equals(PersistedConfigSource.NAME) - && !configSourceName.equals(AbstractRawDefaultConfigSource.NAME) - && !configSourceName.contains("Runtime Defaults") - && !configSourceName.contains("application.properties"); - } - - return false; - } } diff --git a/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/configuration/QuarkusPropertiesConfigSource.java b/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/configuration/QuarkusPropertiesConfigSource.java index 3e26a9707e..6e0660516c 100644 --- a/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/configuration/QuarkusPropertiesConfigSource.java +++ b/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/configuration/QuarkusPropertiesConfigSource.java @@ -45,9 +45,12 @@ import io.smallrye.config.common.utils.ConfigSourceUtil; */ public final class QuarkusPropertiesConfigSource extends AbstractLocationConfigSourceLoader implements ConfigSourceProvider { - private static final String NAME = "QuarkusProperties"; private static final String FILE_NAME = "quarkus.properties"; public static final String QUARKUS_PROPERTY_ENABLED = "kc.quarkus-properties-enabled"; + public static final String NAME = "QuarkusProperties"; + + //for auto-build working with multiple datasources + public static final String QUARKUS_DATASOURCE_BUILDTIME_REGEX = "^quarkus\\.datasource\\.[A-Za-z0-9\\-_]+\\.(db-kind|jdbc\\.driver|jdbc\\.transactions|jdbc\\.enable-metrics)$"; public static boolean isSameSource(ConfigValue value) { if (value == null) { @@ -91,13 +94,7 @@ public final class QuarkusPropertiesConfigSource extends AbstractLocationConfigS @Override public String getValue(String propertyName) { if (propertyName.startsWith(NS_QUARKUS)) { - String value = super.getValue(propertyName); - - if (value == null) { - return PersistedConfigSource.getInstance().getValue(propertyName); - } - - return value; + return super.getValue(propertyName); } return null; @@ -111,12 +108,10 @@ public final class QuarkusPropertiesConfigSource extends AbstractLocationConfigS configSources.addAll(loadConfigSources("META-INF/services/" + FILE_NAME, 450, classLoader)); - if (Environment.isRebuild() || Environment.isRebuildCheck()) { - Path configFile = getConfigurationFile(); + Path configFile = getConfigurationFile(); - if (configFile != null) { - configSources.addAll(loadConfigSources(configFile.toUri().toString(), 500, classLoader)); - } + if (configFile != null) { + configSources.addAll(loadConfigSources(configFile.toUri().toString(), 500, classLoader)); } return configSources; diff --git a/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/configuration/mappers/PropertyMappers.java b/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/configuration/mappers/PropertyMappers.java index c0e32b4aa4..127f98d73a 100644 --- a/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/configuration/mappers/PropertyMappers.java +++ b/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/configuration/mappers/PropertyMappers.java @@ -1,5 +1,7 @@ package org.keycloak.quarkus.runtime.configuration.mappers; +import io.quarkus.runtime.QuarkusApplication; +import io.quarkus.runtime.configuration.ConfigurationRuntimeConfig; import io.smallrye.config.ConfigSourceInterceptorContext; import io.smallrye.config.ConfigValue; import org.keycloak.quarkus.runtime.Environment; @@ -53,14 +55,10 @@ public final class PropertyMappers { } public static boolean isBuildTimeProperty(String name) { - if (isFeaturesBuildTimeProperty(name) || isSpiBuildTimeProperty(name) || name.startsWith(MicroProfileConfigProvider.NS_QUARKUS_PREFIX)) { + if (isFeaturesBuildTimeProperty(name) || isSpiBuildTimeProperty(name)) { return true; } - if (!name.startsWith(MicroProfileConfigProvider.NS_KEYCLOAK_PREFIX)) { - return false; - } - boolean isBuildTimeProperty = MAPPERS.entrySet().stream() .anyMatch(new Predicate>() { @Override diff --git a/quarkus/runtime/src/test/java/org/keycloak/quarkus/runtime/configuration/test/ConfigRegExPatternMatchingTest.java b/quarkus/runtime/src/test/java/org/keycloak/quarkus/runtime/configuration/test/ConfigRegExPatternMatchingTest.java new file mode 100644 index 0000000000..0b8d287888 --- /dev/null +++ b/quarkus/runtime/src/test/java/org/keycloak/quarkus/runtime/configuration/test/ConfigRegExPatternMatchingTest.java @@ -0,0 +1,24 @@ +package org.keycloak.quarkus.runtime.configuration.test; + + +import org.junit.Test; +import org.keycloak.quarkus.runtime.configuration.QuarkusPropertiesConfigSource; + +import java.util.regex.Pattern; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +public class ConfigRegExPatternMatchingTest { + + @Test + public void quarkusPropertyMultipleDatasourcePatternTest(){ + Pattern p = Pattern.compile(QuarkusPropertiesConfigSource.QUARKUS_DATASOURCE_BUILDTIME_REGEX); + assertTrue(p.matcher("quarkus.datasource.user.jdbc.transactions").matches()); + assertTrue(p.matcher("quarkus.datasource.user-store.jdbc.enable-metrics").matches()); + assertTrue(p.matcher("quarkus.datasource.user12_store.db-kind").matches()); + assertTrue(p.matcher("quarkus.datasource.user12_-__--store.db-kind").matches()); + assertFalse(p.matcher("quarkus.datasource.user-store.db-username").matches()); + assertFalse(p.matcher("quarkus.datasource.user-store.db-kin").matches()); + } +} diff --git a/quarkus/tests/integration/src/main/java/org/keycloak/it/junit5/extension/CLIResult.java b/quarkus/tests/integration/src/main/java/org/keycloak/it/junit5/extension/CLIResult.java index f655699fbd..1714a02868 100644 --- a/quarkus/tests/integration/src/main/java/org/keycloak/it/junit5/extension/CLIResult.java +++ b/quarkus/tests/integration/src/main/java/org/keycloak/it/junit5/extension/CLIResult.java @@ -93,6 +93,10 @@ public interface CLIResult extends LaunchResult { assertFalse(getOutput().contains("Server configuration updated and persisted")); } + default void assertBuildRuntimeMismatchWarning(String quarkusBuildtimePropKey) { + assertTrue(getOutput().contains(" - " + quarkusBuildtimePropKey + " is set to 'false' but it is build time fixed to 'true'. Did you change the property " + quarkusBuildtimePropKey + " after building the application?")); + } + default boolean isClustered() { return getOutput().contains("Starting JGroups channel `ISPN`"); } diff --git a/quarkus/tests/integration/src/main/java/org/keycloak/it/junit5/extension/CLITestExtension.java b/quarkus/tests/integration/src/main/java/org/keycloak/it/junit5/extension/CLITestExtension.java index 2b36d4a02f..29606c9205 100644 --- a/quarkus/tests/integration/src/main/java/org/keycloak/it/junit5/extension/CLITestExtension.java +++ b/quarkus/tests/integration/src/main/java/org/keycloak/it/junit5/extension/CLITestExtension.java @@ -82,6 +82,8 @@ public class CLITestExtension extends QuarkusMainTestExtension { } if (distConfig != null) { + onKeepServerAlive(context.getRequiredTestMethod().getAnnotation(KeepServerAlive.class)); + if (launch != null) { if (dist == null) { dist = createDistribution(distConfig); @@ -109,6 +111,16 @@ public class CLITestExtension extends QuarkusMainTestExtension { } } + private void onKeepServerAlive(KeepServerAlive annotation) { + if(annotation != null && dist != null) { + try { + dist.setManualStop(true); + } catch (Exception cause) { + throw new RuntimeException("Error when invoking " + annotation, cause); + } + } + } + @Override public void afterEach(ExtensionContext context) throws Exception { DistributionTest distConfig = getDistributionConfig(context); diff --git a/quarkus/tests/integration/src/main/java/org/keycloak/it/junit5/extension/KeepServerAlive.java b/quarkus/tests/integration/src/main/java/org/keycloak/it/junit5/extension/KeepServerAlive.java new file mode 100644 index 0000000000..c7da32d714 --- /dev/null +++ b/quarkus/tests/integration/src/main/java/org/keycloak/it/junit5/extension/KeepServerAlive.java @@ -0,0 +1,37 @@ +/* + * 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.junit5.extension; + +import org.keycloak.it.utils.KeycloakDistribution; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +import java.util.function.Consumer; + +/** + * {@link KeepServerAlive} is used in a distributiontest to keep the server alive on test / method level. + * Used when when {@link DistributionTest} is invoked on class level not using keepAlive=true param, but + * for a specific test we need to run e.g. RestAssured verifications. + */ +@Target({ ElementType.METHOD }) +@Retention(RetentionPolicy.RUNTIME) +public @interface KeepServerAlive { + +} diff --git a/quarkus/tests/integration/src/main/java/org/keycloak/it/utils/KeycloakDistribution.java b/quarkus/tests/integration/src/main/java/org/keycloak/it/utils/KeycloakDistribution.java index 8a2223d1dc..e5aad15022 100644 --- a/quarkus/tests/integration/src/main/java/org/keycloak/it/utils/KeycloakDistribution.java +++ b/quarkus/tests/integration/src/main/java/org/keycloak/it/utils/KeycloakDistribution.java @@ -40,6 +40,10 @@ public interface KeycloakDistribution { return commands.toArray(new String[0]); } + default void setManualStop(boolean manualStop) { + throw new RuntimeException("Not implemented"); + } + default void setQuarkusProperty(String key, String value) { throw new RuntimeException("Not implemented"); } diff --git a/quarkus/tests/integration/src/main/java/org/keycloak/it/utils/RawKeycloakDistribution.java b/quarkus/tests/integration/src/main/java/org/keycloak/it/utils/RawKeycloakDistribution.java index ab3e58f772..805d0d96cf 100644 --- a/quarkus/tests/integration/src/main/java/org/keycloak/it/utils/RawKeycloakDistribution.java +++ b/quarkus/tests/integration/src/main/java/org/keycloak/it/utils/RawKeycloakDistribution.java @@ -322,6 +322,11 @@ public final class RawKeycloakDistribution implements KeycloakDistribution { keycloak = builder.start(); } + @Override + public void setManualStop(boolean manualStop) { + this.manualStop = manualStop; + } + @Override public void setProperty(String key, String value) { setProperty(key, value, distPath.resolve("conf").resolve("keycloak.conf").toFile()); diff --git a/quarkus/tests/integration/src/test/java/org/keycloak/it/cli/dist/QuarkusPropertiesAutoBuildDistTest.java b/quarkus/tests/integration/src/test/java/org/keycloak/it/cli/dist/QuarkusPropertiesAutoBuildDistTest.java index bc66b86a47..61a621e9df 100644 --- a/quarkus/tests/integration/src/test/java/org/keycloak/it/cli/dist/QuarkusPropertiesAutoBuildDistTest.java +++ b/quarkus/tests/integration/src/test/java/org/keycloak/it/cli/dist/QuarkusPropertiesAutoBuildDistTest.java @@ -17,6 +17,7 @@ package org.keycloak.it.cli.dist; +import static io.restassured.RestAssured.when; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; @@ -35,7 +36,6 @@ import io.quarkus.test.junit.main.Launch; import io.quarkus.test.junit.main.LaunchResult; @DistributionTest(reInstall = DistributionTest.ReInstall.NEVER) -@BeforeStartDistribution(QuarkusPropertiesAutoBuildDistTest.UpdateConsoleLogLevelToWarn.class) @RawDistOnly(reason = "Containers are immutable") @TestMethodOrder(OrderAnnotation.class) public class QuarkusPropertiesAutoBuildDistTest { @@ -43,16 +43,16 @@ public class QuarkusPropertiesAutoBuildDistTest { @Test @Launch({ "start", "--auto-build", "--http-enabled=true", "--hostname-strict=false", "--cache=local" }) @Order(1) - void testReAugOnFirstRun(LaunchResult result) { + void reAugOnFirstRun(LaunchResult result) { CLIResult cliResult = (CLIResult) result; cliResult.assertBuild(); - assertFalse(cliResult.getOutput().contains("INFO [io.quarkus]")); } @Test + @BeforeStartDistribution(QuarkusPropertiesAutoBuildDistTest.UpdateConsoleLogLevelToWarn.class) @Launch({ "start", "--auto-build", "--http-enabled=true", "--hostname-strict=false", "--cache=local" }) @Order(2) - void testSecondStartDoNotTriggerReAug(LaunchResult result) { + void testQuarkusRuntimePropDoesNotTriggerReAug(LaunchResult result) { CLIResult cliResult = (CLIResult) result; cliResult.assertNoBuild(); assertFalse(cliResult.getOutput().contains("INFO [io.quarkus]")); @@ -62,14 +62,60 @@ public class QuarkusPropertiesAutoBuildDistTest { @BeforeStartDistribution(UpdateConsoleLogLevelToInfo.class) @Launch({ "start", "--auto-build", "--http-enabled=true", "--hostname-strict=false", "--cache=local" }) @Order(3) - void testReAugAfterChangingProperty(LaunchResult result) { + void testNoReAugAfterChangingRuntimeProperty(LaunchResult result) { CLIResult cliResult = (CLIResult) result; - cliResult.assertBuild(); + cliResult.assertNoBuild(); assertTrue(cliResult.getOutput().contains("INFO [io.quarkus]")); } - public static class UpdateConsoleLogLevelToWarn implements Consumer { + @Test + @BeforeStartDistribution(AddAdditionalDatasource.class) + @Launch({ "start", "--auto-build", "--http-enabled=true", "--hostname-strict=false", "--cache=local" }) + @Order(4) + void testReAugForAdditionalDatasource(LaunchResult result) { + CLIResult cliResult = (CLIResult) result; + cliResult.assertBuild(); + } + @Test + @BeforeStartDistribution(ChangeAdditionalDatasourceUsername.class) + @Launch({ "start", "--auto-build", "--http-enabled=true", "--hostname-strict=false", "--cache=local" }) + @Order(5) + void testNoReAugForAdditionalDatasourceRuntimeProperty(LaunchResult result) { + CLIResult cliResult = (CLIResult) result; + cliResult.assertNoBuild(); + } + + @Test + @BeforeStartDistribution(ChangeAdditionalDatasourceDbKind.class) + @Launch({ "start", "--auto-build", "--http-enabled=true", "--hostname-strict=false", "--cache=local" }) + @Order(6) + void testNoReAugWhenBuildTimePropertiesAreTheSame(LaunchResult result) { + CLIResult cliResult = (CLIResult) result; + cliResult.assertNoBuild(); + } + + @Test + @BeforeStartDistribution(AddAdditionalDatasource2.class) + @Launch({ "start", "--auto-build", "--http-enabled=true", "--hostname-strict=false", "--cache=local" }) + @Order(7) + void testReAugWhenAnotherDatasourceAdded(LaunchResult result) { + CLIResult cliResult = (CLIResult) result; + cliResult.assertBuild(); + } + + @Test + @BeforeStartDistribution(EnableDatasourceMetrics.class) + @Launch({ "start", "--auto-build", "--http-enabled=true", "--hostname-strict=false", "--cache=local" }) + @Order(8) + void testWrappedBuildPropertyTriggersBuildButGetsIgnoredWhenSetByQuarkus(LaunchResult result) { + CLIResult cliResult = (CLIResult) result; + cliResult.assertBuild(); + when().get("/metrics").then() + .statusCode(404); + } + + public static class UpdateConsoleLogLevelToWarn implements Consumer { @Override public void accept(KeycloakDistribution distribution) { distribution.setQuarkusProperty("quarkus.log.console.level", "WARN"); @@ -84,4 +130,44 @@ public class QuarkusPropertiesAutoBuildDistTest { } } + public static class AddAdditionalDatasource implements Consumer { + @Override + public void accept(KeycloakDistribution distribution) { + distribution.setQuarkusProperty("quarkus.datasource.user-store.db-kind", "h2"); + distribution.setQuarkusProperty("quarkus.datasource.user-store.username","sa"); + distribution.setQuarkusProperty("quarkus.datasource.user-store.jdbc.url","jdbc:h2:mem:user-store;DB_CLOSE_DELAY=-1"); + } + } + + public static class AddAdditionalDatasource2 implements Consumer { + @Override + public void accept(KeycloakDistribution distribution) { + distribution.setQuarkusProperty("quarkus.datasource.user-store2.db-kind", "h2"); + distribution.setQuarkusProperty("quarkus.datasource.user-store2.db-transactions", "enabled"); + distribution.setQuarkusProperty("quarkus.datasource.user-store2.username","sa"); + distribution.setQuarkusProperty("quarkus.datasource.user-store2.jdbc.url","jdbc:h2:mem:user-store2;DB_CLOSE_DELAY=-1"); + } + } + + public static class ChangeAdditionalDatasourceUsername implements Consumer { + @Override + public void accept(KeycloakDistribution distribution) { + distribution.setQuarkusProperty("quarkus.datasource.user-store.username","foo"); + } + } + + public static class ChangeAdditionalDatasourceDbKind implements Consumer { + @Override + public void accept(KeycloakDistribution distribution) { + distribution.setQuarkusProperty("quarkus.datasource.user-store.db-kind","h2"); + } + } + + public static class EnableDatasourceMetrics implements Consumer { + @Override + public void accept(KeycloakDistribution distribution) { + distribution.setManualStop(true); + distribution.setQuarkusProperty("quarkus.datasource.metrics.enabled","true"); + } + } } \ No newline at end of file diff --git a/quarkus/tests/integration/src/test/java/org/keycloak/it/cli/dist/QuarkusPropertiesDistTest.java b/quarkus/tests/integration/src/test/java/org/keycloak/it/cli/dist/QuarkusPropertiesDistTest.java index b24f2e7e6d..b7478584db 100644 --- a/quarkus/tests/integration/src/test/java/org/keycloak/it/cli/dist/QuarkusPropertiesDistTest.java +++ b/quarkus/tests/integration/src/test/java/org/keycloak/it/cli/dist/QuarkusPropertiesDistTest.java @@ -17,6 +17,8 @@ package org.keycloak.it.cli.dist; +import static io.restassured.RestAssured.when; +import static org.hamcrest.Matchers.containsString; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; @@ -28,6 +30,7 @@ import org.junit.jupiter.api.TestMethodOrder; import org.keycloak.it.junit5.extension.BeforeStartDistribution; import org.keycloak.it.junit5.extension.CLIResult; import org.keycloak.it.junit5.extension.DistributionTest; +import org.keycloak.it.junit5.extension.KeepServerAlive; import org.keycloak.it.junit5.extension.RawDistOnly; import org.keycloak.it.utils.KeycloakDistribution; @@ -35,21 +38,23 @@ import io.quarkus.test.junit.main.Launch; import io.quarkus.test.junit.main.LaunchResult; @DistributionTest(reInstall = DistributionTest.ReInstall.NEVER) -@BeforeStartDistribution(QuarkusPropertiesDistTest.UpdateConsoleLogLevelToWarn.class) @RawDistOnly(reason = "Containers are immutable") @TestMethodOrder(OrderAnnotation.class) public class QuarkusPropertiesDistTest { + private static final String QUARKUS_BUILDTIME_HIBERNATE_METRICS_KEY = "quarkus.hibernate-orm.metrics.enabled"; + private static final String QUARKUS_RUNTIME_CONSOLE_LOGLVL_KEY = "quarkus.log.console.level"; + @Test @Launch({ "build", "--cache=local" }) @Order(1) void testBuildWithPropertyFromQuarkusProperties(LaunchResult result) { CLIResult cliResult = (CLIResult) result; - assertFalse(cliResult.getOutput().contains("INFO")); cliResult.assertBuild(); } @Test + @BeforeStartDistribution(QuarkusPropertiesDistTest.UpdateConsoleLogLevelToWarnFromQuarkusProps.class) @Launch({ "start", "--http-enabled=true", "--hostname-strict=false" }) @Order(2) void testPropertyEnabledAtRuntime(LaunchResult result) { @@ -75,7 +80,7 @@ public class QuarkusPropertiesDistTest { } @Test - @BeforeStartDistribution(UpdateConsoleLogLevelToInfo.class) + @BeforeStartDistribution(UpdateConsoleLogLevelToInfoFromKeycloakConf.class) @Launch({ "build" }) @Order(5) void testIgnoreQuarkusPropertyFromKeycloakConf(LaunchResult result) { @@ -84,20 +89,76 @@ public class QuarkusPropertiesDistTest { cliResult.assertBuild(); } - public static class UpdateConsoleLogLevelToWarn implements Consumer { + @Test + @BeforeStartDistribution(UpdateConsoleLogLevelToInfoFromQuarkusProps.class) + @Launch({ "start", "--http-enabled=true", "--hostname-strict=false" }) + @Order(6) + void testRuntimePropFromQuarkusPropsIsAppliedWithoutRebuild(LaunchResult result) { + CLIResult cliResult = (CLIResult) result; + assertTrue(cliResult.getOutput().contains("INFO")); + cliResult.assertNoBuild(); + } + @Test + @BeforeStartDistribution(UpdateHibernateMetricsFromQuarkusProps.class) + @Launch({ "start", "--http-enabled=true", "--hostname-strict=false" }) + @Order(7) + void testBuildRunTimeMismatchOnQuarkusBuildPropWarning(LaunchResult result) { + CLIResult cliResult = (CLIResult) result; + cliResult.assertBuildRuntimeMismatchWarning(QUARKUS_BUILDTIME_HIBERNATE_METRICS_KEY); + } + + @Test + @BeforeStartDistribution(UpdateHibernateMetricsFromQuarkusProps.class) + @Launch({ "build", "--metrics-enabled=true" }) + @Order(8) + void buildFirstWithUnknownQuarkusBuildProperty(LaunchResult result) { + CLIResult cliResult = (CLIResult) result; + cliResult.assertBuild(); + } + + @Test + @KeepServerAlive + @Launch({ "start", "--http-enabled=true", "--hostname-strict=false" }) + @Order(9) + void testUnknownQuarkusBuildTimePropertyApplied(LaunchResult result) { + CLIResult cliResult = (CLIResult) result; + cliResult.assertNoBuild(); + when().get("/metrics").then().statusCode(200) + .body(containsString("vendor_hibernate_cache_query_plan_total")); + } + + public static class UpdateConsoleLogLevelToWarnFromQuarkusProps implements Consumer { @Override public void accept(KeycloakDistribution distribution) { - distribution.setQuarkusProperty("quarkus.log.console.level", "WARN"); + distribution.setQuarkusProperty(QUARKUS_RUNTIME_CONSOLE_LOGLVL_KEY, "WARN"); } } - public static class UpdateConsoleLogLevelToInfo implements Consumer { + public static class UpdateConsoleLogLevelToInfoFromKeycloakConf implements Consumer { @Override public void accept(KeycloakDistribution distribution) { distribution.deleteQuarkusProperties(); - distribution.setProperty("quarkus.log.console.level", "INFO"); + distribution.setProperty(QUARKUS_RUNTIME_CONSOLE_LOGLVL_KEY, "INFO"); + } + } + + public static class UpdateConsoleLogLevelToInfoFromQuarkusProps implements Consumer { + + @Override + public void accept(KeycloakDistribution distribution) { + distribution.deleteQuarkusProperties(); + distribution.setQuarkusProperty(QUARKUS_RUNTIME_CONSOLE_LOGLVL_KEY, "INFO"); + } + } + + public static class UpdateHibernateMetricsFromQuarkusProps implements Consumer { + + @Override + public void accept(KeycloakDistribution distribution) { + distribution.deleteQuarkusProperties(); + distribution.setQuarkusProperty(QUARKUS_BUILDTIME_HIBERNATE_METRICS_KEY, "true"); } } } \ No newline at end of file