diff --git a/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/Environment.java b/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/Environment.java
index 228a31ce2a..a07a3e0cbb 100644
--- a/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/Environment.java
+++ b/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/Environment.java
@@ -41,6 +41,7 @@ public final class Environment {
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() {}
@@ -90,6 +91,7 @@ public final class Environment {
if (isWindows()) {
return "kc.bat";
}
+
return "kc.sh";
}
@@ -133,6 +135,10 @@ public final class Environment {
public static void setProfile(String profile) {
System.setProperty(PROFILE, profile);
System.setProperty("quarkus.profile", profile);
+ if (isTestLaunchMode()) {
+ System.setProperty("mp.config.profile", profile);
+ System.setProperty(ProfileManager.QUARKUS_TEST_PROFILE_PROP, profile);
+ }
}
public static String getProfileOrDefault(String defaultProfile) {
@@ -193,4 +199,16 @@ public final class Environment {
public static boolean isQuarkusDevMode() {
return ProfileManager.getLaunchMode().equals(LaunchMode.DEVELOPMENT);
}
+
+ public static boolean isTestLaunchMode() {
+ return "test".equals(System.getProperty(LAUNCH_MODE));
+ }
+
+ public static void forceTestLaunchMode() {
+ System.setProperty(LAUNCH_MODE, "test");
+ }
+
+ public static boolean isDistribution() {
+ return Environment.getCommand().startsWith("kc.");
+ }
}
diff --git a/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/KeycloakMain.java b/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/KeycloakMain.java
index f5a276553d..655a5f2a69 100644
--- a/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/KeycloakMain.java
+++ b/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/KeycloakMain.java
@@ -19,12 +19,15 @@ package org.keycloak.quarkus.runtime;
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 java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
+import javax.enterprise.context.ApplicationScoped;
+
import io.quarkus.runtime.ApplicationLifecycleManager;
import io.quarkus.runtime.Quarkus;
@@ -41,6 +44,7 @@ import picocli.CommandLine;
* The main entry point, responsible for initialize and run the CLI as well as start the server.
*/
@QuarkusMain(name = "keycloak")
+@ApplicationScoped
public class KeycloakMain implements QuarkusApplication {
private static final Logger LOGGER = Logger.getLogger(KeycloakMain.class);
@@ -61,7 +65,7 @@ public class KeycloakMain implements QuarkusApplication {
public static void start(CommandLine cmd) {
try {
- Quarkus.run(KeycloakMain.class, (integer, cause) -> {
+ Quarkus.run(KeycloakMain.class, (exitCode, cause) -> {
if (cause != null) {
ExecutionExceptionHandler exceptionHandler = (ExecutionExceptionHandler) cmd.getExecutionExceptionHandler();
@@ -69,6 +73,12 @@ public class KeycloakMain implements QuarkusApplication {
String.format("Failed to start server using profile (%s)", getProfileOrDefault("prod")),
cause.getCause());
}
+
+ if (Environment.isDistribution()) {
+ // assume that it is running the distribution
+ // as we are replacing the default exit handler, we need to force exit
+ System.exit(exitCode);
+ }
});
} catch (Throwable cause) {
ExecutionExceptionHandler exceptionHandler = (ExecutionExceptionHandler) cmd.getExecutionExceptionHandler();
@@ -87,7 +97,17 @@ public class KeycloakMain implements QuarkusApplication {
if (isDevProfile()) {
LOGGER.warnf("Running the server in dev mode. DO NOT use this configuration in production.");
}
- Quarkus.waitForExit();
- return ApplicationLifecycleManager.getExitCode();
+
+ int exitCode = ApplicationLifecycleManager.getExitCode();
+
+ if (isTestLaunchMode()) {
+ // in test mode we exit immediately
+ // we should be managing this behavior more dynamically depending on the tests requirements (short/long lived)
+ Quarkus.asyncExit(exitCode);
+ } else {
+ Quarkus.waitForExit();
+ }
+
+ return exitCode;
}
}
diff --git a/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/cli/ExecutionExceptionHandler.java b/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/cli/ExecutionExceptionHandler.java
index ed63c5eb86..dd5b3e7e10 100644
--- a/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/cli/ExecutionExceptionHandler.java
+++ b/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/cli/ExecutionExceptionHandler.java
@@ -29,7 +29,6 @@ import org.keycloak.quarkus.runtime.InitializationException;
import org.keycloak.quarkus.runtime.Messages;
import org.keycloak.quarkus.runtime.integration.QuarkusPlatform;
-import io.quarkus.runtime.Quarkus;
import io.smallrye.config.ConfigValue;
import picocli.CommandLine;
import picocli.CommandLine.ParseResult;
@@ -80,8 +79,6 @@ public final class ExecutionExceptionHandler implements CommandLine.IExecutionEx
logError(errorWriter, "For more details run the same command passing the '--verbose' option. Also you can use '--help' to see the details about the usage of the particular command.");
}
}
-
- Quarkus.asyncExit(1);
}
private void dumpException(PrintWriter errorWriter, Throwable cause) {
diff --git a/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/cli/Picocli.java b/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/cli/Picocli.java
index d70eea2ece..2a381aa7d2 100644
--- a/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/cli/Picocli.java
+++ b/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/cli/Picocli.java
@@ -55,6 +55,7 @@ import org.keycloak.quarkus.runtime.configuration.KeycloakConfigSourceProvider;
import org.keycloak.quarkus.runtime.configuration.mappers.PropertyMapper;
import org.keycloak.quarkus.runtime.Environment;
+import io.quarkus.runtime.Quarkus;
import io.smallrye.config.ConfigValue;
import picocli.CommandLine;
import picocli.CommandLine.Model.CommandSpec;
@@ -80,14 +81,7 @@ public final class Picocli {
runReAugmentationIfNeeded(cliArgs, cmd);
- int exitCode = cmd.execute(cliArgs.toArray(new String[0]));
-
- if (Environment.isQuarkusDevMode()) {
- // do not exit if running in dev mode, otherwise quarkus dev mode will exit when running from IDE
- return;
- }
-
- System.exit(exitCode);
+ cmd.execute(cliArgs.toArray(new String[0]));
}
private static void runReAugmentationIfNeeded(List cliArgs, CommandLine cmd) {
@@ -246,7 +240,7 @@ public final class Picocli {
return key.startsWith("kc.provider.file");
}
- private static CommandLine createCommandLine(List cliArgs) {
+ public static CommandLine createCommandLine(List cliArgs) {
CommandSpec spec = CommandSpec.forAnnotatedObject(new Main())
.name(Environment.getCommand());
diff --git a/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/cli/command/Build.java b/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/cli/command/Build.java
index b71cc0fd9d..853f4f3d26 100644
--- a/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/cli/command/Build.java
+++ b/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/cli/command/Build.java
@@ -30,6 +30,8 @@ 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,
@@ -115,7 +117,11 @@ public final class Build extends AbstractCommand implements Runnable {
private void cleanTempResources() {
if (!ProfileManager.getLaunchMode().isDevOrTest()) {
// only needed for dev/testing purposes
- getHomePath().resolve("quarkus-artifact.properties").toFile().delete();
+ try {
+ Files.delete(getHomePath().resolve("quarkus-artifact.properties"));
+ } catch (IOException cause) {
+ throw new RuntimeException("Failed to delete temporary resources", cause);
+ }
}
}
}
diff --git a/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/cli/command/Main.java b/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/cli/command/Main.java
index 741bbabd7c..0d67617560 100644
--- a/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/cli/command/Main.java
+++ b/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/cli/command/Main.java
@@ -22,6 +22,7 @@ import static org.keycloak.quarkus.runtime.cli.Picocli.NO_PARAM_LABEL;
import org.keycloak.quarkus.runtime.Environment;
import org.keycloak.quarkus.runtime.cli.ExecutionExceptionHandler;
import org.keycloak.quarkus.runtime.configuration.KeycloakConfigSourceProvider;
+import org.keycloak.quarkus.runtime.configuration.KeycloakPropertiesConfigSource;
import picocli.CommandLine;
import picocli.CommandLine.Command;
@@ -104,6 +105,6 @@ public final class Main {
description = "Set the path to a configuration file. By default, configuration properties are read from the \"keycloak.properties\" file in the \"conf\" directory.",
paramLabel = "file")
public void setConfigFile(String path) {
- System.setProperty(KeycloakConfigSourceProvider.KEYCLOAK_CONFIG_FILE_PROP, path);
+ System.setProperty(KeycloakPropertiesConfigSource.KEYCLOAK_CONFIG_FILE_PROP, path);
}
}
\ No newline at end of file
diff --git a/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/cli/command/ShowConfig.java b/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/cli/command/ShowConfig.java
index b25a91d6b8..7271973b37 100644
--- a/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/cli/command/ShowConfig.java
+++ b/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/cli/command/ShowConfig.java
@@ -36,6 +36,7 @@ import org.keycloak.quarkus.runtime.configuration.MicroProfileConfigProvider;
import org.keycloak.quarkus.runtime.configuration.PersistedConfigSource;
import org.keycloak.quarkus.runtime.Environment;
+import io.quarkus.runtime.Quarkus;
import io.smallrye.config.ConfigValue;
import picocli.CommandLine.Command;
import picocli.CommandLine.Parameters;
@@ -70,8 +71,8 @@ public final class ShowConfig extends AbstractCommand implements Runnable {
.forEachOrdered(this::printProperty);
}
- if (!parseBoolean(System.getProperty("kc.show.config.runtime", Boolean.FALSE.toString()))) {
- System.exit(0);
+ if (!Boolean.getBoolean("kc.show.config.runtime")) {
+ Quarkus.asyncExit(0);
}
}
}
diff --git a/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/configuration/ConfigArgsConfigSource.java b/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/configuration/ConfigArgsConfigSource.java
index f8889d6beb..50fb453636 100644
--- a/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/configuration/ConfigArgsConfigSource.java
+++ b/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/configuration/ConfigArgsConfigSource.java
@@ -17,6 +17,7 @@
package org.keycloak.quarkus.runtime.configuration;
+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;
@@ -48,20 +49,29 @@ public class ConfigArgsConfigSource extends PropertiesConfigSource {
private static final Logger log = Logger.getLogger(ConfigArgsConfigSource.class);
+ private final boolean alwaysParseArgs;
+
ConfigArgsConfigSource() {
// higher priority over default Quarkus config sources
super(parseArgument(), "CliConfigSource", 500);
+ alwaysParseArgs = isTestLaunchMode();
}
@Override
public String getValue(String propertyName) {
- String value = super.getValue(propertyName);
+ Map properties = getProperties();
+
+ if (alwaysParseArgs) {
+ properties = parseArgument();
+ }
+
+ String value = properties.get(propertyName);
if (value != null) {
return value;
}
- return super.getValue(propertyName.replace('-', '.'));
+ return properties.get(propertyName.replace('-', '.'));
}
private static Map parseArgument() {
diff --git a/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/configuration/KeycloakConfigSourceProvider.java b/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/configuration/KeycloakConfigSourceProvider.java
index 2c509d110c..45d2db7708 100644
--- a/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/configuration/KeycloakConfigSourceProvider.java
+++ b/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/configuration/KeycloakConfigSourceProvider.java
@@ -17,9 +17,7 @@
package org.keycloak.quarkus.runtime.configuration;
-import static org.keycloak.quarkus.runtime.configuration.MicroProfileConfigProvider.NS_KEYCLOAK_PREFIX;
-import java.io.File;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
@@ -34,8 +32,6 @@ public class KeycloakConfigSourceProvider implements ConfigSourceProvider {
private static final Logger log = Logger.getLogger(KeycloakConfigSourceProvider.class);
- public static final String KEYCLOAK_CONFIG_FILE_ENV = "KC_CONFIG_FILE";
- public static final String KEYCLOAK_CONFIG_FILE_PROP = NS_KEYCLOAK_PREFIX + "config.file";
private static final List CONFIG_SOURCES = new ArrayList<>();
public static PersistedConfigSource PERSISTED_CONFIG_SOURCE;
@@ -57,14 +53,10 @@ public class KeycloakConfigSourceProvider implements ConfigSourceProvider {
PERSISTED_CONFIG_SOURCE = new PersistedConfigSource(getPersistedConfigFile());
CONFIG_SOURCES.add(PERSISTED_CONFIG_SOURCE);
- Path configFile = getConfigurationFile();
+ CONFIG_SOURCES.addAll(new KeycloakPropertiesConfigSource.InFileSystem().getConfigSources(Thread.currentThread().getContextClassLoader()));
- if (configFile != null) {
- CONFIG_SOURCES.add(new KeycloakPropertiesConfigSource.InFileSystem(configFile));
- } else {
- log.debug("Loading the default server configuration");
- CONFIG_SOURCES.add(new KeycloakPropertiesConfigSource.InJar());
- }
+ // by enabling this config source we are able to rely on the default settings when running tests
+ CONFIG_SOURCES.addAll(new KeycloakPropertiesConfigSource.InClassPath().getConfigSources(Thread.currentThread().getContextClassLoader()));
}
/**
@@ -76,31 +68,6 @@ public class KeycloakConfigSourceProvider implements ConfigSourceProvider {
initializeSources();
}
- private static Path getConfigurationFile() {
- String filePath = System.getProperty(KEYCLOAK_CONFIG_FILE_PROP);
-
- if (filePath == null)
- filePath = System.getenv(KEYCLOAK_CONFIG_FILE_ENV);
-
- if (filePath == null) {
- String homeDir = Environment.getHomeDir();
-
- if (homeDir != null) {
- File file = Paths.get(homeDir, "conf", KeycloakPropertiesConfigSource.KEYCLOAK_PROPERTIES).toFile();
-
- if (file.exists()) {
- filePath = file.getAbsolutePath();
- }
- }
- }
-
- if (filePath == null) {
- return null;
- }
-
- return Paths.get(filePath);
- }
-
public static Path getPersistedConfigFile() {
String homeDir = Environment.getHomeDir();
diff --git a/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/configuration/KeycloakPropertiesConfigSource.java b/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/configuration/KeycloakPropertiesConfigSource.java
index 0456513697..01a7209ce0 100644
--- a/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/configuration/KeycloakPropertiesConfigSource.java
+++ b/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/configuration/KeycloakPropertiesConfigSource.java
@@ -17,99 +17,125 @@
package org.keycloak.quarkus.runtime.configuration;
-import java.io.Closeable;
-import java.io.FileNotFoundException;
-import java.io.IOError;
+import java.io.File;
import java.io.IOException;
-import java.io.InputStream;
-import java.nio.file.Files;
+import java.net.URI;
+import java.net.URL;
import java.nio.file.NoSuchFileException;
import java.nio.file.Path;
+import java.nio.file.Paths;
import java.util.Collections;
import java.util.HashMap;
+import java.util.List;
import java.util.Map;
-import java.util.Properties;
import java.util.regex.Pattern;
-import org.jboss.logging.Logger;
+import org.eclipse.microprofile.config.spi.ConfigSource;
+import org.eclipse.microprofile.config.spi.ConfigSourceProvider;
+import org.keycloak.quarkus.runtime.Environment;
+import io.smallrye.config.AbstractLocationConfigSourceLoader;
import io.smallrye.config.PropertiesConfigSource;
+import io.smallrye.config.common.utils.ConfigSourceUtil;
import static org.keycloak.common.util.StringPropertyReplacer.replaceProperties;
import static org.keycloak.quarkus.runtime.configuration.Configuration.getMappedPropertyName;
import static org.keycloak.quarkus.runtime.configuration.MicroProfileConfigProvider.NS_KEYCLOAK;
+import static org.keycloak.quarkus.runtime.configuration.MicroProfileConfigProvider.NS_KEYCLOAK_PREFIX;
import static org.keycloak.quarkus.runtime.configuration.MicroProfileConfigProvider.NS_QUARKUS;
/**
* A configuration source for {@code keycloak.properties}.
*/
-public abstract class KeycloakPropertiesConfigSource extends PropertiesConfigSource {
-
- private static final Logger log = Logger.getLogger(KeycloakPropertiesConfigSource.class);
+public class KeycloakPropertiesConfigSource extends AbstractLocationConfigSourceLoader {
private static final Pattern DOT_SPLIT = Pattern.compile("\\.");
- static final String KEYCLOAK_PROPERTIES = "keycloak.properties";
+ private static final String KEYCLOAK_CONFIG_FILE_ENV = "KC_CONFIG_FILE";
+ private static final String KEYCLOAK_PROPERTIES = "keycloak.properties";
+ public static final String KEYCLOAK_CONFIG_FILE_PROP = NS_KEYCLOAK_PREFIX + "config.file";
- KeycloakPropertiesConfigSource(InputStream is, int ordinal) {
- super(readProperties(is), KEYCLOAK_PROPERTIES, ordinal);
+ @Override
+ protected String[] getFileExtensions() {
+ return new String[] { "properties" };
}
- private static Map readProperties(final InputStream is) {
- if (is == null) {
- return Collections.emptyMap();
- }
- try (Closeable ignored = is) {
- Properties properties = new Properties();
- properties.load(is);
- return transform((Map) properties);
- } catch (IOException e) {
- throw new IOError(e);
- }
+ @Override
+ protected ConfigSource loadConfigSource(URL url, int ordinal) throws IOException {
+ return new PropertiesConfigSource(transform(ConfigSourceUtil.urlToMap(url)), KEYCLOAK_PROPERTIES, ordinal);
}
- public static final class InJar extends KeycloakPropertiesConfigSource {
- public InJar() {
- super(openStream(), 250);
+ public static class InClassPath extends KeycloakPropertiesConfigSource implements ConfigSourceProvider {
+
+ @Override
+ public List getConfigSources(final ClassLoader classLoader) {
+ return loadConfigSources("META-INF/keycloak.properties", 240, classLoader);
}
- private static InputStream openStream() {
- ClassLoader cl = Thread.currentThread().getContextClassLoader();
- if (cl == null) {
- cl = KeycloakPropertiesConfigSource.class.getClassLoader();
- }
- InputStream is;
- String fileName = "META-INF/" + KEYCLOAK_PROPERTIES;
- if (cl == null) {
- is = ClassLoader.getSystemResourceAsStream(fileName);
- } else {
- is = cl.getResourceAsStream(fileName);
- }
- if (is != null) {
- log.debug("Loading the server configuration from the classpath");
- }
- return is;
- }
- }
-
- public static final class InFileSystem extends KeycloakPropertiesConfigSource {
-
- public InFileSystem(Path path) {
- super(openStream(path), 250);
- }
-
- private static InputStream openStream(Path path) {
- if (path == null) {
- throw new IllegalArgumentException("Configuration file path can not be null");
- }
+ @Override
+ protected List tryClassPath(URI uri, int ordinal, ClassLoader classLoader) {
try {
- log.debugf("Loading the server configuration from %s", path);
- return Files.newInputStream(path);
- } catch (NoSuchFileException | FileNotFoundException e) {
- throw new IllegalArgumentException("Configuration file not found at [" + path + "]");
- } catch (IOException e) {
- throw new RuntimeException("Unexpected error reading configuration file at [" + path + "]", e);
+ return super.tryClassPath(uri, ordinal, classLoader);
+ } catch (RuntimeException e) {
+ Throwable cause = e.getCause();
+
+ if (cause instanceof NoSuchFileException) {
+ // configuration step happens before classpath is updated, and it might happen that
+ // provider JARs are still in classpath index but removed from the providers dir
+ return Collections.emptyList();
+ }
+
+ throw e;
}
}
+
+ @Override
+ protected List tryFileSystem(final URI uri, final int ordinal) {
+ return Collections.emptyList();
+ }
+ }
+
+ public static class InFileSystem extends KeycloakPropertiesConfigSource implements ConfigSourceProvider {
+
+ @Override
+ public List getConfigSources(final ClassLoader classLoader) {
+ Path configFile = getConfigurationFile();
+
+ if (configFile == null) {
+ return Collections.emptyList();
+ }
+
+ return loadConfigSources(configFile.toUri().toString(), 250, classLoader);
+ }
+
+ @Override
+ protected List tryClassPath(final URI uri, final int ordinal, final ClassLoader classLoader) {
+ return Collections.emptyList();
+ }
+
+ private Path getConfigurationFile() {
+ String filePath = System.getProperty(KEYCLOAK_CONFIG_FILE_PROP);
+
+ if (filePath == null)
+ filePath = System.getenv(KEYCLOAK_CONFIG_FILE_ENV);
+
+ if (filePath == null) {
+ String homeDir = Environment.getHomeDir();
+
+ if (homeDir != null) {
+ File file = Paths.get(homeDir, "conf", KeycloakPropertiesConfigSource.KEYCLOAK_PROPERTIES).toFile();
+
+ if (file.exists()) {
+ filePath = file.getAbsolutePath();
+ }
+ }
+ }
+
+ if (filePath == null) {
+ return null;
+ }
+
+ return Paths.get(filePath);
+ }
}
private static Map transform(Map properties) {
diff --git a/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/configuration/mappers/DatabasePropertyMappers.java b/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/configuration/mappers/DatabasePropertyMappers.java
index bd1f3cb0c4..85b398e24f 100644
--- a/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/configuration/mappers/DatabasePropertyMappers.java
+++ b/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/configuration/mappers/DatabasePropertyMappers.java
@@ -20,13 +20,13 @@ final class DatabasePropertyMappers {
.mapFrom("db")
.to("quarkus.hibernate-orm.dialect")
.isBuildTimeProperty(true)
- .transformer((db, context) -> Database.getDialect(db).orElse(null))
+ .transformer((db, context) -> Database.getDialect(db).orElse(Database.getDialect("h2-file").get()))
.hidden(true)
.build(),
builder().from("db-driver")
.mapFrom("db")
.to("quarkus.datasource.jdbc.driver")
- .transformer((db, context) -> Database.getDriver(db).orElse(null))
+ .transformer((db, context) -> Database.getDriver(db).orElse(Database.getDriver("h2-file").get()))
.hidden(true)
.build(),
builder().from("db").
@@ -111,7 +111,7 @@ final class DatabasePropertyMappers {
addInitializationException(invalidDatabaseVendor(db, Database.getAliases()));
- return null;
+ return "h2";
};
}
diff --git a/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/integration/QuarkusPlatform.java b/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/integration/QuarkusPlatform.java
index ee14a30485..0b30375cad 100644
--- a/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/integration/QuarkusPlatform.java
+++ b/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/integration/QuarkusPlatform.java
@@ -31,6 +31,8 @@ import org.keycloak.platform.PlatformProvider;
import org.keycloak.quarkus.runtime.InitializationException;
import org.keycloak.quarkus.runtime.Environment;
+import io.quarkus.runtime.Quarkus;
+
public class QuarkusPlatform implements PlatformProvider {
private static final Logger log = Logger.getLogger(QuarkusPlatform.class);
@@ -56,6 +58,9 @@ public class QuarkusPlatform implements PlatformProvider {
for (Throwable inner : platform.getDeferredExceptions()) {
quarkusException.addSuppressed(inner);
}
+ // reset this instance, mainly deferred exceptions, so that the subsequent starts do not fail due to previous errors
+ // this is mainly important when the server is running in test mode
+ platform.reset();
throw quarkusException;
}
}
@@ -91,7 +96,7 @@ public class QuarkusPlatform implements PlatformProvider {
@Override
public void exit(Throwable cause) {
- throw new RuntimeException(cause);
+ Quarkus.asyncExit(1);
}
/**
@@ -162,4 +167,8 @@ public class QuarkusPlatform implements PlatformProvider {
}
return tmpDir;
}
+
+ private void reset() {
+ deferredExceptions.clear();
+ }
}
diff --git a/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/storage/infinispan/CacheInitializer.java b/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/storage/infinispan/CacheInitializer.java
index e5d2b00d91..44dffc29e5 100644
--- a/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/storage/infinispan/CacheInitializer.java
+++ b/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/storage/infinispan/CacheInitializer.java
@@ -21,7 +21,6 @@ import org.infinispan.configuration.parsing.ConfigurationBuilderHolder;
import org.infinispan.configuration.parsing.ParserRegistry;
import org.infinispan.jboss.marshalling.core.JBossUserMarshaller;
import org.infinispan.manager.DefaultCacheManager;
-import org.jboss.logging.Logger;
import org.keycloak.Config;
public class CacheInitializer {
diff --git a/quarkus/server/src/main/resources/META-INF/keycloak.properties b/quarkus/runtime/src/main/resources/META-INF/keycloak.properties
similarity index 100%
rename from quarkus/server/src/main/resources/META-INF/keycloak.properties
rename to quarkus/runtime/src/main/resources/META-INF/keycloak.properties
diff --git a/quarkus/runtime/src/main/resources/application.properties b/quarkus/runtime/src/main/resources/application.properties
new file mode 100644
index 0000000000..95b6228f21
--- /dev/null
+++ b/quarkus/runtime/src/main/resources/application.properties
@@ -0,0 +1,26 @@
+# This is the main configuration for Keycloak on Quarkus
+
+quarkus.package.main-class=keycloak
+quarkus.http.root-path=/
+quarkus.application.name=Keycloak
+quarkus.banner.enabled=false
+
+# Disable health checks from extensions, since we provide our own (default is true)
+quarkus.health.extensions.enabled=false
+
+# Default transaction timeout
+quarkus.transaction-manager.default-transaction-timeout=300
+
+# The JAX-RS application is programmatically registered at build time.
+# When indexing classes, both KeycloakApplication and QuarkusKeycloakApplication are indexed and multuple
+# application classes are no longer supported by resteasy extension
+quarkus.resteasy.ignore-application-classes=true
+
+# Ignore split packages for Keycloak related packages
+quarkus.arc.ignored-split-packages=org.keycloak.*
+
+# No need to generate dependencies list
+quarkus.package.include-dependency-list=false
+
+# we do not want running dev services in distribution
+quarkus.devservices.enabled=false
diff --git a/quarkus/server/src/main/resources/application.properties b/quarkus/server/src/main/resources/application.properties
index 9dd1924f31..a679430a97 100644
--- a/quarkus/server/src/main/resources/application.properties
+++ b/quarkus/server/src/main/resources/application.properties
@@ -1,30 +1,7 @@
-#quarkus.log.level = DEBUG
+# Inherit all configuration from the default runtime settings and sets those specific to the distribution
+
quarkus.package.output-name=keycloak
quarkus.package.type=mutable-jar
quarkus.package.output-directory=lib
quarkus.package.user-providers-directory=../providers
-quarkus.package.main-class=keycloak
-
-quarkus.http.root-path=/
-quarkus.application.name=Keycloak
-quarkus.banner.enabled=false
-
-# Disable health checks from extensions, since we provide our own (default is true)
-quarkus.health.extensions.enabled=false
-
-# Default transaction timeout
-quarkus.transaction-manager.default-transaction-timeout=300
-
-# The JAX-RS application is programmatically registered at build time.
-# When indexing classes, both KeycloakApplication and QuarkusKeycloakApplication are indexed and multuple
-# application classes are no longer supported by resteasy extension
-quarkus.resteasy.ignore-application-classes=true
-
-# Ignore split packages for Keycloak related packages
-quarkus.arc.ignored-split-packages=org.keycloak.*
-
-# No need to generate dependencies list
-quarkus.package.include-dependency-list=false
-
-# we do not want running dev services in distribution
-quarkus.devservices.enabled=false
+quarkus.package.main-class=keycloak
\ No newline at end of file
diff --git a/quarkus/tests/integration/pom.xml b/quarkus/tests/integration/pom.xml
new file mode 100644
index 0000000000..0fb7984c2a
--- /dev/null
+++ b/quarkus/tests/integration/pom.xml
@@ -0,0 +1,65 @@
+
+
+
+
+ 4.0.0
+
+
+ keycloak-quarkus-test-parent
+ org.keycloak
+ 16.0.0-SNAPSHOT
+ ../pom.xml
+
+
+ Keycloak Quarkus Server Integration tests
+ keycloak-quarkus-integration-tests
+ jar
+
+
+
+ org.keycloak
+ keycloak-quarkus-server
+
+
+
+
+ org.keycloak
+ keycloak-quarkus-server-deployment
+ ${project.version}
+ pom
+ test
+
+
+ *
+ *
+
+
+
+
+ io.quarkus
+ quarkus-junit5
+
+
+ io.rest-assured
+ rest-assured
+ test
+
+
+
diff --git a/quarkus/tests/integration/src/main/java/org/keycloak/it/junit5/extension/CLIResult.java b/quarkus/tests/integration/src/main/java/org/keycloak/it/junit5/extension/CLIResult.java
new file mode 100644
index 0000000000..37af70a127
--- /dev/null
+++ b/quarkus/tests/integration/src/main/java/org/keycloak/it/junit5/extension/CLIResult.java
@@ -0,0 +1,107 @@
+/*
+ * 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 static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.junit.jupiter.api.Assertions.fail;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.PrintStream;
+import java.util.Arrays;
+import java.util.List;
+
+import org.keycloak.quarkus.runtime.cli.Picocli;
+
+import io.quarkus.test.junit.main.LaunchResult;
+import picocli.CommandLine;
+
+public interface CLIResult extends LaunchResult {
+
+ static Object create(List outputStream, List errStream, int exitCode, boolean distribution) {
+ return new CLIResult() {
+ @Override
+ public List getOutputStream() {
+ return outputStream;
+ }
+
+ @Override
+ public List getErrorStream() {
+ return errStream;
+ }
+
+ @Override
+ public int exitCode() {
+ return exitCode;
+ }
+
+ @Override
+ public boolean isDistribution() {
+ return distribution;
+ }
+ };
+ }
+
+ boolean isDistribution();
+
+ default void assertStarted() {
+ assertTrue(getOutput().contains("Listening on:"));
+ assertNotDevMode();
+ }
+
+ default void assertNotDevMode() {
+ assertFalse(getOutput().contains("Running the server in dev mode."));
+ }
+
+ default void assertStartedDevMode() {
+ assertTrue(getOutput().contains("Running the server in dev mode."));
+ }
+
+ default void assertError(String msg) {
+ assertTrue(getErrorOutput().contains(msg));
+ }
+
+ default void assertHelp(String command) {
+ if (command == null) {
+ fail("No command provided");
+ }
+
+ CommandLine cmd = Picocli.createCommandLine(Arrays.asList(command, "--help"));
+
+ if (isDistribution()) {
+ cmd.setCommandName("kc.sh");
+ }
+
+ try (
+ ByteArrayOutputStream outStream = new ByteArrayOutputStream();
+ PrintStream printStream = new PrintStream(outStream, true)
+ ) {
+ if ("kc.sh".equals(command)) {
+ cmd.usage(printStream);
+ } else {
+ cmd.getSubcommands().get(command).usage(printStream);
+ }
+
+ // not very reliable, we should be comparing the output with some static reference to the help message.
+ assertTrue(getOutput().equals(outStream.toString().trim()));
+ } catch (IOException cause) {
+ throw new RuntimeException("Failed to assert help", cause);
+ }
+ }
+}
diff --git a/quarkus/tests/integration/src/main/java/org/keycloak/it/junit5/extension/CLITest.java b/quarkus/tests/integration/src/main/java/org/keycloak/it/junit5/extension/CLITest.java
new file mode 100644
index 0000000000..cfb5d59ad8
--- /dev/null
+++ b/quarkus/tests/integration/src/main/java/org/keycloak/it/junit5/extension/CLITest.java
@@ -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.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.extension.ExtendWith;
+
+@Target(ElementType.TYPE)
+@ExtendWith({ CLITestExtension.class })
+@Retention(RetentionPolicy.RUNTIME)
+public @interface CLITest {
+}
diff --git a/quarkus/tests/integration/src/main/java/org/keycloak/it/junit5/extension/CLITestExtension.java b/quarkus/tests/integration/src/main/java/org/keycloak/it/junit5/extension/CLITestExtension.java
new file mode 100644
index 0000000000..22e60739db
--- /dev/null
+++ b/quarkus/tests/integration/src/main/java/org/keycloak/it/junit5/extension/CLITestExtension.java
@@ -0,0 +1,172 @@
+/*
+ * 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 static org.keycloak.it.junit5.extension.DistributionTest.ReInstall.BEFORE_ALL;
+import static org.keycloak.quarkus.runtime.Environment.forceTestLaunchMode;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+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.junit5.extension.DistributionTest.ReInstall;
+import org.keycloak.it.utils.KeycloakDistribution;
+import org.keycloak.quarkus.runtime.Environment;
+import org.keycloak.quarkus.runtime.cli.command.Start;
+import org.keycloak.quarkus.runtime.cli.command.StartDev;
+
+import io.quarkus.test.junit.QuarkusMainTestExtension;
+import io.quarkus.test.junit.main.Launch;
+import io.quarkus.test.junit.main.LaunchResult;
+
+public class CLITestExtension extends QuarkusMainTestExtension {
+
+ private KeycloakDistribution dist;
+
+ @Override
+ public void beforeEach(ExtensionContext context) throws Exception {
+ DistributionTest distConfig = getDistributionConfig(context);
+
+ if (distConfig != null) {
+ Launch launch = context.getRequiredTestMethod().getAnnotation(Launch.class);
+
+ if (launch != null) {
+ if (dist == null) {
+ dist = createDistribution(distConfig);
+ }
+ dist.start(Arrays.asList(launch.value()));
+ }
+ } else {
+ configureProfile(context);
+ super.beforeEach(context);
+ }
+ }
+
+ @Override
+ public void afterEach(ExtensionContext context) throws Exception {
+ DistributionTest distConfig = getDistributionConfig(context);
+
+ if (distConfig != null) {
+ if (distConfig.keepAlive()) {
+ dist.stopIfRunning();
+ }
+ }
+
+ super.afterEach(context);
+ }
+
+ @Override
+ public void afterAll(ExtensionContext context) throws Exception {
+ if (dist != null) {
+ // just to make sure the server is stopped after all tests
+ dist.stopIfRunning();
+ }
+ super.afterAll(context);
+ }
+
+ private KeycloakDistribution createDistribution(DistributionTest config) {
+ KeycloakDistribution distribution = new KeycloakDistribution();
+
+ distribution.setReCreate(!ReInstall.NEVER.equals(config.reInstall()));
+ distribution.setDebug(config.debug());
+ distribution.setManualStop(config.keepAlive());
+
+ return distribution;
+ }
+
+ @Override
+ public void beforeAll(ExtensionContext context) throws Exception {
+ DistributionTest distConfig = getDistributionConfig(context);
+
+ if (distConfig != null) {
+ if (BEFORE_ALL.equals(distConfig.reInstall())) {
+ dist = createDistribution(distConfig);
+ }
+ } else {
+ forceTestLaunchMode();
+ }
+
+ super.beforeAll(context);
+ }
+
+ @Override
+ public Object resolveParameter(ParameterContext parameterContext, ExtensionContext context)
+ throws ParameterResolutionException {
+ Class> type = parameterContext.getParameter().getType();
+
+ if (type == LaunchResult.class) {
+ List outputStream;
+ List errStream;
+ int exitCode;
+
+ boolean isDistribution = getDistributionConfig(context) != null;
+
+ if (isDistribution) {
+ outputStream = dist.getOutputStream();
+ errStream = dist.getErrorStream();
+ exitCode = dist.getExitCode();
+ } else {
+ LaunchResult result = (LaunchResult) super.resolveParameter(parameterContext, context);
+ outputStream = result.getOutputStream();
+ errStream = result.getErrorStream();
+ exitCode = result.exitCode();
+ }
+
+ return CLIResult.create(outputStream, errStream, exitCode, isDistribution);
+ }
+
+ // for now, not support for manual launching using QuarkusMainLauncher
+ throw new RuntimeException("Parameter type [" + type + "] not supported");
+ }
+
+ @Override
+ public boolean supportsParameter(ParameterContext parameterContext, ExtensionContext extensionContext)
+ throws ParameterResolutionException {
+ Class> type = parameterContext.getParameter().getType();
+ return type == LaunchResult.class;
+ }
+
+ private void configureProfile(ExtensionContext context) {
+ List cliArgs = getCliArgs(context);
+
+ // when running tests, build steps happen before executing our CLI so that profiles are not set and not taken
+ // into account when executing the build steps
+ // this is basically reproducing the behavior when using kc.sh
+ if (cliArgs.contains(Start.NAME)) {
+ Environment.setProfile("prod");
+ } else if (cliArgs.contains(StartDev.NAME)) {
+ Environment.forceDevProfile();
+ }
+ }
+
+ private List getCliArgs(ExtensionContext context) {
+ Launch annotation = context.getRequiredTestMethod().getAnnotation(Launch.class);
+
+ if (annotation != null) {
+ return Arrays.asList(annotation.value());
+ }
+
+ return Collections.emptyList();
+ }
+
+ private DistributionTest getDistributionConfig(ExtensionContext context) {
+ return context.getTestClass().get().getDeclaredAnnotation(DistributionTest.class);
+ }
+}
diff --git a/quarkus/tests/integration/src/main/java/org/keycloak/it/junit5/extension/DistributionTest.java b/quarkus/tests/integration/src/main/java/org/keycloak/it/junit5/extension/DistributionTest.java
new file mode 100644
index 0000000000..1aa86f008b
--- /dev/null
+++ b/quarkus/tests/integration/src/main/java/org/keycloak/it/junit5/extension/DistributionTest.java
@@ -0,0 +1,42 @@
+/*
+ * 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.extension.ExtendWith;
+
+@Target(ElementType.TYPE)
+@ExtendWith({ CLITestExtension.class })
+@Retention(RetentionPolicy.RUNTIME)
+public @interface DistributionTest {
+
+ boolean debug() default false;
+ boolean keepAlive() default false;
+
+ enum ReInstall {
+ BEFORE_ALL,
+ BEFORE_TEST,
+ NEVER;
+ }
+
+ ReInstall reInstall() default ReInstall.BEFORE_ALL;
+}
+
diff --git a/quarkus/tests/integration/src/main/java/org/keycloak/it/utils/KeycloakDistribution.java b/quarkus/tests/integration/src/main/java/org/keycloak/it/utils/KeycloakDistribution.java
new file mode 100644
index 0000000000..fa393fb118
--- /dev/null
+++ b/quarkus/tests/integration/src/main/java/org/keycloak/it/utils/KeycloakDistribution.java
@@ -0,0 +1,337 @@
+/*
+ * 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.utils;
+
+import static org.keycloak.quarkus.runtime.Environment.LAUNCH_MODE;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.net.HttpURLConnection;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.security.KeyManagementException;
+import java.security.NoSuchAlgorithmException;
+import java.security.SecureRandom;
+import java.security.cert.X509Certificate;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.TimeUnit;
+import java.util.function.Supplier;
+import javax.net.ssl.HostnameVerifier;
+import javax.net.ssl.HttpsURLConnection;
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.SSLSession;
+import javax.net.ssl.SSLSocketFactory;
+import javax.net.ssl.TrustManager;
+import javax.net.ssl.X509TrustManager;
+import org.apache.commons.io.FileUtils;
+import org.eclipse.aether.artifact.Artifact;
+import org.jboss.logging.Logger;
+
+import io.quarkus.bootstrap.util.ZipUtils;
+
+public final class KeycloakDistribution {
+
+ private static final Logger LOGGER = Logger.getLogger(KeycloakDistribution.class);
+
+ private Process keycloak;
+ private int exitCode = -1;
+ private final Path distPath;
+ private final List outputStream = new ArrayList<>();
+ private final List errorStream = new ArrayList<>();
+ private boolean reCreate;
+ private boolean manualStop;
+ private String relativePath;
+ private int httpPort;
+ private boolean debug;
+ private ExecutorService outputExecutor;
+
+ public KeycloakDistribution() {
+ distPath = prepareDistribution();
+ }
+
+ public void start(List arguments) {
+ reset();
+ if (manualStop && isRunning()) {
+ throw new IllegalStateException("Server already running. You should manually stop the server before starting it again.");
+ }
+ stopIfRunning();
+ try {
+ startServer(arguments);
+ if (manualStop) {
+ asyncReadOutput();
+ waitForReadiness();
+ } else {
+ readOutput();
+ }
+ } catch (Exception cause) {
+ stopIfRunning();
+ throw new RuntimeException("Failed to start the server", cause);
+ } finally {
+ if (!manualStop) {
+ stopIfRunning();
+ }
+ }
+ }
+
+ public void stopIfRunning() {
+ if (isRunning()) {
+ try {
+ keycloak.destroy();
+ keycloak.waitFor(10, TimeUnit.SECONDS);
+ exitCode = keycloak.exitValue();
+ } catch (Exception cause) {
+ keycloak.destroyForcibly();
+ throw new RuntimeException("Failed to stop the server", cause);
+ }
+ }
+
+ shutdownOutputExecutor();
+ }
+
+ public List getOutputStream() {
+ return outputStream;
+ }
+
+ public List getErrorStream() {
+ return errorStream;
+ }
+
+ public int getExitCode() {
+ return exitCode;
+ }
+
+ public void setReCreate(boolean reCreate) {
+ this.reCreate = reCreate;
+ }
+
+ public void setDebug(boolean debug) {
+ this.debug = debug;
+ }
+
+ public void setManualStop(boolean manualStop) {
+ this.manualStop = manualStop;
+ }
+
+ private String[] getCliArgs(List arguments) {
+ List commands = new ArrayList<>();
+
+ commands.add("./kc.sh");
+
+ if (debug) {
+ commands.add("--debug");
+ }
+
+ if (!manualStop) {
+ commands.add("-D" + LAUNCH_MODE + "=test");
+ }
+
+ this.relativePath = arguments.stream().filter(arg -> arg.startsWith("--http-relative-path")).map(arg -> arg.substring(arg.indexOf('=') + 1)).findAny().orElse("/");
+ this.httpPort = Integer.parseInt(arguments.stream().filter(arg -> arg.startsWith("--http-port")).map(arg -> arg.substring(arg.indexOf('=') + 1)).findAny().orElse("8080"));
+
+ commands.addAll(arguments);
+
+ return commands.toArray(new String[0]);
+ }
+
+ private void waitForReadiness() throws MalformedURLException {
+ URL contextRoot = new URL("http://localhost:" + httpPort + ("/" + relativePath + "/realms/master/").replace("//", "/"));
+ HttpURLConnection connection = null;
+ long startTime = System.currentTimeMillis();
+
+ while (true) {
+ if (System.currentTimeMillis() - startTime > getStartTimeout()) {
+ throw new IllegalStateException(
+ "Timeout [" + getStartTimeout() + "] while waiting for Quarkus server");
+ }
+
+ try {
+ // wait before checking for opening a new connection
+ Thread.sleep(1000);
+ if ("https".equals(contextRoot.getProtocol())) {
+ HttpsURLConnection httpsConnection = (HttpsURLConnection) (connection = (HttpURLConnection) contextRoot.openConnection());
+ httpsConnection.setSSLSocketFactory(createInsecureSslSocketFactory());
+ httpsConnection.setHostnameVerifier(createInsecureHostnameVerifier());
+ } else {
+ connection = (HttpURLConnection) contextRoot.openConnection();
+ }
+
+ connection.setReadTimeout((int) getStartTimeout());
+ connection.setConnectTimeout((int) getStartTimeout());
+ connection.connect();
+
+ if (connection.getResponseCode() == 200) {
+ LOGGER.infof("Keycloak is ready at %s", contextRoot);
+ break;
+ }
+ } catch (Exception ignore) {
+ } finally {
+ if (connection != null) {
+ connection.disconnect();
+ }
+ }
+ }
+ }
+
+ private long getStartTimeout() {
+ return TimeUnit.SECONDS.toMillis(120);
+ }
+
+ private HostnameVerifier createInsecureHostnameVerifier() {
+ return new HostnameVerifier() {
+ @Override
+ public boolean verify(String s, SSLSession sslSession) {
+ return true;
+ }
+ };
+ }
+
+ private SSLSocketFactory createInsecureSslSocketFactory() throws IOException {
+ TrustManager[] trustAllCerts = new TrustManager[] {new X509TrustManager() {
+ public void checkClientTrusted(final X509Certificate[] chain, final String authType) {
+ }
+
+ public void checkServerTrusted(final X509Certificate[] chain, final String authType) {
+ }
+
+ public X509Certificate[] getAcceptedIssuers() {
+ return null;
+ }
+ }};
+
+ SSLContext sslContext;
+ SSLSocketFactory socketFactory;
+
+ try {
+ sslContext = SSLContext.getInstance("TLS");
+ sslContext.init(null, trustAllCerts, new SecureRandom());
+ socketFactory = sslContext.getSocketFactory();
+ } catch (NoSuchAlgorithmException | KeyManagementException e) {
+ throw new IOException("Can't create unsecure trust manager");
+ }
+ return socketFactory;
+ }
+
+ private boolean isRunning() {
+ return keycloak != null && keycloak.isAlive();
+ }
+
+ private void asyncReadOutput() {
+ shutdownOutputExecutor();
+ outputExecutor = Executors.newSingleThreadExecutor();
+ outputExecutor.execute(this::readOutput);
+ }
+
+ private void shutdownOutputExecutor() {
+ if (outputExecutor != null) {
+ outputExecutor.shutdown();
+ try {
+ outputExecutor.awaitTermination(30, TimeUnit.SECONDS);
+ } catch (InterruptedException cause) {
+ throw new RuntimeException("Failed to terminate output executor", cause);
+ } finally {
+ outputExecutor = null;
+ }
+ }
+ }
+
+ private void reset() {
+ outputStream.clear();
+ errorStream.clear();
+ exitCode = -1;
+ keycloak = null;
+ shutdownOutputExecutor();
+ }
+
+ private Path prepareDistribution() {
+ try {
+ Path distRootPath = Paths.get(System.getProperty("java.io.tmpdir")).resolve("kc-tests");
+ distRootPath.toFile().mkdirs();
+ File distFile = Maven.resolveArtifact("org.keycloak", "keycloak-server-x-dist", "zip")
+ .map(Artifact::getFile)
+ .orElseThrow(new Supplier() {
+ @Override
+ public RuntimeException get() {
+ return new RuntimeException("Could not obtain distribution artifact");
+ }
+ });
+ String distDirName = distFile.getName().replace("keycloak-server-x-dist", "keycloak.x");
+ Path distPath = distRootPath.resolve(distDirName.substring(0, distDirName.lastIndexOf('.')));
+
+ if (reCreate || !distPath.toFile().exists()) {
+ distPath.toFile().delete();
+ ZipUtils.unzip(distFile.toPath(), distRootPath);
+ }
+
+ // make sure kc.sh is executable
+ distPath.resolve("bin").resolve("kc.sh").toFile().setExecutable(true);
+
+ return distPath;
+ } catch (Exception cause) {
+ throw new RuntimeException("Failed to prepare distribution", cause);
+ }
+ }
+
+ private void readOutput() {
+ try (
+ BufferedReader outStream = new BufferedReader(new InputStreamReader(keycloak.getInputStream()));
+ BufferedReader errStream = new BufferedReader(new InputStreamReader(keycloak.getErrorStream()));
+ ) {
+ while (keycloak.isAlive()) {
+ readStream(outStream, outputStream);
+ readStream(errStream, errorStream);
+ }
+ } catch (Throwable cause) {
+ throw new RuntimeException("Failed to read server output", cause);
+ }
+ }
+
+ private void readStream(BufferedReader reader, List stream) throws IOException {
+ String line;
+
+ while (reader.ready() && (line = reader.readLine()) != null) {
+ stream.add(line);
+ System.out.println(line);
+ }
+ }
+
+ /**
+ * The server is configured to redirect errors to output stream. This adds a limitation when checking whether a
+ * message arrived via error stream.
+ *
+ * @param arguments the list of arguments to run the server
+ * @throws Exception if something bad happens
+ */
+ private void startServer(List arguments) throws Exception {
+ ProcessBuilder pb = new ProcessBuilder(getCliArgs(arguments));
+ ProcessBuilder builder = pb.directory(distPath.resolve("bin").toFile());
+
+ builder.environment().put("KEYCLOAK_ADMIN", "admin");
+ builder.environment().put("KEYCLOAK_ADMIN_PASSWORD", "admin");
+
+ FileUtils.deleteDirectory(distPath.resolve("data").toFile());
+
+ keycloak = builder.start();
+ }
+}
diff --git a/quarkus/tests/integration/src/main/java/org/keycloak/it/utils/Maven.java b/quarkus/tests/integration/src/main/java/org/keycloak/it/utils/Maven.java
new file mode 100644
index 0000000000..256dde7129
--- /dev/null
+++ b/quarkus/tests/integration/src/main/java/org/keycloak/it/utils/Maven.java
@@ -0,0 +1,80 @@
+/*
+ * 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.utils;
+
+import java.net.URL;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.Optional;
+import org.eclipse.aether.artifact.Artifact;
+import org.eclipse.aether.artifact.DefaultArtifact;
+import org.eclipse.aether.resolution.ArtifactResult;
+
+import io.quarkus.bootstrap.resolver.maven.BootstrapMavenContext;
+import io.quarkus.bootstrap.resolver.maven.MavenArtifactResolver;
+import io.quarkus.bootstrap.resolver.maven.workspace.LocalProject;
+import io.quarkus.bootstrap.utils.BuildToolHelper;
+
+public final class Maven {
+
+ private static Path resolveProjectDir() {
+ try {
+ String classFilePath = KeycloakDistribution.class.getName().replace(".", "/") + ".class";
+ URL classFileResource = Thread.currentThread().getContextClassLoader().getResource(classFilePath);
+ String classPath = classFileResource.getPath();
+ classPath = classPath.substring(0, classPath.length() - classFilePath.length());
+ URL newResource = new URL(classFileResource.getProtocol(), classFileResource.getHost(), classFileResource.getPort(),
+ classPath);
+
+ return BuildToolHelper.getProjectDir(Paths.get(newResource.toURI()));
+ } catch (Exception cause) {
+ throw new RuntimeException("Failed to resolve project dir", cause);
+ }
+ }
+
+ static Optional resolveArtifact(String groupId, String artifactId) {
+ return resolveArtifact(groupId, artifactId, "jar");
+ }
+
+ static Optional resolveArtifact(String groupId, String artifactId, String extension) {
+ BootstrapMavenContext mvnCtx = createBootstrapContext();
+ LocalProject project = mvnCtx.getCurrentProject();
+
+ try {
+ MavenArtifactResolver mvnResolver = new MavenArtifactResolver(mvnCtx);
+ ArtifactResult resolve = mvnResolver.resolve(new DefaultArtifact(groupId, artifactId, extension,
+ project.getVersion()));
+
+ if (resolve.isResolved()) {
+ return Optional.of(resolve.getArtifact());
+ }
+ } catch (Exception cause) {
+ throw new RuntimeException("Failed to resolve project artifact [" + groupId + ":" + artifactId + ":" + project.getVersion() + ":" + extension, cause);
+ }
+
+ return Optional.empty();
+ }
+
+ private static BootstrapMavenContext createBootstrapContext() {
+ try {
+ return new BootstrapMavenContext(BootstrapMavenContext.config().setCurrentProject(resolveProjectDir().toString()));
+ } catch (Exception cause) {
+ throw new RuntimeException("Failed to create maven boorstrap context", cause);
+ }
+ }
+}
diff --git a/quarkus/tests/integration/src/test/java/org/keycloak/it/cli/HelpCommandTest.java b/quarkus/tests/integration/src/test/java/org/keycloak/it/cli/HelpCommandTest.java
new file mode 100644
index 0000000000..ef5e48d0ea
--- /dev/null
+++ b/quarkus/tests/integration/src/test/java/org/keycloak/it/cli/HelpCommandTest.java
@@ -0,0 +1,59 @@
+/*
+ * 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;
+
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+import org.keycloak.it.junit5.extension.CLIResult;
+import org.keycloak.it.junit5.extension.CLITest;
+import org.keycloak.quarkus.runtime.cli.command.Main;
+
+import io.quarkus.test.junit.main.Launch;
+import io.quarkus.test.junit.main.LaunchResult;
+
+@CLITest
+public class HelpCommandTest {
+
+ @Test
+ @Launch({ "--help" })
+ void testHelpCommand(LaunchResult result) {
+ CLIResult cliResult = (CLIResult) result;
+ cliResult.assertHelp("kc.sh");
+ }
+
+ @Test
+ @Launch({ "start", "--help" })
+ void testStartHelpCommand(LaunchResult result) {
+ CLIResult cliResult = (CLIResult) result;
+ cliResult.assertHelp("start");
+ }
+
+ @Test
+ @Launch({ "start-dev", "--help" })
+ void testStartDevCommand(LaunchResult result) {
+ CLIResult cliResult = (CLIResult) result;
+ cliResult.assertHelp("start-dev");
+ }
+
+ @Test
+ @Launch({ "build", "--help" })
+ void testBuildCommand(LaunchResult result) {
+ CLIResult cliResult = (CLIResult) result;
+ cliResult.assertHelp("build");
+ }
+}
diff --git a/quarkus/tests/integration/src/test/java/org/keycloak/it/cli/OptionValidationTest.java b/quarkus/tests/integration/src/test/java/org/keycloak/it/cli/OptionValidationTest.java
new file mode 100644
index 0000000000..0076af2e37
--- /dev/null
+++ b/quarkus/tests/integration/src/test/java/org/keycloak/it/cli/OptionValidationTest.java
@@ -0,0 +1,49 @@
+/*
+ * 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;
+
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+import org.keycloak.it.junit5.extension.CLIResult;
+import org.keycloak.it.junit5.extension.CLITest;
+
+import io.quarkus.test.junit.main.Launch;
+import io.quarkus.test.junit.main.LaunchResult;
+import io.quarkus.test.junit.main.QuarkusMainLauncher;
+
+@CLITest
+public class OptionValidationTest {
+
+ @Test
+ @Launch({"build", "--db"})
+ public void failMissingOptionValue(LaunchResult result) {
+ Assertions.assertTrue(result.getErrorOutput().contains("Missing required value for option '--db' (vendor). Expected values are: h2-file, h2-mem, mariadb, mssql, mssql-2012, mysql, oracle, postgres, postgres-95"));
+ }
+
+ @Test
+ @Launch({"build", "--db=invalid"})
+ public void failInvalidOptionValue(LaunchResult result) {
+ Assertions.assertTrue(result.getErrorOutput().contains("Invalid value for option '--db': invalid. Expected values are: h2-file, h2-mem, mariadb, mssql, mssql-2012, mysql, oracle, postgres, postgres-95"));
+ }
+
+ @Test
+ @Launch({"build", "--db", "foo", "bar"})
+ public void failMultipleOptionValue(LaunchResult result) {
+ Assertions.assertTrue(result.getErrorOutput().contains("Option '--db' expects a single value (vendor) Expected values are: h2-file, h2-mem, mariadb, mssql, mssql-2012, mysql, oracle, postgres, postgres-95"));
+ }
+}
diff --git a/quarkus/tests/integration/src/test/java/org/keycloak/it/cli/ShowConfigCommandTest.java b/quarkus/tests/integration/src/test/java/org/keycloak/it/cli/ShowConfigCommandTest.java
new file mode 100644
index 0000000000..6bf92b353e
--- /dev/null
+++ b/quarkus/tests/integration/src/test/java/org/keycloak/it/cli/ShowConfigCommandTest.java
@@ -0,0 +1,47 @@
+/*
+ * 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;
+
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+import org.keycloak.it.junit5.extension.CLITest;
+
+import io.quarkus.test.junit.main.Launch;
+import io.quarkus.test.junit.main.LaunchResult;
+
+@CLITest
+class ShowConfigCommandTest {
+
+ @Test
+ @Launch({ "show-config" })
+ void testShowConfigCommandShowsRuntimeConfig(LaunchResult result) {
+ Assertions.assertTrue(result.getOutput()
+ .contains("Runtime Configuration"));
+ }
+
+ @Test
+ @Launch({ "show-config", "all" })
+ void testShowConfigCommandWithAllShowsAllProfiles(LaunchResult result) {
+ Assertions.assertTrue(result.getOutput()
+ .contains("Runtime Configuration"));
+ Assertions.assertTrue(result.getOutput()
+ .contains("Quarkus Configuration"));
+ Assertions.assertTrue(result.getOutput()
+ .contains("Profile \"import_export\" Configuration"));
+ }
+}
diff --git a/quarkus/tests/integration/src/test/java/org/keycloak/it/cli/StartCommandTest.java b/quarkus/tests/integration/src/test/java/org/keycloak/it/cli/StartCommandTest.java
new file mode 100644
index 0000000000..0ef2476d56
--- /dev/null
+++ b/quarkus/tests/integration/src/test/java/org/keycloak/it/cli/StartCommandTest.java
@@ -0,0 +1,63 @@
+/*
+ * 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;
+
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import org.junit.jupiter.api.Test;
+import org.keycloak.it.junit5.extension.CLIResult;
+import org.keycloak.it.junit5.extension.CLITest;
+
+import io.quarkus.test.junit.main.Launch;
+import io.quarkus.test.junit.main.LaunchResult;
+
+@CLITest
+public class StartCommandTest {
+
+ @Test
+ @Launch({ "start", "--hostname-strict=false" })
+ void failNoTls(LaunchResult result) {
+ assertTrue(result.getOutput().contains("Key material not provided to setup HTTPS"));
+ }
+
+ @Test
+ @Launch({ "start", "--http-enabled=true" })
+ void failNoHostnameNotSet(LaunchResult result) {
+ assertTrue(result.getOutput().contains("ERROR: Strict hostname resolution configured but no hostname was set"));
+ }
+
+ @Test
+ @Launch({ "--profile=dev", "start" })
+ void failUsingDevProfile(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=' with a profile more suitable for production."));
+ }
+
+ @Test
+ @Launch({ "-v", "start", "--http-enabled=true", "--hostname-strict=false" })
+ void testHttpEnabled(LaunchResult result) {
+ CLIResult cliResult = (CLIResult) result;
+ cliResult.assertStarted();
+ }
+
+ @Test
+ @Launch({ "-v", "start", "--db=h2-mem" })
+ void failBuildPropertyNotAvailable(LaunchResult result) {
+ CLIResult cliResult = (CLIResult) result;
+ cliResult.assertError("Unknown option: '--db=h2-mem'");
+ }
+}
diff --git a/quarkus/tests/integration/src/test/java/org/keycloak/it/cli/StartDevCommandTest.java b/quarkus/tests/integration/src/test/java/org/keycloak/it/cli/StartDevCommandTest.java
new file mode 100644
index 0000000000..b2c0042117
--- /dev/null
+++ b/quarkus/tests/integration/src/test/java/org/keycloak/it/cli/StartDevCommandTest.java
@@ -0,0 +1,43 @@
+/*
+ * 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;
+
+import org.junit.jupiter.api.Test;
+import org.keycloak.it.junit5.extension.CLIResult;
+import org.keycloak.it.junit5.extension.CLITest;
+
+import io.quarkus.test.junit.main.Launch;
+import io.quarkus.test.junit.main.LaunchResult;
+
+@CLITest
+public class StartDevCommandTest {
+
+ @Test
+ @Launch({ "start-dev" })
+ void testDevModeWarning(LaunchResult result) {
+ CLIResult cliResult = (CLIResult) result;
+ cliResult.assertStartedDevMode();
+ }
+
+ @Test
+ @Launch({ "start-dev", "--db=h2-mem" })
+ void testBuildPropertyAvailable(LaunchResult result) {
+ CLIResult cliResult = (CLIResult) result;
+ cliResult.assertStartedDevMode();
+ }
+}
diff --git a/quarkus/tests/integration/src/test/java/org/keycloak/it/cli/dist/BuildCommandDistTest.java b/quarkus/tests/integration/src/test/java/org/keycloak/it/cli/dist/BuildCommandDistTest.java
new file mode 100644
index 0000000000..649ddd6221
--- /dev/null
+++ b/quarkus/tests/integration/src/test/java/org/keycloak/it/cli/dist/BuildCommandDistTest.java
@@ -0,0 +1,47 @@
+/*
+ * 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.Test;
+import org.keycloak.it.junit5.extension.DistributionTest;
+
+import io.quarkus.test.junit.main.Launch;
+import io.quarkus.test.junit.main.LaunchResult;
+
+@DistributionTest
+class BuildCommandDistTest {
+
+ @Test
+ @Launch({ "build" })
+ void resetConfig(LaunchResult result) {
+ assertTrue(result.getOutput().contains("Updating the configuration and installing your custom providers, if any. Please wait."));
+ assertTrue(result.getOutput().contains("Quarkus augmentation completed"));
+ assertTrue(result.getOutput().contains("Server configuration updated and persisted. Run the following command to review the configuration:"));
+ assertTrue(result.getOutput().contains("kc.sh show-config"));
+ }
+
+ @Test
+ @Launch({ "--profile=dev", "build" })
+ void failIfDevProfile(LaunchResult result) {
+ assertTrue(result.getErrorOutput().contains("ERROR: Failed to run 'build' command."));
+ assertTrue(result.getErrorOutput().contains("ERROR: You can not 'build' 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=' with a profile more suitable for production."));
+ 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."));
+ }
+}
diff --git a/quarkus/tests/integration/src/test/java/org/keycloak/it/cli/dist/HelpCommandDistTest.java b/quarkus/tests/integration/src/test/java/org/keycloak/it/cli/dist/HelpCommandDistTest.java
new file mode 100644
index 0000000000..5e4feaf75c
--- /dev/null
+++ b/quarkus/tests/integration/src/test/java/org/keycloak/it/cli/dist/HelpCommandDistTest.java
@@ -0,0 +1,25 @@
+/*
+ * 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 org.keycloak.it.cli.HelpCommandTest;
+import org.keycloak.it.junit5.extension.DistributionTest;
+
+@DistributionTest
+public class HelpCommandDistTest extends HelpCommandTest {
+}
diff --git a/quarkus/tests/integration/src/test/java/org/keycloak/it/cli/dist/MetricsDistTest.java b/quarkus/tests/integration/src/test/java/org/keycloak/it/cli/dist/MetricsDistTest.java
new file mode 100644
index 0000000000..4bc71aca55
--- /dev/null
+++ b/quarkus/tests/integration/src/test/java/org/keycloak/it/cli/dist/MetricsDistTest.java
@@ -0,0 +1,48 @@
+/*
+ * 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 io.restassured.RestAssured.when;
+import static org.hamcrest.Matchers.containsString;
+
+import org.junit.jupiter.api.Disabled;
+import org.junit.jupiter.api.Test;
+import org.keycloak.it.junit5.extension.DistributionTest;
+
+import io.quarkus.test.junit.main.Launch;
+
+@DistributionTest(keepAlive =true)
+public class MetricsDistTest {
+
+ @Test
+ @Launch({ "start-dev", "--metrics-enabled=true" })
+ void testMetricsEndpoint() {
+ when().get("/metrics").then()
+ .statusCode(200)
+ .body(containsString("base_gc_total"));
+ }
+
+ @Disabled("https://github.com/keycloak/keycloak/pull/8878")
+ @Test
+ @Launch({ "start-dev", "--http-relative-path=/auth", "--metrics-enabled=true" })
+ void testMetricsEndpointUsingRelativePath() {
+ when().get("/auth/metrics").then()
+ .statusCode(200)
+ .body(containsString("base_gc_total"));
+ }
+}
diff --git a/quarkus/tests/integration/src/test/java/org/keycloak/it/cli/dist/StartCommandDistTest.java b/quarkus/tests/integration/src/test/java/org/keycloak/it/cli/dist/StartCommandDistTest.java
new file mode 100644
index 0000000000..580ee30606
--- /dev/null
+++ b/quarkus/tests/integration/src/test/java/org/keycloak/it/cli/dist/StartCommandDistTest.java
@@ -0,0 +1,25 @@
+/*
+ * 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 org.keycloak.it.cli.StartCommandTest;
+import org.keycloak.it.junit5.extension.DistributionTest;
+
+@DistributionTest
+public class StartCommandDistTest extends StartCommandTest {
+}
diff --git a/quarkus/tests/integration/src/test/java/org/keycloak/it/cli/dist/StartDevCommandDistTest.java b/quarkus/tests/integration/src/test/java/org/keycloak/it/cli/dist/StartDevCommandDistTest.java
new file mode 100644
index 0000000000..44cf85a646
--- /dev/null
+++ b/quarkus/tests/integration/src/test/java/org/keycloak/it/cli/dist/StartDevCommandDistTest.java
@@ -0,0 +1,25 @@
+/*
+ * 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 org.keycloak.it.cli.StartDevCommandTest;
+import org.keycloak.it.junit5.extension.DistributionTest;
+
+@DistributionTest
+public class StartDevCommandDistTest extends StartDevCommandTest {
+}
diff --git a/quarkus/tests/pom.xml b/quarkus/tests/pom.xml
new file mode 100644
index 0000000000..ef0fe07c99
--- /dev/null
+++ b/quarkus/tests/pom.xml
@@ -0,0 +1,39 @@
+
+
+
+
+ 4.0.0
+
+
+ keycloak-quarkus-parent
+ org.keycloak
+ 16.0.0-SNAPSHOT
+ ../pom.xml
+
+
+ Keycloak Quarkus Test Parent
+ keycloak-quarkus-test-parent
+ pom
+
+
+ integration
+
+
+