diff --git a/distribution/server-x/src/main/content/conf/keycloak.properties b/distribution/server-x/src/main/content/conf/keycloak.properties index 0db4526f15..4551da2232 100644 --- a/distribution/server-x/src/main/content/conf/keycloak.properties +++ b/distribution/server-x/src/main/content/conf/keycloak.properties @@ -1,7 +1,7 @@ -# Default, and insecure, and non-production grade HTTP configuration -%dev.http.enabled=true - -# Default Non-Production Grade Datasource +# Default and non-production grade database vendor db=h2-file -db.username = sa -db.password = keycloak \ No newline at end of file + +# Default, and insecure, and non-production grade configuration for the development profile +%dev.http.enabled=true +%dev.db.username = sa +%dev.db.password = keycloak \ No newline at end of file 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 d1fff7e3f6..f99a457f0e 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 @@ -151,7 +151,7 @@ class KeycloakProcessor { } } - recorder.setBuildTimeProperties(properties, Environment.isRebuild()); + recorder.setBuildTimeProperties(properties, Environment.isRebuild(), KeycloakRecorder.getConfig().getRawValue("kc.config.args")); recorder.showConfig(); } diff --git a/quarkus/runtime/src/main/java/org/keycloak/cli/KeycloakMain.java b/quarkus/runtime/src/main/java/org/keycloak/cli/KeycloakMain.java new file mode 100644 index 0000000000..21299b7b04 --- /dev/null +++ b/quarkus/runtime/src/main/java/org/keycloak/cli/KeycloakMain.java @@ -0,0 +1,65 @@ +/* + * 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.cli; + +import static org.keycloak.cli.Picocli.createCommandSpec; +import static org.keycloak.cli.Picocli.parseConfigArgs; + +import java.util.Arrays; +import java.util.LinkedList; +import java.util.List; + +import org.keycloak.common.Version; + +import io.quarkus.runtime.Quarkus; +import io.quarkus.runtime.annotations.QuarkusMain; +import picocli.CommandLine; + +@QuarkusMain(name = "keycloak") +public class KeycloakMain { + + public static void main(String args[]) { + System.setProperty("kc.version", Version.VERSION_KEYCLOAK); + + if (args.length != 0) { + CommandLine cmd = new CommandLine(createCommandSpec()); + List argsList = new LinkedList<>(Arrays.asList(args)); + + try { + System.setProperty("kc.config.args", parseConfigArgs(argsList)); + CommandLine.ParseResult result = cmd.parseArgs(argsList.toArray(new String[argsList.size()])); + + if (!result.hasSubcommand() && (!result.isUsageHelpRequested() && !result.isVersionHelpRequested())) { + argsList.add(0, "start"); + } + } catch (CommandLine.UnmatchedArgumentException e) { + if (!cmd.getParseResult().hasSubcommand()) { + argsList.add(0, "start"); + } else { + cmd.getErr().println(e.getMessage()); + System.exit(CommandLine.ExitCode.SOFTWARE); + } + } + + System.exit(cmd.execute(argsList.toArray(new String[argsList.size()]))); + } + + Quarkus.run(args); + Quarkus.waitForExit(); + } +} diff --git a/quarkus/runtime/src/main/java/org/keycloak/cli/KeycloakQuarkusMain.java b/quarkus/runtime/src/main/java/org/keycloak/cli/KeycloakQuarkusMain.java deleted file mode 100644 index ee4b05d058..0000000000 --- a/quarkus/runtime/src/main/java/org/keycloak/cli/KeycloakQuarkusMain.java +++ /dev/null @@ -1,132 +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.cli; - -import java.util.Arrays; -import java.util.Iterator; -import java.util.LinkedList; -import java.util.List; -import java.util.function.Function; -import java.util.function.IntFunction; - -import org.keycloak.common.Profile; -import org.keycloak.common.Version; -import org.keycloak.configuration.PropertyMapper; -import org.keycloak.configuration.PropertyMappers; -import org.keycloak.util.Environment; - -import io.quarkus.runtime.Quarkus; -import io.quarkus.runtime.annotations.QuarkusMain; -import picocli.CommandLine; - -@QuarkusMain(name = "keycloak") -public class KeycloakQuarkusMain { - - public static void main(String args[]) { - System.setProperty("kc.version", Version.VERSION_KEYCLOAK); - - if (args.length != 0) { - CommandLine.Model.CommandSpec spec = CommandLine.Model.CommandSpec.forAnnotatedObject(new MainCommand()) - .name(Environment.getCommand()); - - addOption(spec, "start", PropertyMappers.getRuntimeMappers()); - - addOption(spec, "config", PropertyMappers.getRuntimeMappers()); - addOption(spec, "config", PropertyMappers.getBuiltTimeMappers()); - spec.subcommands().get("config").getCommandSpec().addOption(CommandLine.Model.OptionSpec.builder("--features") - .description("Enables a group of features. Possible values are: " - + String.join(",", Arrays.asList(Profile.Type.values()).stream().map( - type -> type.name().toLowerCase()).toArray((IntFunction) String[]::new))) - .type(String.class) - .build()); - - for (Profile.Feature feature : Profile.Feature.values()) { - spec.subcommands().get("config").getCommandSpec().addOption(CommandLine.Model.OptionSpec.builder("--features-" + feature.name().toLowerCase()) - .description("Enables the " + feature.name() + " feature. Set enabled to enable the feature or disabled otherwise.") - .type(String.class) - .build()); - } - - CommandLine cmd = new CommandLine(spec); - List argsList = new LinkedList<>(Arrays.asList(args)); - - if (argsList.isEmpty() || argsList.get(0).startsWith("--")) { - argsList.add(0, "start"); - } - - try { - System.setProperty("kc.config.args", parseConfigArgs(argsList)); - cmd.parseArgs(argsList.toArray(new String[argsList.size()])); - } catch (CommandLine.UnmatchedArgumentException e) { - cmd.getErr().println(e.getMessage()); - System.exit(CommandLine.ExitCode.SOFTWARE); - } - - int exitCode = cmd.execute(argsList.toArray(new String[argsList.size()])); - - if (exitCode != -1) { - System.exit(exitCode); - } - } - - Quarkus.run(args); - Quarkus.waitForExit(); - } - - private static String parseConfigArgs(List argsList) { - StringBuilder options = new StringBuilder(); - Iterator iterator = argsList.iterator(); - - while (iterator.hasNext()) { - String key = iterator.next(); - - // TODO: ignore properties for providers for now, need to fetch them from the providers, otherwise CLI will complain about invalid options - if (key.startsWith("--spi")) { - iterator.remove(); - } - - if (key.startsWith("--")) { - if (options.length() > 0) { - options.append(","); - } - options.append(key); - } - } - - return options.toString(); - } - - public static void addOption(CommandLine.Model.CommandSpec spec, String command, List mappers) { - CommandLine.Model.CommandSpec commandSpec = spec.subcommands().get(command).getCommandSpec(); - - for (PropertyMapper mapper : mappers) { - String name = "--" + PropertyMappers.toCLIFormat(mapper.getFrom()).substring(3); - String description = mapper.getDescription(); - - if (description == null || commandSpec.optionsMap().containsKey(name)) { - continue; - } - - CommandLine.Model.OptionSpec.Builder builder = CommandLine.Model.OptionSpec.builder(name).type(String.class); - - builder.description(description); - - commandSpec.addOption(builder.build()); - } - } -} diff --git a/quarkus/runtime/src/main/java/org/keycloak/cli/MainCommand.java b/quarkus/runtime/src/main/java/org/keycloak/cli/MainCommand.java index a03c37bfd2..523aff4dee 100644 --- a/quarkus/runtime/src/main/java/org/keycloak/cli/MainCommand.java +++ b/quarkus/runtime/src/main/java/org/keycloak/cli/MainCommand.java @@ -17,22 +17,10 @@ package org.keycloak.cli; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.function.BiConsumer; -import java.util.stream.Collectors; -import java.util.stream.StreamSupport; - import org.keycloak.configuration.KeycloakConfigSourceProvider; -import org.keycloak.configuration.MicroProfileConfigProvider; -import org.keycloak.quarkus.KeycloakRecorder; -import org.keycloak.util.Environment; import io.quarkus.bootstrap.runner.QuarkusEntryPoint; import io.quarkus.runtime.Quarkus; -import io.smallrye.config.ConfigValue; import picocli.CommandLine; import picocli.CommandLine.Command; import picocli.CommandLine.Model.CommandSpec; @@ -44,8 +32,7 @@ import picocli.CommandLine.Spec; header = "Keycloak - Open Source Identity and Access Management\n\nFind more information at: https://www.keycloak.org/%n", description = "Use this command-line tool to manage your Keycloak cluster%n", footerHeading = "%nUse \"${COMMAND-NAME} --help\" for more information about a command.%nUse \"${COMMAND-NAME} options\" for a list of all command-line options.", footer = "%nby Red Hat", - parameterListHeading = "Server Options%n%n", - optionListHeading = "%nConfiguration Options%n%n", + optionListHeading = "Configuration Options%n%n", commandListHeading = "%nCommands%n%n", version = { "Keycloak ${sys:kc.version}", @@ -63,23 +50,22 @@ public class MainCommand { @Option(names = { "--version" }, versionHelp = true, hidden = true) boolean version; - @CommandLine.Parameters(paramLabel = "server options", description = "Server options") - List serverOptions; - - @CommandLine.Parameters(paramLabel = "system properties", description = "Any Java system property you want set") - List systemProperties; - - @Option(names = "--profile", arity = "1", description = "Set the profile. Use 'dev' profile to enable development mode", scope = CommandLine.ScopeType.INHERIT) + @Option(names = "--profile", arity = "1", description = "Set the profile. Use 'dev' profile to enable development mode.", scope = CommandLine.ScopeType.INHERIT) public void setProfile(String profile) { System.setProperty("kc.profile", profile); } - @Option(names = "--config-file", arity = "1", description = "Set the path to a configuration file", paramLabel = "", scope = CommandLine.ScopeType.INHERIT) + @Option(names = "--config-file", arity = "1", description = "Set the path to a configuration file.", paramLabel = "", scope = CommandLine.ScopeType.INHERIT) public void setConfigFile(String path) { System.setProperty(KeycloakConfigSourceProvider.KEYCLOAK_CONFIG_FILE_PROP, path); } - @Command(name = "config", description = "Update the server configuration", mixinStandardHelpOptions = true, usageHelpAutoWidth = true) + @Command(name = "config", + description = "%nCreates a new server image based on the options passed to this command. Once created, configuration will be read from the server image and the server can be started without passing the same options again. Some configuration options require this command to be executed in order to actually change a configuration. For instance, the database vendor.%n", + mixinStandardHelpOptions = true, + usageHelpAutoWidth = true, + optionListHeading = "%nOptions%n", + parameterListHeading = "Available Commands%n") public void reAugment() { System.setProperty("quarkus.launch.rebuild", "true"); println("Updating the configuration and installing your custom providers, if any. Please wait."); @@ -92,16 +78,90 @@ public class MainCommand { } } - @Command(name = "start-dev", description = "Start the server in development mode", mixinStandardHelpOptions = true) + @Command(name = "start-dev", + description = "%nStart the server in development mode.%n", + mixinStandardHelpOptions = true, + optionListHeading = "%nOptions%n", + parameterListHeading = "Available Commands%n") public void startDev() { System.setProperty("kc.profile", "dev"); start(); } + + @Command(name = "export", + description = "%nExport data from realms to a file or directory.%n", + mixinStandardHelpOptions = true, + showDefaultValues = true, + optionListHeading = "%nOptions%n", + parameterListHeading = "Available Commands%n") + public void runExport(@Option(names = "--dir", arity = "1", description = "Set the path to a directory where files will be created with the exported data.", paramLabel = "") String toDir, + @Option(names = "--file", arity = "1", description = "Set the path to a file that will be created with the exported data.", paramLabel = "") String toFile, + @Option(names = "--realm", arity = "1", description = "Set the name of the realm to export", paramLabel = "") String realm, + @Option(names = "--users", arity = "1", description = "Set how users should be exported. Possible values are: skip, realm_file, same_file, different_files.", paramLabel = "", defaultValue = "different_files") String users, + @Option(names = "--users-per-file", arity = "1", description = "Set the number of users per file. It’s used only if --users=different_files.", paramLabel = "", defaultValue = "50") Integer usersPerFile) { + System.setProperty("keycloak.migration.action", "export"); + + if (toDir != null) { + System.setProperty("keycloak.migration.provider", "dir"); + System.setProperty("keycloak.migration.dir", toDir); + } else if (toFile != null) { + System.setProperty("keycloak.migration.provider", "singleFile"); + System.setProperty("keycloak.migration.file", toFile); + } else { + error("Must specify either --dir or --file options."); + } + + System.setProperty("keycloak.migration.usersExportStrategy", users.toUpperCase()); + + if (usersPerFile != null) { + System.setProperty("keycloak.migration.usersPerFile", usersPerFile.toString()); + } + + if (realm != null) { + System.setProperty("keycloak.migration.realmName", realm); + } + start(); + } + + @Command(name = "import", + description = "%nImport data from a directory or a file.%n", + mixinStandardHelpOptions = true, + showDefaultValues = true, + optionListHeading = "%nOptions%n", + parameterListHeading = "Available Commands%n") + public void runImport(@Option(names = "--dir", arity = "1", description = "Set the path to a directory containing the files with the data to import", paramLabel = "") String toDir, + @Option(names = "--file", arity = "1", description = "Set the path to a file with the data to import.", paramLabel = "") String toFile, + @Option(names = "--realm", arity = "1", description = "Set the name of the realm to import", paramLabel = "") String realm, + @Option(names = "--override", arity = "1", description = "Set if existing data should be skipped or overridden.", paramLabel = "false", defaultValue = "true") boolean override) { + System.setProperty("keycloak.migration.action", "import"); + if (toDir != null) { + System.setProperty("keycloak.migration.provider", "dir"); + System.setProperty("keycloak.migration.dir", toDir); + } else if (toFile != null) { + System.setProperty("keycloak.migration.provider", "singleFile"); + System.setProperty("keycloak.migration.file", toFile); + } else { + error("Must specify either --dir or --file options."); + } + + if (realm != null) { + System.setProperty("keycloak.migration.realmName", realm); + } + + System.setProperty("keycloak.migration.strategy", override ? "OVERWRITE_EXISTING" : "IGNORE_EXISTING"); + + start(); + } - @Command(name = "start", description = "Start the server", mixinStandardHelpOptions = true, usageHelpAutoWidth = true) + @Command(name = "start", + description = "%nStart the server.%n", + mixinStandardHelpOptions = true, + usageHelpAutoWidth = true, + optionListHeading = "%nOptions%n", + parameterListHeading = "Available Commands%n") public void start( @CommandLine.Parameters(paramLabel = "show-config", arity = "0..1", - description = "Show all configuration options before starting the server") String showConfig) { + description = "Print out the configuration options when starting the server.") String showConfig) { if (showConfig != null) { System.setProperty("kc.show.config.runtime", Boolean.TRUE.toString()); System.setProperty("kc.show.config", "all"); @@ -109,7 +169,11 @@ public class MainCommand { start(); } - @Command(name = "show-config", description = "Print out the current configuration", mixinStandardHelpOptions = true) + @Command(name = "show-config", + description = "Print out the current configuration.", + mixinStandardHelpOptions = true, + optionListHeading = "%nOptions%n", + parameterListHeading = "Available Commands%n") public void showConfiguration( @CommandLine.Parameters(paramLabel = "filter", defaultValue = "none", description = "Show all configuration options. Use 'all' to show all options.") String filter) { System.setProperty("kc.show.config", filter); diff --git a/quarkus/runtime/src/main/java/org/keycloak/cli/Picocli.java b/quarkus/runtime/src/main/java/org/keycloak/cli/Picocli.java new file mode 100644 index 0000000000..77ae3ca513 --- /dev/null +++ b/quarkus/runtime/src/main/java/org/keycloak/cli/Picocli.java @@ -0,0 +1,95 @@ +/* + * 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.cli; + +import java.util.Arrays; +import java.util.Iterator; +import java.util.List; +import java.util.function.IntFunction; + +import org.keycloak.common.Profile; +import org.keycloak.configuration.PropertyMapper; +import org.keycloak.configuration.PropertyMappers; +import org.keycloak.util.Environment; +import picocli.CommandLine; + +final class Picocli { + + static CommandLine.Model.CommandSpec createCommandSpec() { + CommandLine.Model.CommandSpec spec = CommandLine.Model.CommandSpec.forAnnotatedObject(new MainCommand()) + .name(Environment.getCommand()); + + addOption(spec, "start", PropertyMappers.getRuntimeMappers()); + addOption(spec, "config", PropertyMappers.getRuntimeMappers()); + addOption(spec, "config", PropertyMappers.getBuiltTimeMappers()); + addOption(spec.subcommands().get("config").getCommandSpec(), "--features", "Enables a group of features. Possible values are: " + + String.join(",", Arrays.asList(Profile.Type.values()).stream().map( + type -> type.name().toLowerCase()).toArray((IntFunction) String[]::new))); + + for (Profile.Feature feature : Profile.Feature.values()) { + addOption(spec.subcommands().get("config").getCommandSpec(), "--features-" + feature.name().toLowerCase(), + "Enables the " + feature.name() + " feature. Set enabled to enable the feature or disabled otherwise."); + } + return spec; + } + + static String parseConfigArgs(List argsList) { + StringBuilder options = new StringBuilder(); + Iterator iterator = argsList.iterator(); + + while (iterator.hasNext()) { + String key = iterator.next(); + + // TODO: ignore properties for providers for now, need to fetch them from the providers, otherwise CLI will complain about invalid options + if (key.startsWith("--spi")) { + iterator.remove(); + } + + if (key.startsWith("--")) { + if (options.length() > 0) { + options.append(","); + } + options.append(key); + } + } + + return options.toString(); + } + + private static void addOption(CommandLine.Model.CommandSpec spec, String command, List mappers) { + CommandLine.Model.CommandSpec commandSpec = spec.subcommands().get(command).getCommandSpec(); + + for (PropertyMapper mapper : mappers) { + String name = "--" + PropertyMappers.toCLIFormat(mapper.getFrom()).substring(3); + String description = mapper.getDescription(); + + if (description == null || commandSpec.optionsMap().containsKey(name)) { + continue; + } + + addOption(commandSpec, name, description); + } + } + + private static void addOption(CommandLine.Model.CommandSpec commandSpec, String name, String description) { + commandSpec.addOption(CommandLine.Model.OptionSpec.builder(name) + .description(description) + .paramLabel("") + .type(String.class).build()); + } +} diff --git a/quarkus/runtime/src/main/java/org/keycloak/configuration/PropertyMappers.java b/quarkus/runtime/src/main/java/org/keycloak/configuration/PropertyMappers.java index 60ba73a178..0460712c3f 100644 --- a/quarkus/runtime/src/main/java/org/keycloak/configuration/PropertyMappers.java +++ b/quarkus/runtime/src/main/java/org/keycloak/configuration/PropertyMappers.java @@ -148,8 +148,12 @@ public final class PropertyMappers { } public static String toCLIFormat(String name) { + if (name.indexOf('.') == -1) { + return name; + } return MicroProfileConfigProvider.NS_KEYCLOAK_PREFIX - .concat(name.substring(3).replaceAll("\\.", "-")); + .concat(name.substring(3, name.lastIndexOf('.') + 1) + .replaceAll("\\.", "-") + name.substring(name.lastIndexOf('.') + 1)); } public static List getRuntimeMappers() { diff --git a/quarkus/runtime/src/main/java/org/keycloak/connections/jpa/QuarkusJpaConnectionProviderFactory.java b/quarkus/runtime/src/main/java/org/keycloak/connections/jpa/QuarkusJpaConnectionProviderFactory.java index 423977e549..2cc48cbfdc 100644 --- a/quarkus/runtime/src/main/java/org/keycloak/connections/jpa/QuarkusJpaConnectionProviderFactory.java +++ b/quarkus/runtime/src/main/java/org/keycloak/connections/jpa/QuarkusJpaConnectionProviderFactory.java @@ -41,6 +41,7 @@ import javax.transaction.SystemException; import javax.transaction.Transaction; import com.fasterxml.jackson.core.type.TypeReference; +import io.quarkus.runtime.Quarkus; import org.hibernate.internal.SessionFactoryImpl; import org.hibernate.internal.SessionImpl; import org.jboss.logging.Logger; @@ -254,6 +255,7 @@ public class QuarkusJpaConnectionProviderFactory implements JpaConnectionProvide if (exportImportManager.isRunExport()) { exportImportManager.runExport(); + Quarkus.asyncExit(); } } @@ -395,6 +397,7 @@ public class QuarkusJpaConnectionProviderFactory implements JpaConnectionProvide if (exportImportManager.isRunImport()) { exportImportManager.runImport(); + Quarkus.asyncExit(); } else { importRealms(); } diff --git a/quarkus/runtime/src/main/java/org/keycloak/quarkus/KeycloakRecorder.java b/quarkus/runtime/src/main/java/org/keycloak/quarkus/KeycloakRecorder.java index a448086391..f4f969010b 100644 --- a/quarkus/runtime/src/main/java/org/keycloak/quarkus/KeycloakRecorder.java +++ b/quarkus/runtime/src/main/java/org/keycloak/quarkus/KeycloakRecorder.java @@ -20,10 +20,11 @@ package org.keycloak.quarkus; import java.util.Collections; import java.util.List; import java.util.Map; +import java.util.function.Supplier; import io.smallrye.config.ConfigValue; +import org.jboss.logging.Logger; import org.keycloak.QuarkusKeycloakSessionFactory; -import org.keycloak.authentication.authenticators.directgrant.ValidateOTP; import org.keycloak.cli.ShowConfigCommand; import org.keycloak.common.Profile; import org.keycloak.configuration.MicroProfileConfigProvider; @@ -39,23 +40,37 @@ import io.smallrye.config.SmallRyeConfig; import io.smallrye.config.SmallRyeConfigProviderResolver; import liquibase.logging.LogFactory; import liquibase.servicelocator.ServiceLocator; +import org.keycloak.util.Environment; @Recorder public class KeycloakRecorder { - private static final SmallRyeConfig CONFIG; + private static final Logger LOGGER = Logger.getLogger(KeycloakRecorder.class); + + private static SmallRyeConfig CONFIG = null; - static { - CONFIG = (SmallRyeConfig) SmallRyeConfigProviderResolver.instance().getConfig(); - } - private static Map BUILD_TIME_PROPERTIES = Collections.emptyMap(); public static String getBuiltTimeProperty(String name) { - return BUILD_TIME_PROPERTIES.get(name); + String value = BUILD_TIME_PROPERTIES.get(name); + + if (value == null) { + String profile = Environment.getProfile(); + + if (profile == null) { + profile = BUILD_TIME_PROPERTIES.get("kc.profile"); + } + + value = BUILD_TIME_PROPERTIES.get("%" + profile + "." + name); + } + + return value; } public static SmallRyeConfig getConfig() { + if (CONFIG == null) { + CONFIG = (SmallRyeConfig) SmallRyeConfigProviderResolver.instance().getConfig(); + } return CONFIG; } @@ -91,37 +106,57 @@ public class KeycloakRecorder { QuarkusKeycloakSessionFactory.setInstance(new QuarkusKeycloakSessionFactory(factories, defaultProviders, reaugmented)); } - public void setBuildTimeProperties(Map buildTimeProperties, Boolean rebuild) { + public void setBuildTimeProperties(Map buildTimeProperties, Boolean rebuild, String configArgs) { BUILD_TIME_PROPERTIES = buildTimeProperties; + String configHelpText = configArgs; for (String propertyName : getConfig().getPropertyNames()) { if (!propertyName.startsWith(MicroProfileConfigProvider.NS_KEYCLOAK_PREFIX)) { continue; } - String buildValue = BUILD_TIME_PROPERTIES.get(propertyName); + String buildValue = Environment.getBuiltTimeProperty(propertyName).orElseGet(new Supplier() { + @Override + public String get() { + return Environment.getBuiltTimeProperty(PropertyMappers.toCLIFormat(propertyName)).orElse(null); + } + }); + ConfigValue value = getConfig().getConfigValue(propertyName); - if (PropertyMappers.isBuildTimeProperty(propertyName)) { - if (!rebuild && buildValue != null && value.getValue() != null && !buildValue.equalsIgnoreCase(value.getValue())) { - System.err.println("The value [" + value.getValue() + "] for property [" + propertyName + "] differs from the value [" + buildValue + "] set into the server image. Please, run the 'config' command to configure the server with the new value."); - System.exit(1); - } - if (!value.getConfigSourceName().contains("keycloak.properties")) { - System.err.println("The property [" + propertyName + "] can only be set when configuring the server. Please, run the 'config' command."); - System.exit(1); - } - } - if (buildValue != null && isRuntimeValue(value) && !buildValue.equalsIgnoreCase(value.getValue())) { - System.err.println("The value [" + value.getValue() + "] of property [" + propertyName + "] differs from the value [" + buildValue + "] set into the server image"); - System.exit(1); + if (configHelpText != null) { + String currentProp = "--" + PropertyMappers.toCLIFormat(propertyName).substring(3) + "=" + buildValue; + String newProp = "--" + PropertyMappers.toCLIFormat(propertyName).substring(3) + "=" + value.getValue(); + + if (configHelpText.contains(currentProp)) { + LOGGER.warnf("The new value [%s] of the property [%s] in [%s] differs from the value [%s] set into the server image. The new value will override the value set into the server image.", value.getValue(), propertyName, value.getConfigSourceName(), buildValue); + configHelpText = configHelpText.replaceAll(currentProp, newProp); + } else if (!configHelpText.contains("--" + PropertyMappers.toCLIFormat(propertyName).substring(3))) { + configHelpText += newProp; + } + } + } else if (configHelpText != null && rebuild && isRuntimeValue(value)) { + String prop = "--" + PropertyMappers.toCLIFormat(propertyName).substring(3) + "=" + value.getValue(); + + if (!configHelpText.contains(prop)) { + LOGGER.infof("New property [%s] set with value [%s] in [%s]. This property is not persisted into the server image.", + propertyName, value.getValue(), value.getConfigSourceName(), buildValue); + configHelpText += " " + prop; + } } } + + if (configArgs != null && !configArgs.equals(configHelpText)) { + LOGGER.infof("Please, run the 'config' command if you want to configure the server image with the new property values:\n\t%s config %s", Environment.getCommand(), String.join(" ", configHelpText.split(","))); + } } private boolean isRuntimeValue(ConfigValue value) { - return value.getValue() != null && !PropertyMappers.isBuildTimeProperty(value.getName()); + String name = value.getName(); + return value.getValue() != null && !PropertyMappers.isBuildTimeProperty(name) + && !"kc.version".equals(name) && !"kc.config.args".equals( + name) && !"kc.home.dir".equals(name); } /** diff --git a/quarkus/runtime/src/main/java/org/keycloak/util/Environment.java b/quarkus/runtime/src/main/java/org/keycloak/util/Environment.java index 0206463cfe..cdbc9d88ae 100644 --- a/quarkus/runtime/src/main/java/org/keycloak/util/Environment.java +++ b/quarkus/runtime/src/main/java/org/keycloak/util/Environment.java @@ -52,13 +52,13 @@ public final class Environment { if (profile == null) { profile = System.getenv("KC_PROFILE"); } - + return profile; } public static Optional getBuiltTimeProperty(String name) { String value = KeycloakRecorder.getBuiltTimeProperty(name); - + if (value == null) { return Optional.empty(); }