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 2de77f3e60..566ad7a836 100644 --- a/distribution/server-x-dist/src/main/content/bin/kc.sh +++ b/distribution/server-x-dist/src/main/content/bin/kc.sh @@ -48,7 +48,7 @@ do if [[ $1 = --* || ! $1 =~ ^-D.* ]]; then CONFIG_ARGS="$CONFIG_ARGS $1" if [[ "$1" = "start-dev" ]]; then - CONFIG_ARGS="$CONFIG_ARGS --auto-build" + CONFIG_ARGS="$CONFIG_ARGS --profile=dev --auto-build" fi else SERVER_OPTS="$SERVER_OPTS $1" diff --git a/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/Environment.java b/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/Environment.java index d6afdaa673..667474d4dc 100644 --- a/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/Environment.java +++ b/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/Environment.java @@ -128,7 +128,11 @@ public final class Environment { } // if running in quarkus:dev mode - return ProfileManager.getLaunchMode() == LaunchMode.DEVELOPMENT; + if (ProfileManager.getLaunchMode() == LaunchMode.DEVELOPMENT) { + return true; + } + + return "dev".equals(getBuiltTimeProperty(PROFILE).orElse(null)); } public static boolean isImportExportMode() { @@ -140,8 +144,7 @@ public final class Environment { } public static void forceDevProfile() { - System.setProperty(PROFILE, "dev"); - System.setProperty("quarkus.profile", "dev"); + setProfile("dev"); } public static Map getProviderFiles() { diff --git a/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/KeycloakMain.java b/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/KeycloakMain.java index f92badbcb6..7378252350 100644 --- a/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/KeycloakMain.java +++ b/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/KeycloakMain.java @@ -17,6 +17,7 @@ package org.keycloak.quarkus.runtime; +import static org.keycloak.quarkus.runtime.Environment.isDevMode; import static org.keycloak.quarkus.runtime.cli.Picocli.error; import static org.keycloak.quarkus.runtime.cli.Picocli.parseAndRun; import static org.keycloak.quarkus.runtime.Environment.getProfileOrDefault; @@ -29,6 +30,7 @@ import java.util.List; import io.quarkus.runtime.ApplicationLifecycleManager; import io.quarkus.runtime.Quarkus; +import org.jboss.logging.Logger; import org.keycloak.quarkus.runtime.cli.Picocli; import org.keycloak.common.Version; @@ -41,6 +43,8 @@ import io.quarkus.runtime.annotations.QuarkusMain; @QuarkusMain(name = "keycloak") public class KeycloakMain implements QuarkusApplication { + private static final Logger LOGGER = Logger.getLogger(KeycloakMain.class); + public static void main(String[] args) { System.setProperty("kc.version", Version.VERSION_KEYCLOAK); List cliArgs = new ArrayList<>(Arrays.asList(args)); @@ -61,13 +65,13 @@ public class KeycloakMain implements QuarkusApplication { Quarkus.run(KeycloakMain.class, (integer, cause) -> { if (cause != null) { error(cliArgs, errorWriter, - String.format("Failed to start server using profile (%s)", getProfileOrDefault("none")), + String.format("Failed to start server using profile (%s)", getProfileOrDefault("prod")), cause.getCause()); } }); } catch (Throwable cause) { error(cliArgs, errorWriter, - String.format("Unexpected error when starting the server using profile (%s)", getProfileOrDefault("none")), + String.format("Unexpected error when starting the server using profile (%s)", getProfileOrDefault("prod")), cause.getCause()); } } @@ -77,6 +81,9 @@ public class KeycloakMain implements QuarkusApplication { */ @Override public int run(String... args) throws Exception { + if (isDevMode()) { + LOGGER.warnf("Running the server in dev mode. DO NOT use this configuration in production."); + } Quarkus.waitForExit(); return ApplicationLifecycleManager.getExitCode(); } diff --git a/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/cli/ExecutionExceptionHandler.java b/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/cli/ExecutionExceptionHandler.java index b366309a2d..6aff6b9ac4 100644 --- a/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/cli/ExecutionExceptionHandler.java +++ b/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/cli/ExecutionExceptionHandler.java @@ -18,11 +18,12 @@ package org.keycloak.quarkus.runtime.cli; import picocli.CommandLine; +import picocli.CommandLine.ParseResult; public class ExecutionExceptionHandler implements CommandLine.IExecutionExceptionHandler { @Override - public int handleExecutionException(Exception ex, CommandLine commandLine, CommandLine.ParseResult parseResult) { + public int handleExecutionException(Exception ex, CommandLine commandLine, ParseResult parseResult) { commandLine.getErr().println(ex.getMessage()); commandLine.usage(commandLine.getErr()); return commandLine.getCommandSpec().exitCodeOnExecutionException(); 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 069244860a..55ac86da8f 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 @@ -28,12 +28,14 @@ import java.io.File; import java.io.FileInputStream; import java.io.InputStream; import java.io.PrintWriter; +import java.nio.file.FileSystemException; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.Iterator; import java.util.List; import java.util.Map; +import java.util.Optional; import java.util.Properties; import java.util.Set; import java.util.function.IntFunction; @@ -47,13 +49,20 @@ import org.keycloak.quarkus.runtime.cli.command.Start; import org.keycloak.quarkus.runtime.cli.command.StartDev; import org.keycloak.common.Profile; import org.keycloak.quarkus.runtime.configuration.KeycloakConfigSourceProvider; +import org.keycloak.quarkus.runtime.configuration.Messages; import org.keycloak.quarkus.runtime.configuration.PropertyMapper; import org.keycloak.quarkus.runtime.configuration.PropertyMappers; import org.keycloak.platform.Platform; import org.keycloak.quarkus.runtime.InitializationException; import org.keycloak.quarkus.runtime.integration.QuarkusPlatform; import org.keycloak.quarkus.runtime.Environment; + +import io.smallrye.config.ConfigValue; import picocli.CommandLine; +import picocli.CommandLine.Model.CommandSpec; +import picocli.CommandLine.UnmatchedArgumentException; +import picocli.CommandLine.ParseResult; +import picocli.CommandLine.Model.OptionSpec; public final class Picocli { @@ -72,13 +81,13 @@ public final class Picocli { CommandLine cmd = createCommandLine(cliArgs); try { - CommandLine.ParseResult result = cmd.parseArgs(cliArgs.toArray(new String[0])); + ParseResult result = cmd.parseArgs(cliArgs.toArray(new String[0])); if (!result.hasSubcommand() && !result.isUsageHelpRequested() && !result.isVersionHelpRequested()) { // if no command was set, the start command becomes the default cliArgs.add(0, Start.NAME); } - } catch (CommandLine.UnmatchedArgumentException e) { + } catch (UnmatchedArgumentException e) { // if no command was set but options were provided, the start command becomes the default if (!cmd.getParseResult().hasSubcommand() && cliArgs.get(0).startsWith("--")) { cliArgs.add(0, "start"); @@ -196,6 +205,13 @@ public final class Picocli { } private static boolean hasConfigChanges() { + Optional currentProfile = Optional.ofNullable(Environment.getProfile()); + Optional persistedProfile = Environment.getBuiltTimeProperty("kc.profile"); + + if (!persistedProfile.orElse("").equals(currentProfile.orElse(""))) { + return true; + } + for (String propertyName : getConfig().getPropertyNames()) { // only check keycloak build-time properties if (!isBuildTimeProperty(propertyName)) { @@ -224,9 +240,11 @@ public final class Picocli { } private static CommandLine createCommandLine(List cliArgs) { - CommandLine.Model.CommandSpec spec = CommandLine.Model.CommandSpec.forAnnotatedObject(new Main()) + CommandSpec spec = CommandSpec.forAnnotatedObject(new Main()) .name(Environment.getCommand()); + spec.usageMessage().width(100); + boolean addBuildOptionsToStartCommand = cliArgs.contains(AUTO_BUILD_OPTION); addOption(spec, Start.NAME, addBuildOptionsToStartCommand); @@ -285,8 +303,8 @@ public final class Picocli { return options.toString(); } - private static void addOption(CommandLine.Model.CommandSpec spec, String command, boolean includeBuildTime) { - CommandLine.Model.CommandSpec commandSpec = spec.subcommands().get(command).getCommandSpec(); + private static void addOption(CommandSpec spec, String command, boolean includeBuildTime) { + CommandSpec commandSpec = spec.subcommands().get(command).getCommandSpec(); List mappers = new ArrayList<>(PropertyMappers.getRuntimeMappers()); if (includeBuildTime) { @@ -309,8 +327,8 @@ public final class Picocli { type -> type.name().toLowerCase()).toArray((IntFunction) String[]::new)), null); } - private static void addOption(CommandLine.Model.CommandSpec commandSpec, String name, String description, PropertyMapper mapper) { - CommandLine.Model.OptionSpec.Builder builder = CommandLine.Model.OptionSpec.builder(name) + private static void addOption(CommandSpec commandSpec, String name, String description, PropertyMapper mapper) { + OptionSpec.Builder builder = OptionSpec.builder(name) .description(description) .paramLabel(name.substring(2)) .type(String.class); @@ -323,7 +341,7 @@ public final class Picocli { } public static List getCliArgs(CommandLine cmd) { - CommandLine.ParseResult parseResult = cmd.getParseResult(); + ParseResult parseResult = cmd.getParseResult(); if (parseResult == null) { return Collections.emptyList(); @@ -385,8 +403,21 @@ public final class Picocli { if (cause.getMessage() != null) { logError(errorWriter, String.format("ERROR: %s", cause.getMessage())); } + printErrorHints(errorWriter, cause); } while ((cause = cause.getCause())!= null); } + printErrorHints(errorWriter, cause); + } + + private static void printErrorHints(PrintWriter errorWriter, Throwable cause) { + if (cause instanceof FileSystemException) { + FileSystemException fse = (FileSystemException) cause; + ConfigValue httpsCertFile = getConfig().getConfigValue("kc.https.certificate.file"); + + if (fse.getFile().equals(Optional.ofNullable(httpsCertFile.getValue()).orElse(null))) { + logError(errorWriter, Messages.httpsConfigurationNotSet().getMessage()); + } + } } private static void logError(PrintWriter errorWriter, String errorMessage) { diff --git a/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/cli/SubCommandListRenderer.java b/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/cli/SubCommandListRenderer.java index 359f16b0cc..6577ce3f58 100644 --- a/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/cli/SubCommandListRenderer.java +++ b/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/cli/SubCommandListRenderer.java @@ -21,22 +21,28 @@ import java.util.Collection; import java.util.Map; import picocli.CommandLine; +import picocli.CommandLine.Help; +import picocli.CommandLine.Help.Column; +import picocli.CommandLine.Help.TextTable; +import picocli.CommandLine.Model.CommandSpec; +import picocli.CommandLine.Model.UsageMessageSpec; /** - * A {@link picocli.CommandLine.IHelpSectionRenderer} based on Quarkus CLI to show subcomands in help messages. + * A {@link picocli.CommandLine.IHelpSectionRenderer} based on Quarkus CLI to show subcommands in help messages. */ class SubCommandListRenderer implements CommandLine.IHelpSectionRenderer { // @Override - public String render(CommandLine.Help help) { - CommandLine.Model.CommandSpec spec = help.commandSpec(); + public String render(Help help) { + CommandSpec spec = help.commandSpec(); if (spec.subcommands().isEmpty()) { return ""; } - CommandLine.Help.Column commands = new CommandLine.Help.Column(24, 2, CommandLine.Help.Column.Overflow.SPAN); - CommandLine.Help.Column descriptions = new CommandLine.Help.Column(spec.usageMessage().width() - 24, 2, - CommandLine.Help.Column.Overflow.WRAP); - CommandLine.Help.TextTable textTable = CommandLine.Help.TextTable.forColumns(help.colorScheme(), commands, descriptions); + Column commands = new Column(24, 2, Column.Overflow.SPAN); + Column descriptions = new Column(spec.usageMessage().width() - 24, 2, + Column.Overflow.WRAP); + + TextTable textTable = TextTable.forColumns(help.colorScheme(), commands, descriptions); textTable.setAdjustLineBreaksForWideCJKCharacters(spec.usageMessage().adjustLineBreaksForWideCJKCharacters()); addHierarchy(spec.subcommands().values(), textTable, ""); @@ -58,7 +64,7 @@ class SubCommandListRenderer implements CommandLine.IHelpSectionRenderer { }); } - private String description(CommandLine.Model.UsageMessageSpec usageMessage) { + private String description(UsageMessageSpec usageMessage) { if (usageMessage.header().length > 0) { return usageMessage.header()[0]; } diff --git a/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/cli/command/AbstractCommand.java b/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/cli/command/AbstractCommand.java index d6a02d5d0c..8e52440f53 100644 --- a/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/cli/command/AbstractCommand.java +++ b/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/cli/command/AbstractCommand.java @@ -19,14 +19,20 @@ package org.keycloak.quarkus.runtime.cli.command; import org.keycloak.quarkus.runtime.Environment; -import picocli.CommandLine; +import picocli.CommandLine.Model.CommandSpec; +import picocli.CommandLine.Spec; +import picocli.CommandLine.Option; +import picocli.CommandLine.ScopeType; public abstract class AbstractCommand { - @CommandLine.Spec - protected CommandLine.Model.CommandSpec spec; + @Spec + protected CommandSpec spec; - @CommandLine.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 = ScopeType.INHERIT) public void setProfile(String profile) { Environment.setProfile(profile); } diff --git a/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/cli/command/AbstractExportImportCommand.java b/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/cli/command/AbstractExportImportCommand.java index ff3c689481..7ba6f8c182 100644 --- a/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/cli/command/AbstractExportImportCommand.java +++ b/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/cli/command/AbstractExportImportCommand.java @@ -22,14 +22,29 @@ import static org.keycloak.quarkus.runtime.cli.Picocli.error; import org.keycloak.quarkus.runtime.Environment; import picocli.CommandLine; +import picocli.CommandLine.Option; public abstract class AbstractExportImportCommand extends AbstractCommand implements Runnable { private final String action; - @CommandLine.Option(names = "--dir", arity = "1", description = "Set the path to a directory where files will be created with the exported data.", paramLabel = "") String toDir; - @CommandLine.Option(names = "--file", arity = "1", description = "Set the path to a file that will be created with the exported data.", paramLabel = "") String toFile; - @CommandLine.Option(names = "--realm", arity = "1", description = "Set the name of the realm to export", paramLabel = "") String realm; + @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; protected AbstractExportImportCommand(String action) { this.action = action; diff --git a/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/cli/command/AbstractStartCommand.java b/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/cli/command/AbstractStartCommand.java index cfd52c5017..99be78fcd7 100644 --- a/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/cli/command/AbstractStartCommand.java +++ b/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/cli/command/AbstractStartCommand.java @@ -20,12 +20,17 @@ package org.keycloak.quarkus.runtime.cli.command; import org.keycloak.quarkus.runtime.KeycloakMain; import picocli.CommandLine; +import picocli.CommandLine.Option; public abstract class AbstractStartCommand extends AbstractCommand implements Runnable { public static final String AUTO_BUILD_OPTION = "--auto-build"; - @CommandLine.Option(names = AUTO_BUILD_OPTION, description = "Automatically detects whether the server configuration changed and a new server image must be built prior to starting the server. This option provides an alternative to manually running the '" + Build.NAME + "' prior to starting the server. Use this configuration carefully in production as it might impact the startup time.", order = 1) + @Option(names = AUTO_BUILD_OPTION, + description = "Automatically detects whether the server configuration changed and a new server image must be built" + + " prior to starting the server. This option provides an alternative to manually running the '" + Build.NAME + "'" + + " prior to starting the server. Use this configuration carefully in production as it might impact the startup time.", + order = 1) Boolean autoConfig; @Override diff --git a/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/cli/command/Build.java b/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/cli/command/Build.java index 4a5eb79c3c..d432f64e97 100644 --- a/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/cli/command/Build.java +++ b/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/cli/command/Build.java @@ -24,19 +24,38 @@ import org.keycloak.quarkus.runtime.Environment; import io.quarkus.bootstrap.runner.QuarkusEntryPoint; import io.quarkus.bootstrap.runner.RunnerClassLoader; -import picocli.CommandLine; -@CommandLine.Command(name = Build.NAME, - header = "Creates a new and optimized server image based on the configuration options passed to this command.", +import picocli.CommandLine.Command; + +@Command(name = Build.NAME, + header = "Creates a new and optimized server image.", description = { - "Creates a new and optimized server image based on the configuration 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.", + "%nCreates a new and optimized server image based on the configuration options passed to this command. Once created, the configuration will be persisted and read during startup without having to pass them over again.", "", - "Some configuration options require this command to be executed in order to actually change a configuration. For instance, the database vendor." + "Some configuration options require this command to be executed in order to actually change a configuration. For instance", + "", + "- Change database vendor%n" + + "- Enable/disable features%n" + + "- Enable/Disable providers or set a default", + "", + "Consider running this command before running the server in production for an optimal runtime." }, + footerHeading = "%nExamples:%n%n" + + " Optimize the server based on a profile configuration:%n%n" + + " $ ${PARENT-COMMAND-FULL-NAME:-$PARENTCOMMAND} ${COMMAND-NAME} --profile=prod%n%n" + + " Change database settings:%n%n" + + " $ ${PARENT-COMMAND-FULL-NAME:-$PARENTCOMMAND} ${COMMAND-NAME} --db=postgres [--db-url][--db-username][--db-password]%n%n" + + " Enable a feature:%n%n" + + " $ ${PARENT-COMMAND-FULL-NAME:-$PARENTCOMMAND} ${COMMAND-NAME} --features-=[enabled|disabled]%n%n" + + " Or alternatively, enable all tech preview features:%n%n" + + " $ ${PARENT-COMMAND-FULL-NAME:-$PARENTCOMMAND} ${COMMAND-NAME} --features=preview%n%n" + + " Enable metrics:%n%n" + + " $ ${PARENT-COMMAND-FULL-NAME:-$PARENTCOMMAND} ${COMMAND-NAME} --metrics-enabled=true%n%n" + + "You can also use the \"--auto-build\" option when starting the server to avoid running this command every time you change a configuration:%n%n" + + " $ ${PARENT-COMMAND-FULL-NAME:-$PARENTCOMMAND} --auto-build %n%n" + + "By doing that you have an additional overhead when the server is starting.%n%n", mixinStandardHelpOptions = true, - usageHelpAutoWidth = true, - optionListHeading = "%nOptions%n", - parameterListHeading = "Available Commands%n") + optionListHeading = "%nConfiguration Options%n%n") public final class Build extends AbstractCommand implements Runnable { public static final String NAME = "build"; @@ -65,7 +84,7 @@ public final class Build extends AbstractCommand implements Runnable { ClassLoader classLoader = Thread.currentThread().getContextClassLoader(); if (classLoader instanceof RunnerClassLoader) { - RunnerClassLoader.class.cast(classLoader).resetInternalCaches(); + ((RunnerClassLoader) classLoader).resetInternalCaches(); } } } diff --git a/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/cli/command/Completion.java b/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/cli/command/Completion.java index faa9a37b4f..7ea8818d74 100644 --- a/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/cli/command/Completion.java +++ b/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/cli/command/Completion.java @@ -19,13 +19,16 @@ package org.keycloak.quarkus.runtime.cli.command; import picocli.AutoComplete; import picocli.CommandLine; +import picocli.CommandLine.Command; -@CommandLine.Command(name = "completion", version = "generate-completion " + CommandLine.VERSION, +@Command(name = "completion", + version = "generate-completion " + CommandLine.VERSION, header = "Generate bash/zsh completion script for ${ROOT-COMMAND-NAME:-the root command of this command}.", helpCommand = false, headerHeading = "%n", commandListHeading = "%nCommands:%n", synopsisHeading = "%nUsage: ", optionListHeading = "Options:%n", description = { - "Generate bash/zsh completion script for ${ROOT-COMMAND-NAME:-the root command of this command}.%nRun the following command to give `${ROOT-COMMAND-NAME:-$PARENTCOMMAND}` TAB completion in the current shell:", + "Generate bash/zsh completion script for ${ROOT-COMMAND-NAME:-the root command of this command}.%n" + + "Run the following command to give `${ROOT-COMMAND-NAME:-$PARENTCOMMAND}` TAB completion in the current shell:", "", " source <(${PARENT-COMMAND-FULL-NAME:-$PARENTCOMMAND} ${COMMAND-NAME})", ""}, diff --git a/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/cli/command/Export.java b/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/cli/command/Export.java index 8732a4da28..67995c1f8e 100644 --- a/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/cli/command/Export.java +++ b/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/cli/command/Export.java @@ -19,9 +19,10 @@ package org.keycloak.quarkus.runtime.cli.command; import static org.keycloak.exportimport.ExportImportConfig.ACTION_EXPORT; -import picocli.CommandLine; +import picocli.CommandLine.Command; +import picocli.CommandLine.Option; -@CommandLine.Command(name = "export", +@Command(name = "export", description = "Export data from realms to a file or directory.", mixinStandardHelpOptions = true, showDefaultValues = true, @@ -29,8 +30,19 @@ import picocli.CommandLine; parameterListHeading = "Available Commands%n") public final class Export extends AbstractExportImportCommand implements Runnable { - @CommandLine.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; - @CommandLine.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; + @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; public Export() { super(ACTION_EXPORT); diff --git a/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/cli/command/Import.java b/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/cli/command/Import.java index 0b9868a73a..705649f638 100644 --- a/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/cli/command/Import.java +++ b/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/cli/command/Import.java @@ -21,9 +21,10 @@ import static org.keycloak.exportimport.ExportImportConfig.ACTION_IMPORT; import static org.keycloak.exportimport.Strategy.IGNORE_EXISTING; import static org.keycloak.exportimport.Strategy.OVERWRITE_EXISTING; -import picocli.CommandLine; +import picocli.CommandLine.Command; +import picocli.CommandLine.Option; -@CommandLine.Command(name = "import", +@Command(name = "import", description = "Import data from a directory or a file.", mixinStandardHelpOptions = true, showDefaultValues = true, @@ -31,7 +32,12 @@ import picocli.CommandLine; parameterListHeading = "Available Commands%n") public final class Import extends AbstractExportImportCommand implements Runnable { - @CommandLine.Option(names = "--override", arity = "1", description = "Set if existing data should be skipped or overridden.", paramLabel = "false", defaultValue = "true") boolean override; + @Option(names = "--override", + arity = "1", + description = "Set if existing data should be skipped or overridden.", + paramLabel = "false", + defaultValue = "true") + boolean override; public Import() { super(ACTION_IMPORT); diff --git a/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/cli/command/Main.java b/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/cli/command/Main.java index 15a41df465..d7f6374e73 100644 --- a/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/cli/command/Main.java +++ b/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/cli/command/Main.java @@ -19,26 +19,38 @@ package org.keycloak.quarkus.runtime.cli.command; import org.keycloak.quarkus.runtime.configuration.KeycloakConfigSourceProvider; -import picocli.CommandLine; import picocli.CommandLine.Command; import picocli.CommandLine.Option; +import picocli.CommandLine.ScopeType; @Command(name = "keycloak", - usageHelpWidth = 150, header = { - "Keycloak - Open Source Identity and Access Management", - "", - "Find more information at: https://www.keycloak.org/", - "" + "Keycloak - Open Source Identity and Access Management", + "", + "Find more information at: https://www.keycloak.org/docs/latest", + "" }, - description = "Use this command-line tool to manage your Keycloak cluster%n", footerHeading = "%nUse \"${COMMAND-NAME} --help\" for more information about a command.", - footer = "%nby Red Hat", + description = "%nUse this command-line tool to manage your Keycloak cluster.%n", + footerHeading = "%nExamples:%n%n" + + " Start the server in development mode for local development or testing:%n%n" + + " $ ${COMMAND-NAME} start-dev%n%n" + + " Building an optimized server runtime:%n%n" + + " $ ${COMMAND-NAME} build %n%n" + + " Start the server in production mode:%n%n" + + " $ ${COMMAND-NAME} %n%n" + + " Please, take a look at the documentation for more details before deploying in production.%n", + footer = { + "", + "Use \"${COMMAND-NAME} start --help\" for the available options when starting the server.", + "Use \"${COMMAND-NAME} --help\" for more information about other commands.", + "", + "by Red Hat" }, optionListHeading = "Configuration Options%n%n", commandListHeading = "%nCommands%n%n", version = { - "Keycloak ${sys:kc.version}", - "JVM: ${java.version} (${java.vendor} ${java.vm.name} ${java.vm.version})", - "OS: ${os.name} ${os.version} ${os.arch}" + "Keycloak ${sys:kc.version}", + "JVM: ${java.version} (${java.vendor} ${java.vm.name} ${java.vm.version})", + "OS: ${os.name} ${os.version} ${os.arch}" }, subcommands = { Build.class, @@ -48,25 +60,35 @@ import picocli.CommandLine.Option; Import.class, ShowConfig.class, Tools.class - } -) + }) public final class Main { - @CommandLine.Option(names = "-D=", description = "Set a Java system property", scope = CommandLine.ScopeType.INHERIT, order = 0) + @Option(names = "-D=", + description = "Set a Java system property", + scope = ScopeType.INHERIT, + order = 0) Boolean sysProps; - @Option(names = { "-h", "--help" }, description = "This help message.", usageHelp = true) + @Option(names = { "-h", "--help" }, + description = "This help message.", + usageHelp = true) boolean help; - @Option(names = { "-V", "--version" }, description = "Show version information", versionHelp = true) + @Option(names = { "-V", "--version" }, + description = "Show version information", + versionHelp = true) boolean version; @Option(names = { "-v", "--verbose" }, - description = "Print out more details when running this command. Useful for troubleshooting if some unexpected error occurs.", required = false, - scope = CommandLine.ScopeType.INHERIT) + description = "Print out more details when running this command. Useful for troubleshooting if some unexpected error occurs.", + required = false, + scope = ScopeType.INHERIT) Boolean verbose; - @Option(names = {"-cf", "--config-file"}, arity = "1", description = "Set the path to a configuration file.", paramLabel = "") + @Option(names = { "-cf", "--config-file" }, + arity = "1", + description = "Set the path to a configuration file. By default, configuration properties are read from the \"keycloak.properties\" file in the \"conf\" directory.", + paramLabel = "") public void setConfigFile(String path) { System.setProperty(KeycloakConfigSourceProvider.KEYCLOAK_CONFIG_FILE_PROP, path); } diff --git a/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/cli/command/ShowConfig.java b/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/cli/command/ShowConfig.java index 3933aa44f2..e3222d265a 100644 --- a/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/cli/command/ShowConfig.java +++ b/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/cli/command/ShowConfig.java @@ -31,22 +31,28 @@ import java.util.function.BiConsumer; import java.util.function.Predicate; import java.util.stream.Collectors; import java.util.stream.StreamSupport; + import org.keycloak.quarkus.runtime.configuration.MicroProfileConfigProvider; import org.keycloak.quarkus.runtime.configuration.PersistedConfigSource; import org.keycloak.quarkus.runtime.configuration.PropertyMappers; import org.keycloak.quarkus.runtime.Environment; import io.smallrye.config.ConfigValue; -import picocli.CommandLine; +import picocli.CommandLine.Command; +import picocli.CommandLine.Parameters; -@CommandLine.Command(name = "show-config", +@Command(name = "show-config", description = "Print out the current configuration.", mixinStandardHelpOptions = true, optionListHeading = "%nOptions%n", parameterListHeading = "Available Commands%n") public final class ShowConfig extends AbstractCommand implements Runnable { - @CommandLine.Parameters(paramLabel = "filter", defaultValue = "none", description = "Show all configuration options. Use 'all' to show all options.") String filter; + @Parameters( + paramLabel = "filter", + defaultValue = "none", + description = "Show all configuration options. Use 'all' to show all options.") + String filter; @Override public void run() { @@ -101,7 +107,7 @@ public final class ShowConfig extends AbstractCommand implements Runnable { .forEach((p, properties1) -> { if (p.equals(profile)) { spec.commandLine().getOut().printf("Profile \"%s\" Configuration (%s):%n", p, - p.equals(profile) ? "current" : ""); + "current"); } else { spec.commandLine().getOut().printf("Profile \"%s\" Configuration:%n", p); } diff --git a/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/cli/command/Start.java b/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/cli/command/Start.java index 2bea0f20d0..cb8b55b6e6 100644 --- a/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/cli/command/Start.java +++ b/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/cli/command/Start.java @@ -17,14 +17,18 @@ package org.keycloak.quarkus.runtime.cli.command; -import picocli.CommandLine; +import picocli.CommandLine.Command; -@CommandLine.Command(name = Start.NAME, - description = "Start the server.", - mixinStandardHelpOptions = true, - usageHelpAutoWidth = true, - optionListHeading = "%nOptions%n", - parameterListHeading = "Available Commands%n") +@Command(name = Start.NAME, + header = "Start the server.", + description = { + "%nUse this command to run the server in production." + }, + footerHeading = "%nYou may use the \"--auto-build\" option when starting the server to avoid running the \"build\" command everytime you need to change a static property:%n%n" + + " $ ${PARENT-COMMAND-FULL-NAME:-$PARENTCOMMAND} ${COMMAND-NAME} --auto-build %n%n" + + "By doing that you have an additional overhead when the server is starting. Run \"${PARENT-COMMAND-FULL-NAME:-$PARENTCOMMAND} build -h\" for more details.%n%n", + optionListHeading = "%nConfiguration Options%n%n", + mixinStandardHelpOptions = true) public final class Start extends AbstractStartCommand implements Runnable { public static final String NAME = "start"; diff --git a/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/cli/command/StartDev.java b/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/cli/command/StartDev.java index 3dab2de85d..31f9122978 100644 --- a/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/cli/command/StartDev.java +++ b/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/cli/command/StartDev.java @@ -19,13 +19,16 @@ package org.keycloak.quarkus.runtime.cli.command; import org.keycloak.quarkus.runtime.Environment; -import picocli.CommandLine; +import picocli.CommandLine.Command; -@CommandLine.Command(name = StartDev.NAME, - description = "Start the server in development mode.", - mixinStandardHelpOptions = true, - optionListHeading = "%nOptions%n", - parameterListHeading = "Available Commands%n") +@Command(name = StartDev.NAME, + header = "Start the server in development mode.", + description = { + "%nUse this command if you want to run the server locally for development or testing purposes.", + }, + footerHeading = "%nDo NOT start the server using this command when deploying to production.%n%n", + optionListHeading = "%nConfiguration Options%n%n", + mixinStandardHelpOptions = true) public final class StartDev extends AbstractStartCommand implements Runnable { public static final String NAME = "start-dev"; @@ -33,6 +36,5 @@ public final class StartDev extends AbstractStartCommand implements Runnable { @Override protected void doBeforeRun() { Environment.forceDevProfile(); - spec.commandLine().getOut().printf("Running the server in dev mode. DO NOT run the '%s' command in production.%n", NAME); } } diff --git a/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/cli/command/Tools.java b/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/cli/command/Tools.java index e909ebf1a7..4c738fed02 100644 --- a/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/cli/command/Tools.java +++ b/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/cli/command/Tools.java @@ -17,10 +17,10 @@ package org.keycloak.quarkus.runtime.cli.command; -import picocli.CommandLine; +import picocli.CommandLine.Command; -@CommandLine.Command(name = "tools", - description = "Provides utilities for using and interacting with the server.", +@Command(name = "tools", + description = "Utilities for use and interaction with the server.", mixinStandardHelpOptions = true, optionListHeading = "%nOptions%n", parameterListHeading = "Available Commands%n", diff --git a/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/configuration/Messages.java b/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/configuration/Messages.java index e57145af01..cae9c82c19 100644 --- a/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/configuration/Messages.java +++ b/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/configuration/Messages.java @@ -29,10 +29,10 @@ public final class Messages { return new IllegalArgumentException("Invalid value [" + mode + "] for configuration property [proxy]."); } - static IllegalStateException httpsConfigurationNotSet() { - StringBuilder builder = new StringBuilder("Key material not provided to setup HTTPS. Please configure your keys/certificates or enable HTTP"); + public static IllegalStateException httpsConfigurationNotSet() { + StringBuilder builder = new StringBuilder("Key material not provided to setup HTTPS. Please configure your keys/certificates"); if (!"dev".equals(Environment.getProfile())) { - builder.append(" or start the server using the 'dev' profile"); + builder.append(" or start the server in development mode"); } builder.append("."); return new IllegalStateException(builder.toString()); diff --git a/quarkus/server/src/main/resources/META-INF/keycloak.properties b/quarkus/server/src/main/resources/META-INF/keycloak.properties index 7198c96a7a..8819093b3a 100644 --- a/quarkus/server/src/main/resources/META-INF/keycloak.properties +++ b/quarkus/server/src/main/resources/META-INF/keycloak.properties @@ -12,6 +12,20 @@ metrics.enabled=false # Themes spi.theme.folder.dir=${kc.home.dir:}/themes +# Basic settings for running in production. Change accordingly before deploying the server. +# Database +#%prod.db=postgres +#%prod.db.username=keycloak +#%prod.db.password=password +#%prod.db.url=jdbc:postgresql://localhost/keycloak +# Observability +#%prod.metrics.enabled=true +# HTTP +#%prod.spi.hostname.frontend-url=https://localhost:8443 +#%prod.https.certificate.file=${kc.home.dir}conf/server.crt.pem +#%prod.https.certificate.key-file=${kc.home.dir}conf/server.key.pem +#%prod.proxy=reencrypt + # Default, and insecure, and non-production grade configuration for the development profile %dev.http.enabled=true %dev.cluster=local