Improvements to error messages
This commit is contained in:
parent
d269af1b70
commit
1a1c42c776
6 changed files with 174 additions and 97 deletions
|
@ -31,7 +31,7 @@
|
|||
<packaging>pom</packaging>
|
||||
|
||||
<properties>
|
||||
<quarkus.version>1.8.2.Final</quarkus.version>
|
||||
<quarkus.version>1.9.0.CR1</quarkus.version>
|
||||
<resteasy.version>4.5.6.Final</resteasy.version>
|
||||
<jackson.version>2.11.2</jackson.version>
|
||||
<jackson.databind.version>${jackson.version}</jackson.databind.version>
|
||||
|
|
|
@ -17,63 +17,80 @@
|
|||
|
||||
package org.keycloak.cli;
|
||||
|
||||
import static org.keycloak.cli.Picocli.createCommandSpec;
|
||||
import static org.keycloak.cli.Picocli.createCommandLine;
|
||||
import static org.keycloak.cli.Picocli.error;
|
||||
import static org.keycloak.cli.Picocli.parseConfigArgs;
|
||||
import static org.keycloak.util.Environment.getProfileOrDefault;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
||||
import io.quarkus.runtime.Quarkus;
|
||||
import org.keycloak.common.Version;
|
||||
|
||||
import io.quarkus.runtime.Quarkus;
|
||||
import io.quarkus.runtime.annotations.QuarkusMain;
|
||||
import org.keycloak.util.Environment;
|
||||
import picocli.CommandLine;
|
||||
|
||||
/**
|
||||
* <p>The main entry point, responsible for initialize and run the CLI as well as start the server.
|
||||
*/
|
||||
@QuarkusMain(name = "keycloak")
|
||||
public class KeycloakMain {
|
||||
|
||||
public static void main(String args[]) {
|
||||
public static void main(String cliArgs[]) {
|
||||
System.setProperty("kc.version", Version.VERSION_KEYCLOAK);
|
||||
CommandLine cmd = createCommandLine();
|
||||
|
||||
if (args.length != 0) {
|
||||
CommandLine cmd = new CommandLine(createCommandSpec());
|
||||
|
||||
cmd.setExecutionExceptionHandler(new CommandLine.IExecutionExceptionHandler() {
|
||||
@Override
|
||||
public int handleExecutionException(Exception ex, CommandLine commandLine,
|
||||
CommandLine.ParseResult parseResult) {
|
||||
commandLine.getErr().println(ex.getMessage());
|
||||
commandLine.usage(commandLine.getErr());
|
||||
return commandLine.getCommandSpec().exitCodeOnExecutionException();
|
||||
}
|
||||
});
|
||||
|
||||
List<String> argsList = new LinkedList<>(Arrays.asList(args));
|
||||
|
||||
try {
|
||||
System.setProperty("kc.config.args", parseConfigArgs(argsList));
|
||||
CommandLine.ParseResult result = cmd.parseArgs(argsList.toArray(new String[argsList.size()]));
|
||||
|
||||
if (!result.hasSubcommand() && (!result.isUsageHelpRequested() && !result.isVersionHelpRequested())) {
|
||||
argsList.add(0, "start");
|
||||
}
|
||||
} catch (CommandLine.UnmatchedArgumentException e) {
|
||||
if (!cmd.getParseResult().hasSubcommand() && argsList.get(0).startsWith("--")) {
|
||||
argsList.add(0, "start");
|
||||
} else {
|
||||
cmd.getErr().println(e.getMessage());
|
||||
System.exit(CommandLine.ExitCode.SOFTWARE);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
cmd.getErr().println(e.getMessage());
|
||||
System.exit(CommandLine.ExitCode.SOFTWARE);
|
||||
}
|
||||
|
||||
System.exit(cmd.execute(argsList.toArray(new String[argsList.size()])));
|
||||
if (cliArgs.length == 0) {
|
||||
// no arguments, just start the server
|
||||
start(cmd);
|
||||
System.exit(cmd.getCommandSpec().exitCodeOnSuccess());
|
||||
}
|
||||
|
||||
Quarkus.run(args);
|
||||
// parse arguments and execute any of the configured commands
|
||||
parseAndRun(cmd, cliArgs);
|
||||
}
|
||||
|
||||
static void start(CommandLine cmd) {
|
||||
// We should use the method call below to start the server once Quarkus is released with https://github.com/quarkusio/quarkus/pull/12532.
|
||||
// It will allow us to properly handle exception thrown during startup and at runtime.
|
||||
// Quarkus.run(null, (integer, throwable) -> {
|
||||
// error(cmd, String.format("Failed to start server using profile (%s).", getProfileOrDefault("none")), throwable.getCause());
|
||||
// });
|
||||
Quarkus.run(null, (integer) -> {
|
||||
error(cmd, String.format("Failed to start server using profile (%s).", getProfileOrDefault("none")), null);
|
||||
});
|
||||
Quarkus.waitForExit();
|
||||
}
|
||||
|
||||
private static void parseAndRun(CommandLine cmd, String[] args) {
|
||||
List<String> cliArgs = new LinkedList<>(Arrays.asList(args));
|
||||
|
||||
// set the arguments as a system property so that arguments can be mapped to their respective configuration options
|
||||
System.setProperty("kc.config.args", parseConfigArgs(cliArgs));
|
||||
|
||||
try {
|
||||
CommandLine.ParseResult result = cmd.parseArgs(cliArgs.toArray(new String[cliArgs.size()]));
|
||||
|
||||
// if no command was set, the start command becomes the default
|
||||
if (!result.hasSubcommand() && (!result.isUsageHelpRequested() && !result.isVersionHelpRequested())) {
|
||||
cliArgs.add(0, "start");
|
||||
}
|
||||
} catch (CommandLine.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");
|
||||
} else {
|
||||
cmd.getErr().println(e.getMessage());
|
||||
System.exit(cmd.getCommandSpec().exitCodeOnInvalidInput());
|
||||
}
|
||||
} catch (Exception e) {
|
||||
cmd.getErr().println(e.getMessage());
|
||||
System.exit(cmd.getCommandSpec().exitCodeOnExecutionException());
|
||||
}
|
||||
|
||||
System.exit(cmd.execute(cliArgs.toArray(new String[cliArgs.size()])));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -17,10 +17,13 @@
|
|||
|
||||
package org.keycloak.cli;
|
||||
|
||||
import static org.keycloak.cli.Picocli.error;
|
||||
import static org.keycloak.cli.Picocli.errorAndExit;
|
||||
import static org.keycloak.cli.Picocli.println;
|
||||
|
||||
import org.keycloak.configuration.KeycloakConfigSourceProvider;
|
||||
|
||||
import io.quarkus.bootstrap.runner.QuarkusEntryPoint;
|
||||
import io.quarkus.runtime.Quarkus;
|
||||
import org.keycloak.util.Environment;
|
||||
import picocli.CommandLine;
|
||||
import picocli.CommandLine.Command;
|
||||
|
@ -68,30 +71,15 @@ public class MainCommand {
|
|||
usageHelpAutoWidth = true,
|
||||
optionListHeading = "%nOptions%n",
|
||||
parameterListHeading = "Available Commands%n")
|
||||
public void reAugment(@Option(names = "--verbose", description = "Print out more details when running this command.", required = false) Boolean debug) {
|
||||
public void reAugment(@Option(names = "--verbose", description = "Print out more details when running this command.", required = false) Boolean verbose) {
|
||||
System.setProperty("quarkus.launch.rebuild", "true");
|
||||
println("Updating the configuration and installing your custom providers, if any. Please wait.");
|
||||
println(spec.commandLine(), "Updating the configuration and installing your custom providers, if any. Please wait.");
|
||||
try {
|
||||
QuarkusEntryPoint.main();
|
||||
println("Server configuration updated and persisted. Run the following command to review the configuration:\n");
|
||||
println("\t" + Environment.getCommand() + " show-config\n");
|
||||
println(spec.commandLine(), "Server configuration updated and persisted. Run the following command to review the configuration:\n");
|
||||
println(spec.commandLine(), "\t" + Environment.getCommand() + " show-config\n");
|
||||
} catch (Throwable throwable) {
|
||||
String message = throwable.getMessage();
|
||||
Throwable cause = throwable.getCause();
|
||||
|
||||
if (cause != null) {
|
||||
message = cause.getMessage();
|
||||
}
|
||||
|
||||
error("Failed to update server configuration: " + message);
|
||||
|
||||
if (debug == null) {
|
||||
errorAndExit("For more details run the same command passing the '--verbose' option.");
|
||||
} else {
|
||||
error("Details:");
|
||||
throwable.printStackTrace();
|
||||
System.exit(spec.exitCodeOnExecutionException());
|
||||
}
|
||||
error(spec.commandLine(), "Failed to update server configuration.", throwable);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -100,10 +88,10 @@ public class MainCommand {
|
|||
mixinStandardHelpOptions = true,
|
||||
optionListHeading = "%nOptions%n",
|
||||
parameterListHeading = "Available Commands%n")
|
||||
public void startDev() {
|
||||
public void startDev(@Option(names = "--verbose", description = "Print out more details when running this command.", required = false) Boolean verbose) {
|
||||
System.setProperty("kc.profile", "dev");
|
||||
System.setProperty("quarkus.profile", "dev");
|
||||
start();
|
||||
KeycloakMain.start(spec.commandLine());
|
||||
}
|
||||
|
||||
@Command(name = "export",
|
||||
|
@ -116,7 +104,8 @@ public class MainCommand {
|
|||
@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,
|
||||
@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. It’s used only if --users=different_files.", paramLabel = "<number>", defaultValue = "50") Integer usersPerFile) {
|
||||
@Option(names = "--users-per-file", arity = "1", description = "Set the number of users per file. It’s used only if --users=different_files.", paramLabel = "<number>", defaultValue = "50") Integer usersPerFile,
|
||||
@Option(names = "--verbose", description = "Print out more details when running this command.", required = false) Boolean verbose) {
|
||||
System.setProperty("keycloak.migration.action", "export");
|
||||
|
||||
if (toDir != null) {
|
||||
|
@ -126,7 +115,7 @@ public class MainCommand {
|
|||
System.setProperty("keycloak.migration.provider", "singleFile");
|
||||
System.setProperty("keycloak.migration.file", toFile);
|
||||
} else {
|
||||
errorAndExit("Must specify either --dir or --file options.");
|
||||
errorAndExit(spec.commandLine(), "Must specify either --dir or --file options.");
|
||||
}
|
||||
|
||||
System.setProperty("keycloak.migration.usersExportStrategy", users.toUpperCase());
|
||||
|
@ -138,7 +127,7 @@ public class MainCommand {
|
|||
if (realm != null) {
|
||||
System.setProperty("keycloak.migration.realmName", realm);
|
||||
}
|
||||
start();
|
||||
KeycloakMain.start(spec.commandLine());
|
||||
}
|
||||
|
||||
@Command(name = "import",
|
||||
|
@ -150,7 +139,8 @@ public class MainCommand {
|
|||
public void runImport(@Option(names = "--dir", arity = "1", description = "Set the path to a directory containing the files with the data to import", paramLabel = "<path>") String toDir,
|
||||
@Option(names = "--file", arity = "1", description = "Set the path to a file with the data to import.", paramLabel = "<path>") String toFile,
|
||||
@Option(names = "--realm", arity = "1", description = "Set the name of the realm to import", paramLabel = "<realm>") String realm,
|
||||
@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,
|
||||
@Option(names = "--verbose", description = "Print out more details when running this command.", required = false) Boolean verbose) {
|
||||
System.setProperty("keycloak.migration.action", "import");
|
||||
if (toDir != null) {
|
||||
System.setProperty("keycloak.migration.provider", "dir");
|
||||
|
@ -159,7 +149,7 @@ public class MainCommand {
|
|||
System.setProperty("keycloak.migration.provider", "singleFile");
|
||||
System.setProperty("keycloak.migration.file", toFile);
|
||||
} else {
|
||||
errorAndExit("Must specify either --dir or --file options.");
|
||||
errorAndExit(spec.commandLine(), "Must specify either --dir or --file options.");
|
||||
}
|
||||
|
||||
if (realm != null) {
|
||||
|
@ -168,7 +158,7 @@ public class MainCommand {
|
|||
|
||||
System.setProperty("keycloak.migration.strategy", override ? "OVERWRITE_EXISTING" : "IGNORE_EXISTING");
|
||||
|
||||
start();
|
||||
KeycloakMain.start(spec.commandLine());
|
||||
}
|
||||
|
||||
@Command(name = "start",
|
||||
|
@ -179,14 +169,15 @@ public class MainCommand {
|
|||
parameterListHeading = "Available Commands%n")
|
||||
public void start(
|
||||
@CommandLine.Parameters(paramLabel = "show-config", arity = "0..1",
|
||||
description = "Print out the configuration options when starting the server.") String showConfig) {
|
||||
description = "Print out the configuration options when starting the server.") String showConfig,
|
||||
@Option(names = "--verbose", description = "Print out more details when running this command.", required = false) Boolean verbose) {
|
||||
if ("show-config".equals(showConfig)) {
|
||||
System.setProperty("kc.show.config.runtime", Boolean.TRUE.toString());
|
||||
System.setProperty("kc.show.config", "all");
|
||||
} else if (showConfig != null) {
|
||||
throw new CommandLine.UnmatchedArgumentException(spec.commandLine(), "Invalid argument: " + showConfig);
|
||||
}
|
||||
start();
|
||||
KeycloakMain.start(spec.commandLine());
|
||||
}
|
||||
|
||||
@Command(name = "show-config",
|
||||
|
@ -195,26 +186,9 @@ public class MainCommand {
|
|||
optionListHeading = "%nOptions%n",
|
||||
parameterListHeading = "Available Commands%n")
|
||||
public void showConfiguration(
|
||||
@CommandLine.Parameters(paramLabel = "filter", defaultValue = "none", description = "Show all configuration options. Use 'all' to show all options.") String filter) {
|
||||
@CommandLine.Parameters(paramLabel = "filter", defaultValue = "none", description = "Show all configuration options. Use 'all' to show all options.") String filter,
|
||||
@Option(names = "--verbose", description = "Print out more details when running this command.", required = false) Boolean verbose) {
|
||||
System.setProperty("kc.show.config", filter);
|
||||
start();
|
||||
}
|
||||
|
||||
private void start() {
|
||||
Quarkus.run();
|
||||
Quarkus.waitForExit();
|
||||
}
|
||||
|
||||
private void println(String message) {
|
||||
spec.commandLine().getOut().println(message);
|
||||
}
|
||||
|
||||
private void errorAndExit(String message) {
|
||||
error(message);
|
||||
System.exit(CommandLine.ExitCode.SOFTWARE);
|
||||
}
|
||||
|
||||
private void error(String message) {
|
||||
spec.commandLine().getErr().println(message);
|
||||
KeycloakMain.start(spec.commandLine());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -18,10 +18,12 @@
|
|||
package org.keycloak.cli;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.function.IntFunction;
|
||||
|
||||
import io.quarkus.runtime.Quarkus;
|
||||
import org.keycloak.common.Profile;
|
||||
import org.keycloak.configuration.PropertyMapper;
|
||||
import org.keycloak.configuration.PropertyMappers;
|
||||
|
@ -30,11 +32,12 @@ import picocli.CommandLine;
|
|||
|
||||
final class Picocli {
|
||||
|
||||
static CommandLine.Model.CommandSpec createCommandSpec() {
|
||||
static CommandLine createCommandLine() {
|
||||
CommandLine.Model.CommandSpec spec = CommandLine.Model.CommandSpec.forAnnotatedObject(new MainCommand())
|
||||
.name(Environment.getCommand());
|
||||
|
||||
addOption(spec, "start", PropertyMappers.getRuntimeMappers());
|
||||
addOption(spec, "start-dev", PropertyMappers.getRuntimeMappers());
|
||||
addOption(spec, "config", PropertyMappers.getRuntimeMappers());
|
||||
addOption(spec, "config", PropertyMappers.getBuiltTimeMappers());
|
||||
addOption(spec.subcommands().get("config").getCommandSpec(), "--features", "Enables a group of features. Possible values are: "
|
||||
|
@ -45,7 +48,20 @@ final class Picocli {
|
|||
addOption(spec.subcommands().get("config").getCommandSpec(), "--features-" + feature.name().toLowerCase(),
|
||||
"Enables the " + feature.name() + " feature. Set enabled to enable the feature or disabled otherwise.");
|
||||
}
|
||||
return spec;
|
||||
|
||||
CommandLine cmd = new CommandLine(spec);
|
||||
|
||||
cmd.setExecutionExceptionHandler(new CommandLine.IExecutionExceptionHandler() {
|
||||
@Override
|
||||
public int handleExecutionException(Exception ex, CommandLine commandLine,
|
||||
CommandLine.ParseResult parseResult) {
|
||||
commandLine.getErr().println(ex.getMessage());
|
||||
commandLine.usage(commandLine.getErr());
|
||||
return commandLine.getCommandSpec().exitCodeOnExecutionException();
|
||||
}
|
||||
});
|
||||
|
||||
return cmd;
|
||||
}
|
||||
|
||||
static String parseConfigArgs(List<String> argsList) {
|
||||
|
@ -92,4 +108,47 @@ final class Picocli {
|
|||
.paramLabel("<value>")
|
||||
.type(String.class).build());
|
||||
}
|
||||
|
||||
static List<String> getCliArgs(CommandLine cmd) {
|
||||
CommandLine.ParseResult parseResult = cmd.getParseResult();
|
||||
|
||||
if (parseResult == null) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
return parseResult.expandedArgs();
|
||||
}
|
||||
|
||||
static void errorAndExit(CommandLine cmd, String message) {
|
||||
error(cmd, message, null);
|
||||
}
|
||||
|
||||
static void error(CommandLine cmd, String message, Throwable throwable) {
|
||||
List<String> cliArgs = getCliArgs(cmd);
|
||||
|
||||
cmd.getErr().println("ERROR: " + message);
|
||||
|
||||
if (throwable != null) {
|
||||
Throwable cause = throwable;
|
||||
|
||||
do {
|
||||
if (cause.getMessage() != null) {
|
||||
cmd.getErr().println(String.format("ERROR: %s", cause.getMessage()));
|
||||
}
|
||||
} while ((cause = cause.getCause())!= null);
|
||||
|
||||
if (cliArgs.stream().anyMatch((arg) -> "--verbose".equals(arg))) {
|
||||
cmd.getErr().println("ERROR: Details:");
|
||||
throwable.printStackTrace();
|
||||
} else {
|
||||
cmd.getErr().println("For more details run the same command passing the '--verbose' option.");
|
||||
}
|
||||
}
|
||||
|
||||
System.exit(cmd.getCommandSpec().exitCodeOnExecutionException());
|
||||
}
|
||||
|
||||
static void println(CommandLine cmd, String message) {
|
||||
cmd.getOut().println(message);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -109,9 +109,9 @@ public final class PropertyMappers {
|
|||
case "postgres-10":
|
||||
return "io.quarkus.hibernate.orm.runtime.dialect.QuarkusPostgreSQL10Dialect";
|
||||
}
|
||||
throw invalidDatabaseVendor(db, "h2-file", "h2-mem", "mariadb", "postgres", "postgres-95", "postgres-10");
|
||||
}, "The database vendor. Possible values are: h2-mem, h2-file, mariadb, postgres95, postgres10.");
|
||||
create("db", "quarkus.datasource.driver", (db, context) -> {
|
||||
return null;
|
||||
}, null);
|
||||
create("db", "quarkus.datasource.jdbc.driver", (db, context) -> {
|
||||
switch (db.toLowerCase()) {
|
||||
case "h2-file":
|
||||
case "h2-mem":
|
||||
|
@ -124,8 +124,21 @@ public final class PropertyMappers {
|
|||
}
|
||||
return null;
|
||||
}, null);
|
||||
create("db", "quarkus.datasource.db-kind", (db, context) -> {
|
||||
switch (db.toLowerCase()) {
|
||||
case "h2-file":
|
||||
case "h2-mem":
|
||||
return "h2";
|
||||
case "mariadb":
|
||||
return "mariadb";
|
||||
case "postgres-95":
|
||||
case "postgres-10":
|
||||
return "postgresql";
|
||||
}
|
||||
throw invalidDatabaseVendor(db, "h2-file", "h2-mem", "mariadb", "postgres", "postgres-95", "postgres-10");
|
||||
}, "The database vendor. Possible values are: h2-mem, h2-file, mariadb, postgres95, postgres10.");
|
||||
create("db", "quarkus.datasource.jdbc.transactions", (db, context) -> "xa", null);
|
||||
create("db.url", "db", "quarkus.datasource.url", (db, context) -> {
|
||||
create("db.url", "db", "quarkus.datasource.jdbc.url", (db, context) -> {
|
||||
switch (db.toLowerCase()) {
|
||||
case "h2-file":
|
||||
return "jdbc:h2:file:${kc.home.dir:${kc.db.url.path:~}}/${kc.data.dir:data}/keycloakdb${kc.db.url.properties:;;AUTO_SERVER=TRUE}";
|
||||
|
|
|
@ -56,6 +56,20 @@ public final class Environment {
|
|||
return profile;
|
||||
}
|
||||
|
||||
public static String getProfileOrDefault(String defaultProfile) {
|
||||
String profile = System.getProperty("kc.profile");
|
||||
|
||||
if (profile == null) {
|
||||
profile = System.getenv("KC_PROFILE");
|
||||
}
|
||||
|
||||
if (profile == null) {
|
||||
profile = defaultProfile;
|
||||
}
|
||||
|
||||
return profile;
|
||||
}
|
||||
|
||||
public static Optional<String> getBuiltTimeProperty(String name) {
|
||||
String value = KeycloakRecorder.getBuiltTimeProperty(name);
|
||||
|
||||
|
|
Loading…
Reference in a new issue