[KEYCLOAK-14255] - Avoid creating CLI during startup

This commit is contained in:
Pedro Igor 2020-10-26 10:20:57 -03:00 committed by Marek Posolda
parent 561ba1541d
commit 3acfad98cf
5 changed files with 63 additions and 57 deletions

View file

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

View file

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

View file

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

View file

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

View file

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