[KEYCLOAK-19767] - Changing paramLabel and options formatting in help
This commit is contained in:
parent
9be4c289d8
commit
430fd35e2f
23 changed files with 289 additions and 60 deletions
|
@ -0,0 +1,80 @@
|
|||
/*
|
||||
* Copyright 2021 Red Hat, Inc. and/or its affiliates
|
||||
* and other contributors as indicated by the @author tags.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.keycloak.quarkus.runtime.cli;
|
||||
|
||||
import static picocli.CommandLine.Help.Column.Overflow.SPAN;
|
||||
import static picocli.CommandLine.Help.Column.Overflow.WRAP;
|
||||
|
||||
import org.keycloak.utils.StringUtil;
|
||||
|
||||
import picocli.CommandLine;
|
||||
|
||||
public class Help extends CommandLine.Help {
|
||||
|
||||
private static final int HELP_WIDTH = 100;
|
||||
|
||||
public Help(CommandLine.Model.CommandSpec commandSpec, ColorScheme colorScheme) {
|
||||
super(commandSpec, colorScheme);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Layout createDefaultLayout() {
|
||||
return new Layout(colorScheme(), createTextTable(), createDefaultOptionRenderer(), createDefaultParameterRenderer());
|
||||
}
|
||||
|
||||
private TextTable createTextTable() {
|
||||
int longOptionsColumnWidth = commandSpec().commandLine().getUsageHelpLongOptionsMaxWidth();
|
||||
int descriptionWidth = HELP_WIDTH - longOptionsColumnWidth;
|
||||
|
||||
// save space by using only two columns with better control over how option names and description are rendered
|
||||
// for now, no support for required options
|
||||
// picocli has a limit of 2 chars for shortnames, we do not
|
||||
TextTable textTable = TextTable.forColumns(colorScheme(),
|
||||
new Column(longOptionsColumnWidth, 0, SPAN), // " -cf, --config-file"
|
||||
new Column(descriptionWidth, 1, WRAP));
|
||||
|
||||
textTable.setAdjustLineBreaksForWideCJKCharacters(commandSpec().usageMessage().adjustLineBreaksForWideCJKCharacters());
|
||||
|
||||
return textTable;
|
||||
}
|
||||
|
||||
@Override
|
||||
public IOptionRenderer createDefaultOptionRenderer() {
|
||||
return new OptionRenderer();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String createHeading(String text, Object... params) {
|
||||
if (StringUtil.isBlank(text)) {
|
||||
return super.createHeading(text, params);
|
||||
}
|
||||
return super.createHeading("%n@|bold " + text + "|@%n%n", params);
|
||||
}
|
||||
|
||||
@Override
|
||||
public IParameterRenderer createDefaultParameterRenderer() {
|
||||
return new IParameterRenderer() {
|
||||
@Override
|
||||
public Ansi.Text[][] render(CommandLine.Model.PositionalParamSpec param,
|
||||
IParamLabelRenderer parameterLabelRenderer, ColorScheme scheme) {
|
||||
// we do our own formatting of parameters and labels when rendering optionsq
|
||||
return new Ansi.Text[0][];
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
|
@ -0,0 +1,86 @@
|
|||
/*
|
||||
* Copyright 2021 Red Hat, Inc. and/or its affiliates
|
||||
* and other contributors as indicated by the @author tags.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.keycloak.quarkus.runtime.cli;
|
||||
|
||||
import static org.keycloak.quarkus.runtime.cli.Picocli.NO_PARAM_LABEL;
|
||||
import static picocli.CommandLine.Help.Ansi.OFF;
|
||||
|
||||
import org.keycloak.utils.StringUtil;
|
||||
|
||||
import picocli.CommandLine;
|
||||
import picocli.CommandLine.Help.Ansi.Text;
|
||||
import picocli.CommandLine.Help.ColorScheme;
|
||||
import picocli.CommandLine.Help.IParamLabelRenderer;
|
||||
import picocli.CommandLine.Model.OptionSpec;
|
||||
|
||||
public class OptionRenderer implements CommandLine.Help.IOptionRenderer {
|
||||
|
||||
private static final String OPTION_NAME_SEPARATOR = ", ";
|
||||
private static final Text EMPTY_TEXT = OFF.text("");
|
||||
|
||||
@Override
|
||||
public Text[][] render(OptionSpec option, IParamLabelRenderer paramLabelRenderer, ColorScheme scheme) {
|
||||
String[] names = option.names();
|
||||
|
||||
if (names.length > 2) {
|
||||
throw new CommandLine.PicocliException("Options should have 2 names at most.");
|
||||
}
|
||||
|
||||
Text shortName = names.length > 1 ? scheme.optionText(names[0]) : EMPTY_TEXT;
|
||||
Text longName = createLongName(option, scheme);
|
||||
Text[][] result = new Text[1][];
|
||||
String[] descriptions = option.description();
|
||||
|
||||
// for better formatting, only a single line is expected in the description
|
||||
// formatting is done by customizations to the text table
|
||||
if (descriptions.length > 1) {
|
||||
throw new CommandLine.PicocliException("Option[" + option + "] description should have a single line.");
|
||||
}
|
||||
|
||||
Text description = scheme.text(descriptions[0]);
|
||||
|
||||
if (EMPTY_TEXT.equals(shortName)) {
|
||||
result[0] = new Text[] { longName, description };
|
||||
} else {
|
||||
result[0] = new Text[] { shortName.concat(OPTION_NAME_SEPARATOR).concat(longName), description };
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private Text createLongName(OptionSpec option, ColorScheme scheme) {
|
||||
Text name = scheme.optionText(option.longestName());
|
||||
String paramLabel = formatParamLabel(option);
|
||||
|
||||
if (StringUtil.isNotBlank(paramLabel) && !NO_PARAM_LABEL.equals(paramLabel) && !option.usageHelp() && !option.versionHelp()) {
|
||||
name = name.concat(" ").concat(paramLabel);
|
||||
}
|
||||
|
||||
return name;
|
||||
}
|
||||
|
||||
private String formatParamLabel(OptionSpec option) {
|
||||
String label = option.paramLabel();
|
||||
|
||||
if (label.startsWith("<") || NO_PARAM_LABEL.equals(label)) {
|
||||
return label;
|
||||
}
|
||||
|
||||
return "<" + label + ">";
|
||||
}
|
||||
}
|
|
@ -71,6 +71,7 @@ public final class Picocli {
|
|||
public static final char ARG_KEY_VALUE_SEPARATOR = '=';
|
||||
public static final Pattern ARG_SPLIT = Pattern.compile(";;");
|
||||
public static final Pattern ARG_KEY_VALUE_SPLIT = Pattern.compile("=");
|
||||
public static final String NO_PARAM_LABEL = "none";
|
||||
|
||||
private Picocli() {
|
||||
}
|
||||
|
@ -256,12 +257,11 @@ public final class Picocli {
|
|||
private static CommandLine createCommandLine(List<String> cliArgs) {
|
||||
CommandSpec spec = CommandSpec.forAnnotatedObject(new Main())
|
||||
.name(Environment.getCommand());
|
||||
|
||||
boolean isStartCommand = cliArgs.size() == 1 && cliArgs.contains(Start.NAME);
|
||||
|
||||
// avoid unnecessary processing when starting the server
|
||||
if (!isStartCommand) {
|
||||
spec.usageMessage().width(100);
|
||||
|
||||
addOption(spec, Start.NAME, hasAutoBuildOption(cliArgs));
|
||||
addOption(spec, StartDev.NAME, true);
|
||||
addOption(spec, Build.NAME, true);
|
||||
|
@ -272,6 +272,7 @@ public final class Picocli {
|
|||
cmd.setExecutionExceptionHandler(new ExecutionExceptionHandler());
|
||||
|
||||
if (!isStartCommand) {
|
||||
cmd.setHelpFactory(Help::new);
|
||||
cmd.getHelpSectionMap().put(SECTION_KEY_COMMAND_LIST, new SubCommandListRenderer());
|
||||
}
|
||||
|
||||
|
@ -339,17 +340,19 @@ public final class Picocli {
|
|||
|
||||
featureGroupBuilder.addArg(OptionSpec.builder(new String[] {"-ft", "--features"})
|
||||
.description("Enables a group of features. Possible values are: " + String.join(",", featuresExpectedValues))
|
||||
.paramLabel("<feature>")
|
||||
.paramLabel("feature")
|
||||
.completionCandidates(featuresExpectedValues)
|
||||
.type(String.class)
|
||||
.build());
|
||||
|
||||
List<String> expectedValues = asList("enabled", "disabled");
|
||||
|
||||
for (Profile.Feature feature : Profile.Feature.values()) {
|
||||
featureGroupBuilder.addArg(OptionSpec.builder("--features-" + feature.name().toLowerCase())
|
||||
.description("Enables the " + feature.name() + " feature.")
|
||||
.paramLabel("[enabled|disabled]")
|
||||
.paramLabel(String.join("|", expectedValues))
|
||||
.type(String.class)
|
||||
.completionCandidates(Arrays.asList("enabled", "disabled"))
|
||||
.completionCandidates(expectedValues)
|
||||
.build());
|
||||
}
|
||||
|
||||
|
@ -386,7 +389,7 @@ public final class Picocli {
|
|||
argGroupBuilder.addArg(OptionSpec.builder(name)
|
||||
.defaultValue(defaultValue)
|
||||
.description(description + (defaultValue == null ? "" : " Default: ${DEFAULT-VALUE}."))
|
||||
.paramLabel("<" + name.substring(2) + ">")
|
||||
.paramLabel(mapper.getParamLabel())
|
||||
.completionCandidates(mapper.getExpectedValues())
|
||||
.type(String.class)
|
||||
.build());
|
||||
|
|
|
@ -31,7 +31,8 @@ import picocli.CommandLine.Model.UsageMessageSpec;
|
|||
* A {@link picocli.CommandLine.IHelpSectionRenderer} based on Quarkus CLI to show subcommands in help messages.
|
||||
*/
|
||||
class SubCommandListRenderer implements CommandLine.IHelpSectionRenderer {
|
||||
// @Override
|
||||
|
||||
@Override
|
||||
public String render(Help help) {
|
||||
CommandSpec spec = help.commandSpec();
|
||||
if (spec.subcommands().isEmpty()) {
|
||||
|
|
|
@ -44,7 +44,7 @@ public abstract class AbstractCommand {
|
|||
@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 = "<config-file>",
|
||||
paramLabel = "file",
|
||||
scope = CommandLine.ScopeType.INHERIT)
|
||||
public void setConfigFile(String path) {
|
||||
System.setProperty(KeycloakConfigSourceProvider.KEYCLOAK_CONFIG_FILE_PROP, path);
|
||||
|
|
|
@ -42,8 +42,8 @@ import picocli.CommandLine.Command;
|
|||
"",
|
||||
"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"
|
||||
footerHeading = "Examples:",
|
||||
footer = " 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"
|
||||
|
@ -55,9 +55,10 @@ import picocli.CommandLine.Command;
|
|||
+ " $ ${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} start --auto-build <OPTIONS>%n%n"
|
||||
+ "By doing that you have an additional overhead when the server is starting.%n%n",
|
||||
+ "By doing that you have an additional overhead when the server is starting.",
|
||||
abbreviateSynopsis = true,
|
||||
optionListHeading = "%nOptions%n%n")
|
||||
optionListHeading = "Options:",
|
||||
commandListHeading = "Commands:")
|
||||
public final class Build extends AbstractCommand implements Runnable {
|
||||
|
||||
public static final String NAME = "build";
|
||||
|
|
|
@ -22,16 +22,14 @@ import picocli.CommandLine.Command;
|
|||
|
||||
@Command(name = "completion",
|
||||
header = "Generate bash/zsh completion script for ${ROOT-COMMAND-NAME:-the root command of this command}.",
|
||||
headerHeading = "%n",
|
||||
commandListHeading = "%nCommands:%n",
|
||||
synopsisHeading = "%nUsage: ",
|
||||
optionListHeading = "Options:%n",
|
||||
optionListHeading = "Options:",
|
||||
commandListHeading = "Commands:",
|
||||
description = {
|
||||
"",
|
||||
"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})",
|
||||
""},
|
||||
" source <(${PARENT-COMMAND-FULL-NAME:-$PARENTCOMMAND} ${COMMAND-NAME})"},
|
||||
abbreviateSynopsis = true)
|
||||
public class Completion extends AutoComplete.GenerateCompletion {
|
||||
}
|
||||
|
|
|
@ -23,11 +23,12 @@ import picocli.CommandLine.Command;
|
|||
import picocli.CommandLine.Option;
|
||||
|
||||
@Command(name = "export",
|
||||
description = "Export data from realms to a file or directory.",
|
||||
header = "Export data from realms to a file or directory.",
|
||||
description = "%nExport data from realms to a file or directory.",
|
||||
showDefaultValues = true,
|
||||
abbreviateSynopsis = true,
|
||||
optionListHeading = "%nOptions%n",
|
||||
parameterListHeading = "Available Commands%n")
|
||||
optionListHeading = "Options:",
|
||||
commandListHeading = "Commands:")
|
||||
public final class Export extends AbstractExportImportCommand implements Runnable {
|
||||
|
||||
@Option(names = "--users",
|
||||
|
|
|
@ -25,11 +25,12 @@ import picocli.CommandLine.Command;
|
|||
import picocli.CommandLine.Option;
|
||||
|
||||
@Command(name = "import",
|
||||
description = "Import data from a directory or a file.",
|
||||
header = "Import data from a directory or a file.",
|
||||
description = "%nImport data from a directory or a file.",
|
||||
showDefaultValues = true,
|
||||
abbreviateSynopsis = true,
|
||||
optionListHeading = "%nOptions%n",
|
||||
parameterListHeading = "Available Commands%n")
|
||||
optionListHeading = "Options:",
|
||||
commandListHeading = "Commands:")
|
||||
public final class Import extends AbstractExportImportCommand implements Runnable {
|
||||
|
||||
@Option(names = "--override",
|
||||
|
|
|
@ -17,6 +17,8 @@
|
|||
|
||||
package org.keycloak.quarkus.runtime.cli.command;
|
||||
|
||||
import static org.keycloak.quarkus.runtime.cli.Picocli.NO_PARAM_LABEL;
|
||||
|
||||
import picocli.CommandLine.Command;
|
||||
import picocli.CommandLine.Option;
|
||||
import picocli.CommandLine.ScopeType;
|
||||
|
@ -25,12 +27,11 @@ import picocli.CommandLine.ScopeType;
|
|||
header = {
|
||||
"Keycloak - Open Source Identity and Access Management",
|
||||
"",
|
||||
"Find more information at: https://www.keycloak.org/docs/latest",
|
||||
""
|
||||
"Find more information at: https://www.keycloak.org/docs/latest"
|
||||
},
|
||||
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"
|
||||
description = "%nUse this command-line tool to manage your Keycloak cluster.",
|
||||
footerHeading = "Examples:",
|
||||
footer = { " 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"
|
||||
|
@ -38,19 +39,18 @@ import picocli.CommandLine.ScopeType;
|
|||
+ " $ ${COMMAND-NAME} start <OPTIONS>%n%n"
|
||||
+ " Enable auto-completion to bash/zsh:%n%n"
|
||||
+ " $ source <(${COMMAND-NAME} tools completion)%n%n"
|
||||
+ " Please, take a look at the documentation for more details before deploying in production.%n",
|
||||
footer = {
|
||||
+ " Please, take a look at the documentation for more details before deploying in production.",
|
||||
"",
|
||||
"Use \"${COMMAND-NAME} start --help\" for the available options when starting the server.",
|
||||
"Use \"${COMMAND-NAME} <command> --help\" for more information about other commands.",
|
||||
"Use \"${COMMAND-NAME} <command> --help\" for more information about other commands."
|
||||
},
|
||||
version = {
|
||||
"Keycloak ${sys:kc.version}",
|
||||
"JVM: ${java.version} (${java.vendor} ${java.vm.name} ${java.vm.version})",
|
||||
"OS: ${os.name} ${os.version} ${os.arch}"
|
||||
},
|
||||
optionListHeading = "Options%n%n",
|
||||
commandListHeading = "%nCommands%n%n",
|
||||
optionListHeading = "Options:",
|
||||
commandListHeading = "Commands:",
|
||||
abbreviateSynopsis = true,
|
||||
subcommands = {
|
||||
Build.class,
|
||||
|
@ -79,8 +79,8 @@ public final class Main {
|
|||
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,
|
||||
description = "Print out error details when running this command.",
|
||||
paramLabel = NO_PARAM_LABEL,
|
||||
scope = ScopeType.INHERIT)
|
||||
Boolean verbose;
|
||||
}
|
|
@ -41,10 +41,10 @@ import picocli.CommandLine.Command;
|
|||
import picocli.CommandLine.Parameters;
|
||||
|
||||
@Command(name = "show-config",
|
||||
description = "Print out the current configuration.",
|
||||
header = "Print out the current configuration.",
|
||||
description = "%nPrint out the current configuration.",
|
||||
abbreviateSynopsis = true,
|
||||
optionListHeading = "%nOptions%n",
|
||||
parameterListHeading = "Available Commands%n")
|
||||
optionListHeading = "Options:")
|
||||
public final class ShowConfig extends AbstractCommand implements Runnable {
|
||||
|
||||
@Parameters(
|
||||
|
|
|
@ -17,18 +17,23 @@
|
|||
|
||||
package org.keycloak.quarkus.runtime.cli.command;
|
||||
|
||||
import static org.keycloak.quarkus.runtime.cli.Picocli.NO_PARAM_LABEL;
|
||||
|
||||
import org.keycloak.quarkus.runtime.cli.Picocli;
|
||||
|
||||
import picocli.CommandLine;
|
||||
import picocli.CommandLine.Command;
|
||||
|
||||
@Command(name = Start.NAME,
|
||||
header = "Start the server.%n",
|
||||
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"
|
||||
footer = "%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 = "%nOptions%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.",
|
||||
optionListHeading = "Options:",
|
||||
commandListHeading = "Commands:",
|
||||
abbreviateSynopsis = true)
|
||||
public final class Start extends AbstractStartCommand implements Runnable {
|
||||
|
||||
|
@ -38,6 +43,7 @@ public final class Start extends AbstractStartCommand implements Runnable {
|
|||
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.",
|
||||
paramLabel = NO_PARAM_LABEL,
|
||||
order = 1)
|
||||
Boolean autoConfig;
|
||||
}
|
||||
|
|
|
@ -27,8 +27,9 @@ import picocli.CommandLine.Command;
|
|||
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 = "%nOptions%n%n",
|
||||
footer = "%nDo NOT start the server using this command when deploying to production.",
|
||||
optionListHeading = "Options:",
|
||||
commandListHeading = "Commands:",
|
||||
abbreviateSynopsis = true)
|
||||
public final class StartDev extends AbstractStartCommand implements Runnable {
|
||||
|
||||
|
|
|
@ -17,13 +17,19 @@
|
|||
|
||||
package org.keycloak.quarkus.runtime.cli.command;
|
||||
|
||||
import picocli.CommandLine;
|
||||
import picocli.CommandLine.Command;
|
||||
|
||||
@Command(name = "tools",
|
||||
description = "Utilities for use and interaction with the server.",
|
||||
description = "%nUtilities for use and interaction with the server.",
|
||||
abbreviateSynopsis = true,
|
||||
optionListHeading = "%nOptions%n",
|
||||
parameterListHeading = "Available Commands%n",
|
||||
commandListHeading = "Commands:",
|
||||
optionListHeading = "Options:",
|
||||
subcommands = {Completion.class})
|
||||
public class Tools {
|
||||
|
||||
@CommandLine.Option(names = { "-h", "--help" },
|
||||
description = "This help message.",
|
||||
usageHelp = true)
|
||||
boolean help;
|
||||
}
|
||||
|
|
|
@ -17,6 +17,7 @@ final class ClusteringPropertyMappers {
|
|||
"configuration file prefixed with the 'cluster-` inside the distribution configuration directory. " +
|
||||
"Value 'local' effectively disables clustering and use infinispan caches in the local mode. " +
|
||||
"Value 'default' enables clustering for infinispan caches.")
|
||||
.paramLabel("mode")
|
||||
.isBuildTimeProperty(true)
|
||||
.expectedValues(Arrays.asList("local", "default"))
|
||||
.build(),
|
||||
|
@ -24,6 +25,7 @@ final class ClusteringPropertyMappers {
|
|||
.to("kc.spi.connections-infinispan.quarkus.stack")
|
||||
.description("Define the default stack to use for cluster communication and node discovery.")
|
||||
.defaultValue("udp")
|
||||
.paramLabel("stack")
|
||||
.isBuildTimeProperty(true)
|
||||
.expectedValues(Arrays.asList("tcp", "udp", "kubernetes", "ec2", "azure", "google"))
|
||||
.build()
|
||||
|
|
|
@ -2,15 +2,15 @@ package org.keycloak.quarkus.runtime.configuration.mappers;
|
|||
|
||||
public enum ConfigCategory {
|
||||
// ordered by name asc
|
||||
CLUSTERING("%nCluster:%n%n", 10),
|
||||
DATABASE("%nDatabase:%n%n", 20),
|
||||
FEATURE("%nFeature:%n%n", 30),
|
||||
HOSTNAME("%nHostname:%n%n", 40),
|
||||
HTTP("%nHTTP/TLS:%n%n", 50),
|
||||
METRICS("%nMetrics:%n%n", 60),
|
||||
PROXY("%nProxy:%n%n", 70),
|
||||
VAULT("%nVault:%n%n", 80),
|
||||
GENERAL("%nGeneral:%n%n", 999);
|
||||
CLUSTERING("Cluster:", 10),
|
||||
DATABASE("Database:", 20),
|
||||
FEATURE("Feature:", 30),
|
||||
HOSTNAME("Hostname:", 40),
|
||||
HTTP("HTTP/TLS:", 50),
|
||||
METRICS("Metrics:", 60),
|
||||
PROXY("Proxy:", 70),
|
||||
VAULT("Vault:", 80),
|
||||
GENERAL("General:", 999);
|
||||
|
||||
private final String heading;
|
||||
|
||||
|
|
|
@ -31,6 +31,7 @@ final class DatabasePropertyMappers {
|
|||
.isBuildTimeProperty(true)
|
||||
.transformer(getSupportedDbValue())
|
||||
.description("The database vendor. Possible values are: " + String.join(",", supportedDatabaseVendors))
|
||||
.paramLabel("vendor")
|
||||
.expectedValues(Arrays.asList(supportedDatabaseVendors))
|
||||
.build(),
|
||||
builder().from("db")
|
||||
|
@ -45,32 +46,39 @@ final class DatabasePropertyMappers {
|
|||
"For instance, if using 'postgres', the JDBC URL would be 'jdbc:postgresql://localhost/keycloak'. " +
|
||||
"The host, database and properties can be overridden by setting the following system properties," +
|
||||
" respectively: -Dkc.db.url.host, -Dkc.db.url.database, -Dkc.db.url.properties.")
|
||||
.paramLabel("jdbc-url")
|
||||
.build(),
|
||||
builder().from("db.username")
|
||||
.to("quarkus.datasource.username")
|
||||
.description("The username of the database user.")
|
||||
.paramLabel("username")
|
||||
.build(),
|
||||
builder().from("db.password")
|
||||
.to("quarkus.datasource.password")
|
||||
.description("The password of the database user.")
|
||||
.paramLabel("password")
|
||||
.isMasked(true)
|
||||
.build(),
|
||||
builder().from("db.schema")
|
||||
.to("quarkus.datasource.schema")
|
||||
.description("The database schema to be used.")
|
||||
.paramLabel("schema")
|
||||
.build(),
|
||||
builder().from("db.pool.initial-size")
|
||||
.to("quarkus.datasource.jdbc.initial-size")
|
||||
.description("The initial size of the connection pool.")
|
||||
.paramLabel("size")
|
||||
.build(),
|
||||
builder().from("db.pool.min-size")
|
||||
.to("quarkus.datasource.jdbc.min-size")
|
||||
.description("The minimal size of the connection pool.")
|
||||
.paramLabel("size")
|
||||
.build(),
|
||||
builder().from("db.pool.max-size")
|
||||
.to("quarkus.datasource.jdbc.max-size")
|
||||
.defaultValue(String.valueOf(100))
|
||||
.description("The maximum size of the connection pool.")
|
||||
.paramLabel("size")
|
||||
.build()
|
||||
};
|
||||
}
|
||||
|
|
|
@ -12,14 +12,17 @@ final class HostnamePropertyMappers {
|
|||
builder().from("hostname-frontend-url")
|
||||
.to("kc.spi.hostname.default.frontend-url")
|
||||
.description("The URL that should be used to serve frontend requests that are usually sent through a public domain.")
|
||||
.paramLabel("url")
|
||||
.build(),
|
||||
builder().from("hostname-admin-url")
|
||||
.to("kc.spi.hostname.default.admin-url")
|
||||
.description("The URL that should be used to expose the admin endpoints and console.")
|
||||
.paramLabel("url")
|
||||
.build(),
|
||||
builder().from("hostname-force-backend-url-to-frontend-url")
|
||||
.to("kc.spi.hostname.default.force-backend-url-to-frontend-url")
|
||||
.description("Forces backend requests to go through the URL defined as the frontend-url. Defaults to false. Possible values are true or false.")
|
||||
.paramLabel(Boolean.TRUE + "|" + Boolean.FALSE)
|
||||
.expectedValues(Arrays.asList(Boolean.TRUE.toString(), Boolean.FALSE.toString()))
|
||||
.build()
|
||||
};
|
||||
|
|
|
@ -23,67 +23,81 @@ final class HttpPropertyMappers {
|
|||
.defaultValue(Boolean.FALSE.toString())
|
||||
.transformer(HttpPropertyMappers::getHttpEnabledTransformer)
|
||||
.description("Enables the HTTP listener.")
|
||||
.paramLabel(Boolean.TRUE + "|" + Boolean.FALSE)
|
||||
.expectedValues(Arrays.asList(Boolean.TRUE.toString(), Boolean.FALSE.toString()))
|
||||
.build(),
|
||||
builder().from("http.host")
|
||||
.to("quarkus.http.host")
|
||||
.defaultValue("0.0.0.0")
|
||||
.description("The used HTTP Host.")
|
||||
.paramLabel("host")
|
||||
.build(),
|
||||
builder().from("http.port")
|
||||
.to("quarkus.http.port")
|
||||
.defaultValue(String.valueOf(8080))
|
||||
.description("The used HTTP port.")
|
||||
.paramLabel("port")
|
||||
.build(),
|
||||
builder().from("https.port")
|
||||
.to("quarkus.http.ssl-port")
|
||||
.defaultValue(String.valueOf(8443))
|
||||
.description("The used HTTPS port.")
|
||||
.paramLabel("port")
|
||||
.build(),
|
||||
builder().from("https.client-auth")
|
||||
.to("quarkus.http.ssl.client-auth")
|
||||
.defaultValue("none")
|
||||
.description("Configures the server to require/request client authentication. Possible Values: none, request, required.")
|
||||
.paramLabel("auth")
|
||||
.expectedValues(Arrays.asList("none", "request", "required"))
|
||||
.build(),
|
||||
builder().from("https.cipher-suites")
|
||||
.to("quarkus.http.ssl.cipher-suites")
|
||||
.description("The cipher suites to use. If none is given, a reasonable default is selected.")
|
||||
.paramLabel("ciphers")
|
||||
.build(),
|
||||
builder().from("https.protocols")
|
||||
.to("quarkus.http.ssl.protocols")
|
||||
.description("The list of protocols to explicitly enable.")
|
||||
.paramLabel("protocols")
|
||||
.build(),
|
||||
builder().from("https.certificate.file")
|
||||
.to("quarkus.http.ssl.certificate.file")
|
||||
.description("The file path to a server certificate or certificate chain in PEM format.")
|
||||
.paramLabel("file")
|
||||
.build(),
|
||||
builder().from("https.certificate.key-file")
|
||||
.to("quarkus.http.ssl.certificate.key-file")
|
||||
.description("The file path to a private key in PEM format.")
|
||||
.paramLabel("file")
|
||||
.build(),
|
||||
builder().from("https.certificate.key-store-file")
|
||||
.to("quarkus.http.ssl.certificate.key-store-file")
|
||||
.defaultValue(getDefaultKeystorePathValue())
|
||||
.description("The key store which holds the certificate information instead of specifying separate files.")
|
||||
.paramLabel("file")
|
||||
.build(),
|
||||
builder().from("https.certificate.key-store-password")
|
||||
.to("quarkus.http.ssl.certificate.key-store-password")
|
||||
.description("The password of the key store file. If not given, the default (\"password\") is used.")
|
||||
.paramLabel("password")
|
||||
.isMasked(true)
|
||||
.build(),
|
||||
builder().from("https.certificate.key-store-file-type")
|
||||
.to("quarkus.http.ssl.certificate.key-store-file-type")
|
||||
.description("The type of the key store file. " +
|
||||
"If not given, the type is automatically detected based on the file name.")
|
||||
.paramLabel("type")
|
||||
.build(),
|
||||
builder().from("https.certificate.trust-store-file")
|
||||
.to("quarkus.http.ssl.certificate.trust-store-file")
|
||||
.description("The trust store which holds the certificate information of the certificates to trust.")
|
||||
.paramLabel("file")
|
||||
.build(),
|
||||
builder().from("https.certificate.trust-store-password")
|
||||
.to("quarkus.http.ssl.certificate.trust-store-password")
|
||||
.description("The password of the trust store file.")
|
||||
.paramLabel("password")
|
||||
.isMasked(true)
|
||||
.build(),
|
||||
builder().from("https.certificate.trust-store-file-type")
|
||||
|
@ -91,6 +105,7 @@ final class HttpPropertyMappers {
|
|||
.defaultValue(getDefaultKeystorePathValue())
|
||||
.description("The type of the trust store file. " +
|
||||
"If not given, the type is automatically detected based on the file name.")
|
||||
.paramLabel("type")
|
||||
.build()
|
||||
|
||||
};
|
||||
|
|
|
@ -14,6 +14,7 @@ final class MetricsPropertyMappers {
|
|||
.isBuildTimeProperty(true)
|
||||
.defaultValue(Boolean.FALSE.toString())
|
||||
.description("If the server should expose metrics and healthcheck. If enabled, metrics are available at the '/metrics' endpoint and healthcheck at the '/health' endpoint.")
|
||||
.paramLabel(Boolean.TRUE + "|" + Boolean.FALSE)
|
||||
.expectedValues(Arrays.asList(Boolean.TRUE.toString(), Boolean.FALSE.toString()))
|
||||
.build()
|
||||
};
|
||||
|
|
|
@ -25,7 +25,8 @@ import org.keycloak.quarkus.runtime.configuration.MicroProfileConfigProvider;
|
|||
|
||||
public class PropertyMapper {
|
||||
|
||||
static PropertyMapper IDENTITY = new PropertyMapper(null, null, null, null, null,false,null,false,Collections.emptyList(),null) {
|
||||
static PropertyMapper IDENTITY = new PropertyMapper(null, null, null, null, null,
|
||||
false,null, null, false,Collections.emptyList(),null) {
|
||||
@Override
|
||||
public ConfigValue getOrDefault(String name, ConfigSourceInterceptorContext context, ConfigValue current) {
|
||||
return current;
|
||||
|
@ -42,9 +43,10 @@ public class PropertyMapper {
|
|||
private final boolean mask;
|
||||
private final Iterable<String> expectedValues;
|
||||
private final ConfigCategory category;
|
||||
private final String paramLabel;
|
||||
|
||||
PropertyMapper(String from, String to, String defaultValue, BiFunction<String, ConfigSourceInterceptorContext, String> mapper,
|
||||
String mapFrom, boolean buildTime, String description, boolean mask, Iterable<String> expectedValues, ConfigCategory category) {
|
||||
String mapFrom, boolean buildTime, String description, String paramLabel, boolean mask, Iterable<String> expectedValues, ConfigCategory category) {
|
||||
this.from = MicroProfileConfigProvider.NS_KEYCLOAK_PREFIX + from;
|
||||
this.to = to;
|
||||
this.defaultValue = defaultValue;
|
||||
|
@ -52,6 +54,7 @@ public class PropertyMapper {
|
|||
this.mapFrom = mapFrom;
|
||||
this.buildTime = buildTime;
|
||||
this.description = description;
|
||||
this.paramLabel = paramLabel;
|
||||
this.mask = mask;
|
||||
this.expectedValues = expectedValues == null ? Collections.emptyList() : expectedValues;
|
||||
this.category = category != null ? category : ConfigCategory.GENERAL;
|
||||
|
@ -174,6 +177,10 @@ public class PropertyMapper {
|
|||
return to;
|
||||
}
|
||||
|
||||
public String getParamLabel() {
|
||||
return paramLabel;
|
||||
}
|
||||
|
||||
public static class Builder {
|
||||
|
||||
private String from;
|
||||
|
@ -186,6 +193,7 @@ public class PropertyMapper {
|
|||
private boolean isBuildTimeProperty = false;
|
||||
private boolean isMasked = false;
|
||||
private ConfigCategory category = ConfigCategory.GENERAL;
|
||||
private String paramLabel;
|
||||
|
||||
public Builder(ConfigCategory category) {
|
||||
this.category = category;
|
||||
|
@ -222,6 +230,11 @@ public class PropertyMapper {
|
|||
return this;
|
||||
}
|
||||
|
||||
public Builder paramLabel(String label) {
|
||||
this.paramLabel = label;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder mapFrom(String mapFrom) {
|
||||
this.mapFrom = mapFrom;
|
||||
return this;
|
||||
|
@ -248,7 +261,7 @@ public class PropertyMapper {
|
|||
}
|
||||
|
||||
public PropertyMapper build() {
|
||||
return new PropertyMapper(from, to, defaultValue, mapper, mapFrom, isBuildTimeProperty, description, isMasked, expectedValues, category);
|
||||
return new PropertyMapper(from, to, defaultValue, mapper, mapFrom, isBuildTimeProperty, description, paramLabel, isMasked, expectedValues, category);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -22,6 +22,7 @@ final class ProxyPropertyMappers {
|
|||
.expectedValues(Arrays.asList(possibleProxyValues))
|
||||
.description("The proxy address forwarding mode if the server is behind a reverse proxy. " +
|
||||
"Possible values are: " + String.join(",",possibleProxyValues))
|
||||
.paramLabel("mode")
|
||||
.category(ConfigCategory.PROXY)
|
||||
.build()
|
||||
};
|
||||
|
|
|
@ -11,6 +11,7 @@ final class VaultPropertyMappers {
|
|||
.from("vault.file.path")
|
||||
.to("kc.spi.vault.files-plaintext.dir")
|
||||
.description("If set, secrets can be obtained by reading the content of files within the given path.")
|
||||
.paramLabel("dir")
|
||||
.build(),
|
||||
builder()
|
||||
.from("vault.hashicorp.")
|
||||
|
@ -21,6 +22,7 @@ final class VaultPropertyMappers {
|
|||
.from("vault.hashicorp.paths")
|
||||
.to("kc.spi.vault.hashicorp.paths")
|
||||
.description("A set of one or more paths that should be used when looking up secrets.")
|
||||
.paramLabel("paths")
|
||||
.build()
|
||||
};
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue