[KEYCLOAK-14255] - Avoid creating CLI during startup
This commit is contained in:
parent
561ba1541d
commit
3acfad98cf
5 changed files with 63 additions and 57 deletions
|
@ -19,10 +19,13 @@ package org.keycloak.cli;
|
|||
|
||||
import static org.keycloak.cli.Picocli.createCommandLine;
|
||||
import static org.keycloak.cli.Picocli.error;
|
||||
import static org.keycloak.cli.Picocli.getCliArgs;
|
||||
import static org.keycloak.cli.Picocli.parseConfigArgs;
|
||||
import static org.keycloak.util.Environment.getProfileOrDefault;
|
||||
|
||||
import java.io.PrintWriter;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
||||
|
@ -35,33 +38,40 @@ import picocli.CommandLine;
|
|||
|
||||
/**
|
||||
* <p>The main entry point, responsible for initialize and run the CLI as well as start the server.
|
||||
*
|
||||
* <p>For optimal startup of the server, the server should be configured first by running the {@link MainCommand#reAugment(Boolean)}
|
||||
* command so that subsequent server starts, without any args, do not need to build the CLI, which is costly.
|
||||
*/
|
||||
@QuarkusMain(name = "keycloak")
|
||||
public class KeycloakMain {
|
||||
|
||||
public static void main(String cliArgs[]) {
|
||||
System.setProperty("kc.version", Version.VERSION_KEYCLOAK);
|
||||
CommandLine cmd = createCommandLine();
|
||||
|
||||
if (cliArgs.length == 0) {
|
||||
// no arguments, just start the server
|
||||
start(cmd);
|
||||
System.exit(cmd.getCommandSpec().exitCodeOnSuccess());
|
||||
start(Collections.emptyList(), new PrintWriter(System.err));
|
||||
System.exit(CommandLine.ExitCode.OK);
|
||||
}
|
||||
|
||||
// parse arguments and execute any of the configured commands
|
||||
parseAndRun(cmd, cliArgs);
|
||||
parseAndRun(cliArgs);
|
||||
}
|
||||
|
||||
static void start(CommandLine cmd) {
|
||||
start(getCliArgs(cmd), cmd.getErr());
|
||||
}
|
||||
|
||||
private static void start(List<String> cliArgs, PrintWriter errorWriter) {
|
||||
Quarkus.run(null, (integer, throwable) -> {
|
||||
error(cmd, String.format("Failed to start server using profile (%s).", getProfileOrDefault("none")), throwable.getCause());
|
||||
error(cliArgs, errorWriter, String.format("Failed to start server using profile (%s).", getProfileOrDefault("none")), throwable.getCause());
|
||||
});
|
||||
Quarkus.waitForExit();
|
||||
}
|
||||
|
||||
private static void parseAndRun(CommandLine cmd, String[] args) {
|
||||
private static void parseAndRun(String[] args) {
|
||||
List<String> cliArgs = new LinkedList<>(Arrays.asList(args));
|
||||
CommandLine cmd = createCommandLine();
|
||||
|
||||
// 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));
|
||||
|
|
|
@ -18,7 +18,6 @@
|
|||
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;
|
||||
|
@ -115,7 +114,7 @@ public class MainCommand {
|
|||
System.setProperty("keycloak.migration.provider", "singleFile");
|
||||
System.setProperty("keycloak.migration.file", toFile);
|
||||
} else {
|
||||
errorAndExit(spec.commandLine(), "Must specify either --dir or --file options.");
|
||||
error(spec.commandLine(), "Must specify either --dir or --file options.");
|
||||
}
|
||||
|
||||
System.setProperty("keycloak.migration.usersExportStrategy", users.toUpperCase());
|
||||
|
@ -149,7 +148,7 @@ public class MainCommand {
|
|||
System.setProperty("keycloak.migration.provider", "singleFile");
|
||||
System.setProperty("keycloak.migration.file", toFile);
|
||||
} else {
|
||||
errorAndExit(spec.commandLine(), "Must specify either --dir or --file options.");
|
||||
error(spec.commandLine(), "Must specify either --dir or --file options.");
|
||||
}
|
||||
|
||||
if (realm != null) {
|
||||
|
|
|
@ -17,6 +17,7 @@
|
|||
|
||||
package org.keycloak.cli;
|
||||
|
||||
import java.io.PrintWriter;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.Iterator;
|
||||
|
@ -124,14 +125,8 @@ final class Picocli {
|
|||
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);
|
||||
|
||||
logError(cmd, "ERROR: " + message);
|
||||
static void error(List<String> cliArgs, PrintWriter errorWriter, String message, Throwable throwable) {
|
||||
logError(errorWriter, "ERROR: " + message);
|
||||
|
||||
if (throwable != null) {
|
||||
boolean verbose = cliArgs.stream().anyMatch((arg) -> "--verbose".equals(arg));
|
||||
|
@ -139,50 +134,58 @@ final class Picocli {
|
|||
if (throwable instanceof InitializationException) {
|
||||
InitializationException initializationException = (InitializationException) throwable;
|
||||
if (initializationException.getSuppressed() == null || initializationException.getSuppressed().length == 0) {
|
||||
dumpException(cmd, initializationException, verbose);
|
||||
dumpException(errorWriter, initializationException, verbose);
|
||||
} else if (initializationException.getSuppressed().length == 1) {
|
||||
dumpException(cmd, initializationException.getSuppressed()[0], verbose);
|
||||
dumpException(errorWriter, initializationException.getSuppressed()[0], verbose);
|
||||
} else {
|
||||
logError(cmd, "ERROR: Multiple configuration errors during startup");
|
||||
logError(errorWriter, "ERROR: Multiple configuration errors during startup");
|
||||
int counter = 0;
|
||||
for (Throwable inner : initializationException.getSuppressed()) {
|
||||
counter++;
|
||||
logError(cmd, "ERROR " + counter);
|
||||
dumpException(cmd, inner, verbose);
|
||||
logError(errorWriter, "ERROR " + counter);
|
||||
dumpException(errorWriter, inner, verbose);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!verbose) {
|
||||
logError(cmd, "For more details run the same command passing the '--verbose' option. Also you can use '--help' to see the details about the usage of the particular command.");
|
||||
logError(errorWriter, "For more details run the same command passing the '--verbose' option. Also you can use '--help' to see the details about the usage of the particular command.");
|
||||
}
|
||||
}
|
||||
|
||||
System.exit(cmd.getCommandSpec().exitCodeOnExecutionException());
|
||||
System.exit(1);
|
||||
}
|
||||
|
||||
static void error(CommandLine cmd, String message, Throwable throwable) {
|
||||
error(getCliArgs(cmd), cmd.getErr(), message, throwable);
|
||||
}
|
||||
|
||||
static void error(CommandLine cmd, String message) {
|
||||
error(getCliArgs(cmd), cmd.getErr(), message, null);
|
||||
}
|
||||
|
||||
static void println(CommandLine cmd, String message) {
|
||||
cmd.getOut().println(message);
|
||||
}
|
||||
|
||||
private static void dumpException(CommandLine cmd, Throwable cause, boolean verbose) {
|
||||
private static void dumpException(PrintWriter errorWriter, Throwable cause, boolean verbose) {
|
||||
if (verbose) {
|
||||
logError(cmd, "ERROR: Details:", cause);
|
||||
logError(errorWriter, "ERROR: Details:", cause);
|
||||
} else {
|
||||
do {
|
||||
if (cause.getMessage() != null) {
|
||||
logError(cmd, String.format("ERROR: %s", cause.getMessage()));
|
||||
logError(errorWriter, String.format("ERROR: %s", cause.getMessage()));
|
||||
}
|
||||
} while ((cause = cause.getCause())!= null);
|
||||
}
|
||||
}
|
||||
|
||||
private static void logError(CommandLine cmd, String errorMessage) {
|
||||
logError(cmd, errorMessage, null);
|
||||
private static void logError(PrintWriter errorWriter, String errorMessage) {
|
||||
logError(errorWriter, errorMessage, null);
|
||||
}
|
||||
|
||||
// The "cause" can be null
|
||||
private static void logError(CommandLine cmd, String errorMessage, Throwable cause) {
|
||||
private static void logError(PrintWriter errorWriter, String errorMessage, Throwable cause) {
|
||||
QuarkusPlatform platform = (QuarkusPlatform) Platform.getPlatform();
|
||||
if (platform.isStarted()) {
|
||||
// Can delegate to proper logger once the platform is started
|
||||
|
@ -193,9 +196,9 @@ final class Picocli {
|
|||
}
|
||||
} else {
|
||||
if (cause == null) {
|
||||
cmd.getErr().println(errorMessage);
|
||||
errorWriter.println(errorMessage);
|
||||
} else {
|
||||
cmd.getErr().println(errorMessage);
|
||||
errorWriter.println(errorMessage);
|
||||
cause.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -16,7 +16,6 @@
|
|||
*/
|
||||
package org.keycloak.configuration;
|
||||
|
||||
import static org.keycloak.configuration.MicroProfileConfigProvider.NS_KEYCLOAK_PREFIX;
|
||||
import static org.keycloak.util.Environment.getBuiltTimeProperty;
|
||||
|
||||
import java.util.HashMap;
|
||||
|
@ -58,7 +57,7 @@ public class PropertyMapper {
|
|||
return MAPPERS.computeIfAbsent(toProperty, s -> new PropertyMapper(fromProperty, s, null, transformer, mapFrom, description));
|
||||
}
|
||||
|
||||
static PropertyMapper forBuildTimeProperty(String fromProperty, String toProperty, BiFunction<String, ConfigSourceInterceptorContext, String> transformer, String description) {
|
||||
static PropertyMapper createBuildTimeProperty(String fromProperty, String toProperty, BiFunction<String, ConfigSourceInterceptorContext, String> transformer, String description) {
|
||||
return MAPPERS.computeIfAbsent(toProperty, s -> new PropertyMapper(fromProperty, s, null, transformer, null, true, description, false));
|
||||
}
|
||||
|
||||
|
@ -95,10 +94,6 @@ public class PropertyMapper {
|
|||
this(from, to, defaultValue, mapper, null, description);
|
||||
}
|
||||
|
||||
PropertyMapper(String from, String to, String defaultValue, BiFunction<String, ConfigSourceInterceptorContext, String> mapper, String description, boolean mask) {
|
||||
this(from, to, defaultValue, mapper, null, false, description, mask);
|
||||
}
|
||||
|
||||
PropertyMapper(String from, String to, String defaultValue, BiFunction<String, ConfigSourceInterceptorContext, String> mapper, String mapFrom, String description) {
|
||||
this(from, to, defaultValue, mapper, mapFrom, false, description, false);
|
||||
}
|
||||
|
@ -127,8 +122,7 @@ public class PropertyMapper {
|
|||
ConfigValue config = context.proceed(from);
|
||||
|
||||
if (config == null) {
|
||||
// fist try runtime configuration
|
||||
Optional<ConfigValue> buildConfig = getBuiltTimeConfig(from, context);
|
||||
Optional<ConfigValue> buildConfig = getBuiltTimeValue(from, context);
|
||||
|
||||
if (buildConfig.isPresent()) {
|
||||
return buildConfig.get();
|
||||
|
@ -137,7 +131,7 @@ public class PropertyMapper {
|
|||
if (mapFrom != null) {
|
||||
// if the property we want to map depends on another one, we use the value from the other property to call the mapper
|
||||
String parentKey = MicroProfileConfigProvider.NS_KEYCLOAK + "." + mapFrom;
|
||||
ConfigValue parentValue = getBuiltTimeConfig(parentKey, context).orElseGet(() -> {
|
||||
ConfigValue parentValue = getBuiltTimeValue(parentKey, context).orElseGet(() -> {
|
||||
ConfigValue value = context.proceed(parentKey);
|
||||
|
||||
if (value == null) {
|
||||
|
@ -179,9 +173,18 @@ public class PropertyMapper {
|
|||
return value;
|
||||
}
|
||||
|
||||
public Optional<ConfigValue> getBuiltTimeConfig(String name, ConfigSourceInterceptorContext context) {
|
||||
public String getFrom() {
|
||||
return from;
|
||||
}
|
||||
|
||||
public String getDescription() {
|
||||
return description;
|
||||
}
|
||||
|
||||
private Optional<ConfigValue> getBuiltTimeValue(String name, ConfigSourceInterceptorContext context) {
|
||||
ConfigValue value = transformValue(getBuiltTimeProperty(name)
|
||||
.orElseGet(() -> getBuiltTimeProperty(PropertyMappers.toCLIFormat(name)).orElse(null)), context);
|
||||
.orElseGet(() -> getBuiltTimeProperty(PropertyMappers.toCLIFormat(name))
|
||||
.orElse(null)), context);
|
||||
|
||||
if (value == null) {
|
||||
return Optional.empty();
|
||||
|
@ -208,23 +211,16 @@ public class PropertyMapper {
|
|||
return null;
|
||||
}
|
||||
|
||||
public boolean isBuildTime() {
|
||||
boolean isBuildTime() {
|
||||
return buildTime;
|
||||
}
|
||||
|
||||
public String getFrom() {
|
||||
return from;
|
||||
}
|
||||
|
||||
public String getDescription() {
|
||||
return description;
|
||||
}
|
||||
|
||||
public boolean isMask() {
|
||||
boolean isMask() {
|
||||
return mask;
|
||||
}
|
||||
|
||||
public String getTo() {
|
||||
String getTo() {
|
||||
return to;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -20,7 +20,7 @@ import static org.keycloak.configuration.Messages.invalidDatabaseVendor;
|
|||
import static org.keycloak.configuration.PropertyMapper.MAPPERS;
|
||||
import static org.keycloak.configuration.PropertyMapper.create;
|
||||
import static org.keycloak.configuration.PropertyMapper.createWithDefault;
|
||||
import static org.keycloak.configuration.PropertyMapper.forBuildTimeProperty;
|
||||
import static org.keycloak.configuration.PropertyMapper.createBuildTimeProperty;
|
||||
import static org.keycloak.provider.quarkus.QuarkusPlatform.addInitializationException;
|
||||
|
||||
import java.io.File;
|
||||
|
@ -33,8 +33,6 @@ import java.util.stream.Collectors;
|
|||
import io.quarkus.runtime.configuration.ProfileManager;
|
||||
import io.smallrye.config.ConfigSourceInterceptorContext;
|
||||
import io.smallrye.config.ConfigValue;
|
||||
import org.keycloak.platform.Platform;
|
||||
import org.keycloak.provider.quarkus.QuarkusPlatform;
|
||||
import org.keycloak.util.Environment;
|
||||
|
||||
/**
|
||||
|
@ -121,7 +119,7 @@ public final class PropertyMappers {
|
|||
}
|
||||
|
||||
private static void configureDatabasePropertyMappers() {
|
||||
forBuildTimeProperty("db", "quarkus.hibernate-orm.dialect", (db, context) -> {
|
||||
createBuildTimeProperty("db", "quarkus.hibernate-orm.dialect", (db, context) -> {
|
||||
switch (db.toLowerCase()) {
|
||||
case "h2-file":
|
||||
case "h2-mem":
|
||||
|
|
Loading…
Reference in a new issue