KEYCLOAK-19307 provide hints in CLI

This commit is contained in:
Dominik Guhr 2021-10-26 11:54:56 +02:00 committed by Pedro Igor
parent af97849feb
commit 5628370099
20 changed files with 254 additions and 92 deletions

View file

@ -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"

View file

@ -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<String, File> getProviderFiles() {

View file

@ -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<String> 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();
}

View file

@ -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();

View file

@ -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<String> currentProfile = Optional.ofNullable(Environment.getProfile());
Optional<String> 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<String> 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<PropertyMapper> mappers = new ArrayList<>(PropertyMappers.getRuntimeMappers());
if (includeBuildTime) {
@ -309,8 +327,8 @@ public final class Picocli {
type -> type.name().toLowerCase()).toArray((IntFunction<CharSequence[]>) 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<String> 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) {

View file

@ -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];
}

View file

@ -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);
}

View file

@ -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 = "<path>") String toDir;
@CommandLine.Option(names = "--file", arity = "1", description = "Set the path to a file that will be created with the exported data.", paramLabel = "<path>") String toFile;
@CommandLine.Option(names = "--realm", arity = "1", description = "Set the name of the realm to export", paramLabel = "<realm>") String realm;
@Option(names = "--dir",
arity = "1",
description = "Set the path to a directory where files will be created with the exported data.",
paramLabel = "<path>")
String toDir;
@Option(names = "--file",
arity = "1",
description = "Set the path to a file that will be created with the exported data.",
paramLabel = "<path>")
String toFile;
@Option(names = "--realm",
arity = "1",
description = "Set the name of the realm to export",
paramLabel = "<realm>")
String realm;
protected AbstractExportImportCommand(String action) {
this.action = action;

View file

@ -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

View file

@ -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-<feature_name>=[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 <OPTIONS>%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();
}
}
}

View file

@ -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})",
""},

View file

@ -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 = "<strategy>", defaultValue = "different_files") String users;
@CommandLine.Option(names = "--users-per-file", arity = "1", description = "Set the number of users per file. Its used only if --users=different_files.", paramLabel = "<number>", 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 = "<strategy>",
defaultValue = "different_files")
String users;
@Option(names = "--users-per-file",
arity = "1",
description = "Set the number of users per file. Its used only if --users=different_files.",
paramLabel = "<number>",
defaultValue = "50")
Integer usersPerFile;
public Export() {
super(ACTION_EXPORT);

View file

@ -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);

View file

@ -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} <command> --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 <OPTIONS>%n%n"
+ " Start the server in production mode:%n%n"
+ " $ ${COMMAND-NAME} <OPTIONS>%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} <command> --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<key>=<value>", description = "Set a Java system property", scope = CommandLine.ScopeType.INHERIT, order = 0)
@Option(names = "-D<key>=<value>",
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 = "<path>")
@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 = "<path>")
public void setConfigFile(String path) {
System.setProperty(KeycloakConfigSourceProvider.KEYCLOAK_CONFIG_FILE_PROP, path);
}

View file

@ -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);
}

View file

@ -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 <OPTIONS>%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";

View file

@ -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);
}
}

View file

@ -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",

View file

@ -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());

View file

@ -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