[KEYCLOAK-19847] - Optimizations and refactoring for better/stable startup time
This commit is contained in:
parent
f64441a54a
commit
9a4ab82d08
39 changed files with 746 additions and 513 deletions
2
.github/workflows/ci.yml
vendored
2
.github/workflows/ci.yml
vendored
|
@ -414,7 +414,7 @@ jobs:
|
|||
|
||||
- name: Run Quarkus Tests in Docker
|
||||
run: |
|
||||
mvn clean install -nsu -B -f quarkus/tests/pom.xml -Dkc.quarkus.tests.dist=docker | misc/log/trimmer.sh
|
||||
mvn clean install -nsu -B -f quarkus/tests/pom.xml -Dkc.quarkus.tests.dist=docker -Dtest=StartDevCommandTest | misc/log/trimmer.sh
|
||||
TEST_RESULT=${PIPESTATUS[0]}
|
||||
exit $TEST_RESULT
|
||||
|
||||
|
|
|
@ -0,0 +1,88 @@
|
|||
/*
|
||||
* 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 static org.keycloak.quarkus.runtime.configuration.Configuration.getConfigValue;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.stream.Collectors;
|
||||
import javax.enterprise.context.ApplicationScoped;
|
||||
import org.infinispan.commons.util.FileLookupFactory;
|
||||
import org.keycloak.quarkus.runtime.Environment;
|
||||
import org.keycloak.quarkus.runtime.KeycloakRecorder;
|
||||
import org.keycloak.quarkus.runtime.storage.infinispan.CacheInitializer;
|
||||
|
||||
import io.quarkus.arc.deployment.SyntheticBeanBuildItem;
|
||||
import io.quarkus.deployment.annotations.BuildProducer;
|
||||
import io.quarkus.deployment.annotations.BuildStep;
|
||||
import io.quarkus.deployment.annotations.ExecutionTime;
|
||||
import io.quarkus.deployment.annotations.Record;
|
||||
|
||||
public class CLusteringBuildSteps {
|
||||
|
||||
@Record(ExecutionTime.RUNTIME_INIT)
|
||||
@BuildStep
|
||||
void configureInfinispan(KeycloakRecorder recorder, BuildProducer<SyntheticBeanBuildItem> syntheticBeanBuildItems) {
|
||||
String pathPrefix;
|
||||
String homeDir = Environment.getHomeDir();
|
||||
|
||||
if (homeDir == null) {
|
||||
pathPrefix = "";
|
||||
} else {
|
||||
pathPrefix = homeDir + "/conf/";
|
||||
}
|
||||
|
||||
String configFile = getConfigValue("kc.spi.connections-infinispan.quarkus.config-file").getValue();
|
||||
|
||||
if (configFile != null) {
|
||||
Path configPath = Paths.get(pathPrefix + configFile);
|
||||
String path;
|
||||
|
||||
if (configPath.toFile().exists()) {
|
||||
path = configPath.toFile().getAbsolutePath();
|
||||
} else {
|
||||
path = configPath.getFileName().toString();
|
||||
}
|
||||
|
||||
InputStream url = FileLookupFactory.newInstance().lookupFile(path, KeycloakProcessor.class.getClassLoader());
|
||||
|
||||
if (url == null) {
|
||||
throw new IllegalArgumentException("Could not load cluster configuration file at [" + configPath + "]");
|
||||
}
|
||||
|
||||
try (BufferedReader reader = new BufferedReader(new InputStreamReader(url))) {
|
||||
String config = reader.lines().collect(Collectors.joining("\n"));
|
||||
|
||||
syntheticBeanBuildItems.produce(SyntheticBeanBuildItem.configure(CacheInitializer.class)
|
||||
.scope(ApplicationScoped.class)
|
||||
.unremovable()
|
||||
.setRuntimeInit()
|
||||
.runtimeValue(recorder.createCacheInitializer(config)).done());
|
||||
} catch (Exception cause) {
|
||||
throw new RuntimeException("Failed to read clustering configuration from [" + url + "]", cause);
|
||||
}
|
||||
} else {
|
||||
throw new IllegalArgumentException("Option 'configFile' needs to be specified");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -17,7 +17,7 @@
|
|||
|
||||
package org.keycloak.quarkus.deployment;
|
||||
|
||||
import static org.keycloak.quarkus.runtime.configuration.Configuration.getConfigValue;
|
||||
import static org.keycloak.quarkus.runtime.configuration.ConfigArgsConfigSource.CLI_ARGS;
|
||||
import static org.keycloak.quarkus.runtime.configuration.Configuration.getPropertyNames;
|
||||
import static org.keycloak.quarkus.runtime.storage.database.jpa.QuarkusJpaConnectionProviderFactory.QUERY_PROPERTY_PREFIX;
|
||||
import static org.keycloak.connections.jpa.util.JpaUtils.loadSpecificNamedQueries;
|
||||
|
@ -25,22 +25,16 @@ import static org.keycloak.quarkus.runtime.configuration.MicroProfileConfigProvi
|
|||
import static org.keycloak.representations.provider.ScriptProviderDescriptor.AUTHENTICATORS;
|
||||
import static org.keycloak.representations.provider.ScriptProviderDescriptor.MAPPERS;
|
||||
import static org.keycloak.representations.provider.ScriptProviderDescriptor.POLICIES;
|
||||
import static org.keycloak.quarkus.runtime.Environment.CLI_ARGS;
|
||||
import static org.keycloak.quarkus.runtime.Environment.getProviderFiles;
|
||||
|
||||
import javax.enterprise.context.ApplicationScoped;
|
||||
import javax.persistence.Entity;
|
||||
import javax.persistence.spi.PersistenceUnitTransactionType;
|
||||
import java.io.BufferedReader;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.net.URL;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
|
@ -56,12 +50,11 @@ import java.util.function.Consumer;
|
|||
import java.util.function.Function;
|
||||
import java.util.jar.JarEntry;
|
||||
import java.util.jar.JarFile;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import io.quarkus.agroal.spi.JdbcDataSourceBuildItem;
|
||||
import io.quarkus.arc.deployment.SyntheticBeanBuildItem;
|
||||
import io.quarkus.deployment.IsDevelopment;
|
||||
import io.quarkus.deployment.builditem.CombinedIndexBuildItem;
|
||||
import io.quarkus.deployment.builditem.GeneratedResourceBuildItem;
|
||||
import io.quarkus.deployment.builditem.HotDeploymentWatchedFileBuildItem;
|
||||
import io.quarkus.deployment.builditem.IndexDependencyBuildItem;
|
||||
import io.quarkus.deployment.builditem.LaunchModeBuildItem;
|
||||
|
@ -77,7 +70,6 @@ import io.vertx.core.Handler;
|
|||
import io.vertx.ext.web.RoutingContext;
|
||||
import org.hibernate.cfg.AvailableSettings;
|
||||
import org.hibernate.jpa.boot.internal.ParsedPersistenceXmlDescriptor;
|
||||
import org.infinispan.commons.util.FileLookupFactory;
|
||||
import org.jboss.jandex.AnnotationInstance;
|
||||
import org.jboss.jandex.AnnotationTarget;
|
||||
import org.jboss.jandex.DotName;
|
||||
|
@ -86,6 +78,7 @@ import org.jboss.logging.Logger;
|
|||
import org.jboss.resteasy.plugins.server.servlet.ResteasyContextParameters;
|
||||
import org.jboss.resteasy.spi.ResteasyDeployment;
|
||||
import org.keycloak.Config;
|
||||
import org.keycloak.quarkus.runtime.configuration.PersistedConfigSource;
|
||||
import org.keycloak.quarkus.runtime.integration.jaxrs.QuarkusKeycloakApplication;
|
||||
import org.keycloak.authentication.AuthenticatorSpi;
|
||||
import org.keycloak.authentication.authenticators.browser.DeployedScriptAuthenticatorFactory;
|
||||
|
@ -118,7 +111,6 @@ import io.quarkus.deployment.annotations.Record;
|
|||
import io.quarkus.deployment.builditem.FeatureBuildItem;
|
||||
import io.quarkus.vertx.http.deployment.FilterBuildItem;
|
||||
|
||||
import org.keycloak.quarkus.runtime.storage.infinispan.CacheInitializer;
|
||||
import org.keycloak.representations.provider.ScriptProviderDescriptor;
|
||||
import org.keycloak.representations.provider.ScriptProviderMetadata;
|
||||
import org.keycloak.quarkus.runtime.integration.web.NotFoundHandler;
|
||||
|
@ -275,12 +267,9 @@ class KeycloakProcessor {
|
|||
/**
|
||||
* <p>Make the build time configuration available at runtime so that the server can run without having to specify some of
|
||||
* the properties again.
|
||||
*
|
||||
* @param recorder the recorder
|
||||
*/
|
||||
@Record(ExecutionTime.STATIC_INIT)
|
||||
@BuildStep(onlyIf = isReAugmentation.class)
|
||||
void setBuildTimeProperties(KeycloakRecorder recorder) {
|
||||
void persistBuildTimeProperties(BuildProducer<GeneratedResourceBuildItem> resources) {
|
||||
Properties properties = new Properties();
|
||||
|
||||
for (String name : getPropertyNames()) {
|
||||
|
@ -299,62 +288,11 @@ class KeycloakProcessor {
|
|||
properties.put(String.format("kc.provider.file.%s.last-modified", jar.getName()), String.valueOf(jar.lastModified()));
|
||||
}
|
||||
|
||||
File file = KeycloakConfigSourceProvider.getPersistedConfigFile().toFile();
|
||||
|
||||
if (file.exists()) {
|
||||
file.delete();
|
||||
}
|
||||
|
||||
try (FileOutputStream fos = new FileOutputStream(file)) {
|
||||
properties.store(fos, " Auto-generated, DO NOT change this file");
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("Failed to generate persisted.properties file", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Record(ExecutionTime.RUNTIME_INIT)
|
||||
@BuildStep
|
||||
void configureInfinispan(KeycloakRecorder recorder, BuildProducer<SyntheticBeanBuildItem> syntheticBeanBuildItems) {
|
||||
String pathPrefix;
|
||||
String homeDir = Environment.getHomeDir();
|
||||
|
||||
if (homeDir == null) {
|
||||
pathPrefix = "";
|
||||
} else {
|
||||
pathPrefix = homeDir + "/conf/";
|
||||
}
|
||||
|
||||
String configFile = getConfigValue("kc.spi.connections-infinispan.quarkus.config-file").getValue();
|
||||
|
||||
if (configFile != null) {
|
||||
Path configPath = Paths.get(pathPrefix + configFile);
|
||||
String path;
|
||||
|
||||
if (configPath.toFile().exists()) {
|
||||
path = configPath.toFile().getAbsolutePath();
|
||||
} else {
|
||||
path = configPath.getFileName().toString();
|
||||
}
|
||||
|
||||
InputStream url = FileLookupFactory.newInstance().lookupFile(path, KeycloakProcessor.class.getClassLoader());
|
||||
|
||||
if (url == null) {
|
||||
throw new IllegalArgumentException("Could not load cluster configuration file at [" + configPath + "]");
|
||||
}
|
||||
|
||||
try (BufferedReader reader = new BufferedReader(new InputStreamReader(url))) {
|
||||
String config = reader.lines().collect(Collectors.joining("\n"));
|
||||
|
||||
syntheticBeanBuildItems.produce(SyntheticBeanBuildItem.configure(CacheInitializer.class)
|
||||
.scope(ApplicationScoped.class)
|
||||
.unremovable()
|
||||
.setRuntimeInit()
|
||||
.runtimeValue(recorder.createCacheInitializer(config)).done());
|
||||
} catch (Exception cause) {
|
||||
throw new RuntimeException("Failed to read clustering configuration from [" + url + "]", cause);
|
||||
}
|
||||
} else {
|
||||
throw new IllegalArgumentException("Option 'configFile' needs to be specified");
|
||||
try (ByteArrayOutputStream outputStream = new ByteArrayOutputStream()) {
|
||||
properties.store(outputStream, " Auto-generated, DO NOT change this file");
|
||||
resources.produce(new GeneratedResourceBuildItem(PersistedConfigSource.PERSISTED_PROPERTIES, outputStream.toByteArray()));
|
||||
} catch (Exception cause) {
|
||||
throw new RuntimeException("Failed to persist configuration", cause);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -17,13 +17,16 @@
|
|||
|
||||
package org.keycloak.quarkus.runtime;
|
||||
|
||||
import static org.keycloak.quarkus.runtime.configuration.Configuration.getBuiltTimeProperty;
|
||||
import static org.keycloak.quarkus.runtime.configuration.Configuration.getBuildTimeProperty;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FilenameFilter;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.*;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
|
@ -34,13 +37,11 @@ import org.apache.commons.lang3.SystemUtils;
|
|||
public final class Environment {
|
||||
|
||||
public static final String IMPORT_EXPORT_MODE = "import_export";
|
||||
public static final String CLI_ARGS = "kc.config.args";
|
||||
public static final String PROFILE ="kc.profile";
|
||||
public static final String ENV_PROFILE ="KC_PROFILE";
|
||||
public static final String DATA_PATH = "/data";
|
||||
public static final String DEFAULT_THEMES_PATH = "/themes";
|
||||
public static final String DEV_PROFILE_VALUE = "dev";
|
||||
public static final String USER_INVOKED_CLI_COMMAND = "picocli.invoked.command";
|
||||
public static final String LAUNCH_MODE = "kc.launch.mode";
|
||||
|
||||
private Environment() {}
|
||||
|
@ -95,33 +96,6 @@ public final class Environment {
|
|||
return "kc.sh";
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the originally invoked cli args. Useful to verify the originally invoked command
|
||||
* when calling another cli command internally (e.g. start-dev calls build internally)
|
||||
*/
|
||||
public static void setUserInvokedCliArgs(List<String> cliArgs) {
|
||||
System.setProperty(USER_INVOKED_CLI_COMMAND, String.join(",", cliArgs));
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads the previously set system property for the originally command.
|
||||
* Use the System variable, when you trigger other command executions internally, but need a reference to the
|
||||
* actually invoked command.
|
||||
*
|
||||
* @return the invoked command from the CLI, or empty List if not set.
|
||||
*/
|
||||
public static List<String> getUserInvokedCliArgs() {
|
||||
if(System.getProperty(USER_INVOKED_CLI_COMMAND) == null) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
return List.of(System.getProperty(USER_INVOKED_CLI_COMMAND).split(","));
|
||||
}
|
||||
|
||||
public static String getConfigArgs() {
|
||||
return System.getProperty(CLI_ARGS, "");
|
||||
}
|
||||
|
||||
public static String getProfile() {
|
||||
String profile = System.getProperty(PROFILE);
|
||||
|
||||
|
@ -156,7 +130,7 @@ public final class Environment {
|
|||
return true;
|
||||
}
|
||||
|
||||
return DEV_PROFILE_VALUE.equals(getBuiltTimeProperty(PROFILE).orElse(null));
|
||||
return DEV_PROFILE_VALUE.equals(getBuildTimeProperty(PROFILE).orElse(null));
|
||||
}
|
||||
|
||||
public static boolean isDevProfile(){
|
||||
|
|
|
@ -21,7 +21,9 @@ import static org.keycloak.quarkus.runtime.Environment.isDevProfile;
|
|||
import static org.keycloak.quarkus.runtime.Environment.getProfileOrDefault;
|
||||
import static org.keycloak.quarkus.runtime.Environment.isTestLaunchMode;
|
||||
import static org.keycloak.quarkus.runtime.cli.Picocli.parseAndRun;
|
||||
import static org.keycloak.quarkus.runtime.cli.command.Start.isDevProfileNotAllowed;
|
||||
|
||||
import java.io.PrintWriter;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
@ -35,10 +37,10 @@ import org.jboss.logging.Logger;
|
|||
import org.keycloak.quarkus.runtime.cli.ExecutionExceptionHandler;
|
||||
import org.keycloak.quarkus.runtime.cli.Picocli;
|
||||
import org.keycloak.common.Version;
|
||||
import org.keycloak.quarkus.runtime.cli.command.Start;
|
||||
|
||||
import io.quarkus.runtime.QuarkusApplication;
|
||||
import io.quarkus.runtime.annotations.QuarkusMain;
|
||||
import picocli.CommandLine;
|
||||
|
||||
/**
|
||||
* <p>The main entry point, responsible for initialize and run the CLI as well as start the server.
|
||||
|
@ -51,25 +53,36 @@ public class KeycloakMain implements QuarkusApplication {
|
|||
|
||||
public static void main(String[] args) {
|
||||
System.setProperty("kc.version", Version.VERSION_KEYCLOAK);
|
||||
List<String> cliArgs = new ArrayList<>(Arrays.asList(args));
|
||||
System.setProperty(Environment.CLI_ARGS, Picocli.parseConfigArgs(cliArgs));
|
||||
List<String> cliArgs = Picocli.parseArgs(args);
|
||||
|
||||
if (cliArgs.isEmpty()) {
|
||||
cliArgs = new ArrayList<>(cliArgs);
|
||||
// default to show help message
|
||||
cliArgs.add("-h");
|
||||
} else if (cliArgs.contains(Start.NAME) && cliArgs.size() == 1) {
|
||||
// fast path for starting the server without bootstrapping CLI
|
||||
ExecutionExceptionHandler errorHandler = new ExecutionExceptionHandler();
|
||||
PrintWriter errStream = new PrintWriter(System.err, true);
|
||||
|
||||
if (isDevProfileNotAllowed(Arrays.asList(args))) {
|
||||
errorHandler.error(errStream, Messages.devProfileNotAllowedError(Start.NAME), null);
|
||||
return;
|
||||
}
|
||||
|
||||
start(errorHandler, errStream);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// parse arguments and execute any of the configured commands
|
||||
parseAndRun(cliArgs);
|
||||
}
|
||||
|
||||
public static void start(CommandLine cmd) {
|
||||
public static void start(ExecutionExceptionHandler errorHandler, PrintWriter errStream) {
|
||||
try {
|
||||
Quarkus.run(KeycloakMain.class, (exitCode, cause) -> {
|
||||
if (cause != null) {
|
||||
ExecutionExceptionHandler exceptionHandler = (ExecutionExceptionHandler) cmd.getExecutionExceptionHandler();
|
||||
|
||||
exceptionHandler.error(cmd.getErr(),
|
||||
errorHandler.error(errStream,
|
||||
String.format("Failed to start server using profile (%s)", getProfileOrDefault("prod")),
|
||||
cause.getCause());
|
||||
}
|
||||
|
@ -81,9 +94,7 @@ public class KeycloakMain implements QuarkusApplication {
|
|||
}
|
||||
});
|
||||
} catch (Throwable cause) {
|
||||
ExecutionExceptionHandler exceptionHandler = (ExecutionExceptionHandler) cmd.getExecutionExceptionHandler();
|
||||
|
||||
exceptionHandler.error(cmd.getErr(),
|
||||
errorHandler.error(errStream,
|
||||
String.format("Unexpected error when starting the server using profile (%s)", getProfileOrDefault("prod")),
|
||||
cause.getCause());
|
||||
}
|
||||
|
|
|
@ -17,7 +17,7 @@
|
|||
|
||||
package org.keycloak.quarkus.runtime;
|
||||
|
||||
import static org.keycloak.quarkus.runtime.configuration.Configuration.getBuiltTimeProperty;
|
||||
import static org.keycloak.quarkus.runtime.configuration.Configuration.getBuildTimeProperty;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
@ -85,10 +85,10 @@ public class KeycloakRecorder {
|
|||
feature = "kc.features";
|
||||
}
|
||||
|
||||
Optional<String> value = getBuiltTimeProperty(feature);
|
||||
Optional<String> value = getBuildTimeProperty(feature);
|
||||
|
||||
if (value.isEmpty()) {
|
||||
value = getBuiltTimeProperty(feature.replaceAll("\\.features\\.", "\\.features-"));
|
||||
value = getBuildTimeProperty(feature.replaceAll("\\.features\\.", "\\.features-"));
|
||||
}
|
||||
|
||||
if (value.isPresent()) {
|
||||
|
|
|
@ -45,4 +45,8 @@ public final class Messages {
|
|||
public static void cliExecutionError(CommandLine cmd, String message, Throwable cause) {
|
||||
throw new CommandLine.ExecutionException(cmd, message, cause);
|
||||
}
|
||||
|
||||
public static String devProfileNotAllowedError(String cmd) {
|
||||
return String.format("You can not '%s' the server using the '%s' configuration profile. Please re-build the server first, using 'kc.sh build' for the default production profile, or using 'kc.sh build --profile=<profile>' with a profile more suitable for production.%n", cmd, Environment.DEV_PROFILE_VALUE);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,29 @@
|
|||
/*
|
||||
* Copyright 2021 Red Hat, Inc. and/or its affiliates
|
||||
* and other contributors as indicated by the @author tags.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.keycloak.quarkus.runtime.cli;
|
||||
|
||||
import picocli.CommandLine.IFactory;
|
||||
|
||||
public class DefaultFactory implements IFactory {
|
||||
|
||||
@Override
|
||||
public <K> K create(Class<K> cls) throws Exception {
|
||||
// picocli tries different approaches for creating instances, this is what we need
|
||||
return cls.getDeclaredConstructor().newInstance();
|
||||
}
|
||||
}
|
|
@ -38,7 +38,7 @@ public final class ExecutionExceptionHandler implements CommandLine.IExecutionEx
|
|||
private Logger logger;
|
||||
private boolean verbose;
|
||||
|
||||
ExecutionExceptionHandler() {}
|
||||
public ExecutionExceptionHandler() {}
|
||||
|
||||
@Override
|
||||
public int handleExecutionException(Exception cause, CommandLine cmd, ParseResult parseResult) {
|
||||
|
|
|
@ -21,7 +21,9 @@ import static io.smallrye.config.common.utils.StringUtil.replaceNonAlphanumericB
|
|||
import static java.util.Arrays.asList;
|
||||
import static org.keycloak.quarkus.runtime.cli.command.AbstractStartCommand.AUTO_BUILD_OPTION_LONG;
|
||||
import static org.keycloak.quarkus.runtime.cli.command.AbstractStartCommand.AUTO_BUILD_OPTION_SHORT;
|
||||
import static org.keycloak.quarkus.runtime.configuration.Configuration.getBuiltTimeProperty;
|
||||
import static org.keycloak.quarkus.runtime.configuration.ConfigArgsConfigSource.hasOptionValue;
|
||||
import static org.keycloak.quarkus.runtime.configuration.ConfigArgsConfigSource.parseConfigArgs;
|
||||
import static org.keycloak.quarkus.runtime.configuration.Configuration.getBuildTimeProperty;
|
||||
import static org.keycloak.quarkus.runtime.configuration.Configuration.getConfig;
|
||||
import static org.keycloak.quarkus.runtime.Environment.isDevMode;
|
||||
import static org.keycloak.quarkus.runtime.configuration.Configuration.getRuntimeProperty;
|
||||
|
@ -31,19 +33,16 @@ import static org.keycloak.utils.StringUtil.isNotBlank;
|
|||
import static picocli.CommandLine.Model.UsageMessageSpec.SECTION_KEY_COMMAND_LIST;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.InputStream;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.Properties;
|
||||
import java.util.Set;
|
||||
import java.util.function.BiConsumer;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.function.UnaryOperator;
|
||||
import java.util.regex.Pattern;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.keycloak.quarkus.runtime.cli.command.Build;
|
||||
|
@ -51,10 +50,10 @@ import org.keycloak.quarkus.runtime.cli.command.Main;
|
|||
import org.keycloak.quarkus.runtime.cli.command.Start;
|
||||
import org.keycloak.quarkus.runtime.cli.command.StartDev;
|
||||
import org.keycloak.common.Profile;
|
||||
import org.keycloak.quarkus.runtime.configuration.MicroProfileConfigProvider;
|
||||
import org.keycloak.quarkus.runtime.configuration.ConfigArgsConfigSource;
|
||||
import org.keycloak.quarkus.runtime.configuration.PersistedConfigSource;
|
||||
import org.keycloak.quarkus.runtime.configuration.mappers.ConfigCategory;
|
||||
import org.keycloak.quarkus.runtime.configuration.mappers.PropertyMappers;
|
||||
import org.keycloak.quarkus.runtime.configuration.KeycloakConfigSourceProvider;
|
||||
import org.keycloak.quarkus.runtime.configuration.mappers.PropertyMapper;
|
||||
import org.keycloak.quarkus.runtime.Environment;
|
||||
|
||||
|
@ -66,13 +65,10 @@ import picocli.CommandLine.Model.ArgGroupSpec;
|
|||
|
||||
public final class Picocli {
|
||||
|
||||
private static final String ARG_SEPARATOR = ";;";
|
||||
public static final String ARG_PREFIX = "--";
|
||||
private static final String ARG_KEY_VALUE_SEPARATOR = "=";
|
||||
public static final String ARG_SHORT_PREFIX = "-";
|
||||
public static final String ARG_PART_SEPARATOR = "-";
|
||||
public static final char ARG_KEY_VALUE_SEPARATOR = '=';
|
||||
public static final Pattern ARG_SPLIT = Pattern.compile(";;");
|
||||
public static final Pattern ARG_KEY_VALUE_SPLIT = Pattern.compile("=");
|
||||
public static final String NO_PARAM_LABEL = "none";
|
||||
|
||||
private Picocli() {
|
||||
|
@ -95,8 +91,6 @@ public final class Picocli {
|
|||
// force the server image to be set with the dev profile
|
||||
Environment.forceDevProfile();
|
||||
}
|
||||
|
||||
Environment.setUserInvokedCliArgs(cliArgs);
|
||||
}
|
||||
if (requiresReAugmentation(cmd)) {
|
||||
runReAugmentation(cliArgs, cmd);
|
||||
|
@ -112,20 +106,30 @@ public final class Picocli {
|
|||
return cliArgs.contains("--help") || cliArgs.contains("-h") || cliArgs.contains("--help-all");
|
||||
}
|
||||
|
||||
private static boolean hasAutoBuildOption(List<String> cliArgs) {
|
||||
public static boolean hasAutoBuildOption(List<String> cliArgs) {
|
||||
return cliArgs.contains(AUTO_BUILD_OPTION_LONG) || cliArgs.contains(AUTO_BUILD_OPTION_SHORT);
|
||||
}
|
||||
|
||||
private static boolean requiresReAugmentation(CommandLine cmd) {
|
||||
public static boolean requiresReAugmentation(CommandLine cmd) {
|
||||
if (hasConfigChanges()) {
|
||||
cmd.getOut().println("Changes detected in configuration. Updating the server image.");
|
||||
Predicate<String> profileOptionMatcher = Main.PROFILE_LONG_NAME::equals;
|
||||
profileOptionMatcher = profileOptionMatcher.or(Main.PROFILE_SHORT_NAME::equals);
|
||||
|
||||
if (hasOptionValue(profileOptionMatcher, "dev") && !ConfigArgsConfigSource.getAllCliArgs().contains(StartDev.NAME)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if(!isDevMode()) {
|
||||
List<String> cliInput = getSanitizedCliInput();
|
||||
cmd.getOut().printf("For an optional runtime and bypass this step, please run the '%s' command prior to starting the server:%n%n\t%s %s %s%n",
|
||||
Build.NAME,
|
||||
Environment.getCommand(),
|
||||
Build.NAME,
|
||||
String.join(" ", cliInput) + "\n");
|
||||
if (cmd != null) {
|
||||
cmd.getOut().println("Changes detected in configuration. Updating the server image.");
|
||||
List<String> cliInput = getSanitizedCliInput();
|
||||
cmd.getOut()
|
||||
.printf("For an optional runtime and bypass this step, please run the '%s' command prior to starting the server:%n%n\t%s %s %s%n%n",
|
||||
Build.NAME,
|
||||
Environment.getCommand(),
|
||||
Build.NAME,
|
||||
String.join(" ", cliInput) + "\n");
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
@ -140,29 +144,15 @@ public final class Picocli {
|
|||
* instead of the actual passwords value.
|
||||
*/
|
||||
private static List<String> getSanitizedCliInput() {
|
||||
|
||||
if(Environment.getConfigArgs().isEmpty()) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
List<String> rawCliArgs = asList(ARG_SPLIT.split(Environment.getConfigArgs()));
|
||||
List<String> properties = new ArrayList<>();
|
||||
|
||||
if (!rawCliArgs.isEmpty()) {
|
||||
for(String rawCliArg : rawCliArgs) {
|
||||
String rawKey = rawCliArg.split("=")[0];
|
||||
PropertyMapper mapper = PropertyMappers.getMapper(rawKey);
|
||||
String value = rawCliArg.split("=")[1];
|
||||
|
||||
if (mapper != null) {
|
||||
value = formatValue(
|
||||
mapper.getFrom(),
|
||||
value);
|
||||
}
|
||||
|
||||
properties.add(rawKey + "=" + value);
|
||||
parseConfigArgs(new BiConsumer<String, String>() {
|
||||
@Override
|
||||
public void accept(String key, String value) {
|
||||
properties.add(key + "=" + formatValue(key, value));
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return properties;
|
||||
}
|
||||
|
||||
|
@ -190,23 +180,14 @@ public final class Picocli {
|
|||
}
|
||||
|
||||
private static boolean hasProviderChanges() {
|
||||
File propertiesFile = KeycloakConfigSourceProvider.getPersistedConfigFile().toFile();
|
||||
Map<String, String> persistedProps = PersistedConfigSource.getInstance().getProperties();
|
||||
Map<String, File> deployedProviders = Environment.getProviderFiles();
|
||||
|
||||
if (!propertiesFile.exists()) {
|
||||
if (persistedProps.isEmpty()) {
|
||||
return !deployedProviders.isEmpty();
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
Set<String> providerKeys = properties.stringPropertyNames().stream().filter(Picocli::isProviderKey).collect(
|
||||
Collectors.toSet());
|
||||
Set<String> providerKeys = persistedProps.keySet().stream().filter(Picocli::isProviderKey).collect(Collectors.toSet());
|
||||
|
||||
if (deployedProviders.size() != providerKeys.size()) {
|
||||
return true;
|
||||
|
@ -220,7 +201,7 @@ public final class Picocli {
|
|||
}
|
||||
|
||||
File file = deployedProviders.get(fileName);
|
||||
String lastModified = properties.getProperty(key);
|
||||
String lastModified = persistedProps.get(key);
|
||||
|
||||
if (!lastModified.equals(String.valueOf(file.lastModified()))) {
|
||||
return true;
|
||||
|
@ -232,7 +213,7 @@ public final class Picocli {
|
|||
|
||||
private static boolean hasConfigChanges() {
|
||||
Optional<String> currentProfile = Optional.ofNullable(Environment.getProfile());
|
||||
Optional<String> persistedProfile = getBuiltTimeProperty("kc.profile");
|
||||
Optional<String> persistedProfile = getBuildTimeProperty("kc.profile");
|
||||
|
||||
if (!persistedProfile.orElse("").equals(currentProfile.orElse(""))) {
|
||||
return true;
|
||||
|
@ -255,7 +236,7 @@ public final class Picocli {
|
|||
propertyName = propertyName.substring(propertyName.indexOf('.') + 1);
|
||||
}
|
||||
|
||||
String persistedValue = getBuiltTimeProperty(propertyName).orElse("");
|
||||
String persistedValue = getBuildTimeProperty(propertyName).orElse("");
|
||||
String runtimeValue = getRuntimeProperty(propertyName).orElse(null);
|
||||
|
||||
if (runtimeValue == null && isNotBlank(persistedValue)) {
|
||||
|
@ -277,8 +258,7 @@ public final class Picocli {
|
|||
}
|
||||
|
||||
public static CommandLine createCommandLine(List<String> cliArgs) {
|
||||
CommandSpec spec = CommandSpec.forAnnotatedObject(new Main())
|
||||
.name(Environment.getCommand());
|
||||
CommandSpec spec = CommandSpec.forAnnotatedObject(new Main(), new DefaultFactory()).name(Environment.getCommand());
|
||||
|
||||
for (CommandLine subCommand : spec.subcommands().values()) {
|
||||
CommandSpec subCommandSpec = subCommand.getCommandSpec();
|
||||
|
@ -290,66 +270,20 @@ public final class Picocli {
|
|||
.build());
|
||||
}
|
||||
|
||||
boolean isStartCommand = cliArgs.size() == 1 && cliArgs.contains(Start.NAME);
|
||||
|
||||
// avoid unnecessary processing when starting the server
|
||||
if (!isStartCommand) {
|
||||
addOption(spec, Start.NAME, hasAutoBuildOption(cliArgs));
|
||||
addOption(spec, StartDev.NAME, true);
|
||||
addOption(spec, Build.NAME, true);
|
||||
}
|
||||
addOption(spec, Start.NAME, hasAutoBuildOption(cliArgs));
|
||||
addOption(spec, StartDev.NAME, true);
|
||||
addOption(spec, Build.NAME, true);
|
||||
|
||||
CommandLine cmd = new CommandLine(spec);
|
||||
|
||||
cmd.setExecutionExceptionHandler(new ExecutionExceptionHandler());
|
||||
|
||||
if (!isStartCommand) {
|
||||
cmd.setHelpFactory(new HelpFactory());
|
||||
cmd.getHelpSectionMap().put(SECTION_KEY_COMMAND_LIST, new SubCommandListRenderer());
|
||||
}
|
||||
cmd.setHelpFactory(new HelpFactory());
|
||||
cmd.getHelpSectionMap().put(SECTION_KEY_COMMAND_LIST, new SubCommandListRenderer());
|
||||
|
||||
return cmd;
|
||||
}
|
||||
|
||||
public static String parseConfigArgs(List<String> argsList) {
|
||||
StringBuilder options = new StringBuilder();
|
||||
Iterator<String> iterator = argsList.iterator();
|
||||
boolean expectValue = false;
|
||||
List<String> ignoredArgs = asList("--verbose", "-v", "--help", "-h", AUTO_BUILD_OPTION_LONG, AUTO_BUILD_OPTION_SHORT);
|
||||
|
||||
while (iterator.hasNext()) {
|
||||
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
|
||||
// change this once we are able to obtain properties from providers
|
||||
if (key.startsWith("--spi")) {
|
||||
iterator.remove();
|
||||
}
|
||||
|
||||
if (ignoredArgs.contains(key)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (key.startsWith(ARG_PREFIX)) {
|
||||
if (options.length() > 0) {
|
||||
options.append(ARG_SEPARATOR);
|
||||
}
|
||||
|
||||
options.append(key);
|
||||
|
||||
if (key.indexOf(ARG_KEY_VALUE_SEPARATOR) == -1) {
|
||||
// values can be set using spaces (e.g.: --option <value>)
|
||||
expectValue = true;
|
||||
}
|
||||
} else if (expectValue) {
|
||||
options.append(ARG_KEY_VALUE_SEPARATOR).append(key);
|
||||
expectValue = false;
|
||||
}
|
||||
}
|
||||
|
||||
return options.toString();
|
||||
}
|
||||
|
||||
private static void addOption(CommandSpec spec, String command, boolean includeBuildTime) {
|
||||
CommandSpec commandSpec = spec.subcommands().get(command).getCommandSpec();
|
||||
List<PropertyMapper> mappers = new ArrayList<>(PropertyMappers.getRuntimeMappers());
|
||||
|
@ -443,4 +377,36 @@ public final class Picocli {
|
|||
public static String normalizeKey(String key) {
|
||||
return replaceNonAlphanumericByUnderscores(key).replace('_', '.');
|
||||
}
|
||||
|
||||
public static List<String> parseArgs(String[] rawArgs) {
|
||||
if (rawArgs.length == 0) {
|
||||
return List.of();
|
||||
}
|
||||
|
||||
// makes sure cli args are available to the config source
|
||||
ConfigArgsConfigSource.setCliArgs(rawArgs);
|
||||
List<String> args = new ArrayList<>(List.of(rawArgs));
|
||||
Iterator<String> iterator = args.iterator();
|
||||
|
||||
while (iterator.hasNext()) {
|
||||
String arg = iterator.next();
|
||||
|
||||
if (arg.startsWith("--spi")) {
|
||||
// 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
|
||||
iterator.remove();
|
||||
|
||||
if (!arg.contains(ARG_KEY_VALUE_SEPARATOR)) {
|
||||
String next = iterator.next();
|
||||
|
||||
if (!next.startsWith("--")) {
|
||||
// ignore the value if the arg is using space as separator
|
||||
iterator.remove();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return args;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -19,8 +19,6 @@ package org.keycloak.quarkus.runtime.cli.command;
|
|||
|
||||
import static org.keycloak.quarkus.runtime.Messages.cliExecutionError;
|
||||
|
||||
import org.keycloak.quarkus.runtime.Environment;
|
||||
|
||||
import picocli.CommandLine;
|
||||
import picocli.CommandLine.Model.CommandSpec;
|
||||
import picocli.CommandLine.Spec;
|
||||
|
@ -30,10 +28,6 @@ public abstract class AbstractCommand {
|
|||
@Spec
|
||||
protected CommandSpec spec;
|
||||
|
||||
protected void devProfileNotAllowedError(String cmd) {
|
||||
executionError(spec.commandLine(), String.format("You can not '%s' the server using the '%s' configuration profile. Please re-build the server first, using 'kc.sh build' for the default production profile, or using 'kc.sh build --profile=<profile>' with a profile more suitable for production.%n", cmd, Environment.DEV_PROFILE_VALUE));
|
||||
}
|
||||
|
||||
protected void executionError(CommandLine cmd, String message) {
|
||||
executionError(cmd, message, null);
|
||||
}
|
||||
|
|
|
@ -18,6 +18,9 @@
|
|||
package org.keycloak.quarkus.runtime.cli.command;
|
||||
|
||||
import org.keycloak.quarkus.runtime.KeycloakMain;
|
||||
import org.keycloak.quarkus.runtime.cli.ExecutionExceptionHandler;
|
||||
|
||||
import picocli.CommandLine;
|
||||
|
||||
public abstract class AbstractStartCommand extends AbstractCommand implements Runnable {
|
||||
|
||||
|
@ -27,7 +30,8 @@ public abstract class AbstractStartCommand extends AbstractCommand implements Ru
|
|||
@Override
|
||||
public void run() {
|
||||
doBeforeRun();
|
||||
KeycloakMain.start(spec.commandLine());
|
||||
CommandLine cmd = spec.commandLine();
|
||||
KeycloakMain.start((ExecutionExceptionHandler) cmd.getExecutionExceptionHandler(), cmd.getErr());
|
||||
}
|
||||
|
||||
protected void doBeforeRun() {
|
||||
|
|
|
@ -20,8 +20,10 @@ package org.keycloak.quarkus.runtime.cli.command;
|
|||
import static org.keycloak.quarkus.runtime.Environment.getHomePath;
|
||||
import static org.keycloak.quarkus.runtime.Environment.isDevMode;
|
||||
import static org.keycloak.quarkus.runtime.cli.Picocli.println;
|
||||
import static org.keycloak.quarkus.runtime.configuration.ConfigArgsConfigSource.getAllCliArgs;
|
||||
|
||||
import org.keycloak.quarkus.runtime.Environment;
|
||||
import org.keycloak.quarkus.runtime.Messages;
|
||||
|
||||
import io.quarkus.bootstrap.runner.QuarkusEntryPoint;
|
||||
import io.quarkus.bootstrap.runner.RunnerClassLoader;
|
||||
|
@ -30,10 +32,6 @@ import io.quarkus.runtime.configuration.ProfileManager;
|
|||
import picocli.CommandLine.Command;
|
||||
import picocli.CommandLine.Mixin;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.util.List;
|
||||
|
||||
@Command(name = Build.NAME,
|
||||
header = "Creates a new and optimized server image.",
|
||||
description = {
|
||||
|
@ -94,9 +92,8 @@ public final class Build extends AbstractCommand implements Runnable {
|
|||
}
|
||||
|
||||
private void exitWithErrorIfDevProfileIsSetAndNotStartDev() {
|
||||
List<String> userInvokedCliArgs = Environment.getUserInvokedCliArgs();
|
||||
if(Environment.isDevProfile() && !userInvokedCliArgs.contains(StartDev.NAME)) {
|
||||
devProfileNotAllowedError(Build.NAME);
|
||||
if (Environment.isDevProfile() && !getAllCliArgs().contains(StartDev.NAME)) {
|
||||
executionError(spec.commandLine(), Messages.devProfileNotAllowedError(NAME));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -117,11 +114,7 @@ public final class Build extends AbstractCommand implements Runnable {
|
|||
private void cleanTempResources() {
|
||||
if (!ProfileManager.getLaunchMode().isDevOrTest()) {
|
||||
// only needed for dev/testing purposes
|
||||
try {
|
||||
Files.delete(getHomePath().resolve("quarkus-artifact.properties"));
|
||||
} catch (IOException cause) {
|
||||
throw new RuntimeException("Failed to delete temporary resources", cause);
|
||||
}
|
||||
getHomePath().resolve("quarkus-artifact.properties").toFile().delete();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -21,7 +21,7 @@ import org.keycloak.quarkus.runtime.cli.Help;
|
|||
|
||||
import picocli.CommandLine;
|
||||
|
||||
final class HelpAllMixin {
|
||||
public final class HelpAllMixin {
|
||||
|
||||
@CommandLine.Spec
|
||||
private CommandLine.Model.CommandSpec spec;
|
||||
|
|
|
@ -68,6 +68,9 @@ import picocli.CommandLine.Option;
|
|||
})
|
||||
public final class Main {
|
||||
|
||||
public static final String PROFILE_SHORT_NAME = "-pf";
|
||||
public static final String PROFILE_LONG_NAME = "--profile";
|
||||
|
||||
@CommandLine.Spec
|
||||
CommandLine.Model.CommandSpec spec;
|
||||
|
||||
|
@ -94,7 +97,7 @@ public final class Main {
|
|||
exceptionHandler.setVerbose(verbose);
|
||||
}
|
||||
|
||||
@Option(names = {"-pf", "--profile"},
|
||||
@Option(names = { PROFILE_SHORT_NAME, PROFILE_LONG_NAME },
|
||||
description = "Set the profile. Use 'dev' profile to enable development mode.")
|
||||
public void setProfile(String profile) {
|
||||
Environment.setProfile(profile);
|
||||
|
|
|
@ -17,8 +17,7 @@
|
|||
|
||||
package org.keycloak.quarkus.runtime.cli.command;
|
||||
|
||||
import static java.lang.Boolean.parseBoolean;
|
||||
import static org.keycloak.quarkus.runtime.configuration.Configuration.getBuiltTimeProperty;
|
||||
import static org.keycloak.quarkus.runtime.configuration.Configuration.getBuildTimeProperty;
|
||||
import static org.keycloak.quarkus.runtime.configuration.Configuration.getConfigValue;
|
||||
import static org.keycloak.quarkus.runtime.configuration.Configuration.getPropertyNames;
|
||||
import static org.keycloak.quarkus.runtime.configuration.mappers.PropertyMappers.canonicalFormat;
|
||||
|
@ -119,7 +118,7 @@ public final class ShowConfig extends AbstractCommand implements Runnable {
|
|||
String profile = Environment.getProfile();
|
||||
|
||||
if (profile == null) {
|
||||
return getBuiltTimeProperty("quarkus.profile").orElse(null);
|
||||
return getBuildTimeProperty("quarkus.profile").orElse(null);
|
||||
}
|
||||
|
||||
return profile;
|
||||
|
|
|
@ -17,12 +17,13 @@
|
|||
|
||||
package org.keycloak.quarkus.runtime.cli.command;
|
||||
|
||||
import static org.keycloak.quarkus.runtime.Environment.isDevProfile;
|
||||
import static org.keycloak.quarkus.runtime.Environment.setProfile;
|
||||
import static org.keycloak.quarkus.runtime.cli.Picocli.NO_PARAM_LABEL;
|
||||
import static org.keycloak.quarkus.runtime.configuration.Configuration.getBuiltTimeProperty;
|
||||
import static org.keycloak.quarkus.runtime.configuration.Configuration.getRawPersistedProperty;
|
||||
|
||||
import org.keycloak.quarkus.runtime.Environment;
|
||||
import org.keycloak.quarkus.runtime.Messages;
|
||||
|
||||
import picocli.CommandLine;
|
||||
import picocli.CommandLine.Command;
|
||||
|
||||
|
@ -51,23 +52,25 @@ public final class Start extends AbstractStartCommand implements Runnable {
|
|||
|
||||
@Override
|
||||
protected void doBeforeRun() {
|
||||
checkIfProfileIsNotDev();
|
||||
devProfileNotAllowedError();
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the profile provided by either the current argument, the system environment or the persisted properties is dev.
|
||||
* Fails with an error when dev profile is used for the start command, or continues with the found profile if its not the dev profile.
|
||||
*/
|
||||
private void checkIfProfileIsNotDev() {
|
||||
List<String> currentCliArgs = spec.commandLine().getParseResult().expandedArgs();
|
||||
private void devProfileNotAllowedError() {
|
||||
if (isDevProfileNotAllowed(spec.commandLine().getParseResult().expandedArgs())) {
|
||||
executionError(spec.commandLine(), Messages.devProfileNotAllowedError(NAME));
|
||||
}
|
||||
}
|
||||
|
||||
public static boolean isDevProfileNotAllowed(List<String> currentCliArgs) {
|
||||
Optional<String> currentProfile = Optional.ofNullable(Environment.getProfile());
|
||||
Optional<String> persistedProfile = getBuiltTimeProperty("kc.profile");
|
||||
Optional<String> persistedProfile = getRawPersistedProperty("kc.profile");
|
||||
|
||||
setProfile(currentProfile.orElse(persistedProfile.orElse("prod")));
|
||||
|
||||
if (isDevProfile() && (!currentCliArgs.contains(AUTO_BUILD_OPTION_LONG) || !currentCliArgs.contains(AUTO_BUILD_OPTION_SHORT))) {
|
||||
devProfileNotAllowedError(Start.NAME);
|
||||
if (Environment.isDevProfile() && (!currentCliArgs.contains(AUTO_BUILD_OPTION_LONG) || !currentCliArgs.contains(AUTO_BUILD_OPTION_SHORT))) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -17,21 +17,27 @@
|
|||
|
||||
package org.keycloak.quarkus.runtime.configuration;
|
||||
|
||||
import static java.util.Arrays.asList;
|
||||
import static org.keycloak.quarkus.runtime.Environment.isTestLaunchMode;
|
||||
import static org.keycloak.quarkus.runtime.cli.Picocli.ARG_KEY_VALUE_SPLIT;
|
||||
import static org.keycloak.quarkus.runtime.cli.Picocli.ARG_PREFIX;
|
||||
import static org.keycloak.quarkus.runtime.cli.Picocli.ARG_SPLIT;
|
||||
import static org.keycloak.quarkus.runtime.cli.Picocli.ARG_SHORT_PREFIX;
|
||||
import static org.keycloak.quarkus.runtime.cli.command.AbstractStartCommand.AUTO_BUILD_OPTION_LONG;
|
||||
import static org.keycloak.quarkus.runtime.cli.command.AbstractStartCommand.AUTO_BUILD_OPTION_SHORT;
|
||||
import static org.keycloak.quarkus.runtime.configuration.Configuration.getMappedPropertyName;
|
||||
import static org.keycloak.quarkus.runtime.configuration.MicroProfileConfigProvider.NS_KEYCLOAK_PREFIX;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.function.BiConsumer;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import org.jboss.logging.Logger;
|
||||
|
||||
import io.smallrye.config.PropertiesConfigSource;
|
||||
import org.keycloak.quarkus.runtime.Environment;
|
||||
|
||||
import org.keycloak.quarkus.runtime.cli.Picocli;
|
||||
import org.keycloak.quarkus.runtime.configuration.mappers.PropertyMapper;
|
||||
import org.keycloak.quarkus.runtime.configuration.mappers.PropertyMappers;
|
||||
|
@ -49,14 +55,48 @@ public class ConfigArgsConfigSource extends PropertiesConfigSource {
|
|||
|
||||
private static final Logger log = Logger.getLogger(ConfigArgsConfigSource.class);
|
||||
|
||||
public static final String CLI_ARGS = "kc.config.args";
|
||||
private static final String ARG_SEPARATOR = ";;";
|
||||
private static final Pattern ARG_SPLIT = Pattern.compile(";;");
|
||||
private static final Pattern ARG_KEY_VALUE_SPLIT = Pattern.compile("=");
|
||||
private static final ConfigArgsConfigSource INSTANCE = new ConfigArgsConfigSource();
|
||||
private static List<String> IGNORED_ARGS;
|
||||
|
||||
private final boolean alwaysParseArgs;
|
||||
|
||||
public static ConfigArgsConfigSource getInstance() {
|
||||
return INSTANCE;
|
||||
}
|
||||
|
||||
ConfigArgsConfigSource() {
|
||||
// higher priority over default Quarkus config sources
|
||||
super(parseArgument(), "CliConfigSource", 500);
|
||||
alwaysParseArgs = isTestLaunchMode();
|
||||
}
|
||||
|
||||
public static void setCliArgs(String[] args) {
|
||||
System.setProperty(CLI_ARGS, String.join(ARG_SEPARATOR, args));
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads the previously set system property for the originally command.
|
||||
* Use the System variable, when you trigger other command executions internally, but need a reference to the
|
||||
* actually invoked command.
|
||||
*
|
||||
* @return the invoked command from the CLI, or empty List if not set.
|
||||
*/
|
||||
public static List<String> getAllCliArgs() {
|
||||
if(System.getProperty(CLI_ARGS) == null) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
return List.of(System.getProperty(CLI_ARGS).split(ARG_SEPARATOR));
|
||||
}
|
||||
|
||||
private static String getRawConfigArgs() {
|
||||
return System.getProperty(CLI_ARGS, "");
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getValue(String propertyName) {
|
||||
Map<String, String> properties = getProperties();
|
||||
|
@ -75,18 +115,77 @@ public class ConfigArgsConfigSource extends PropertiesConfigSource {
|
|||
}
|
||||
|
||||
private static Map<String, String> parseArgument() {
|
||||
String args = Environment.getConfigArgs();
|
||||
// init here because the class might be loaded by CL without init
|
||||
IGNORED_ARGS = asList("--verbose", "-v", "--help", "-h", AUTO_BUILD_OPTION_LONG, AUTO_BUILD_OPTION_SHORT);
|
||||
String rawArgs = getRawConfigArgs();
|
||||
|
||||
if (args == null || "".equals(args.trim())) {
|
||||
if (rawArgs == null || "".equals(rawArgs.trim())) {
|
||||
log.trace("No command-line arguments provided");
|
||||
return Collections.emptyMap();
|
||||
}
|
||||
|
||||
Map<String, String> properties = new HashMap<>();
|
||||
|
||||
for (String arg : ARG_SPLIT.split(args)) {
|
||||
if (!arg.startsWith(ARG_PREFIX)) {
|
||||
throw new IllegalArgumentException("Invalid argument format [" + arg + "], arguments must start with '--'");
|
||||
parseConfigArgs(new BiConsumer<String, String>() {
|
||||
@Override
|
||||
public void accept(String key, String value) {
|
||||
key = NS_KEYCLOAK_PREFIX + key.substring(2);
|
||||
|
||||
log.tracef("Adding property [%s=%s] from command-line", key, value);
|
||||
properties.put(key, value);
|
||||
|
||||
String mappedPropertyName = getMappedPropertyName(key);
|
||||
|
||||
properties.put(mappedPropertyName, value);
|
||||
|
||||
PropertyMapper mapper = PropertyMappers.getMapper(mappedPropertyName);
|
||||
|
||||
if (mapper != null) {
|
||||
properties.put(mapper.getFrom(), value);
|
||||
}
|
||||
|
||||
// to make lookup easier, we normalize the key
|
||||
properties.put(Picocli.normalizeKey(key), value);
|
||||
}
|
||||
});
|
||||
|
||||
return properties;
|
||||
}
|
||||
|
||||
public static boolean hasOptionValue(Predicate<String> keyMatcher, String expectedValue) {
|
||||
AtomicBoolean result = new AtomicBoolean();
|
||||
|
||||
parseConfigArgs(new BiConsumer<String, String>() {
|
||||
@Override
|
||||
public void accept(String key, String value) {
|
||||
if (keyMatcher.test(key) && expectedValue.equals(value)) {
|
||||
result.set(true);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return result.get();
|
||||
}
|
||||
|
||||
public static void parseConfigArgs(BiConsumer<String, String> cliArgConsumer) {
|
||||
String rawArgs = getRawConfigArgs();
|
||||
|
||||
if (rawArgs == null || "".equals(rawArgs.trim())) {
|
||||
log.trace("No command-line arguments provided");
|
||||
return;
|
||||
}
|
||||
|
||||
String[] args = ARG_SPLIT.split(rawArgs);
|
||||
|
||||
for (int i = 0; i < args.length; i++) {
|
||||
String arg = args[i];
|
||||
|
||||
if (IGNORED_ARGS.contains(arg)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!arg.startsWith(ARG_SHORT_PREFIX)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
String[] keyValue = ARG_KEY_VALUE_SPLIT.split(arg);
|
||||
|
@ -99,7 +198,10 @@ public class ConfigArgsConfigSource extends PropertiesConfigSource {
|
|||
String value;
|
||||
|
||||
if (keyValue.length == 1) {
|
||||
continue;
|
||||
if (args.length <= i + 1) {
|
||||
continue;
|
||||
}
|
||||
value = args[i + 1];
|
||||
} else if (keyValue.length == 2) {
|
||||
// the argument has a simple value. Eg.: key=pair
|
||||
value = keyValue[1];
|
||||
|
@ -108,25 +210,7 @@ public class ConfigArgsConfigSource extends PropertiesConfigSource {
|
|||
value = arg.substring(key.length() + 1);
|
||||
}
|
||||
|
||||
key = NS_KEYCLOAK_PREFIX + key.substring(2);
|
||||
|
||||
log.tracef("Adding property [%s=%s] from command-line", key, value);
|
||||
properties.put(key, value);
|
||||
|
||||
String mappedPropertyName = getMappedPropertyName(key);
|
||||
|
||||
properties.put(mappedPropertyName, value);
|
||||
|
||||
PropertyMapper mapper = PropertyMappers.getMapper(mappedPropertyName);
|
||||
|
||||
if (mapper != null) {
|
||||
properties.put(mapper.getFrom(), value);
|
||||
}
|
||||
|
||||
// to make lookup easier, we normalize the key
|
||||
properties.put(Picocli.normalizeKey(key), value);
|
||||
cliArgConsumer.accept(key, value);
|
||||
}
|
||||
|
||||
return properties;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -52,24 +52,28 @@ public final class Configuration {
|
|||
return CONFIG;
|
||||
}
|
||||
|
||||
public static Optional<String> getBuiltTimeProperty(String name) {
|
||||
String value = KeycloakConfigSourceProvider.PERSISTED_CONFIG_SOURCE.getValue(name);
|
||||
public static Optional<String> getBuildTimeProperty(String name) {
|
||||
Optional<String> value = getRawPersistedProperty(name);
|
||||
|
||||
if (value == null) {
|
||||
value = KeycloakConfigSourceProvider.PERSISTED_CONFIG_SOURCE.getValue(getMappedPropertyName(name));
|
||||
if (value.isEmpty()) {
|
||||
value = getRawPersistedProperty(getMappedPropertyName(name));
|
||||
}
|
||||
|
||||
if (value == null) {
|
||||
if (value.isEmpty()) {
|
||||
String profile = Environment.getProfile();
|
||||
|
||||
if (profile == null) {
|
||||
profile = getConfig().getRawValue(Environment.PROFILE);
|
||||
}
|
||||
|
||||
value = KeycloakConfigSourceProvider.PERSISTED_CONFIG_SOURCE.getValue("%" + profile + "." + name);
|
||||
value = getRawPersistedProperty("%" + profile + "." + name);
|
||||
}
|
||||
|
||||
return Optional.ofNullable(value);
|
||||
return value;
|
||||
}
|
||||
|
||||
public static Optional<String> getRawPersistedProperty(String name) {
|
||||
return Optional.ofNullable(PersistedConfigSource.getInstance().getValue(name));
|
||||
}
|
||||
|
||||
public static String getRawValue(String propertyName) {
|
||||
|
|
|
@ -18,22 +18,16 @@
|
|||
package org.keycloak.quarkus.runtime.configuration;
|
||||
|
||||
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.eclipse.microprofile.config.spi.ConfigSource;
|
||||
import org.eclipse.microprofile.config.spi.ConfigSourceProvider;
|
||||
import org.jboss.logging.Logger;
|
||||
import org.keycloak.quarkus.runtime.Environment;
|
||||
|
||||
public class KeycloakConfigSourceProvider implements ConfigSourceProvider {
|
||||
|
||||
private static final Logger log = Logger.getLogger(KeycloakConfigSourceProvider.class);
|
||||
|
||||
private static final List<ConfigSource> CONFIG_SOURCES = new ArrayList<>();
|
||||
public static PersistedConfigSource PERSISTED_CONFIG_SOURCE;
|
||||
|
||||
// we initialize in a static block to avoid discovering the config sources multiple times when starting the application
|
||||
static {
|
||||
|
@ -50,8 +44,7 @@ public class KeycloakConfigSourceProvider implements ConfigSourceProvider {
|
|||
CONFIG_SOURCES.add(new ConfigArgsConfigSource());
|
||||
CONFIG_SOURCES.add(new SysPropConfigSource());
|
||||
CONFIG_SOURCES.add(new KcEnvConfigSource());
|
||||
PERSISTED_CONFIG_SOURCE = new PersistedConfigSource(getPersistedConfigFile());
|
||||
CONFIG_SOURCES.add(PERSISTED_CONFIG_SOURCE);
|
||||
CONFIG_SOURCES.add(PersistedConfigSource.getInstance());
|
||||
|
||||
CONFIG_SOURCES.addAll(new KeycloakPropertiesConfigSource.InFileSystem().getConfigSources(Thread.currentThread().getContextClassLoader()));
|
||||
|
||||
|
@ -68,20 +61,6 @@ public class KeycloakConfigSourceProvider implements ConfigSourceProvider {
|
|||
initializeSources();
|
||||
}
|
||||
|
||||
public static Path getPersistedConfigFile() {
|
||||
String homeDir = Environment.getHomeDir();
|
||||
|
||||
if (homeDir == null) {
|
||||
return Paths.get(System.getProperty("java.io.tmpdir"), PersistedConfigSource.KEYCLOAK_PROPERTIES);
|
||||
}
|
||||
|
||||
Path generatedPath = Paths.get(homeDir, "data", "generated");
|
||||
|
||||
generatedPath.toFile().mkdirs();
|
||||
|
||||
return generatedPath.resolve(PersistedConfigSource.KEYCLOAK_PROPERTIES);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterable<ConfigSource> getConfigSources(ClassLoader forClassLoader) {
|
||||
return CONFIG_SOURCES;
|
||||
|
|
|
@ -17,27 +17,33 @@
|
|||
|
||||
package org.keycloak.quarkus.runtime.configuration;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Path;
|
||||
import java.io.InputStream;
|
||||
import java.net.URL;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Properties;
|
||||
|
||||
import io.smallrye.config.PropertiesConfigSource;
|
||||
import io.smallrye.config.common.utils.ConfigSourceUtil;
|
||||
import org.keycloak.quarkus.runtime.Environment;
|
||||
|
||||
/**
|
||||
* A {@link org.eclipse.microprofile.config.spi.ConfigSource} based on the configuration properties persisted into the server
|
||||
* image.
|
||||
*/
|
||||
public class PersistedConfigSource extends PropertiesConfigSource {
|
||||
public final class PersistedConfigSource extends PropertiesConfigSource {
|
||||
|
||||
public static final String NAME = "PersistedConfigSource";
|
||||
static final String KEYCLOAK_PROPERTIES = "persisted.properties";
|
||||
public static final String PERSISTED_PROPERTIES = "/META-INF/keycloak-persisted.properties";
|
||||
private static final PersistedConfigSource INSTANCE = new PersistedConfigSource();
|
||||
|
||||
public PersistedConfigSource(Path file) {
|
||||
super(readProperties(file), "", 300);
|
||||
private PersistedConfigSource() {
|
||||
super(readProperties(), "", 300);
|
||||
}
|
||||
|
||||
public static PersistedConfigSource getInstance() {
|
||||
return INSTANCE;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -45,11 +51,6 @@ public class PersistedConfigSource extends PropertiesConfigSource {
|
|||
return NAME;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, String> getProperties() {
|
||||
return Collections.emptyMap();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getValue(String propertyName) {
|
||||
String value = super.getValue(propertyName);
|
||||
|
@ -61,19 +62,46 @@ public class PersistedConfigSource extends PropertiesConfigSource {
|
|||
return null;
|
||||
}
|
||||
|
||||
private static Map<String, String> readProperties(Path path) {
|
||||
private static Map<String, String> readProperties() {
|
||||
if (!Environment.isRebuild()) {
|
||||
File file = path.toFile();
|
||||
InputStream fileStream = loadPersistedConfig();
|
||||
|
||||
if (file.exists()) {
|
||||
try {
|
||||
return ConfigSourceUtil.urlToMap(file.toURL());
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException("Failed to load persisted properties from [" + file.getAbsolutePath() + ".", e);
|
||||
if (fileStream == null) {
|
||||
return Collections.emptyMap();
|
||||
}
|
||||
|
||||
try (fileStream) {
|
||||
Properties properties = new Properties();
|
||||
|
||||
properties.load(fileStream);
|
||||
|
||||
Map<String, String> props = new HashMap<>();
|
||||
|
||||
for (Map.Entry<Object, Object> entry : properties.entrySet()) {
|
||||
props.put(entry.getKey().toString(), entry.getValue().toString());
|
||||
}
|
||||
|
||||
return props;
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException("Failed to load persisted properties.", e);
|
||||
}
|
||||
}
|
||||
|
||||
return Collections.emptyMap();
|
||||
}
|
||||
|
||||
private static InputStream loadPersistedConfig() {
|
||||
URL resource = Thread.currentThread().getContextClassLoader().getResource(PERSISTED_PROPERTIES);
|
||||
|
||||
if (resource == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
return resource.openStream();
|
||||
} catch (Exception cause) {
|
||||
throw new RuntimeException("Failed to resolve persisted propertied file", cause);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -133,7 +133,7 @@ final class HttpPropertyMappers {
|
|||
ConfigValue proceed = context.proceed("kc.https.certificate.file");
|
||||
|
||||
if (proceed == null || proceed.getValue() == null) {
|
||||
proceed = getMapper("quarkus.http.ssl.certificate.key-store-file").getOrDefault(context, null);
|
||||
proceed = getMapper("quarkus.http.ssl.certificate.key-store-file").getConfigValue(context);
|
||||
}
|
||||
|
||||
if (proceed == null || proceed.getValue() == null) {
|
||||
|
|
|
@ -30,8 +30,8 @@ public class PropertyMapper {
|
|||
static PropertyMapper IDENTITY = new PropertyMapper(null, null, null, null, null,
|
||||
false,null, null, false,Collections.emptyList(),null, true) {
|
||||
@Override
|
||||
public ConfigValue getOrDefault(String name, ConfigSourceInterceptorContext context, ConfigValue current) {
|
||||
return current;
|
||||
public ConfigValue getConfigValue(String name, ConfigSourceInterceptorContext context) {
|
||||
return context.proceed(name);
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -80,11 +80,11 @@ public class PropertyMapper {
|
|||
return value;
|
||||
}
|
||||
|
||||
ConfigValue getOrDefault(ConfigSourceInterceptorContext context, ConfigValue current) {
|
||||
return getOrDefault(null, context, current);
|
||||
ConfigValue getConfigValue(ConfigSourceInterceptorContext context) {
|
||||
return getConfigValue(to, context);
|
||||
}
|
||||
|
||||
ConfigValue getOrDefault(String name, ConfigSourceInterceptorContext context, ConfigValue current) {
|
||||
ConfigValue getConfigValue(String name, ConfigSourceInterceptorContext context) {
|
||||
String from = this.from;
|
||||
|
||||
if (to != null && to.endsWith(".")) {
|
||||
|
@ -92,7 +92,7 @@ public class PropertyMapper {
|
|||
from = name.replace(to.substring(0, to.lastIndexOf('.')), from.substring(0, from.lastIndexOf('.')));
|
||||
}
|
||||
|
||||
// try to obtain the value for the property we want to map
|
||||
// try to obtain the value for the property we want to map first
|
||||
ConfigValue config = context.proceed(from);
|
||||
|
||||
if (config == null) {
|
||||
|
@ -107,28 +107,18 @@ public class PropertyMapper {
|
|||
if (value != null) {
|
||||
return value;
|
||||
}
|
||||
|
||||
return parentValue;
|
||||
}
|
||||
}
|
||||
|
||||
// if not defined, return the current value from the property as a default if the property is not explicitly set
|
||||
if (defaultValue == null
|
||||
|| (current != null && !current.getConfigSourceName().equalsIgnoreCase("default values"))) {
|
||||
if (defaultValue == null && mapper != null) {
|
||||
String value = current == null ? null : current.getValue();
|
||||
return ConfigValue.builder().withName(to).withValue(mapper.apply(value, context)).build();
|
||||
}
|
||||
return current;
|
||||
}
|
||||
ConfigValue current = context.proceed(name);
|
||||
|
||||
if (mapper != null) {
|
||||
if (current == null) {
|
||||
return transformValue(defaultValue, context);
|
||||
}
|
||||
|
||||
return ConfigValue.builder().withName(to).withValue(defaultValue).build();
|
||||
}
|
||||
|
||||
if (mapFrom != null) {
|
||||
return config;
|
||||
return current;
|
||||
}
|
||||
|
||||
if (config.getName().equals(name)) {
|
||||
|
@ -139,7 +129,7 @@ public class PropertyMapper {
|
|||
|
||||
// we always fallback to the current value from the property we are mapping
|
||||
if (value == null) {
|
||||
return current;
|
||||
return context.proceed(name);
|
||||
}
|
||||
|
||||
return value;
|
||||
|
|
|
@ -3,6 +3,7 @@ package org.keycloak.quarkus.runtime.configuration.mappers;
|
|||
import io.smallrye.config.ConfigSourceInterceptorContext;
|
||||
import io.smallrye.config.ConfigValue;
|
||||
import org.keycloak.quarkus.runtime.Environment;
|
||||
import org.keycloak.quarkus.runtime.configuration.ConfigArgsConfigSource;
|
||||
import org.keycloak.quarkus.runtime.configuration.MicroProfileConfigProvider;
|
||||
|
||||
import java.util.Collection;
|
||||
|
@ -32,14 +33,13 @@ public final class PropertyMappers {
|
|||
|
||||
public static ConfigValue getValue(ConfigSourceInterceptorContext context, String name) {
|
||||
PropertyMapper mapper = MAPPERS.getOrDefault(name, PropertyMapper.IDENTITY);
|
||||
ConfigValue configValue = mapper
|
||||
.getOrDefault(name, context, context.proceed(name));
|
||||
ConfigValue configValue = mapper.getConfigValue(name, context);
|
||||
|
||||
if (configValue == null) {
|
||||
Optional<String> prefixedMapper = getPrefixedMapper(name);
|
||||
|
||||
if (prefixedMapper.isPresent()) {
|
||||
return MAPPERS.get(prefixedMapper.get()).getOrDefault(name, context, configValue);
|
||||
return MAPPERS.get(prefixedMapper.get()).getConfigValue(name, context);
|
||||
}
|
||||
} else {
|
||||
configValue.withName(mapper.getTo());
|
||||
|
@ -76,7 +76,7 @@ public final class PropertyMappers {
|
|||
|
||||
return isBuildTimeProperty
|
||||
&& !"kc.version".equals(name)
|
||||
&& !Environment.CLI_ARGS.equals(name)
|
||||
&& !ConfigArgsConfigSource.CLI_ARGS.equals(name)
|
||||
&& !"kc.home.dir".equals(name)
|
||||
&& !"kc.config.file".equals(name)
|
||||
&& !Environment.PROFILE.equals(name)
|
||||
|
|
|
@ -22,21 +22,11 @@ import io.quarkus.runtime.StartupEvent;
|
|||
import javax.enterprise.context.ApplicationScoped;
|
||||
import javax.enterprise.event.Observes;
|
||||
|
||||
import org.keycloak.Config;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.KeycloakSessionFactory;
|
||||
import org.keycloak.models.KeycloakTransactionManager;
|
||||
import org.keycloak.platform.Platform;
|
||||
import org.keycloak.services.ServicesLogger;
|
||||
import org.keycloak.services.managers.ApplianceBootstrap;
|
||||
import org.keycloak.services.resources.KeycloakApplication;
|
||||
|
||||
@ApplicationScoped
|
||||
public class QuarkusLifecycleObserver {
|
||||
|
||||
private static final String KEYCLOAK_ADMIN_ENV_VAR = "KEYCLOAK_ADMIN";
|
||||
private static final String KEYCLOAK_ADMIN_PASSWORD_ENV_VAR = "KEYCLOAK_ADMIN_PASSWORD";
|
||||
|
||||
void onStartupEvent(@Observes StartupEvent event) {
|
||||
QuarkusPlatform platform = (QuarkusPlatform) Platform.getPlatform();
|
||||
platform.started();
|
||||
|
@ -45,7 +35,6 @@ public class QuarkusLifecycleObserver {
|
|||
|
||||
if (startupHook != null) {
|
||||
startupHook.run();
|
||||
createAdminUser();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -57,35 +46,4 @@ public class QuarkusLifecycleObserver {
|
|||
shutdownHook.run();
|
||||
|
||||
}
|
||||
|
||||
private void createAdminUser() {
|
||||
String adminUserName = System.getenv(KEYCLOAK_ADMIN_ENV_VAR);
|
||||
String adminPassword = System.getenv(KEYCLOAK_ADMIN_PASSWORD_ENV_VAR);
|
||||
|
||||
if ((adminUserName == null || adminUserName.trim().length() == 0)
|
||||
|| (adminPassword == null || adminPassword.trim().length() == 0)) {
|
||||
return;
|
||||
}
|
||||
|
||||
KeycloakSessionFactory sessionFactory = KeycloakApplication.getSessionFactory();
|
||||
KeycloakSession session = sessionFactory.create();
|
||||
KeycloakTransactionManager transaction = session.getTransactionManager();
|
||||
|
||||
try {
|
||||
transaction.begin();
|
||||
|
||||
new ApplianceBootstrap(session).createMasterRealmUser(adminUserName, adminPassword);
|
||||
ServicesLogger.LOGGER.addUserSuccess(adminUserName, Config.getAdminRealm());
|
||||
|
||||
transaction.commit();
|
||||
} catch (IllegalStateException e) {
|
||||
session.getTransactionManager().rollback();
|
||||
ServicesLogger.LOGGER.addUserFailedUserExists(adminUserName, Config.getAdminRealm());
|
||||
} catch (Throwable t) {
|
||||
session.getTransactionManager().rollback();
|
||||
ServicesLogger.LOGGER.addUserFailed(t, adminUserName, Config.getAdminRealm());
|
||||
} finally {
|
||||
session.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -65,18 +65,6 @@ public class QuarkusPlatform implements PlatformProvider {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Similar behavior as per {@code #exitOnError} but convenient to throw a {@link InitializationException} with a single
|
||||
* {@code cause}
|
||||
*
|
||||
* @param cause the cause
|
||||
* @throws InitializationException the initialization exception with the given {@code cause}.
|
||||
*/
|
||||
public static void exitOnError(Throwable cause) throws InitializationException{
|
||||
addInitializationException(cause);
|
||||
exitOnError();
|
||||
}
|
||||
|
||||
Runnable startupHook;
|
||||
Runnable shutdownHook;
|
||||
|
||||
|
@ -155,7 +143,7 @@ public class QuarkusPlatform implements PlatformProvider {
|
|||
} else {
|
||||
String dataDir = Environment.getDataDir();
|
||||
tmpDir = new File(dataDir, "tmp");
|
||||
tmpDir.mkdir();
|
||||
tmpDir.mkdirs();
|
||||
}
|
||||
|
||||
if (tmpDir.isDirectory()) {
|
||||
|
|
|
@ -25,9 +25,14 @@ import javax.inject.Inject;
|
|||
import javax.persistence.EntityManagerFactory;
|
||||
import javax.ws.rs.ApplicationPath;
|
||||
|
||||
import org.keycloak.Config;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.KeycloakSessionFactory;
|
||||
import org.keycloak.models.KeycloakTransactionManager;
|
||||
import org.keycloak.models.utils.PostMigrationEvent;
|
||||
import org.keycloak.quarkus.runtime.integration.QuarkusKeycloakSessionFactory;
|
||||
import org.keycloak.quarkus.runtime.integration.QuarkusPlatform;
|
||||
import org.keycloak.services.ServicesLogger;
|
||||
import org.keycloak.services.managers.ApplianceBootstrap;
|
||||
import org.keycloak.services.resources.KeycloakApplication;
|
||||
import org.keycloak.quarkus.runtime.services.resources.QuarkusWelcomeResource;
|
||||
import org.keycloak.services.resources.WelcomeResource;
|
||||
|
@ -35,6 +40,9 @@ import org.keycloak.services.resources.WelcomeResource;
|
|||
@ApplicationPath("/")
|
||||
public class QuarkusKeycloakApplication extends KeycloakApplication {
|
||||
|
||||
private static final String KEYCLOAK_ADMIN_ENV_VAR = "KEYCLOAK_ADMIN";
|
||||
private static final String KEYCLOAK_ADMIN_PASSWORD_ENV_VAR = "KEYCLOAK_ADMIN_PASSWORD";
|
||||
|
||||
private static boolean filterSingletons(Object o) {
|
||||
return !WelcomeResource.class.isInstance(o);
|
||||
}
|
||||
|
@ -44,13 +52,10 @@ public class QuarkusKeycloakApplication extends KeycloakApplication {
|
|||
|
||||
@Override
|
||||
protected void startup() {
|
||||
try {
|
||||
forceEntityManagerInitialization();
|
||||
initializeKeycloakSessionFactory();
|
||||
setupScheduledTasks(sessionFactory);
|
||||
} catch (Throwable cause) {
|
||||
QuarkusPlatform.exitOnError(cause);
|
||||
}
|
||||
forceEntityManagerInitialization();
|
||||
initializeKeycloakSessionFactory();
|
||||
setupScheduledTasks(sessionFactory);
|
||||
createAdminUser();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -60,7 +65,6 @@ public class QuarkusKeycloakApplication extends KeycloakApplication {
|
|||
.collect(Collectors.toSet());
|
||||
|
||||
singletons.add(new QuarkusWelcomeResource());
|
||||
singletons.add(new QuarkusWelcomeResource());
|
||||
|
||||
return singletons;
|
||||
}
|
||||
|
@ -77,4 +81,35 @@ public class QuarkusKeycloakApplication extends KeycloakApplication {
|
|||
// when first creating an entity manager
|
||||
entityManagerFactory.get().createEntityManager().close();
|
||||
}
|
||||
|
||||
private void createAdminUser() {
|
||||
String adminUserName = System.getenv(KEYCLOAK_ADMIN_ENV_VAR);
|
||||
String adminPassword = System.getenv(KEYCLOAK_ADMIN_PASSWORD_ENV_VAR);
|
||||
|
||||
if ((adminUserName == null || adminUserName.trim().length() == 0)
|
||||
|| (adminPassword == null || adminPassword.trim().length() == 0)) {
|
||||
return;
|
||||
}
|
||||
|
||||
KeycloakSessionFactory sessionFactory = KeycloakApplication.getSessionFactory();
|
||||
KeycloakSession session = sessionFactory.create();
|
||||
KeycloakTransactionManager transaction = session.getTransactionManager();
|
||||
|
||||
try {
|
||||
transaction.begin();
|
||||
|
||||
new ApplianceBootstrap(session).createMasterRealmUser(adminUserName, adminPassword);
|
||||
ServicesLogger.LOGGER.addUserSuccess(adminUserName, Config.getAdminRealm());
|
||||
|
||||
transaction.commit();
|
||||
} catch (IllegalStateException e) {
|
||||
session.getTransactionManager().rollback();
|
||||
ServicesLogger.LOGGER.addUserFailedUserExists(adminUserName, Config.getAdminRealm());
|
||||
} catch (Throwable t) {
|
||||
session.getTransactionManager().rollback();
|
||||
ServicesLogger.LOGGER.addUserFailed(t, adminUserName, Config.getAdminRealm());
|
||||
} finally {
|
||||
session.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -28,42 +28,42 @@
|
|||
<key media-type="application/x-java-object"/>
|
||||
<value media-type="application/x-java-object"/>
|
||||
</encoding>
|
||||
<memory storage="HEAP" max-count="10000"/>
|
||||
<memory max-count="10000"/>
|
||||
</local-cache>
|
||||
<local-cache name="users">
|
||||
<encoding>
|
||||
<key media-type="application/x-java-object"/>
|
||||
<value media-type="application/x-java-object"/>
|
||||
</encoding>
|
||||
<memory storage="HEAP" max-count="10000"/>
|
||||
<memory max-count="10000"/>
|
||||
</local-cache>
|
||||
<distributed-cache name="sessions" owners="1">
|
||||
<expiration lifespan="900000000000000000"/>
|
||||
<distributed-cache name="sessions" owners="2">
|
||||
<expiration lifespan="-1"/>
|
||||
</distributed-cache>
|
||||
<distributed-cache name="authenticationSessions" owners="1">
|
||||
<expiration lifespan="900000000000000000"/>
|
||||
<distributed-cache name="authenticationSessions" owners="2">
|
||||
<expiration lifespan="-1"/>
|
||||
</distributed-cache>
|
||||
<distributed-cache name="offlineSessions" owners="1">
|
||||
<expiration lifespan="900000000000000000"/>
|
||||
<distributed-cache name="offlineSessions" owners="2">
|
||||
<expiration lifespan="-1"/>
|
||||
</distributed-cache>
|
||||
<distributed-cache name="clientSessions" owners="1">
|
||||
<expiration lifespan="900000000000000000"/>
|
||||
<distributed-cache name="clientSessions" owners="2">
|
||||
<expiration lifespan="-1"/>
|
||||
</distributed-cache>
|
||||
<distributed-cache name="offlineClientSessions" owners="1">
|
||||
<expiration lifespan="900000000000000000"/>
|
||||
<distributed-cache name="offlineClientSessions" owners="2">
|
||||
<expiration lifespan="-1"/>
|
||||
</distributed-cache>
|
||||
<distributed-cache name="loginFailures" owners="1">
|
||||
<expiration lifespan="900000000000000000"/>
|
||||
<distributed-cache name="loginFailures" owners="2">
|
||||
<expiration lifespan="-1"/>
|
||||
</distributed-cache>
|
||||
<local-cache name="authorization">
|
||||
<encoding>
|
||||
<key media-type="application/x-java-object"/>
|
||||
<value media-type="application/x-java-object"/>
|
||||
</encoding>
|
||||
<memory storage="HEAP" max-count="10000"/>
|
||||
<memory max-count="10000"/>
|
||||
</local-cache>
|
||||
<replicated-cache name="work">
|
||||
<expiration lifespan="900000000000000000"/>
|
||||
<expiration lifespan="-1"/>
|
||||
</replicated-cache>
|
||||
<local-cache name="keys">
|
||||
<encoding>
|
||||
|
@ -71,15 +71,15 @@
|
|||
<value media-type="application/x-java-object"/>
|
||||
</encoding>
|
||||
<expiration max-idle="3600000"/>
|
||||
<memory storage="HEAP" max-count="1000"/>
|
||||
<memory max-count="1000"/>
|
||||
</local-cache>
|
||||
<distributed-cache name="actionTokens" owners="2">
|
||||
<encoding>
|
||||
<key media-type="application/x-java-object"/>
|
||||
<value media-type="application/x-java-object"/>
|
||||
</encoding>
|
||||
<expiration max-idle="-1" lifespan="900000000000000000" interval="300000"/>
|
||||
<memory storage="HEAP" max-count="-1"/>
|
||||
<expiration max-idle="-1" lifespan="-1" interval="300000"/>
|
||||
<memory max-count="-1"/>
|
||||
</distributed-cache>
|
||||
</cache-container>
|
||||
</infinispan>
|
||||
|
|
|
@ -30,28 +30,42 @@
|
|||
<key media-type="application/x-java-object"/>
|
||||
<value media-type="application/x-java-object"/>
|
||||
</encoding>
|
||||
<memory storage="HEAP" max-count="10000"/>
|
||||
<memory max-count="10000"/>
|
||||
</local-cache>
|
||||
<local-cache name="users">
|
||||
<encoding>
|
||||
<key media-type="application/x-java-object"/>
|
||||
<value media-type="application/x-java-object"/>
|
||||
</encoding>
|
||||
<memory storage="HEAP" max-count="10000"/>
|
||||
<memory max-count="10000"/>
|
||||
</local-cache>
|
||||
<local-cache name="sessions">
|
||||
<expiration lifespan="-1"/>
|
||||
</local-cache>
|
||||
<local-cache name="authenticationSessions">
|
||||
<expiration lifespan="-1"/>
|
||||
</local-cache>
|
||||
<local-cache name="offlineSessions">
|
||||
<expiration lifespan="-1"/>
|
||||
</local-cache>
|
||||
<local-cache name="clientSessions">
|
||||
<expiration lifespan="-1"/>
|
||||
</local-cache>
|
||||
<local-cache name="offlineClientSessions">
|
||||
<expiration lifespan="-1"/>
|
||||
</local-cache>
|
||||
<local-cache name="loginFailures">
|
||||
<expiration lifespan="-1"/>
|
||||
</local-cache>
|
||||
<local-cache name="sessions"/>
|
||||
<local-cache name="authenticationSessions"/>
|
||||
<local-cache name="offlineSessions"/>
|
||||
<local-cache name="clientSessions"/>
|
||||
<local-cache name="offlineClientSessions"/>
|
||||
<local-cache name="loginFailures"/>
|
||||
<local-cache name="work"/>
|
||||
<local-cache name="authorization">
|
||||
<encoding>
|
||||
<key media-type="application/x-java-object"/>
|
||||
<value media-type="application/x-java-object"/>
|
||||
</encoding>
|
||||
<memory storage="HEAP" max-count="10000"/>
|
||||
<memory max-count="10000"/>
|
||||
</local-cache>
|
||||
<local-cache name="work">
|
||||
<expiration lifespan="-1"/>
|
||||
</local-cache>
|
||||
<local-cache name="keys">
|
||||
<encoding>
|
||||
|
@ -59,15 +73,15 @@
|
|||
<value media-type="application/x-java-object"/>
|
||||
</encoding>
|
||||
<expiration max-idle="3600000"/>
|
||||
<memory storage="HEAP" max-count="10000"/>
|
||||
<memory max-count="1000"/>
|
||||
</local-cache>
|
||||
<local-cache name="actionTokens">
|
||||
<encoding>
|
||||
<key media-type="application/x-java-object"/>
|
||||
<value media-type="application/x-java-object"/>
|
||||
</encoding>
|
||||
<expiration max-idle="-1" interval="300000"/>
|
||||
<memory storage="HEAP" max-count="-1"/>
|
||||
<expiration max-idle="-1" lifespan="-1" interval="300000"/>
|
||||
<memory max-count="-1"/>
|
||||
</local-cache>
|
||||
</cache-container>
|
||||
</infinispan>
|
||||
|
|
|
@ -19,7 +19,7 @@ package org.keycloak.provider.quarkus;
|
|||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.keycloak.quarkus.runtime.Environment.CLI_ARGS;
|
||||
import static org.keycloak.quarkus.runtime.configuration.ConfigArgsConfigSource.CLI_ARGS;
|
||||
|
||||
import java.io.File;
|
||||
import java.lang.reflect.Field;
|
||||
|
|
|
@ -18,6 +18,7 @@
|
|||
package org.keycloak.it.junit5.extension;
|
||||
|
||||
import static org.keycloak.it.junit5.extension.DistributionTest.ReInstall.BEFORE_ALL;
|
||||
import static org.keycloak.it.junit5.extension.DistributionType.RAW;
|
||||
import static org.keycloak.quarkus.runtime.Environment.forceTestLaunchMode;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
@ -27,8 +28,6 @@ import org.junit.jupiter.api.extension.ExtensionContext;
|
|||
import org.junit.jupiter.api.extension.ParameterContext;
|
||||
import org.junit.jupiter.api.extension.ParameterResolutionException;
|
||||
import org.keycloak.it.utils.KeycloakDistribution;
|
||||
import org.keycloak.it.utils.DockerKeycloakDistribution;
|
||||
import org.keycloak.it.utils.RawKeycloakDistribution;
|
||||
import org.keycloak.quarkus.runtime.Environment;
|
||||
import org.keycloak.quarkus.runtime.cli.command.Start;
|
||||
import org.keycloak.quarkus.runtime.cli.command.StartDev;
|
||||
|
@ -83,26 +82,7 @@ public class CLITestExtension extends QuarkusMainTestExtension {
|
|||
}
|
||||
|
||||
private KeycloakDistribution createDistribution(DistributionTest config) {
|
||||
KeycloakDistribution distribution = null;
|
||||
|
||||
switch (System.getProperty("kc.quarkus.tests.dist", "raw")) {
|
||||
case "docker":
|
||||
distribution = new DockerKeycloakDistribution(
|
||||
config.debug(),
|
||||
config.keepAlive(),
|
||||
!DistributionTest.ReInstall.NEVER.equals(config.reInstall())
|
||||
);
|
||||
break;
|
||||
case "raw":
|
||||
default:
|
||||
distribution = new RawKeycloakDistribution(
|
||||
config.debug(),
|
||||
config.keepAlive(),
|
||||
!DistributionTest.ReInstall.NEVER.equals(config.reInstall())
|
||||
);
|
||||
}
|
||||
|
||||
return distribution;
|
||||
return DistributionType.getCurrent().orElse(RAW).newInstance(config);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -0,0 +1,69 @@
|
|||
/*
|
||||
* 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.it.junit5.extension;
|
||||
|
||||
import java.util.Optional;
|
||||
import java.util.function.Function;
|
||||
import org.keycloak.it.utils.DockerKeycloakDistribution;
|
||||
import org.keycloak.it.utils.KeycloakDistribution;
|
||||
import org.keycloak.it.utils.RawKeycloakDistribution;
|
||||
import org.keycloak.utils.StringUtil;
|
||||
|
||||
public enum DistributionType {
|
||||
|
||||
RAW(DistributionType::createRawDistribution),
|
||||
DOCKER(DistributionType::createDockerDistribution);
|
||||
|
||||
private static KeycloakDistribution createDockerDistribution(DistributionTest config) {
|
||||
return new DockerKeycloakDistribution(
|
||||
config.debug(),
|
||||
config.keepAlive(),
|
||||
!DistributionTest.ReInstall.NEVER.equals(config.reInstall()));
|
||||
}
|
||||
|
||||
private static KeycloakDistribution createRawDistribution(DistributionTest config) {
|
||||
return new RawKeycloakDistribution(
|
||||
config.debug(),
|
||||
config.keepAlive(),
|
||||
!DistributionTest.ReInstall.NEVER.equals(config.reInstall()));
|
||||
}
|
||||
|
||||
private final Function<DistributionTest, KeycloakDistribution> factory;
|
||||
|
||||
DistributionType(Function<DistributionTest, KeycloakDistribution> factory) {
|
||||
this.factory = factory;
|
||||
}
|
||||
|
||||
public static Optional<DistributionType> getCurrent() {
|
||||
String distributionType = System.getProperty("kc.quarkus.tests.dist");
|
||||
|
||||
if (StringUtil.isBlank(distributionType)) {
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
try {
|
||||
return Optional.of(valueOf(distributionType.toUpperCase()));
|
||||
} catch (IllegalStateException cause) {
|
||||
throw new RuntimeException("Invalid distribution type: " + distributionType);
|
||||
}
|
||||
}
|
||||
|
||||
public KeycloakDistribution newInstance(DistributionTest config) {
|
||||
return factory.apply(config);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,39 @@
|
|||
/*
|
||||
* 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.it.junit5.extension;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
import org.junit.jupiter.api.condition.EnabledIfSystemProperty;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
|
||||
/**
|
||||
* {@link RawDistOnly} is used to signal that the annotated tests class is only enabled when running tests using the {@link DistributionType#RAW}.
|
||||
*/
|
||||
@Target(ElementType.TYPE)
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@EnabledIfSystemProperty(named = "kc.quarkus.tests.dist", matches = "^$|raw")
|
||||
public @interface RawDistOnly {
|
||||
|
||||
/**
|
||||
* The reason why the test is disabled.
|
||||
*/
|
||||
String reason();
|
||||
}
|
|
@ -29,6 +29,13 @@ import io.quarkus.test.junit.main.LaunchResult;
|
|||
@CLITest
|
||||
public class HelpCommandTest {
|
||||
|
||||
@Test
|
||||
@Launch({})
|
||||
void testDefaultToHelp(LaunchResult result) {
|
||||
CLIResult cliResult = (CLIResult) result;
|
||||
cliResult.assertHelp("kc.sh");
|
||||
}
|
||||
|
||||
@Test
|
||||
@Launch({ "--help" })
|
||||
void testHelpCommand(LaunchResult result) {
|
||||
|
|
54
quarkus/tests/integration/src/test/java/org/keycloak/it/cli/dist/BuildAndStartDistTest.java
vendored
Normal file
54
quarkus/tests/integration/src/test/java/org/keycloak/it/cli/dist/BuildAndStartDistTest.java
vendored
Normal file
|
@ -0,0 +1,54 @@
|
|||
/*
|
||||
* 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.it.cli.dist;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
import org.junit.jupiter.api.MethodOrderer;
|
||||
import org.junit.jupiter.api.MethodOrderer.OrderAnnotation;
|
||||
import org.junit.jupiter.api.Order;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.TestMethodOrder;
|
||||
import org.junit.jupiter.api.condition.DisabledIf;
|
||||
import org.junit.jupiter.api.condition.EnabledIfSystemProperty;
|
||||
import org.keycloak.it.junit5.extension.CLIResult;
|
||||
import org.keycloak.it.junit5.extension.DistributionTest;
|
||||
import org.keycloak.it.junit5.extension.RawDistOnly;
|
||||
|
||||
import io.quarkus.test.junit.main.Launch;
|
||||
import io.quarkus.test.junit.main.LaunchResult;
|
||||
|
||||
@DistributionTest(reInstall = DistributionTest.ReInstall.NEVER)
|
||||
@RawDistOnly(reason = "Containers are immutable")
|
||||
@TestMethodOrder(OrderAnnotation.class)
|
||||
public class BuildAndStartDistTest {
|
||||
|
||||
@Test
|
||||
@Launch({ "build", "--http-enabled=true", "--hostname-strict=false", "--cluster=local" })
|
||||
@Order(1)
|
||||
void firstYouBuild(LaunchResult result) {
|
||||
}
|
||||
|
||||
@Test
|
||||
@Launch({ "start" })
|
||||
@Order(2)
|
||||
void thenYouStart(LaunchResult result) {
|
||||
CLIResult cliResult = (CLIResult) result;
|
||||
cliResult.assertStarted();
|
||||
}
|
||||
}
|
|
@ -17,6 +17,7 @@
|
|||
|
||||
package org.keycloak.it.cli.dist;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
@ -50,5 +51,6 @@ class BuildCommandDistTest {
|
|||
() -> "The Error Output:\n" + result.getErrorOutput() + "doesn't contains the expected string.");
|
||||
assertTrue(result.getErrorOutput().contains("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."),
|
||||
() -> "The Error Output:\n" + result.getErrorOutput() + "doesn't contains the expected string.");
|
||||
assertEquals(4, result.getErrorStream().size());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -17,9 +17,24 @@
|
|||
|
||||
package org.keycloak.it.cli.dist;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.keycloak.it.cli.StartCommandTest;
|
||||
import org.keycloak.it.junit5.extension.DistributionTest;
|
||||
|
||||
import io.quarkus.test.junit.main.Launch;
|
||||
import io.quarkus.test.junit.main.LaunchResult;
|
||||
|
||||
@DistributionTest
|
||||
public class StartCommandDistTest extends StartCommandTest {
|
||||
|
||||
@Test
|
||||
@Launch({ "-pf=dev", "start", "--auto-build", "--http-enabled=true", "--hostname-strict=false" })
|
||||
void failIfAutoBuildUsingDevProfile(LaunchResult result) {
|
||||
assertTrue(result.getErrorOutput().contains("ERROR: You can not 'start' the server using the 'dev' configuration profile. Please re-build the server first, using 'kc.sh build' for the default production profile, or using 'kc.sh build --profile=<profile>' with a profile more suitable for production."),
|
||||
() -> "The Output:\n" + result.getErrorOutput() + "doesn't contains the expected string.");
|
||||
assertEquals(4, result.getErrorStream().size());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -28,6 +28,7 @@ spi.truststore.file.file=${kc.home.dir}/conf/keycloak.truststore
|
|||
spi.truststore.file.password=secret
|
||||
|
||||
# Declarative User Profile
|
||||
spi.user-profile.provider=declarative-user-profile
|
||||
spi.user-profile.declarative-user-profile.read-only-attributes=deniedFoo,deniedBar*,deniedSome/thing,deniedsome*thing
|
||||
spi.user-profile.declarative-user-profile.admin-read-only-attributes=deniedSomeAdmin
|
||||
|
||||
|
|
|
@ -46,9 +46,6 @@ import org.keycloak.testsuite.arquillian.SuiteContext;
|
|||
public class KeycloakQuarkusServerDeployableContainer implements DeployableContainer<KeycloakQuarkusConfiguration> {
|
||||
|
||||
private static final Logger log = Logger.getLogger(KeycloakQuarkusServerDeployableContainer.class);
|
||||
public static final String CONF_PERSISTED_PROPERTIES_FILENAME = "data/generated/persisted.properties";
|
||||
public static final String SPI_PROVIDER_PROPERTY_PREFIX = "kc.spi-";
|
||||
public static final String SPI_PROVIDER_PROPERTY_SUFFIX = "-provider";
|
||||
|
||||
private KeycloakQuarkusConfiguration configuration;
|
||||
private Process container;
|
||||
|
@ -129,23 +126,6 @@ public class KeycloakQuarkusServerDeployableContainer implements DeployableConta
|
|||
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the currently configured default provider for the passed spi.
|
||||
*
|
||||
* @param spi the spi to get the current default provider for (dash-cased)
|
||||
* @return the current configured provider or null if not configured
|
||||
*/
|
||||
public String getCurrentlyConfiguredSpiProviderFor(String spi) {
|
||||
try {
|
||||
File persistedPropertiesFile = configuration.getProvidersPath().resolve(CONF_PERSISTED_PROPERTIES_FILENAME).toFile();
|
||||
Properties props = new Properties();
|
||||
props.load(new FileInputStream(persistedPropertiesFile));
|
||||
return props.get(SPI_PROVIDER_PROPERTY_PREFIX + spi + SPI_PROVIDER_PROPERTY_SUFFIX).toString().trim();
|
||||
} catch (IOException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private Process startContainer() throws IOException {
|
||||
ProcessBuilder pb = new ProcessBuilder(getProcessCommands());
|
||||
File wrkDir = configuration.getProvidersPath().resolve("bin").toFile();
|
||||
|
|
Loading…
Reference in a new issue