[KEYCLOAK-19306] - Automatic re-augmentation
This commit is contained in:
parent
15b3af7b06
commit
36706c7bd1
12 changed files with 328 additions and 240 deletions
|
@ -29,7 +29,7 @@ DEBUG_MODE="${DEBUG:-false}"
|
||||||
DEBUG_PORT="${DEBUG_PORT:-8787}"
|
DEBUG_PORT="${DEBUG_PORT:-8787}"
|
||||||
|
|
||||||
CONFIG_ARGS=${CONFIG_ARGS:-""}
|
CONFIG_ARGS=${CONFIG_ARGS:-""}
|
||||||
IS_CONFIGURE="false"
|
IS_DEV_MODE="false"
|
||||||
|
|
||||||
while [ "$#" -gt 0 ]
|
while [ "$#" -gt 0 ]
|
||||||
do
|
do
|
||||||
|
@ -47,6 +47,9 @@ do
|
||||||
;;
|
;;
|
||||||
*)
|
*)
|
||||||
if [[ $1 = --* || ! $1 =~ ^-.* ]]; then
|
if [[ $1 = --* || ! $1 =~ ^-.* ]]; then
|
||||||
|
if [ "$1" = "start-dev" ]; then
|
||||||
|
IS_DEV_MODE=true
|
||||||
|
fi
|
||||||
CONFIG_ARGS="$CONFIG_ARGS $1"
|
CONFIG_ARGS="$CONFIG_ARGS $1"
|
||||||
else
|
else
|
||||||
SERVER_OPTS="$SERVER_OPTS $1"
|
SERVER_OPTS="$SERVER_OPTS $1"
|
||||||
|
@ -77,4 +80,10 @@ fi
|
||||||
|
|
||||||
CLASSPATH_OPTS="$DIRNAME/../lib/quarkus-run.jar"
|
CLASSPATH_OPTS="$DIRNAME/../lib/quarkus-run.jar"
|
||||||
|
|
||||||
exec java $JAVA_OPTS $SERVER_OPTS -cp $CLASSPATH_OPTS io.quarkus.bootstrap.runner.QuarkusEntryPoint ${CONFIG_ARGS#?}
|
JAVA_RUN_OPTS="$JAVA_OPTS $SERVER_OPTS -cp $CLASSPATH_OPTS io.quarkus.bootstrap.runner.QuarkusEntryPoint ${CONFIG_ARGS#?}"
|
||||||
|
|
||||||
|
if [ "$IS_DEV_MODE" = "true" ]; then
|
||||||
|
java -Dkc.dev.rebuild=true $JAVA_RUN_OPTS
|
||||||
|
fi
|
||||||
|
|
||||||
|
exec java $JAVA_RUN_OPTS
|
|
@ -1,61 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright 2020 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.deployment;
|
|
||||||
|
|
||||||
import java.io.File;
|
|
||||||
import java.io.FilenameFilter;
|
|
||||||
import java.net.MalformedURLException;
|
|
||||||
import java.net.URL;
|
|
||||||
import java.net.URLClassLoader;
|
|
||||||
|
|
||||||
import org.jboss.logging.Logger;
|
|
||||||
import org.keycloak.util.Environment;
|
|
||||||
|
|
||||||
public class BuildClassLoader extends URLClassLoader {
|
|
||||||
|
|
||||||
private static final Logger logger = Logger.getLogger(BuildClassLoader.class);
|
|
||||||
|
|
||||||
public BuildClassLoader() {
|
|
||||||
super(new URL[] {}, Thread.currentThread().getContextClassLoader());
|
|
||||||
String homeDir = Environment.getHomeDir();
|
|
||||||
|
|
||||||
if (homeDir == null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
File providersDir = new File(homeDir + File.separator + "providers");
|
|
||||||
|
|
||||||
if (providersDir.isDirectory()) {
|
|
||||||
for (File file : providersDir.listFiles(new JarFilter())) {
|
|
||||||
try {
|
|
||||||
addURL(file.toURI().toURL());
|
|
||||||
logger.debug("Loading providers from " + file.getAbsolutePath());
|
|
||||||
} catch (MalformedURLException e) {
|
|
||||||
throw new RuntimeException("Failed to add provider JAR at " + file.getAbsolutePath());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class JarFilter implements FilenameFilter {
|
|
||||||
@Override
|
|
||||||
public boolean accept(File dir, String name) {
|
|
||||||
return name.toLowerCase().endsWith(".jar");
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -19,12 +19,14 @@ package org.keycloak.quarkus.deployment;
|
||||||
|
|
||||||
import static java.util.Collections.emptyList;
|
import static java.util.Collections.emptyList;
|
||||||
import static org.keycloak.configuration.Configuration.getPropertyNames;
|
import static org.keycloak.configuration.Configuration.getPropertyNames;
|
||||||
import static org.keycloak.configuration.Configuration.getRawValue;
|
|
||||||
import static org.keycloak.connections.jpa.QuarkusJpaConnectionProviderFactory.QUERY_PROPERTY_PREFIX;
|
import static org.keycloak.connections.jpa.QuarkusJpaConnectionProviderFactory.QUERY_PROPERTY_PREFIX;
|
||||||
import static org.keycloak.connections.jpa.util.JpaUtils.loadSpecificNamedQueries;
|
import static org.keycloak.connections.jpa.util.JpaUtils.loadSpecificNamedQueries;
|
||||||
|
import static org.keycloak.configuration.MicroProfileConfigProvider.NS_KEYCLOAK;
|
||||||
import static org.keycloak.representations.provider.ScriptProviderDescriptor.AUTHENTICATORS;
|
import static org.keycloak.representations.provider.ScriptProviderDescriptor.AUTHENTICATORS;
|
||||||
import static org.keycloak.representations.provider.ScriptProviderDescriptor.MAPPERS;
|
import static org.keycloak.representations.provider.ScriptProviderDescriptor.MAPPERS;
|
||||||
import static org.keycloak.representations.provider.ScriptProviderDescriptor.POLICIES;
|
import static org.keycloak.representations.provider.ScriptProviderDescriptor.POLICIES;
|
||||||
|
import static org.keycloak.util.Environment.CLI_ARGS;
|
||||||
|
import static org.keycloak.util.Environment.getProviderFiles;
|
||||||
|
|
||||||
import javax.persistence.spi.PersistenceUnitTransactionType;
|
import javax.persistence.spi.PersistenceUnitTransactionType;
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
|
@ -78,10 +80,8 @@ import org.keycloak.configuration.Configuration;
|
||||||
import org.keycloak.configuration.KeycloakConfigSourceProvider;
|
import org.keycloak.configuration.KeycloakConfigSourceProvider;
|
||||||
import org.keycloak.configuration.MicroProfileConfigProvider;
|
import org.keycloak.configuration.MicroProfileConfigProvider;
|
||||||
import org.keycloak.connections.jpa.DefaultJpaConnectionProviderFactory;
|
import org.keycloak.connections.jpa.DefaultJpaConnectionProviderFactory;
|
||||||
import org.keycloak.connections.jpa.QuarkusJpaConnectionProviderFactory;
|
|
||||||
import org.keycloak.connections.jpa.updater.liquibase.LiquibaseJpaUpdaterProviderFactory;
|
import org.keycloak.connections.jpa.updater.liquibase.LiquibaseJpaUpdaterProviderFactory;
|
||||||
import org.keycloak.connections.jpa.updater.liquibase.conn.DefaultLiquibaseConnectionProvider;
|
import org.keycloak.connections.jpa.updater.liquibase.conn.DefaultLiquibaseConnectionProvider;
|
||||||
import org.keycloak.connections.jpa.util.JpaUtils;
|
|
||||||
import org.keycloak.protocol.ProtocolMapperSpi;
|
import org.keycloak.protocol.ProtocolMapperSpi;
|
||||||
import org.keycloak.protocol.oidc.mappers.DeployedScriptOIDCProtocolMapper;
|
import org.keycloak.protocol.oidc.mappers.DeployedScriptOIDCProtocolMapper;
|
||||||
import org.keycloak.provider.EnvironmentDependentProviderFactory;
|
import org.keycloak.provider.EnvironmentDependentProviderFactory;
|
||||||
|
@ -106,7 +106,6 @@ import org.keycloak.representations.provider.ScriptProviderMetadata;
|
||||||
import org.keycloak.services.NotFoundHandler;
|
import org.keycloak.services.NotFoundHandler;
|
||||||
import org.keycloak.services.ServicesLogger;
|
import org.keycloak.services.ServicesLogger;
|
||||||
import org.keycloak.services.health.KeycloakMetricsHandler;
|
import org.keycloak.services.health.KeycloakMetricsHandler;
|
||||||
import org.keycloak.services.resources.KeycloakApplication;
|
|
||||||
import org.keycloak.transaction.JBossJtaTransactionManagerLookup;
|
import org.keycloak.transaction.JBossJtaTransactionManagerLookup;
|
||||||
import org.keycloak.util.Environment;
|
import org.keycloak.util.Environment;
|
||||||
import org.keycloak.util.JsonSerialization;
|
import org.keycloak.util.JsonSerialization;
|
||||||
|
@ -226,7 +225,7 @@ class KeycloakProcessor {
|
||||||
* @param recorder the recorder
|
* @param recorder the recorder
|
||||||
*/
|
*/
|
||||||
@Record(ExecutionTime.STATIC_INIT)
|
@Record(ExecutionTime.STATIC_INIT)
|
||||||
@BuildStep
|
@BuildStep(onlyIf = isReAugmentation.class)
|
||||||
void setBuildTimeProperties(KeycloakRecorder recorder) {
|
void setBuildTimeProperties(KeycloakRecorder recorder) {
|
||||||
Properties properties = new Properties();
|
Properties properties = new Properties();
|
||||||
|
|
||||||
|
@ -242,6 +241,10 @@ class KeycloakProcessor {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for (File jar : getProviderFiles()) {
|
||||||
|
properties.put(String.format("kc.provider.file.%s.last-modified", jar.getName()), String.valueOf(jar.lastModified()));
|
||||||
|
}
|
||||||
|
|
||||||
File file = KeycloakConfigSourceProvider.getPersistedConfigFile().toFile();
|
File file = KeycloakConfigSourceProvider.getPersistedConfigFile().toFile();
|
||||||
|
|
||||||
if (file.exists()) {
|
if (file.exists()) {
|
||||||
|
@ -253,15 +256,11 @@ class KeycloakProcessor {
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
throw new RuntimeException("Failed to generate persisted.properties file", e);
|
throw new RuntimeException("Failed to generate persisted.properties file", e);
|
||||||
}
|
}
|
||||||
|
|
||||||
recorder.validateAndSetBuildTimeProperties(Environment.isRebuild(), getRawValue("kc.config.args"));
|
|
||||||
|
|
||||||
recorder.showConfig();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean isNotPersistentProperty(String name) {
|
private boolean isNotPersistentProperty(String name) {
|
||||||
// these properties are ignored from the build time properties as they are runtime-specific
|
// these properties are ignored from the build time properties as they are runtime-specific
|
||||||
return !name.startsWith("kc") || "kc.home.dir".equals(name) || "kc.config.args".equals(name);
|
return !name.startsWith(NS_KEYCLOAK) || "kc.home.dir".equals(name) || CLI_ARGS.equals(name);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -343,14 +342,14 @@ class KeycloakProcessor {
|
||||||
private Map<Spi, Map<Class<? extends Provider>, Map<String, ProviderFactory>>> loadFactories(
|
private Map<Spi, Map<Class<? extends Provider>, Map<String, ProviderFactory>>> loadFactories(
|
||||||
Map<String, ProviderFactory> preConfiguredProviders) {
|
Map<String, ProviderFactory> preConfiguredProviders) {
|
||||||
Config.init(new MicroProfileConfigProvider());
|
Config.init(new MicroProfileConfigProvider());
|
||||||
BuildClassLoader providerClassLoader = new BuildClassLoader();
|
ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
|
||||||
ProviderManager pm = new ProviderManager(KeycloakDeploymentInfo.create().services(), providerClassLoader);
|
ProviderManager pm = new ProviderManager(KeycloakDeploymentInfo.create().services(), classLoader);
|
||||||
Map<Spi, Map<Class<? extends Provider>, Map<String, ProviderFactory>>> factories = new HashMap<>();
|
Map<Spi, Map<Class<? extends Provider>, Map<String, ProviderFactory>>> factories = new HashMap<>();
|
||||||
|
|
||||||
for (Spi spi : pm.loadSpis()) {
|
for (Spi spi : pm.loadSpis()) {
|
||||||
Map<Class<? extends Provider>, Map<String, ProviderFactory>> providers = new HashMap<>();
|
Map<Class<? extends Provider>, Map<String, ProviderFactory>> providers = new HashMap<>();
|
||||||
List<ProviderFactory> loadedFactories = new ArrayList<>(pm.load(spi));
|
List<ProviderFactory> loadedFactories = new ArrayList<>(pm.load(spi));
|
||||||
Map<String, ProviderFactory> deployedScriptProviders = loadDeployedScriptProviders(providerClassLoader, spi);
|
Map<String, ProviderFactory> deployedScriptProviders = loadDeployedScriptProviders(classLoader, spi);
|
||||||
|
|
||||||
loadedFactories.addAll(deployedScriptProviders.values());
|
loadedFactories.addAll(deployedScriptProviders.values());
|
||||||
preConfiguredProviders.putAll(deployedScriptProviders);
|
preConfiguredProviders.putAll(deployedScriptProviders);
|
||||||
|
@ -384,12 +383,12 @@ class KeycloakProcessor {
|
||||||
return factories;
|
return factories;
|
||||||
}
|
}
|
||||||
|
|
||||||
private Map<String, ProviderFactory> loadDeployedScriptProviders(BuildClassLoader providerClassLoader, Spi spi) {
|
private Map<String, ProviderFactory> loadDeployedScriptProviders(ClassLoader classLoader, Spi spi) {
|
||||||
Map<String, ProviderFactory> providers = new HashMap<>();
|
Map<String, ProviderFactory> providers = new HashMap<>();
|
||||||
|
|
||||||
if (supportsDeployeableScripts(spi)) {
|
if (supportsDeployeableScripts(spi)) {
|
||||||
try {
|
try {
|
||||||
Enumeration<URL> urls = providerClassLoader.getResources(KEYCLOAK_SCRIPTS_JSON_PATH);
|
Enumeration<URL> urls = classLoader.getResources(KEYCLOAK_SCRIPTS_JSON_PATH);
|
||||||
|
|
||||||
while (urls.hasMoreElements()) {
|
while (urls.hasMoreElements()) {
|
||||||
URL url = urls.nextElement();
|
URL url = urls.nextElement();
|
||||||
|
|
|
@ -0,0 +1,30 @@
|
||||||
|
/*
|
||||||
|
* 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.deployment;
|
||||||
|
|
||||||
|
import java.util.function.BooleanSupplier;
|
||||||
|
import org.keycloak.util.Environment;
|
||||||
|
|
||||||
|
public class isReAugmentation implements BooleanSupplier {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean getAsBoolean() {
|
||||||
|
return Environment.isRebuild();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -17,24 +17,35 @@
|
||||||
|
|
||||||
package org.keycloak.cli;
|
package org.keycloak.cli;
|
||||||
|
|
||||||
|
import static org.keycloak.cli.MainCommand.CONFIG_COMMAND;
|
||||||
|
import static org.keycloak.cli.MainCommand.START_COMMAND;
|
||||||
|
import static org.keycloak.cli.MainCommand.isStartDevCommand;
|
||||||
import static org.keycloak.cli.Picocli.createCommandLine;
|
import static org.keycloak.cli.Picocli.createCommandLine;
|
||||||
import static org.keycloak.cli.Picocli.error;
|
import static org.keycloak.cli.Picocli.error;
|
||||||
import static org.keycloak.cli.Picocli.getCliArgs;
|
import static org.keycloak.cli.Picocli.getCliArgs;
|
||||||
import static org.keycloak.cli.Picocli.parseConfigArgs;
|
import static org.keycloak.cli.Picocli.parseConfigArgs;
|
||||||
|
import static org.keycloak.configuration.Configuration.getConfig;
|
||||||
|
import static org.keycloak.configuration.PropertyMappers.isBuildTimeProperty;
|
||||||
import static org.keycloak.util.Environment.getProfileOrDefault;
|
import static org.keycloak.util.Environment.getProfileOrDefault;
|
||||||
import static org.keycloak.util.Environment.isDevMode;
|
import static org.keycloak.util.Environment.isDevMode;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.FileInputStream;
|
||||||
|
import java.io.InputStream;
|
||||||
import java.io.PrintWriter;
|
import java.io.PrintWriter;
|
||||||
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.LinkedList;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Properties;
|
||||||
|
|
||||||
import io.quarkus.runtime.Quarkus;
|
import io.quarkus.runtime.Quarkus;
|
||||||
import org.keycloak.common.Version;
|
import org.keycloak.common.Version;
|
||||||
|
|
||||||
import io.quarkus.runtime.annotations.QuarkusMain;
|
import io.quarkus.runtime.annotations.QuarkusMain;
|
||||||
|
|
||||||
|
import org.keycloak.configuration.KeycloakConfigSourceProvider;
|
||||||
import org.keycloak.util.Environment;
|
import org.keycloak.util.Environment;
|
||||||
|
|
||||||
import picocli.CommandLine;
|
import picocli.CommandLine;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -46,12 +57,14 @@ import picocli.CommandLine;
|
||||||
@QuarkusMain(name = "keycloak")
|
@QuarkusMain(name = "keycloak")
|
||||||
public class KeycloakMain {
|
public class KeycloakMain {
|
||||||
|
|
||||||
public static void main(String cliArgs[]) {
|
public static void main(String[] args) {
|
||||||
System.setProperty("kc.version", Version.VERSION_KEYCLOAK);
|
System.setProperty("kc.version", Version.VERSION_KEYCLOAK);
|
||||||
|
List<String> cliArgs = new ArrayList<>(Arrays.asList(args));
|
||||||
|
System.setProperty(Environment.CLI_ARGS, parseConfigArgs(cliArgs));
|
||||||
|
|
||||||
if (cliArgs.length == 0) {
|
if (cliArgs.isEmpty()) {
|
||||||
// no arguments, just start the server
|
// no arguments, just start the server
|
||||||
start(Collections.emptyList(), new PrintWriter(System.err));
|
start(cliArgs, new PrintWriter(System.err));
|
||||||
if (!isDevMode()) {
|
if (!isDevMode()) {
|
||||||
System.exit(CommandLine.ExitCode.OK);
|
System.exit(CommandLine.ExitCode.OK);
|
||||||
}
|
}
|
||||||
|
@ -84,19 +97,26 @@ public class KeycloakMain {
|
||||||
Quarkus.waitForExit();
|
Quarkus.waitForExit();
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void parseAndRun(String[] args) {
|
private static void parseAndRun(List<String> cliArgs) {
|
||||||
List<String> cliArgs = new LinkedList<>(Arrays.asList(args));
|
|
||||||
CommandLine cmd = createCommandLine();
|
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));
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
CommandLine.ParseResult result = cmd.parseArgs(cliArgs.toArray(new String[cliArgs.size()]));
|
CommandLine.ParseResult result = cmd.parseArgs(cliArgs.toArray(new String[0]));
|
||||||
|
|
||||||
|
if (result.hasSubcommand()) {
|
||||||
|
if (isStartDevCommand(result.subcommand().commandSpec())) {
|
||||||
|
String profile = Environment.getProfile();
|
||||||
|
|
||||||
|
if (profile == null) {
|
||||||
|
// force the server image to be set with the dev profile
|
||||||
|
Environment.forceDevProfile();
|
||||||
|
}
|
||||||
|
|
||||||
|
runReAugmentationIfNeeded(cliArgs, cmd);
|
||||||
|
}
|
||||||
|
} else if ((!result.isUsageHelpRequested() && !result.isVersionHelpRequested())) {
|
||||||
// if no command was set, the start command becomes the default
|
// if no command was set, the start command becomes the default
|
||||||
if (!result.hasSubcommand() && (!result.isUsageHelpRequested() && !result.isVersionHelpRequested())) {
|
cliArgs.add(0, START_COMMAND);
|
||||||
cliArgs.add(0, "start");
|
|
||||||
}
|
}
|
||||||
} catch (CommandLine.UnmatchedArgumentException e) {
|
} catch (CommandLine.UnmatchedArgumentException e) {
|
||||||
// if no command was set but options were provided, the start command becomes the default
|
// if no command was set but options were provided, the start command becomes the default
|
||||||
|
@ -111,10 +131,114 @@ public class KeycloakMain {
|
||||||
System.exit(cmd.getCommandSpec().exitCodeOnExecutionException());
|
System.exit(cmd.getCommandSpec().exitCodeOnExecutionException());
|
||||||
}
|
}
|
||||||
|
|
||||||
int exitCode = cmd.execute(cliArgs.toArray(new String[cliArgs.size()]));
|
int exitCode = cmd.execute(cliArgs.toArray(new String[0]));
|
||||||
|
|
||||||
if (!isDevMode()) {
|
if (!isDevMode()) {
|
||||||
System.exit(exitCode);
|
System.exit(exitCode);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static void runReAugmentationIfNeeded(List<String> cliArgs, CommandLine cmd) {
|
||||||
|
if (Boolean.getBoolean("kc.dev.rebuild")) {
|
||||||
|
if (requiresReAugmentation(cliArgs)) {
|
||||||
|
runReAugmentation(cliArgs, cmd);
|
||||||
|
}
|
||||||
|
System.exit(cmd.getCommandSpec().exitCodeOnSuccess());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean requiresReAugmentation(List<String> cliArgs) {
|
||||||
|
if (hasConfigChanges()) {
|
||||||
|
System.out.printf("Changes detected in configuration. Updating the server image.\n");
|
||||||
|
|
||||||
|
List<String> suggestedArgs = cliArgs.subList(1, cliArgs.size());
|
||||||
|
|
||||||
|
suggestedArgs.removeAll(Arrays.asList("--verbose", "--help"));
|
||||||
|
|
||||||
|
System.out.printf("For an optional runtime and bypass this step, please run the 'config' command prior to starting the server:\n\n\t%s config %s\n",
|
||||||
|
Environment.getCommand(),
|
||||||
|
String.join(" ", suggestedArgs) + "\n");
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return hasProviderChanges();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void runReAugmentation(List<String> cliArgs, CommandLine cmd) {
|
||||||
|
List<String> configArgsList = new ArrayList<>(cliArgs);
|
||||||
|
|
||||||
|
if (!configArgsList.get(0).startsWith("--")) {
|
||||||
|
configArgsList.remove(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
configArgsList.add(0, CONFIG_COMMAND);
|
||||||
|
|
||||||
|
cmd.execute(configArgsList.toArray(new String[0]));
|
||||||
|
|
||||||
|
System.out.printf("Next time you run the server, just run:\n\n\t%s\n\n", Environment.getCommand());
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean hasProviderChanges() {
|
||||||
|
File propertiesFile = KeycloakConfigSourceProvider.getPersistedConfigFile().toFile();
|
||||||
|
File[] providerFiles = Environment.getProviderFiles();
|
||||||
|
|
||||||
|
if (!propertiesFile.exists()) {
|
||||||
|
return providerFiles.length > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
Properties properties = new Properties();
|
||||||
|
|
||||||
|
try (InputStream is = new FileInputStream(propertiesFile)) {
|
||||||
|
properties.load(is);
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new RuntimeException("Failed to load persisted properties", e);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (String key : properties.stringPropertyNames()) {
|
||||||
|
if (key.startsWith("kc.provider.file")) {
|
||||||
|
if (providerFiles.length == 0) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
String fileName = key.substring("kc.provider.file".length() + 1, key.lastIndexOf('.'));
|
||||||
|
String lastModified = properties.getProperty(key);
|
||||||
|
|
||||||
|
for (File file : providerFiles) {
|
||||||
|
if (file.getName().equals(fileName) && !lastModified.equals(String.valueOf(file.lastModified()))) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return providerFiles.length > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean hasConfigChanges() {
|
||||||
|
for (String propertyName : getConfig().getPropertyNames()) {
|
||||||
|
// only check keycloak build-time properties
|
||||||
|
if (!isBuildTimeProperty(propertyName)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// try to resolve any property set using profiles
|
||||||
|
if (propertyName.startsWith("%")) {
|
||||||
|
propertyName = propertyName.substring(propertyName.indexOf('.') + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
String currentValue = Environment.getBuiltTimeProperty(propertyName).orElse(null);
|
||||||
|
String newValue = getConfig().getConfigValue(propertyName).getValue();
|
||||||
|
|
||||||
|
if (newValue != null && !newValue.equalsIgnoreCase(currentValue)) {
|
||||||
|
// changes to a single property are enough to indicate changes to configuration
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -38,7 +38,7 @@ import picocli.CommandLine.Spec;
|
||||||
|
|
||||||
@Command(name = "keycloak",
|
@Command(name = "keycloak",
|
||||||
usageHelpWidth = 150,
|
usageHelpWidth = 150,
|
||||||
header = "Keycloak - Open Source Identity and Access Management\n\nFind more information at: https://www.keycloak.org/%n",
|
header = "Keycloak - Open Source Identity and Access Management%n%nFind more information at: https://www.keycloak.org/%n",
|
||||||
description = "Use this command-line tool to manage your Keycloak cluster%n", footerHeading = "%nUse \"${COMMAND-NAME} <command> --help\" for more information about a command.%nUse \"${COMMAND-NAME} options\" for a list of all command-line options.",
|
description = "Use this command-line tool to manage your Keycloak cluster%n", footerHeading = "%nUse \"${COMMAND-NAME} <command> --help\" for more information about a command.%nUse \"${COMMAND-NAME} options\" for a list of all command-line options.",
|
||||||
footer = "%nby Red Hat",
|
footer = "%nby Red Hat",
|
||||||
optionListHeading = "Configuration Options%n%n",
|
optionListHeading = "Configuration Options%n%n",
|
||||||
|
@ -50,6 +50,14 @@ import picocli.CommandLine.Spec;
|
||||||
})
|
})
|
||||||
public class MainCommand {
|
public class MainCommand {
|
||||||
|
|
||||||
|
static final String START_DEV_COMMAND = "start-dev";
|
||||||
|
static final String START_COMMAND = "start";
|
||||||
|
static final String CONFIG_COMMAND = "config";
|
||||||
|
|
||||||
|
public static boolean isStartDevCommand(CommandSpec commandSpec) {
|
||||||
|
return START_DEV_COMMAND.equals(commandSpec.name());
|
||||||
|
}
|
||||||
|
|
||||||
@Spec
|
@Spec
|
||||||
CommandSpec spec;
|
CommandSpec spec;
|
||||||
|
|
||||||
|
@ -70,7 +78,7 @@ public class MainCommand {
|
||||||
System.setProperty(KeycloakConfigSourceProvider.KEYCLOAK_CONFIG_FILE_PROP, path);
|
System.setProperty(KeycloakConfigSourceProvider.KEYCLOAK_CONFIG_FILE_PROP, path);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Command(name = "config",
|
@Command(name = CONFIG_COMMAND,
|
||||||
description = "%nCreates a new server image based on the 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. Some configuration options require this command to be executed in order to actually change a configuration. For instance, the database vendor.%n",
|
description = "%nCreates a new server image based on the 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. Some configuration options require this command to be executed in order to actually change a configuration. For instance, the database vendor.%n",
|
||||||
mixinStandardHelpOptions = true,
|
mixinStandardHelpOptions = true,
|
||||||
usageHelpAutoWidth = true,
|
usageHelpAutoWidth = true,
|
||||||
|
@ -104,14 +112,18 @@ public class MainCommand {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Command(name = "start-dev",
|
@Command(name = START_DEV_COMMAND,
|
||||||
description = "%nStart the server in development mode.%n",
|
description = "%nStart the server in development mode.%n",
|
||||||
mixinStandardHelpOptions = true,
|
mixinStandardHelpOptions = true,
|
||||||
optionListHeading = "%nOptions%n",
|
optionListHeading = "%nOptions%n",
|
||||||
parameterListHeading = "Available Commands%n")
|
parameterListHeading = "Available Commands%n")
|
||||||
public void startDev(@Option(names = "--verbose", description = "Print out more details when running this command.", required = false) Boolean verbose) {
|
public void startDev(@Option(names = "--verbose", description = "Print out more details when running this command.", required = false) Boolean verbose) {
|
||||||
setProfile("dev");
|
Environment.forceDevProfile();
|
||||||
KeycloakMain.start(spec.commandLine());
|
CommandLine cmd = spec.commandLine();
|
||||||
|
|
||||||
|
cmd.getOut().printf("Running the server in dev mode. DO NOT run the '%s' command in production.\n", START_DEV_COMMAND);
|
||||||
|
|
||||||
|
KeycloakMain.start(cmd);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Command(name = "export",
|
@Command(name = "export",
|
||||||
|
@ -151,7 +163,7 @@ public class MainCommand {
|
||||||
runImportExport(ACTION_IMPORT, toDir, toFile, realm, verbose);
|
runImportExport(ACTION_IMPORT, toDir, toFile, realm, verbose);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Command(name = "start",
|
@Command(name = START_COMMAND,
|
||||||
description = "%nStart the server.%n",
|
description = "%nStart the server.%n",
|
||||||
mixinStandardHelpOptions = true,
|
mixinStandardHelpOptions = true,
|
||||||
usageHelpAutoWidth = true,
|
usageHelpAutoWidth = true,
|
||||||
|
@ -180,7 +192,7 @@ public class MainCommand {
|
||||||
@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) {
|
@Option(names = "--verbose", description = "Print out more details when running this command.", required = false) Boolean verbose) {
|
||||||
System.setProperty("kc.show.config", filter);
|
System.setProperty("kc.show.config", filter);
|
||||||
KeycloakMain.start(spec.commandLine());
|
ShowConfigCommand.run();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void runImportExport(String action, String toDir, String toFile, String realm, Boolean verbose) {
|
private void runImportExport(String action, String toDir, String toFile, String realm, Boolean verbose) {
|
||||||
|
|
|
@ -17,7 +17,12 @@
|
||||||
|
|
||||||
package org.keycloak.cli;
|
package org.keycloak.cli;
|
||||||
|
|
||||||
|
import static org.keycloak.cli.MainCommand.CONFIG_COMMAND;
|
||||||
|
import static org.keycloak.cli.MainCommand.START_COMMAND;
|
||||||
|
import static org.keycloak.cli.MainCommand.START_DEV_COMMAND;
|
||||||
|
|
||||||
import java.io.PrintWriter;
|
import java.io.PrintWriter;
|
||||||
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
|
@ -42,16 +47,12 @@ final class Picocli {
|
||||||
CommandLine.Model.CommandSpec spec = CommandLine.Model.CommandSpec.forAnnotatedObject(new MainCommand())
|
CommandLine.Model.CommandSpec spec = CommandLine.Model.CommandSpec.forAnnotatedObject(new MainCommand())
|
||||||
.name(Environment.getCommand());
|
.name(Environment.getCommand());
|
||||||
|
|
||||||
addOption(spec, "start", PropertyMappers.getRuntimeMappers());
|
addOption(spec, START_COMMAND, false);
|
||||||
addOption(spec, "start-dev", PropertyMappers.getRuntimeMappers());
|
addOption(spec, START_DEV_COMMAND, true);
|
||||||
addOption(spec, "config", PropertyMappers.getRuntimeMappers());
|
addOption(spec, CONFIG_COMMAND, true);
|
||||||
addOption(spec, "config", PropertyMappers.getBuiltTimeMappers());
|
|
||||||
addOption(spec.subcommands().get("config").getCommandSpec(), "--features", "Enables a group of features. Possible values are: "
|
|
||||||
+ String.join(",", Arrays.asList(Profile.Type.values()).stream().map(
|
|
||||||
type -> type.name().toLowerCase()).toArray((IntFunction<CharSequence[]>) String[]::new)));
|
|
||||||
|
|
||||||
for (Profile.Feature feature : Profile.Feature.values()) {
|
for (Profile.Feature feature : Profile.Feature.values()) {
|
||||||
addOption(spec.subcommands().get("config").getCommandSpec(), "--features-" + feature.name().toLowerCase(),
|
addOption(spec.subcommands().get(CONFIG_COMMAND).getCommandSpec(), "--features-" + feature.name().toLowerCase(),
|
||||||
"Enables the " + feature.name() + " feature. Set enabled to enable the feature or disabled otherwise.");
|
"Enables the " + feature.name() + " feature. Set enabled to enable the feature or disabled otherwise.");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -78,6 +79,7 @@ final class Picocli {
|
||||||
String key = iterator.next();
|
String key = iterator.next();
|
||||||
|
|
||||||
// TODO: ignore properties for providers for now, need to fetch them from the providers, otherwise CLI will complain about invalid options
|
// TODO: ignore properties for providers for now, need to fetch them from the providers, otherwise CLI will complain about invalid options
|
||||||
|
// change this once we are able to obtain properties from providers
|
||||||
if (key.startsWith("--spi")) {
|
if (key.startsWith("--spi")) {
|
||||||
iterator.remove();
|
iterator.remove();
|
||||||
}
|
}
|
||||||
|
@ -93,8 +95,13 @@ final class Picocli {
|
||||||
return options.toString();
|
return options.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void addOption(CommandLine.Model.CommandSpec spec, String command, List<PropertyMapper> mappers) {
|
private static void addOption(CommandLine.Model.CommandSpec spec, String command, boolean includeBuildTime) {
|
||||||
CommandLine.Model.CommandSpec commandSpec = spec.subcommands().get(command).getCommandSpec();
|
CommandLine.Model.CommandSpec commandSpec = spec.subcommands().get(command).getCommandSpec();
|
||||||
|
List<PropertyMapper> mappers = new ArrayList<>(PropertyMappers.getRuntimeMappers());
|
||||||
|
|
||||||
|
if (includeBuildTime) {
|
||||||
|
mappers.addAll(PropertyMappers.getBuiltTimeMappers());
|
||||||
|
}
|
||||||
|
|
||||||
for (PropertyMapper mapper : mappers) {
|
for (PropertyMapper mapper : mappers) {
|
||||||
String name = "--" + PropertyMappers.toCLIFormat(mapper.getFrom()).substring(3);
|
String name = "--" + PropertyMappers.toCLIFormat(mapper.getFrom()).substring(3);
|
||||||
|
@ -106,6 +113,10 @@ final class Picocli {
|
||||||
|
|
||||||
addOption(commandSpec, name, description);
|
addOption(commandSpec, name, description);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
addOption(commandSpec, "--features", "Enables a group of features. Possible values are: "
|
||||||
|
+ String.join(",", Arrays.stream(Profile.Type.values()).map(
|
||||||
|
type -> type.name().toLowerCase()).toArray((IntFunction<CharSequence[]>) String[]::new)));
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void addOption(CommandLine.Model.CommandSpec commandSpec, String name, String description) {
|
private static void addOption(CommandLine.Model.CommandSpec commandSpec, String name, String description) {
|
||||||
|
@ -129,7 +140,7 @@ final class Picocli {
|
||||||
logError(errorWriter, "ERROR: " + message);
|
logError(errorWriter, "ERROR: " + message);
|
||||||
|
|
||||||
if (throwable != null) {
|
if (throwable != null) {
|
||||||
boolean verbose = cliArgs.stream().anyMatch((arg) -> "--verbose".equals(arg));
|
boolean verbose = cliArgs.stream().anyMatch("--verbose"::equals);
|
||||||
|
|
||||||
if (throwable instanceof InitializationException) {
|
if (throwable instanceof InitializationException) {
|
||||||
InitializationException initializationException = (InitializationException) throwable;
|
InitializationException initializationException = (InitializationException) throwable;
|
||||||
|
|
|
@ -122,12 +122,12 @@ public final class ShowConfigCommand {
|
||||||
return PersistedConfigSource.NAME.equals(configValue.getConfigSourceName());
|
return PersistedConfigSource.NAME.equals(configValue.getConfigSourceName());
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.filter(property -> filterByGroup(property))
|
.filter(ShowConfigCommand::filterByGroup)
|
||||||
.collect(Collectors.groupingBy(ShowConfigCommand::groupProperties, Collectors.toSet()))
|
.collect(Collectors.groupingBy(ShowConfigCommand::groupProperties, Collectors.toSet()))
|
||||||
.forEach(new BiConsumer<String, Set<String>>() {
|
.forEach(new BiConsumer<String, Set<String>>() {
|
||||||
@Override
|
@Override
|
||||||
public void accept(String group, Set<String> propertyNames) {
|
public void accept(String group, Set<String> propertyNames) {
|
||||||
properties.computeIfAbsent(group, (name) -> new HashSet<>()).addAll(propertyNames);
|
properties.computeIfAbsent(group, name -> new HashSet<>()).addAll(propertyNames);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -163,8 +163,20 @@ public final class PropertyMappers {
|
||||||
}
|
}
|
||||||
|
|
||||||
public static boolean isBuildTimeProperty(String name) {
|
public static boolean isBuildTimeProperty(String name) {
|
||||||
return PropertyMapper.MAPPERS.entrySet().stream()
|
if ("kc.features".equals(name)) {
|
||||||
.anyMatch(entry -> entry.getValue().getFrom().equals(name) && entry.getValue().isBuildTime());
|
return true;
|
||||||
|
}
|
||||||
|
return name.startsWith(MicroProfileConfigProvider.NS_KEYCLOAK_PREFIX)
|
||||||
|
&& PropertyMapper.MAPPERS.entrySet().stream()
|
||||||
|
.anyMatch(entry -> entry.getValue().getFrom().equals(name) && entry.getValue().isBuildTime())
|
||||||
|
&& !"kc.version".equals(name)
|
||||||
|
&& !Environment.CLI_ARGS.equals(name)
|
||||||
|
&& !"kc.home.dir".equals(name)
|
||||||
|
&& !"kc.config.file".equals(name)
|
||||||
|
&& !"kc.profile".equals(name)
|
||||||
|
&& !"kc.show.config".equals(name)
|
||||||
|
&& !"kc.show.config.runtime".equals(name)
|
||||||
|
&& !PropertyMappers.toCLIFormat("kc.config.file").equals(name);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static String toCLIFormat(String name) {
|
public static String toCLIFormat(String name) {
|
||||||
|
|
|
@ -84,106 +84,6 @@ public class KeycloakRecorder {
|
||||||
QuarkusKeycloakSessionFactory.setInstance(new QuarkusKeycloakSessionFactory(factories, defaultProviders, preConfiguredProviders, reaugmented));
|
QuarkusKeycloakSessionFactory.setInstance(new QuarkusKeycloakSessionFactory(factories, defaultProviders, preConfiguredProviders, reaugmented));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* <p>Validate the build time properties with any property passed during runtime in order to advertise any difference with the
|
|
||||||
* server image state.
|
|
||||||
*
|
|
||||||
* <p>This method also keep the build time properties available at runtime.
|
|
||||||
*
|
|
||||||
*
|
|
||||||
* @param buildTimeProperties the build time properties set when running the last re-augmentation
|
|
||||||
* @param rebuild indicates whether or not the server was re-augmented
|
|
||||||
* @param configArgs the configuration args if provided when the server was re-augmented
|
|
||||||
*/
|
|
||||||
public void validateAndSetBuildTimeProperties(Boolean rebuild, String configArgs) {
|
|
||||||
String configHelpText = configArgs;
|
|
||||||
|
|
||||||
for (String propertyName : getConfig().getPropertyNames()) {
|
|
||||||
// we should only validate if there is a server image and if the property is a runtime property
|
|
||||||
if (!shouldValidate(propertyName, rebuild)) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// try to resolve any property set using profiles
|
|
||||||
if (propertyName.startsWith("%")) {
|
|
||||||
propertyName = propertyName.substring(propertyName.indexOf('.') + 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
String buildValue = Environment.getBuiltTimeProperty(propertyName).orElse(null);
|
|
||||||
ConfigValue value = getConfig().getConfigValue(propertyName);
|
|
||||||
|
|
||||||
if (value.getValue() != null && !value.getValue().equalsIgnoreCase(buildValue)) {
|
|
||||||
if (configHelpText != null) {
|
|
||||||
String cliNameFormat = PropertyMappers.toCLIFormat(propertyName);
|
|
||||||
|
|
||||||
if (buildValue != null) {
|
|
||||||
String currentProp = "--" + cliNameFormat.substring(3) + "=" + buildValue;
|
|
||||||
String newProp = "--" + cliNameFormat.substring(3) + "=" + value.getValue();
|
|
||||||
|
|
||||||
if (configHelpText.contains(currentProp)) {
|
|
||||||
LOGGER.warnf("The new value [%s] of the property [%s] in [%s] differs from the value [%s] set into the server image. The new value will override the value set into the server image.",
|
|
||||||
value.getValue(), propertyName, value.getConfigSourceName(), buildValue);
|
|
||||||
configHelpText = configHelpText.replaceAll(currentProp, newProp);
|
|
||||||
} else if (!configHelpText
|
|
||||||
.contains("--" + cliNameFormat.substring(3))) {
|
|
||||||
LOGGER.warnf("The new value [%s] of the property [%s] in [%s] differs from the value [%s] set into the server image. The new value will override the value set into the server image.",
|
|
||||||
value.getValue(), propertyName, value.getConfigSourceName(), buildValue);
|
|
||||||
configHelpText += " " + newProp;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
String finalPropertyName = propertyName;
|
|
||||||
|
|
||||||
if (!StreamSupport.stream(getConfig().getPropertyNames().spliterator(), false)
|
|
||||||
.filter(new Predicate<String>() {
|
|
||||||
@Override
|
|
||||||
public boolean test(String propertyName) {
|
|
||||||
ConfigValue configValue = getConfigValue(propertyName);
|
|
||||||
|
|
||||||
if (configValue == null) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return PersistedConfigSource.NAME.equals(configValue.getSourceName());
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.anyMatch(new Predicate<String>() {
|
|
||||||
@Override
|
|
||||||
public boolean test(String propertyName) {
|
|
||||||
return PropertyMappers.canonicalFormat(finalPropertyName)
|
|
||||||
.equalsIgnoreCase(PropertyMappers.canonicalFormat(propertyName));
|
|
||||||
}
|
|
||||||
})) {
|
|
||||||
String prop = "--" + cliNameFormat.substring(3) + "=" + value.getValue();
|
|
||||||
|
|
||||||
if (!configHelpText.contains(prop)) {
|
|
||||||
LOGGER.warnf("New property [%s] set with value [%s] in [%s]. This property is not persisted into the server image.",
|
|
||||||
propertyName, value.getValue(), value.getConfigSourceName(), buildValue);
|
|
||||||
configHelpText += " " + prop;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (configArgs != null && !configArgs.equals(configHelpText)) {
|
|
||||||
LOGGER.warnf("Please, run the 'config' command if you want to persist the new configuration into the server image:\n\n\t%s config %s\n", Environment.getCommand(), String.join(" ", configHelpText.split(",")));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean shouldValidate(String name, boolean rebuild) {
|
|
||||||
return rebuild && name.contains(MicroProfileConfigProvider.NS_KEYCLOAK_PREFIX)
|
|
||||||
&& (!PropertyMappers.isBuildTimeProperty(name)
|
|
||||||
&& !"kc.version".equals(name)
|
|
||||||
&& !"kc.config.args".equals(name)
|
|
||||||
&& !"kc.home.dir".equals(name)
|
|
||||||
&& !"kc.config.file".equals(name)
|
|
||||||
&& !"kc.profile".equals(name)
|
|
||||||
&& !"kc.show.config".equals(name)
|
|
||||||
&& !"kc.show.config.runtime".equals(name)
|
|
||||||
&& !PropertyMappers.toCLIFormat("kc.config.file").equals(name));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This method should be executed during static init so that the configuration is printed (if demanded) based on the properties
|
* This method should be executed during static init so that the configuration is printed (if demanded) based on the properties
|
||||||
* set from the previous reaugmentation
|
* set from the previous reaugmentation
|
||||||
|
|
|
@ -17,6 +17,10 @@
|
||||||
|
|
||||||
package org.keycloak.util;
|
package org.keycloak.util;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.FilenameFilter;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.nio.file.Paths;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
|
|
||||||
import io.quarkus.runtime.LaunchMode;
|
import io.quarkus.runtime.LaunchMode;
|
||||||
|
@ -27,15 +31,36 @@ import org.keycloak.configuration.Configuration;
|
||||||
public final class Environment {
|
public final class Environment {
|
||||||
|
|
||||||
public static final String IMPORT_EXPORT_MODE = "import_export";
|
public static final String IMPORT_EXPORT_MODE = "import_export";
|
||||||
|
public static final String CLI_ARGS = "kc.config.args";
|
||||||
|
|
||||||
public static Boolean isRebuild() {
|
public static Boolean isRebuild() {
|
||||||
return Boolean.valueOf(System.getProperty("quarkus.launch.rebuild"));
|
return Boolean.getBoolean("quarkus.launch.rebuild");
|
||||||
}
|
}
|
||||||
|
|
||||||
public static String getHomeDir() {
|
public static String getHomeDir() {
|
||||||
return System.getProperty("kc.home.dir");
|
return System.getProperty("kc.home.dir");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static Path getHomePath() {
|
||||||
|
String homeDir = getHomeDir();
|
||||||
|
|
||||||
|
if (homeDir != null) {
|
||||||
|
return Paths.get(homeDir);
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Path getProvidersPath() {
|
||||||
|
Path homePath = Environment.getHomePath();
|
||||||
|
|
||||||
|
if (homePath != null) {
|
||||||
|
return homePath.resolve("providers");
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
public static String getCommand() {
|
public static String getCommand() {
|
||||||
String homeDir = getHomeDir();
|
String homeDir = getHomeDir();
|
||||||
|
|
||||||
|
@ -50,7 +75,7 @@ public final class Environment {
|
||||||
}
|
}
|
||||||
|
|
||||||
public static String getConfigArgs() {
|
public static String getConfigArgs() {
|
||||||
return System.getProperty("kc.config.args");
|
return System.getProperty(CLI_ARGS);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static String getProfile() {
|
public static String getProfile() {
|
||||||
|
@ -99,4 +124,30 @@ public final class Environment {
|
||||||
public static boolean isWindows() {
|
public static boolean isWindows() {
|
||||||
return SystemUtils.IS_OS_WINDOWS;
|
return SystemUtils.IS_OS_WINDOWS;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static void forceDevProfile() {
|
||||||
|
System.setProperty("kc.profile", "dev");
|
||||||
|
System.setProperty("quarkus.profile", "dev");
|
||||||
|
}
|
||||||
|
|
||||||
|
public static File[] getProviderFiles() {
|
||||||
|
Path providersPath = Environment.getProvidersPath();
|
||||||
|
|
||||||
|
if (providersPath == null) {
|
||||||
|
return new File[] {};
|
||||||
|
}
|
||||||
|
|
||||||
|
File providersDir = providersPath.toFile();
|
||||||
|
|
||||||
|
if (!providersDir.exists() || !providersDir.isDirectory()) {
|
||||||
|
throw new RuntimeException("The 'providers' directory does not exist or is not a valid directory.");
|
||||||
|
}
|
||||||
|
|
||||||
|
return providersDir.listFiles(new FilenameFilter() {
|
||||||
|
@Override
|
||||||
|
public boolean accept(File dir, String name) {
|
||||||
|
return name.endsWith(".jar");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,6 +18,7 @@
|
||||||
package org.keycloak.provider.quarkus;
|
package org.keycloak.provider.quarkus;
|
||||||
|
|
||||||
import static org.junit.Assert.assertEquals;
|
import static org.junit.Assert.assertEquals;
|
||||||
|
import static org.keycloak.util.Environment.CLI_ARGS;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.lang.reflect.Field;
|
import java.lang.reflect.Field;
|
||||||
|
@ -123,7 +124,7 @@ public class ConfigurationTest {
|
||||||
@Test
|
@Test
|
||||||
public void testCLIPriorityOverSysProp() {
|
public void testCLIPriorityOverSysProp() {
|
||||||
System.setProperty("kc.spi.hostname.default.frontend-url", "http://propvar.com");
|
System.setProperty("kc.spi.hostname.default.frontend-url", "http://propvar.com");
|
||||||
System.setProperty("kc.config.args", "--spi-hostname-default-frontend-url=http://cli.com");
|
System.setProperty(CLI_ARGS, "--spi-hostname-default-frontend-url=http://cli.com");
|
||||||
assertEquals("http://cli.com", initConfig("hostname", "default").get("frontendUrl"));
|
assertEquals("http://cli.com", initConfig("hostname", "default").get("frontendUrl"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -152,29 +153,29 @@ public class ConfigurationTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testCommandLineArguments() {
|
public void testCommandLineArguments() {
|
||||||
System.setProperty("kc.config.args", "--spi-hostname-default-frontend-url=http://fromargs.com,--no-ssl");
|
System.setProperty(CLI_ARGS, "--spi-hostname-default-frontend-url=http://fromargs.com,--no-ssl");
|
||||||
assertEquals("http://fromargs.com", initConfig("hostname", "default").get("frontendUrl"));
|
assertEquals("http://fromargs.com", initConfig("hostname", "default").get("frontendUrl"));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testSpiConfigurationUsingCommandLineArguments() {
|
public void testSpiConfigurationUsingCommandLineArguments() {
|
||||||
System.setProperty("kc.config.args", "--spi-hostname-default-frontend-url=http://spifull.com");
|
System.setProperty(CLI_ARGS, "--spi-hostname-default-frontend-url=http://spifull.com");
|
||||||
assertEquals("http://spifull.com", initConfig("hostname", "default").get("frontendUrl"));
|
assertEquals("http://spifull.com", initConfig("hostname", "default").get("frontendUrl"));
|
||||||
|
|
||||||
// test multi-word SPI names using camel cases
|
// test multi-word SPI names using camel cases
|
||||||
System.setProperty("kc.config.args", "--spi-action-token-handler-verify-email-some-property=test");
|
System.setProperty(CLI_ARGS, "--spi-action-token-handler-verify-email-some-property=test");
|
||||||
assertEquals("test", initConfig("action-token-handler", "verify-email").get("some-property"));
|
assertEquals("test", initConfig("action-token-handler", "verify-email").get("some-property"));
|
||||||
System.setProperty("kc.config.args", "--spi-action-token-handler-verify-email-some-property=test");
|
System.setProperty(CLI_ARGS, "--spi-action-token-handler-verify-email-some-property=test");
|
||||||
assertEquals("test", initConfig("actionTokenHandler", "verifyEmail").get("someProperty"));
|
assertEquals("test", initConfig("actionTokenHandler", "verifyEmail").get("someProperty"));
|
||||||
|
|
||||||
// test multi-word SPI names using slashes
|
// test multi-word SPI names using slashes
|
||||||
System.setProperty("kc.config.args", "--spi-client-registration-openid-connect-static-jwk-url=http://c.jwk.url");
|
System.setProperty(CLI_ARGS, "--spi-client-registration-openid-connect-static-jwk-url=http://c.jwk.url");
|
||||||
assertEquals("http://c.jwk.url", initConfig("client-registration", "openid-connect").get("static-jwk-url"));
|
assertEquals("http://c.jwk.url", initConfig("client-registration", "openid-connect").get("static-jwk-url"));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testPropertyMapping() {
|
public void testPropertyMapping() {
|
||||||
System.setProperty("kc.config.args", "--db=mariadb,--db-url=jdbc:mariadb://localhost/keycloak");
|
System.setProperty(CLI_ARGS, "--db=mariadb,--db-url=jdbc:mariadb://localhost/keycloak");
|
||||||
SmallRyeConfig config = createConfig();
|
SmallRyeConfig config = createConfig();
|
||||||
assertEquals(MariaDBDialect.class.getName(), config.getConfigValue("quarkus.hibernate-orm.dialect").getValue());
|
assertEquals(MariaDBDialect.class.getName(), config.getConfigValue("quarkus.hibernate-orm.dialect").getValue());
|
||||||
assertEquals("jdbc:mariadb://localhost/keycloak", config.getConfigValue("quarkus.datasource.jdbc.url").getValue());
|
assertEquals("jdbc:mariadb://localhost/keycloak", config.getConfigValue("quarkus.datasource.jdbc.url").getValue());
|
||||||
|
@ -182,7 +183,7 @@ public class ConfigurationTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testDatabaseUrlProperties() {
|
public void testDatabaseUrlProperties() {
|
||||||
System.setProperty("kc.config.args", "--db=mariadb,--db-url=jdbc:mariadb:aurora://foo/bar?a=1&b=2");
|
System.setProperty(CLI_ARGS, "--db=mariadb,--db-url=jdbc:mariadb:aurora://foo/bar?a=1&b=2");
|
||||||
SmallRyeConfig config = createConfig();
|
SmallRyeConfig config = createConfig();
|
||||||
assertEquals(MariaDBDialect.class.getName(), config.getConfigValue("quarkus.hibernate-orm.dialect").getValue());
|
assertEquals(MariaDBDialect.class.getName(), config.getConfigValue("quarkus.hibernate-orm.dialect").getValue());
|
||||||
assertEquals("jdbc:mariadb:aurora://foo/bar?a=1&b=2", config.getConfigValue("quarkus.datasource.jdbc.url").getValue());
|
assertEquals("jdbc:mariadb:aurora://foo/bar?a=1&b=2", config.getConfigValue("quarkus.datasource.jdbc.url").getValue());
|
||||||
|
@ -190,12 +191,12 @@ public class ConfigurationTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testDatabaseDefaults() {
|
public void testDatabaseDefaults() {
|
||||||
System.setProperty("kc.config.args", "--db=h2-file");
|
System.setProperty(CLI_ARGS, "--db=h2-file");
|
||||||
SmallRyeConfig config = createConfig();
|
SmallRyeConfig config = createConfig();
|
||||||
assertEquals(QuarkusH2Dialect.class.getName(), config.getConfigValue("quarkus.hibernate-orm.dialect").getValue());
|
assertEquals(QuarkusH2Dialect.class.getName(), config.getConfigValue("quarkus.hibernate-orm.dialect").getValue());
|
||||||
assertEquals("jdbc:h2:file:~/data/keycloakdb;;AUTO_SERVER=TRUE", config.getConfigValue("quarkus.datasource.jdbc.url").getValue());
|
assertEquals("jdbc:h2:file:~/data/keycloakdb;;AUTO_SERVER=TRUE", config.getConfigValue("quarkus.datasource.jdbc.url").getValue());
|
||||||
|
|
||||||
System.setProperty("kc.config.args", "--db=h2-mem");
|
System.setProperty(CLI_ARGS, "--db=h2-mem");
|
||||||
config = createConfig();
|
config = createConfig();
|
||||||
assertEquals(QuarkusH2Dialect.class.getName(), config.getConfigValue("quarkus.hibernate-orm.dialect").getValue());
|
assertEquals(QuarkusH2Dialect.class.getName(), config.getConfigValue("quarkus.hibernate-orm.dialect").getValue());
|
||||||
assertEquals("jdbc:h2:mem:keycloakdb", config.getConfigValue("quarkus.datasource.jdbc.url").getValue());
|
assertEquals("jdbc:h2:mem:keycloakdb", config.getConfigValue("quarkus.datasource.jdbc.url").getValue());
|
||||||
|
@ -204,7 +205,7 @@ public class ConfigurationTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testDatabaseKindProperties() {
|
public void testDatabaseKindProperties() {
|
||||||
System.setProperty("kc.config.args", "--db=postgres-10,--db-url=jdbc:postgresql://localhost/keycloak");
|
System.setProperty(CLI_ARGS, "--db=postgres-10,--db-url=jdbc:postgresql://localhost/keycloak");
|
||||||
SmallRyeConfig config = createConfig();
|
SmallRyeConfig config = createConfig();
|
||||||
assertEquals("io.quarkus.hibernate.orm.runtime.dialect.QuarkusPostgreSQL10Dialect",
|
assertEquals("io.quarkus.hibernate.orm.runtime.dialect.QuarkusPostgreSQL10Dialect",
|
||||||
config.getConfigValue("quarkus.hibernate-orm.dialect").getValue());
|
config.getConfigValue("quarkus.hibernate-orm.dialect").getValue());
|
||||||
|
@ -216,13 +217,13 @@ public class ConfigurationTest {
|
||||||
public void testDatabaseProperties() {
|
public void testDatabaseProperties() {
|
||||||
System.setProperty("kc.db.url.properties", ";;test=test;test1=test1");
|
System.setProperty("kc.db.url.properties", ";;test=test;test1=test1");
|
||||||
System.setProperty("kc.db.url.path", "test-dir");
|
System.setProperty("kc.db.url.path", "test-dir");
|
||||||
System.setProperty("kc.config.args", "--db=h2-file");
|
System.setProperty(CLI_ARGS, "--db=h2-file");
|
||||||
SmallRyeConfig config = createConfig();
|
SmallRyeConfig config = createConfig();
|
||||||
assertEquals(QuarkusH2Dialect.class.getName(), config.getConfigValue("quarkus.hibernate-orm.dialect").getValue());
|
assertEquals(QuarkusH2Dialect.class.getName(), config.getConfigValue("quarkus.hibernate-orm.dialect").getValue());
|
||||||
assertEquals("jdbc:h2:file:test-dir" + File.separator + "data" + File.separator + "keycloakdb;;test=test;test1=test1", config.getConfigValue("quarkus.datasource.jdbc.url").getValue());
|
assertEquals("jdbc:h2:file:test-dir" + File.separator + "data" + File.separator + "keycloakdb;;test=test;test1=test1", config.getConfigValue("quarkus.datasource.jdbc.url").getValue());
|
||||||
|
|
||||||
System.setProperty("kc.db.url.properties", "?test=test&test1=test1");
|
System.setProperty("kc.db.url.properties", "?test=test&test1=test1");
|
||||||
System.setProperty("kc.config.args", "--db=mariadb");
|
System.setProperty(CLI_ARGS, "--db=mariadb");
|
||||||
config = createConfig();
|
config = createConfig();
|
||||||
assertEquals("jdbc:mariadb://localhost/keycloak?test=test&test1=test1", config.getConfigValue("quarkus.datasource.jdbc.url").getValue());
|
assertEquals("jdbc:mariadb://localhost/keycloak?test=test&test1=test1", config.getConfigValue("quarkus.datasource.jdbc.url").getValue());
|
||||||
}
|
}
|
||||||
|
@ -256,13 +257,13 @@ public class ConfigurationTest {
|
||||||
|
|
||||||
// If explicitly set, then it is always used regardless of the profile
|
// If explicitly set, then it is always used regardless of the profile
|
||||||
System.clearProperty("kc.profile");
|
System.clearProperty("kc.profile");
|
||||||
System.setProperty("kc.config.args", "--cluster=foo");
|
System.setProperty(CLI_ARGS, "--cluster=foo");
|
||||||
|
|
||||||
Assert.assertEquals("cluster-foo.xml", initConfig("connectionsInfinispan", "quarkus").get("configFile"));
|
Assert.assertEquals("cluster-foo.xml", initConfig("connectionsInfinispan", "quarkus").get("configFile"));
|
||||||
System.setProperty("kc.profile", "dev");
|
System.setProperty("kc.profile", "dev");
|
||||||
Assert.assertEquals("cluster-foo.xml", initConfig("connectionsInfinispan", "quarkus").get("configFile"));
|
Assert.assertEquals("cluster-foo.xml", initConfig("connectionsInfinispan", "quarkus").get("configFile"));
|
||||||
|
|
||||||
System.setProperty("kc.config.args", "--cluster-stack=foo");
|
System.setProperty(CLI_ARGS, "--cluster-stack=foo");
|
||||||
Assert.assertEquals("foo", initConfig("connectionsInfinispan", "quarkus").get("stack"));
|
Assert.assertEquals("foo", initConfig("connectionsInfinispan", "quarkus").get("stack"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue