Support runnning tests using an embedded distribution

Closes #16420
This commit is contained in:
Pedro Igor 2023-01-11 18:30:04 -03:00
parent 3119566407
commit 33cb1ad7cd
21 changed files with 810 additions and 350 deletions

View file

@ -3,7 +3,7 @@ package org.keycloak.quarkus.deployment;
import io.quarkus.deployment.IsTest; import io.quarkus.deployment.IsTest;
import io.quarkus.runtime.LaunchMode; import io.quarkus.runtime.LaunchMode;
import static org.keycloak.quarkus.runtime.Environment.LAUNCH_MODE; import org.keycloak.quarkus.runtime.Environment;
public class IsIntegrationTest extends IsTest { public class IsIntegrationTest extends IsTest {
@ -13,7 +13,7 @@ public class IsIntegrationTest extends IsTest {
@Override @Override
public boolean getAsBoolean() { public boolean getAsBoolean() {
return super.getAsBoolean() && (System.getProperty(LAUNCH_MODE) != null && System.getProperty(LAUNCH_MODE).equals("test")); return super.getAsBoolean() && Environment.isTestLaunchMode();
} }
} }

View file

@ -18,7 +18,6 @@
package org.keycloak.quarkus.runtime; package org.keycloak.quarkus.runtime;
import static org.keycloak.quarkus.runtime.configuration.Configuration.getBuildTimeProperty; import static org.keycloak.quarkus.runtime.configuration.Configuration.getBuildTimeProperty;
import static org.keycloak.quarkus.runtime.configuration.Configuration.getConfig;
import java.io.File; import java.io.File;
import java.io.FilenameFilter; import java.io.FilenameFilter;
@ -177,10 +176,6 @@ public final class Environment {
})).collect(Collectors.toMap(File::getName, Function.identity())); })).collect(Collectors.toMap(File::getName, Function.identity()));
} }
public static boolean isQuarkusDevMode() {
return ProfileManager.getLaunchMode().equals(LaunchMode.DEVELOPMENT);
}
public static boolean isTestLaunchMode() { public static boolean isTestLaunchMode() {
return "test".equals(System.getProperty(LAUNCH_MODE)); return "test".equals(System.getProperty(LAUNCH_MODE));
} }
@ -220,7 +215,7 @@ public final class Environment {
} }
public static boolean isDistribution() { public static boolean isDistribution() {
if (isQuarkusDevMode()) { if (LaunchMode.current().isDevOrTest()) {
return false; return false;
} }
return getHomeDir() != null; return getHomeDir() != null;
@ -233,4 +228,8 @@ public final class Environment {
public static boolean isRebuilt() { public static boolean isRebuilt() {
return Boolean.getBoolean("kc.config.built"); return Boolean.getBoolean("kc.config.built");
} }
public static void setHomeDir(Path path) {
System.setProperty("kc.home.dir", path.toFile().getAbsolutePath());
}
} }

View file

@ -38,9 +38,7 @@ import io.quarkus.runtime.Quarkus;
import org.jboss.logging.Logger; import org.jboss.logging.Logger;
import org.keycloak.Config; import org.keycloak.Config;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.KeycloakSessionFactory; import org.keycloak.models.KeycloakSessionFactory;
import org.keycloak.models.KeycloakTransactionManager;
import org.keycloak.models.utils.KeycloakModelUtils; import org.keycloak.models.utils.KeycloakModelUtils;
import org.keycloak.quarkus.runtime.cli.ExecutionExceptionHandler; import org.keycloak.quarkus.runtime.cli.ExecutionExceptionHandler;
import org.keycloak.quarkus.runtime.cli.Picocli; import org.keycloak.quarkus.runtime.cli.Picocli;

View file

@ -17,7 +17,6 @@
package org.keycloak.quarkus.runtime.configuration; package org.keycloak.quarkus.runtime.configuration;
import static io.smallrye.config.common.utils.StringUtil.replaceNonAlphanumericByUnderscores;
import static org.keycloak.quarkus.runtime.Environment.getProfileOrDefault; import static org.keycloak.quarkus.runtime.Environment.getProfileOrDefault;
import static org.keycloak.quarkus.runtime.cli.Picocli.ARG_PREFIX; import static org.keycloak.quarkus.runtime.cli.Picocli.ARG_PREFIX;
@ -156,6 +155,22 @@ public final class Configuration {
return sb.toString(); return sb.toString();
} }
public static String replaceNonAlphanumericByUnderscores(String name) {
int length = name.length();
StringBuilder sb = new StringBuilder(length);
for(int i = 0; i < length; ++i) {
char c = name.charAt(i);
if (('a' > c || c > 'z') && ('A' > c || c > 'Z') && ('0' > c || c > '9')) {
sb.append('_');
} else {
sb.append(c);
}
}
return sb.toString();
}
private static String getValue(ConfigSource configSource, String name) { private static String getValue(ConfigSource configSource, String name) {
String value = configSource.getValue("%".concat(getProfileOrDefault("prod").concat(".").concat(name))); String value = configSource.getValue("%".concat(getProfileOrDefault("prod").concat(".").concat(name)));

View file

@ -62,6 +62,12 @@
<groupId>io.quarkus</groupId> <groupId>io.quarkus</groupId>
<artifactId>quarkus-junit5-internal</artifactId> <artifactId>quarkus-junit5-internal</artifactId>
</dependency> </dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>${junit.version}</version>
<scope>compile</scope>
</dependency>
<dependency> <dependency>
<groupId>org.keycloak</groupId> <groupId>org.keycloak</groupId>
<artifactId>keycloak-quarkus-dist</artifactId> <artifactId>keycloak-quarkus-dist</artifactId>
@ -100,6 +106,10 @@
<groupId>org.testcontainers</groupId> <groupId>org.testcontainers</groupId>
<artifactId>mysql</artifactId> <artifactId>mysql</artifactId>
</dependency> </dependency>
<dependency>
<groupId>org.apache.maven.wagon</groupId>
<artifactId>wagon-http-shared</artifactId>
</dependency>
</dependencies> </dependencies>
<build> <build>

View file

@ -1,6 +0,0 @@
package org.junit.rules;
// WORKAROUND: https://github.com/testcontainers/testcontainers-java/issues/970#issuecomment-625044008
@SuppressWarnings("unused")
public interface TestRule {
}

View file

@ -0,0 +1,237 @@
/*
* Copyright 2022 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;
import static java.util.concurrent.CompletableFuture.runAsync;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeoutException;
import org.eclipse.microprofile.config.spi.ConfigProviderResolver;
import org.keycloak.common.Version;
import org.keycloak.platform.Platform;
import org.keycloak.quarkus.runtime.Environment;
import org.keycloak.quarkus.runtime.configuration.ConfigArgsConfigSource;
import io.quarkus.bootstrap.app.AugmentAction;
import io.quarkus.bootstrap.app.CuratedApplication;
import io.quarkus.bootstrap.app.QuarkusBootstrap;
import io.quarkus.bootstrap.app.RunningQuarkusApplication;
import io.quarkus.bootstrap.app.StartupAction;
import io.quarkus.bootstrap.model.ApplicationModel;
import io.quarkus.bootstrap.resolver.AppModelResolverException;
import io.quarkus.bootstrap.resolver.BootstrapAppModelResolver;
import io.quarkus.bootstrap.resolver.maven.BootstrapMavenException;
import io.quarkus.bootstrap.resolver.maven.MavenArtifactResolver;
import io.quarkus.bootstrap.workspace.WorkspaceModule;
import io.quarkus.bootstrap.workspace.WorkspaceModuleId;
import io.quarkus.maven.dependency.Dependency;
import io.quarkus.maven.dependency.DependencyBuilder;
import io.quarkus.runtime.configuration.QuarkusConfigFactory;
public class Keycloak {
public static class Builder {
private String version;
private Path homeDir;
private List<Dependency> dependencies = new ArrayList<>();
private Builder() {
}
public Builder setVersion(String version) {
this.version = version;
return this;
}
public Builder setHomeDir(Path path) {
this.homeDir = path;
return this;
}
public Builder addDependency(String groupId, String artifactId, String version) {
addDependency(groupId, artifactId, version, null);
return this;
}
public Builder addDependency(String groupId, String artifactId, String version, String classifier) {
this.dependencies.add(DependencyBuilder.newInstance()
.setGroupId(groupId)
.setArtifactId(artifactId)
.setVersion(version)
.setClassifier(classifier)
.build());
return this;
}
public Keycloak start(String... args) {
return start(List.of(args));
}
public Keycloak start(List<String> args) {
if (homeDir == null) {
homeDir = Platform.getPlatform().getTmpDirectory().toPath();
}
return new Keycloak(homeDir, version, dependencies).start(args);
}
}
public static Builder builder() {
return new Builder();
}
private RunningQuarkusApplication application;
private ApplicationModel applicationModel;
private Path homeDir;
private List<Dependency> dependencies;
public Keycloak() {
this(null, Version.VERSION, List.of());
}
public Keycloak(Path homeDir, String version, List<Dependency> dependencies) {
this.homeDir = homeDir;
this.dependencies = dependencies;
try {
applicationModel = createApplicationModel(version);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
private Keycloak start(List<String> args) {
QuarkusBootstrap.Builder builder = QuarkusBootstrap.builder()
.setExistingModel(applicationModel)
.setApplicationRoot(applicationModel.getApplicationModule().getModuleDir().toPath())
.setTargetDirectory(applicationModel.getApplicationModule().getModuleDir().toPath())
.setIsolateDeployment(true)
.setMode(QuarkusBootstrap.Mode.TEST);
try (CuratedApplication curated = builder.build().bootstrap()) {
AugmentAction action = curated.createAugmentor();
Environment.setHomeDir(homeDir);
ConfigArgsConfigSource.setCliArgs(args.toArray(new String[0]));
StartupAction startupAction = action.createInitialRuntimeApplication();
application = startupAction.runMainClass(args.toArray(new String[0]));
return this;
} catch (Exception cause) {
throw new RuntimeException("Fail to start the server", cause);
}
}
public void stop() throws TimeoutException {
if (isRunning()) {
closeApplication();
}
}
private ApplicationModel createApplicationModel(String keycloakVersion)
throws AppModelResolverException {
// initialize Quarkus application model resolver
BootstrapAppModelResolver appModelResolver = new BootstrapAppModelResolver(getMavenArtifactResolver());
// configure server dependencies
WorkspaceModule module = createWorkspaceModule(keycloakVersion);
// resolve Keycloak server Quarkus application model
return appModelResolver.resolveModel(module);
}
private WorkspaceModule createWorkspaceModule(String keycloakVersion) {
Path moduleDir = createModuleDir();
WorkspaceModule.Mutable builder = WorkspaceModule.builder()
.setModuleId(WorkspaceModuleId.of("io.playground", "keycloak-app", "1"))
.setModuleDir(moduleDir)
.setBuildDir(moduleDir)
.addDependencyConstraint(
Dependency.pomImport("org.keycloak", "keycloak-quarkus-parent", keycloakVersion))
.addDependency(DependencyBuilder.newInstance()
.setGroupId("org.keycloak")
.setArtifactId("keycloak-quarkus-server-app")
.setVersion(keycloakVersion)
.addExclusion("org.jboss.logmanager", "log4j-jboss-logmanager")
.addExclusion("org.keycloak", "keycloak-crypto-fips1402") //TODO: enable fips
.build());
for (Dependency dependency : dependencies) {
builder.addDependency(dependency);
}
return builder.build();
}
private static Path createModuleDir() {
Path moduleDir;
try {
moduleDir = Files.createTempDirectory("kc-embedded");
} catch (IOException e) {
throw new RuntimeException(e);
}
return moduleDir;
}
MavenArtifactResolver getMavenArtifactResolver() throws BootstrapMavenException {
return MavenArtifactResolver.builder()
.setWorkspaceDiscovery(true)
.setOffline(false)
.build();
}
private boolean isRunning() {
return application != null;
}
private void closeApplication() {
if (application != null) {
ClassLoader old = Thread.currentThread().getContextClassLoader();
Thread.currentThread().setContextClassLoader(application.getClassLoader());
try {
application.close();
} catch (Exception e) {
e.printStackTrace();
} finally {
Thread.currentThread().setContextClassLoader(old);
}
}
QuarkusConfigFactory.setConfig(null);
ClassLoader old = Thread.currentThread().getContextClassLoader();
Thread.currentThread().setContextClassLoader(getClass().getClassLoader());
try {
ConfigProviderResolver cpr = ConfigProviderResolver.instance();
cpr.releaseConfig(cpr.getConfig());
} catch (Throwable ignored) {
// just means no config was installed, which is fine
} finally {
Thread.currentThread().setContextClassLoader(old);
}
application = null;
}
}

View file

@ -44,6 +44,23 @@ This can be achieved by add the `auth-server-quarkus` profile when running the t
Unlike the "development" setup described above, this requires re-build the whole distribution Unlike the "development" setup described above, this requires re-build the whole distribution
after doing any change in the code. after doing any change in the code.
### Running tests using an embedded server
For test driven development, it is possible to run the Keycloak server deployed on real Quarkus server.
This can be achieved by add the `auth-server-quarkus-embedded` profile when running the testsuite.
mvn -f testsuite/integration-arquillian/pom.xml -Pauth-server-quarkus-embedded clean install -Dtest=LoginTest
After running this command, you should also be able to run tests from your IDE. For that, make sure you have the `auth-server-quarkus-embedded` profile enabled.
When running in embedded mode, the `build` phase happens every time the server is started, and it is based on the same configuration used during a full-distribution test run(e.g.: `auth-server-quarkus` profile is active).
There are a few limitations when running tests. The well-known limitations are:
* FIPS tests not working
* Deploying script providers not working. Probably any test deploying JAR files.
* Re-starting the server during a test execution is taking too much metaspace. Need more investigation.
## Debugging - tips & tricks ## Debugging - tips & tricks
### Arquillian debugging ### Arquillian debugging
@ -465,7 +482,6 @@ This is temporary and database configuration should be more integrated with the
Activate the following profiles: Activate the following profiles:
* `quarkus`
* `auth-server-cluster-quarkus` * `auth-server-cluster-quarkus`
Then run any cluster test as usual. Then run any cluster test as usual.

View file

@ -176,6 +176,19 @@
<artifactId>infinispan-server-hotrod</artifactId> <artifactId>infinispan-server-hotrod</artifactId>
<version>${infinispan.version}</version> <version>${infinispan.version}</version>
</dependency> </dependency>
<!-- Embedded Distribution -->
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-quarkus-integration-tests</artifactId>
<version>${project.version}</version>
<exclusions>
<exclusion>
<groupId>*</groupId>
<artifactId>*</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies> </dependencies>
<build> <build>
@ -1056,6 +1069,51 @@
</build> </build>
</profile> </profile>
<profile>
<id>auth-server-quarkus-embedded</id>
<properties>
<surefire.memory.Xms>1024m</surefire.memory.Xms>
<surefire.memory.Xmx>1024m</surefire.memory.Xmx>
<surefire.memory.metaspace>512m</surefire.memory.metaspace>
<surefire.memory.metaspace.max>512m</surefire.memory.metaspace.max>
</properties>
<dependencies>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-quarkus-integration-tests</artifactId>
<version>${project.version}</version>
<exclusions>
<!-- TODO: FIPS not yet supported -->
<exclusion>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-crypto-fips1402</artifactId>
</exclusion>
<!-- Remove Junit5 deps-->
<exclusion>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
</exclusion>
<exclusion>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-junit5-internal</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.jboss.logmanager</groupId>
<artifactId>log4j-jboss-logmanager</artifactId>
<version>1.3.0.Final</version>
</dependency>
<dependency>
<groupId>net.bytebuddy</groupId>
<artifactId>byte-buddy</artifactId>
<version>1.12.18</version>
</dependency>
</dependencies>
</profile>
</profiles> </profiles>
</project> </project>

View file

@ -0,0 +1,355 @@
/*
* Copyright 2022 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.testsuite.arquillian.containers;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
import java.nio.file.Files;
import java.nio.file.StandardCopyOption;
import java.security.KeyManagementException;
import java.security.NoSuchAlgorithmException;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
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.lang3.SystemUtils;
import org.jboss.arquillian.container.spi.client.container.DeployableContainer;
import org.jboss.arquillian.container.spi.client.container.DeploymentException;
import org.jboss.arquillian.container.spi.client.container.LifecycleException;
import org.jboss.arquillian.container.spi.client.protocol.ProtocolDescription;
import org.jboss.arquillian.container.spi.client.protocol.metadata.ProtocolMetaData;
import org.jboss.arquillian.core.api.Instance;
import org.jboss.arquillian.core.api.annotation.Inject;
import org.jboss.logging.Logger;
import org.jboss.shrinkwrap.api.Archive;
import org.jboss.shrinkwrap.api.exporter.ZipExporter;
import org.jboss.shrinkwrap.descriptor.api.Descriptor;
import org.keycloak.common.crypto.FipsMode;
import org.keycloak.testsuite.arquillian.SuiteContext;
import org.keycloak.testsuite.model.StoreProvider;
public abstract class AbstractQuarkusDeployableContainer implements DeployableContainer<KeycloakQuarkusConfiguration> {
private static final Logger log = Logger.getLogger(AbstractQuarkusDeployableContainer.class);
protected static AtomicBoolean restart = new AtomicBoolean();
@Inject
protected Instance<SuiteContext> suiteContext;
protected KeycloakQuarkusConfiguration configuration;
protected List<String> additionalBuildArgs = Collections.emptyList();
@Override
public Class<KeycloakQuarkusConfiguration> getConfigurationClass() {
return KeycloakQuarkusConfiguration.class;
}
@Override
public void setup(KeycloakQuarkusConfiguration configuration) {
this.configuration = configuration;
}
@Override
public ProtocolMetaData deploy(Archive<?> archive) throws DeploymentException {
try {
deployArchiveToServer(archive);
restartServer();
} catch (Exception e) {
throw new DeploymentException(e.getMessage(),e);
}
return new ProtocolMetaData();
}
@Override
public void undeploy(Archive<?> archive) throws DeploymentException {
File wrkDir = configuration.getProvidersPath().resolve("providers").toFile();
try {
if (isWindows()) {
// stop before updating providers to avoid file locking issues on Windows
stop();
}
Files.deleteIfExists(wrkDir.toPath().resolve(archive.getName()));
restartServer();
} catch (Exception e) {
throw new DeploymentException(e.getMessage(),e);
}
}
@Override
public ProtocolDescription getDefaultProtocol() {
return null;
}
@Override
public void deploy(Descriptor descriptor) throws DeploymentException {
}
@Override
public void undeploy(Descriptor descriptor) throws DeploymentException {
}
public void restartServer() throws Exception {
stop();
start();
}
protected List<String> getArgs() {
return getArgs(new HashMap<>());
}
protected List<String> getArgs(Map<String, String> env) {
List<String> commands = new ArrayList<>();
commands.add("-v");
commands.add("start");
commands.add("--http-enabled=true");
if (Boolean.parseBoolean(System.getProperty("auth.server.debug", "false"))) {
commands.add("--debug");
String debugPort = configuration.getDebugPort() > 0 ? Integer.toString(configuration.getDebugPort()) : System.getProperty("auth.server.debug.port", "5005");
env.put("DEBUG_PORT", debugPort);
String debugSuspend = System.getProperty("auth.server.debug.suspend");
if (debugSuspend != null) {
env.put("DEBUG_SUSPEND", debugSuspend);
}
}
commands.add("--http-port=" + configuration.getBindHttpPort());
commands.add("--https-port=" + configuration.getBindHttpsPort());
if (configuration.getRoute() != null) {
commands.add("-Djboss.node.name=" + configuration.getRoute());
}
if (System.getProperty("auth.server.quarkus.log-level") != null) {
commands.add("--log-level=" + System.getProperty("auth.server.quarkus.log-level"));
}
commands.addAll(getAdditionalBuildArgs());
commands = configureArgs(commands);
final StoreProvider storeProvider = StoreProvider.getCurrentProvider();
final Supplier<Boolean> shouldSetUpDb = () -> !restart.get() && !storeProvider.equals(StoreProvider.DEFAULT);
final Supplier<String> getClusterConfig = () -> System.getProperty("auth.server.quarkus.cluster.config", "local");
log.debugf("FIPS Mode: %s", configuration.getFipsMode());
// only run build during first execution of the server (if the DB is specified), restarts or when running cluster tests
if (restart.get() || shouldSetUpDb.get() || "ha".equals(getClusterConfig.get()) || configuration.getFipsMode() != FipsMode.disabled) {
commands.removeIf("--optimized"::equals);
commands.add("--http-relative-path=/auth");
if (!storeProvider.isMapStore()) {
String cacheMode = getClusterConfig.get();
if ("local".equals(cacheMode)) {
commands.add("--cache=local");
} else {
commands.add("--cache-config-file=cluster-" + cacheMode + ".xml");
}
}
if (configuration.getFipsMode() != FipsMode.disabled) {
addFipsOptions(commands);
}
}
addStorageOptions(storeProvider, commands);
return commands;
}
protected List<String> configureArgs(List<String> commands) {
return commands;
}
private void deployArchiveToServer(Archive<?> archive) throws IOException, LifecycleException {
if (isWindows()) {
// stop before updating providers to avoid file locking issues on Windows
stop();
}
File providersDir = configuration.getProvidersPath().resolve("providers").toFile();
InputStream zipStream = archive.as(ZipExporter.class).exportAsInputStream();
Files.copy(zipStream, providersDir.toPath().resolve(archive.getName()), StandardCopyOption.REPLACE_EXISTING);
}
protected static boolean isWindows() {
return SystemUtils.IS_OS_WINDOWS;
}
public List<String> getAdditionalBuildArgs() {
return additionalBuildArgs;
}
public void setAdditionalBuildArgs(List<String> newArgs) {
additionalBuildArgs = newArgs;
}
public void resetConfiguration() {
additionalBuildArgs = Collections.emptyList();
}
protected void waitForReadiness() throws MalformedURLException, LifecycleException {
SuiteContext suiteContext = this.suiteContext.get();
//TODO: not sure if the best endpoint but it makes sure that everything is properly initialized. Once we have
// support for MP Health this should change
URL contextRoot = new URL(getBaseUrl(suiteContext) + "/auth/realms/master/");
HttpURLConnection connection;
long startTime = System.currentTimeMillis();
while (true) {
if (System.currentTimeMillis() - startTime > getStartTimeout()) {
stop();
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) {
break;
}
connection.disconnect();
} catch (Exception ignore) {
}
}
log.infof("Keycloak is ready at %s", contextRoot);
}
private URL getBaseUrl(SuiteContext suiteContext) throws MalformedURLException {
URL baseUrl = suiteContext.getAuthServerInfo().getContextRoot();
// might be running behind a load balancer
if ("https".equals(baseUrl.getProtocol())) {
baseUrl = new URL(baseUrl.toString().replace(String.valueOf(baseUrl.getPort()), String.valueOf(configuration.getBindHttpsPort())));
} else {
baseUrl = new URL(baseUrl.toString().replace(String.valueOf(baseUrl.getPort()), String.valueOf(configuration.getBindHttpPort())));
}
return baseUrl;
}
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 java.security.SecureRandom());
socketFactory = sslContext.getSocketFactory();
} catch (NoSuchAlgorithmException | KeyManagementException e) {
throw new IOException("Can't create unsecure trust manager");
}
return socketFactory;
}
private long getStartTimeout() {
return TimeUnit.SECONDS.toMillis(configuration.getStartupTimeoutInSeconds());
}
private void addStorageOptions(StoreProvider storeProvider, List<String> commands) {
log.debugf("Store '%s' is used.", storeProvider.name());
storeProvider.addStoreOptions(commands);
}
private void addFipsOptions(List<String> commands) {
commands.add("--fips-mode=" + configuration.getFipsMode().toString());
log.debugf("Keystore file: %s, keystore type: %s, truststore file: %s, truststore type: %s",
configuration.getKeystoreFile(), configuration.getKeystoreType(),
configuration.getTruststoreFile(), configuration.getTruststoreType());
commands.add("--https-key-store-file=" + configuration.getKeystoreFile());
commands.add("--https-key-store-type=" + configuration.getKeystoreType());
commands.add("--https-key-store-password=" + configuration.getKeystorePassword());
commands.add("--https-trust-store-file=" + configuration.getTruststoreFile());
commands.add("--https-trust-store-type=" + configuration.getTruststoreType());
commands.add("--https-trust-store-password=" + configuration.getTruststorePassword());
commands.add("--spi-truststore-file-file=" + configuration.getTruststoreFile());
commands.add("--spi-truststore-file-password=" + configuration.getTruststorePassword());
commands.add("--spi-truststore-file-type=" + configuration.getTruststoreType());
// BCFIPS approved mode requires passwords of at least 112 bits (14 characters) to be used. To bypass this, we use this by default
// as testsuite uses shorter passwords everywhere
if (FipsMode.strict == configuration.getFipsMode()) {
commands.add("--spi-password-hashing-pbkdf2-max-padding-length=14");
commands.add("--spi-password-hashing-pbkdf2-sha256-max-padding-length=14");
commands.add("--spi-password-hashing-pbkdf2-sha512-max-padding-length=14");
}
commands.add("--log-level=INFO,org.keycloak.common.crypto:TRACE,org.keycloak.crypto:TRACE,org.keycloak.truststore:TRACE");
configuration.appendJavaOpts("-Djava.security.properties=" + System.getProperty("auth.server.java.security.file"));
}
}

View file

@ -0,0 +1,56 @@
package org.keycloak.testsuite.arquillian.containers;
import java.util.List;
import org.jboss.arquillian.container.spi.client.container.LifecycleException;
import org.keycloak.Keycloak;
import org.keycloak.common.Version;
/**
* @author mhajas
*/
public class KeycloakQuarkusEmbeddedDeployableContainer extends AbstractQuarkusDeployableContainer {
private static final String KEYCLOAK_VERSION = Version.VERSION;
private Keycloak keycloak;
@Override
public void start() throws LifecycleException {
try {
keycloak = configure().start(getArgs());
waitForReadiness();
} catch (Exception e) {
throw new RuntimeException(e);
}
}
@Override
public void stop() throws LifecycleException {
if (keycloak != null) {
try {
keycloak.stop();
} catch (Exception e) {
throw new RuntimeException("Failed to stop the server", e);
} finally {
keycloak = null;
}
}
}
private Keycloak.Builder configure() {
return Keycloak.builder()
.setHomeDir(configuration.getProvidersPath())
.setVersion(KEYCLOAK_VERSION)
.addDependency("org.keycloak.testsuite", "integration-arquillian-testsuite-providers", KEYCLOAK_VERSION)
.addDependency("org.keycloak.testsuite", "integration-arquillian-testsuite-providers", KEYCLOAK_VERSION)
.addDependency("org.keycloak.testsuite", "integration-arquillian-tests-base", KEYCLOAK_VERSION)
.addDependency("org.keycloak.testsuite", "integration-arquillian-tests-base", KEYCLOAK_VERSION, "tests");
}
@Override
protected List<String> configureArgs(List<String> args) {
System.setProperty("quarkus.http.test-port", String.valueOf(configuration.getBindHttpPort()));
System.setProperty("quarkus.http.test-ssl-port", String.valueOf(configuration.getBindHttpsPort()));
return args;
}
}

View file

@ -1,17 +1,7 @@
package org.keycloak.testsuite.arquillian.containers; package org.keycloak.testsuite.arquillian.containers;
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 java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URISyntaxException; import java.net.URISyntaxException;
import java.net.URL; import java.net.URL;
import java.nio.file.FileVisitResult; import java.nio.file.FileVisitResult;
@ -20,68 +10,32 @@ import java.nio.file.LinkOption;
import java.nio.file.Path; import java.nio.file.Path;
import java.nio.file.Paths; import java.nio.file.Paths;
import java.nio.file.SimpleFileVisitor; import java.nio.file.SimpleFileVisitor;
import java.nio.file.StandardCopyOption;
import java.nio.file.attribute.BasicFileAttributes; import java.nio.file.attribute.BasicFileAttributes;
import java.security.KeyManagementException;
import java.security.NoSuchAlgorithmException;
import java.security.cert.X509Certificate;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.Supplier;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import org.apache.commons.exec.StreamPumper; import org.apache.commons.exec.StreamPumper;
import org.apache.commons.lang3.SystemUtils;
import org.jboss.arquillian.container.spi.client.container.DeployableContainer;
import org.jboss.arquillian.container.spi.client.container.DeploymentException;
import org.jboss.arquillian.container.spi.client.container.LifecycleException; import org.jboss.arquillian.container.spi.client.container.LifecycleException;
import org.jboss.arquillian.container.spi.client.protocol.ProtocolDescription;
import org.jboss.arquillian.container.spi.client.protocol.metadata.ProtocolMetaData;
import org.jboss.arquillian.core.api.Instance;
import org.jboss.arquillian.core.api.annotation.Inject;
import org.jboss.logging.Logger; import org.jboss.logging.Logger;
import org.jboss.shrinkwrap.api.Archive;
import org.jboss.shrinkwrap.api.exporter.ZipExporter;
import org.jboss.shrinkwrap.descriptor.api.Descriptor;
import org.keycloak.common.crypto.FipsMode;
import org.keycloak.testsuite.arquillian.SuiteContext;
import org.keycloak.testsuite.model.StoreProvider; import org.keycloak.testsuite.model.StoreProvider;
/** /**
* @author mhajas * @author mhajas
*/ */
public class KeycloakQuarkusServerDeployableContainer implements DeployableContainer<KeycloakQuarkusConfiguration> { public class KeycloakQuarkusServerDeployableContainer extends AbstractQuarkusDeployableContainer {
private static final int DEFAULT_SHUTDOWN_TIMEOUT_SECONDS = 10; private static final int DEFAULT_SHUTDOWN_TIMEOUT_SECONDS = 10;
private static final Logger log = Logger.getLogger(KeycloakQuarkusServerDeployableContainer.class); private static final Logger log = Logger.getLogger(KeycloakQuarkusServerDeployableContainer.class);
private KeycloakQuarkusConfiguration configuration;
private Process container; private Process container;
private static AtomicBoolean restart = new AtomicBoolean();
private Thread stdoutForwarderThread; private Thread stdoutForwarderThread;
@Inject
private Instance<SuiteContext> suiteContext;
private List<String> additionalBuildArgs = Collections.emptyList();
@Override
public Class<KeycloakQuarkusConfiguration> getConfigurationClass() {
return KeycloakQuarkusConfiguration.class;
}
@Override
public void setup(KeycloakQuarkusConfiguration configuration) {
this.configuration = configuration;
}
@Override @Override
public void start() throws LifecycleException { public void start() throws LifecycleException {
try { try {
@ -109,50 +63,6 @@ public class KeycloakQuarkusServerDeployableContainer implements DeployableConta
} }
} }
@Override
public ProtocolDescription getDefaultProtocol() {
return null;
}
@Override
public ProtocolMetaData deploy(Archive<?> archive) throws DeploymentException {
log.infof("Trying to deploy: " + archive.getName());
try {
deployArchiveToServer(archive);
restartServer();
} catch (Exception e) {
throw new DeploymentException(e.getMessage(),e);
}
return new ProtocolMetaData();
}
@Override
public void undeploy(Archive<?> archive) throws DeploymentException {
File wrkDir = configuration.getProvidersPath().resolve("providers").toFile();
try {
if (isWindows()) {
// stop before updating providers to avoid file locking issues on Windows
stop();
}
Files.deleteIfExists(wrkDir.toPath().resolve(archive.getName()));
restartServer();
} catch (Exception e) {
throw new DeploymentException(e.getMessage(),e);
}
}
@Override
public void deploy(Descriptor descriptor) throws DeploymentException {
}
@Override
public void undeploy(Descriptor descriptor) throws DeploymentException {
}
private void importRealm() throws IOException, URISyntaxException { private void importRealm() throws IOException, URISyntaxException {
if (suiteContext.get().isAuthServerMigrationEnabled() && configuration.getImportFile() != null) { if (suiteContext.get().isAuthServerMigrationEnabled() && configuration.getImportFile() != null) {
final String importFileName = configuration.getImportFile(); final String importFileName = configuration.getImportFile();
@ -201,224 +111,28 @@ public class KeycloakQuarkusServerDeployableContainer implements DeployableConta
return builder.start(); return builder.start();
} }
private ProcessBuilder getProcessBuilder() { @Override
List<String> commands = new ArrayList<>(); protected List<String> configureArgs(List<String> args) {
Map<String, String> env = new HashMap<>(); List<String> commands = new ArrayList<>(args);
commands.add(getCommand()); commands.add(0, getCommand());
commands.add("-v");
commands.add("start");
commands.add("--optimized"); commands.add("--optimized");
commands.add("--http-enabled=true");
if (Boolean.parseBoolean(System.getProperty("auth.server.debug", "false"))) {
commands.add("--debug");
String debugPort = configuration.getDebugPort() > 0 ? Integer.toString(configuration.getDebugPort()) : System.getProperty("auth.server.debug.port", "5005");
env.put("DEBUG_PORT", debugPort);
String debugSuspend = System.getProperty("auth.server.debug.suspend");
if (debugSuspend != null) {
env.put("DEBUG_SUSPEND", debugSuspend);
}
}
commands.add("--http-port=" + configuration.getBindHttpPort());
commands.add("--https-port=" + configuration.getBindHttpsPort());
if (configuration.getRoute() != null) {
commands.add("-Djboss.node.name=" + configuration.getRoute());
}
final StoreProvider storeProvider = StoreProvider.getCurrentProvider();
final Supplier<Boolean> shouldSetUpDb = () -> !restart.get() && !storeProvider.equals(StoreProvider.DEFAULT);
final Supplier<String> getClusterConfig = () -> System.getProperty("auth.server.quarkus.cluster.config", "local");
log.debugf("FIPS Mode: %s", configuration.getFipsMode());
// only run build during first execution of the server (if the DB is specified), restarts or when running cluster tests
if (restart.get() || shouldSetUpDb.get() || "ha".equals(getClusterConfig.get()) || configuration.getFipsMode() != FipsMode.disabled) {
commands.removeIf("--optimized"::equals);
commands.add("--http-relative-path=/auth");
if (!storeProvider.isMapStore()) {
String cacheMode = getClusterConfig.get();
if ("local".equals(cacheMode)) {
commands.add("--cache=local");
} else {
commands.add("--cache-config-file=cluster-" + cacheMode + ".xml");
}
}
if (configuration.getFipsMode() != FipsMode.disabled) {
addFipsOptions(commands);
}
}
addStorageOptions(storeProvider, commands);
if (System.getProperty("auth.server.quarkus.log-level") != null) {
commands.add("--log-level=" + System.getProperty("auth.server.quarkus.log-level"));
}
commands.addAll(getAdditionalBuildArgs());
log.debugf("Quarkus parameters: %s", commands); log.debugf("Quarkus parameters: %s", commands);
String[] processCommands = commands.toArray(new String[0]); return commands;
}
private ProcessBuilder getProcessBuilder() {
Map<String, String> env = new HashMap<>();
String[] processCommands = getArgs(env).toArray(new String[0]);
ProcessBuilder pb = new ProcessBuilder(processCommands); ProcessBuilder pb = new ProcessBuilder(processCommands);
pb.environment().putAll(env); pb.environment().putAll(env);
return pb; return pb;
} }
private void addStorageOptions(StoreProvider storeProvider, List<String> commands) {
log.debugf("Store '%s' is used.", storeProvider.name());
storeProvider.addStoreOptions(commands);
}
private void addFipsOptions(List<String> commands) {
commands.add("--fips-mode=" + configuration.getFipsMode().toString());
log.debugf("Keystore file: %s, keystore type: %s, truststore file: %s, truststore type: %s",
configuration.getKeystoreFile(), configuration.getKeystoreType(),
configuration.getTruststoreFile(), configuration.getTruststoreType());
commands.add("--https-key-store-file=" + configuration.getKeystoreFile());
commands.add("--https-key-store-type=" + configuration.getKeystoreType());
commands.add("--https-key-store-password=" + configuration.getKeystorePassword());
commands.add("--https-trust-store-file=" + configuration.getTruststoreFile());
commands.add("--https-trust-store-type=" + configuration.getTruststoreType());
commands.add("--https-trust-store-password=" + configuration.getTruststorePassword());
commands.add("--spi-truststore-file-file=" + configuration.getTruststoreFile());
commands.add("--spi-truststore-file-password=" + configuration.getTruststorePassword());
commands.add("--spi-truststore-file-type=" + configuration.getTruststoreType());
// BCFIPS approved mode requires passwords of at least 112 bits (14 characters) to be used. To bypass this, we use this by default
// as testsuite uses shorter passwords everywhere
if (FipsMode.strict == configuration.getFipsMode()) {
commands.add("--spi-password-hashing-pbkdf2-max-padding-length=14");
commands.add("--spi-password-hashing-pbkdf2-sha256-max-padding-length=14");
commands.add("--spi-password-hashing-pbkdf2-sha512-max-padding-length=14");
}
commands.add("--log-level=INFO,org.keycloak.common.crypto:TRACE,org.keycloak.crypto:TRACE,org.keycloak.truststore:TRACE");
configuration.appendJavaOpts("-Djava.security.properties=" + System.getProperty("auth.server.java.security.file"));
}
private void waitForReadiness() throws MalformedURLException, LifecycleException {
SuiteContext suiteContext = this.suiteContext.get();
//TODO: not sure if the best endpoint but it makes sure that everything is properly initialized. Once we have
// support for MP Health this should change
URL contextRoot = new URL(getBaseUrl(suiteContext) + "/auth/realms/master/");
HttpURLConnection connection;
long startTime = System.currentTimeMillis();
while (true) {
if (System.currentTimeMillis() - startTime > getStartTimeout()) {
stop();
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) {
break;
}
connection.disconnect();
} catch (Exception ignore) {
}
}
log.infof("Keycloak is ready at %s", contextRoot);
}
private URL getBaseUrl(SuiteContext suiteContext) throws MalformedURLException {
URL baseUrl = suiteContext.getAuthServerInfo().getContextRoot();
// might be running behind a load balancer
if ("https".equals(baseUrl.getProtocol())) {
baseUrl = new URL(baseUrl.toString().replace(String.valueOf(baseUrl.getPort()), String.valueOf(configuration.getBindHttpsPort())));
} else {
baseUrl = new URL(baseUrl.toString().replace(String.valueOf(baseUrl.getPort()), String.valueOf(configuration.getBindHttpPort())));
}
return baseUrl;
}
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 java.security.SecureRandom());
socketFactory = sslContext.getSocketFactory();
} catch (NoSuchAlgorithmException | KeyManagementException e) {
throw new IOException("Can't create unsecure trust manager");
}
return socketFactory;
}
private long getStartTimeout() {
return TimeUnit.SECONDS.toMillis(configuration.getStartupTimeoutInSeconds());
}
public void resetConfiguration() {
additionalBuildArgs = Collections.emptyList();
}
private void deployArchiveToServer(Archive<?> archive) throws IOException, LifecycleException {
if (isWindows()) {
// stop before updating providers to avoid file locking issues on Windows
stop();
}
File providersDir = configuration.getProvidersPath().resolve("providers").toFile();
InputStream zipStream = archive.as(ZipExporter.class).exportAsInputStream();
Files.copy(zipStream, providersDir.toPath().resolve(archive.getName()), StandardCopyOption.REPLACE_EXISTING);
}
public void restartServer() throws Exception {
stop();
start();
}
private String getCommand() { private String getCommand() {
if (isWindows()) { if (isWindows()) {
return configuration.getProvidersPath().resolve("bin").resolve("kc.bat").toString(); return configuration.getProvidersPath().resolve("bin").resolve("kc.bat").toString();
@ -426,14 +140,6 @@ public class KeycloakQuarkusServerDeployableContainer implements DeployableConta
return "./kc.sh"; return "./kc.sh";
} }
public List<String> getAdditionalBuildArgs() {
return additionalBuildArgs;
}
public void setAdditionalBuildArgs(List<String> newArgs) {
additionalBuildArgs = newArgs;
}
private void destroyDescendantsOnWindows(Process parent, boolean force) { private void destroyDescendantsOnWindows(Process parent, boolean force) {
if (!isWindows()) { if (!isWindows()) {
return; return;
@ -467,10 +173,6 @@ public class KeycloakQuarkusServerDeployableContainer implements DeployableConta
} }
} }
private static boolean isWindows() {
return SystemUtils.IS_OS_WINDOWS;
}
public static void deleteDirectory(final Path directory) throws IOException { public static void deleteDirectory(final Path directory) throws IOException {
if (Files.isDirectory(directory, new LinkOption[0])) { if (Files.isDirectory(directory, new LinkOption[0])) {
Files.walkFileTree(directory, new SimpleFileVisitor<Path>() { Files.walkFileTree(directory, new SimpleFileVisitor<Path>() {

View file

@ -44,6 +44,7 @@ public class MultipleContainersExtension implements LoadableExtension {
logger.info("Multiple containers extension registering."); logger.info("Multiple containers extension registering.");
builder.service(DeployableContainer.class, KeycloakQuarkusServerDeployableContainer.class) builder.service(DeployableContainer.class, KeycloakQuarkusServerDeployableContainer.class)
.service(DeployableContainer.class, KeycloakQuarkusEmbeddedDeployableContainer.class)
.service(DeployableContainer.class, InfinispanServerDeployableContainer.class); .service(DeployableContainer.class, InfinispanServerDeployableContainer.class);
builder.context(ContainerContextImpl.class).context(DeploymentContextImpl.class); builder.context(ContainerContextImpl.class).context(DeploymentContextImpl.class);

View file

@ -38,7 +38,7 @@ public enum StoreProvider {
public void addStoreOptions(List<String> commands) { public void addStoreOptions(List<String> commands) {
commands.add("--storage=" + getAlias()); commands.add("--storage=" + getAlias());
getDbVendor().ifPresent(vendor -> commands.add("--db=" + vendor)); getDbVendor().ifPresent(vendor -> commands.add("--db=" + vendor));
commands.add("--db-url='" + System.getProperty("keycloak.map.storage.connectionsJpa.url") + "'"); commands.add("--db-url=" + System.getProperty("keycloak.map.storage.connectionsJpa.url"));
commands.add("--db-username=" + System.getProperty("keycloak.map.storage.connectionsJpa.user")); commands.add("--db-username=" + System.getProperty("keycloak.map.storage.connectionsJpa.user"));
commands.add("--db-password=" + System.getProperty("keycloak.map.storage.connectionsJpa.password")); commands.add("--db-password=" + System.getProperty("keycloak.map.storage.connectionsJpa.password"));
} }

View file

@ -57,11 +57,11 @@ public class ContainerAssume {
public static void assumeNotAuthServerQuarkus() { public static void assumeNotAuthServerQuarkus() {
Assume.assumeFalse("Doesn't work on auth-server-quarkus", Assume.assumeFalse("Doesn't work on auth-server-quarkus",
AuthServerTestEnricher.AUTH_SERVER_CONTAINER.equals("auth-server-quarkus")); AuthServerTestEnricher.AUTH_SERVER_CONTAINER.startsWith("auth-server-quarkus"));
} }
public static void assumeAuthServerQuarkus() { public static void assumeAuthServerQuarkus() {
Assume.assumeTrue("Only works on auth-server-quarkus", Assume.assumeTrue("Only works on auth-server-quarkus",
AuthServerTestEnricher.AUTH_SERVER_CONTAINER.equals("auth-server-quarkus")); AuthServerTestEnricher.AUTH_SERVER_CONTAINER.startsWith("auth-server-quarkus"));
} }
} }

View file

@ -2,17 +2,12 @@ package org.keycloak.testsuite.util;
import org.jboss.arquillian.container.spi.Container; import org.jboss.arquillian.container.spi.Container;
import org.jboss.logging.Logger; import org.jboss.logging.Logger;
import org.keycloak.testsuite.arquillian.AuthServerTestEnricher;
import org.keycloak.testsuite.arquillian.ContainerInfo; import org.keycloak.testsuite.arquillian.ContainerInfo;
import org.keycloak.testsuite.arquillian.SuiteContext; import org.keycloak.testsuite.arquillian.SuiteContext;
import org.keycloak.testsuite.arquillian.annotation.SetDefaultProvider; import org.keycloak.testsuite.arquillian.annotation.SetDefaultProvider;
import org.keycloak.testsuite.arquillian.containers.KeycloakQuarkusServerDeployableContainer; import org.keycloak.testsuite.arquillian.containers.AbstractQuarkusDeployableContainer;
import org.keycloak.utils.StringUtil; import org.keycloak.utils.StringUtil;
import org.wildfly.extras.creaper.core.online.CliException;
import org.wildfly.extras.creaper.core.online.ModelNodeResult;
import org.wildfly.extras.creaper.core.online.OnlineManagementClient;
import java.io.IOException;
import java.util.Collections; import java.util.Collections;
import java.util.Map; import java.util.Map;
import java.util.Optional; import java.util.Optional;
@ -58,8 +53,8 @@ public class SpiProvidersSwitchingUtils {
getQuarkusContainer(container).setAdditionalBuildArgs(Collections.emptyList()); getQuarkusContainer(container).setAdditionalBuildArgs(Collections.emptyList());
} }
private KeycloakQuarkusServerDeployableContainer getQuarkusContainer(Container container) { private AbstractQuarkusDeployableContainer getQuarkusContainer(Container container) {
return (KeycloakQuarkusServerDeployableContainer) container.getDeployableContainer(); return (AbstractQuarkusDeployableContainer) container.getDeployableContainer();
} }
/** /**

View file

@ -25,7 +25,7 @@ import org.junit.Before;
import org.junit.Test; import org.junit.Test;
import org.keycloak.models.ClientProvider; import org.keycloak.models.ClientProvider;
import org.keycloak.representations.idm.ClientRepresentation; import org.keycloak.representations.idm.ClientRepresentation;
import org.keycloak.testsuite.arquillian.containers.KeycloakQuarkusServerDeployableContainer; import org.keycloak.testsuite.arquillian.containers.AbstractQuarkusDeployableContainer;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collections; import java.util.Collections;
@ -147,7 +147,7 @@ public class ClientSearchTest extends AbstractClientTest {
.toArray(String[]::new); .toArray(String[]::new);
String s = String.join(",",searchableAttributes); String s = String.join(",",searchableAttributes);
controller.stop(suiteContext.getAuthServerInfo().getQualifier()); controller.stop(suiteContext.getAuthServerInfo().getQualifier());
KeycloakQuarkusServerDeployableContainer container = (KeycloakQuarkusServerDeployableContainer)suiteContext.getAuthServerInfo().getArquillianContainer().getDeployableContainer(); AbstractQuarkusDeployableContainer container = (AbstractQuarkusDeployableContainer)suiteContext.getAuthServerInfo().getArquillianContainer().getDeployableContainer();
container.setAdditionalBuildArgs(Collections.singletonList("--spi-client-jpa-searchable-attributes=\""+ s + "\"")); container.setAdditionalBuildArgs(Collections.singletonList("--spi-client-jpa-searchable-attributes=\""+ s + "\""));
controller.start(suiteContext.getAuthServerInfo().getQualifier()); controller.start(suiteContext.getAuthServerInfo().getQualifier());
} else { } else {
@ -165,7 +165,7 @@ public class ClientSearchTest extends AbstractClientTest {
System.clearProperty(SEARCHABLE_ATTRS_PROP); System.clearProperty(SEARCHABLE_ATTRS_PROP);
controller.start(suiteContext.getAuthServerInfo().getQualifier()); controller.start(suiteContext.getAuthServerInfo().getQualifier());
} else if (suiteContext.getAuthServerInfo().isQuarkus()) { } else if (suiteContext.getAuthServerInfo().isQuarkus()) {
KeycloakQuarkusServerDeployableContainer container = (KeycloakQuarkusServerDeployableContainer) suiteContext.getAuthServerInfo().getArquillianContainer().getDeployableContainer(); AbstractQuarkusDeployableContainer container = (AbstractQuarkusDeployableContainer) suiteContext.getAuthServerInfo().getArquillianContainer().getDeployableContainer();
container.setAdditionalBuildArgs(Collections.emptyList()); container.setAdditionalBuildArgs(Collections.emptyList());
container.restartServer(); container.restartServer();
} else { } else {

View file

@ -23,11 +23,8 @@ import org.keycloak.admin.client.resource.RealmResource;
import org.keycloak.models.GroupProvider; import org.keycloak.models.GroupProvider;
import org.keycloak.representations.idm.GroupRepresentation; import org.keycloak.representations.idm.GroupRepresentation;
import org.keycloak.representations.idm.RealmRepresentation; import org.keycloak.representations.idm.RealmRepresentation;
import org.keycloak.testsuite.arquillian.AuthServerTestEnricher; import org.keycloak.testsuite.arquillian.containers.AbstractQuarkusDeployableContainer;
import org.keycloak.testsuite.arquillian.containers.KeycloakQuarkusServerDeployableContainer;
import org.keycloak.testsuite.updaters.Creator; import org.keycloak.testsuite.updaters.Creator;
import org.wildfly.extras.creaper.core.online.OnlineManagementClient;
import org.wildfly.extras.creaper.core.online.operations.admin.Administration;
public class GroupSearchTest extends AbstractGroupTest { public class GroupSearchTest extends AbstractGroupTest {
@ArquillianResource @ArquillianResource
@ -166,7 +163,7 @@ public class GroupSearchTest extends AbstractGroupTest {
.toArray(String[]::new); .toArray(String[]::new);
String s = String.join(",", searchableAttributes); String s = String.join(",", searchableAttributes);
controller.stop(suiteContext.getAuthServerInfo().getQualifier()); controller.stop(suiteContext.getAuthServerInfo().getQualifier());
KeycloakQuarkusServerDeployableContainer container = (KeycloakQuarkusServerDeployableContainer) suiteContext.getAuthServerInfo().getArquillianContainer().getDeployableContainer(); AbstractQuarkusDeployableContainer container = (AbstractQuarkusDeployableContainer) suiteContext.getAuthServerInfo().getArquillianContainer().getDeployableContainer();
container.setAdditionalBuildArgs( container.setAdditionalBuildArgs(
Collections.singletonList("--spi-group-jpa-searchable-attributes=\"" + s + "\"")); Collections.singletonList("--spi-group-jpa-searchable-attributes=\"" + s + "\""));
controller.start(suiteContext.getAuthServerInfo().getQualifier()); controller.start(suiteContext.getAuthServerInfo().getQualifier());
@ -184,7 +181,7 @@ public class GroupSearchTest extends AbstractGroupTest {
System.clearProperty(SEARCHABLE_ATTRS_PROP); System.clearProperty(SEARCHABLE_ATTRS_PROP);
controller.start(suiteContext.getAuthServerInfo().getQualifier()); controller.start(suiteContext.getAuthServerInfo().getQualifier());
} else if (suiteContext.getAuthServerInfo().isQuarkus()) { } else if (suiteContext.getAuthServerInfo().isQuarkus()) {
KeycloakQuarkusServerDeployableContainer container = (KeycloakQuarkusServerDeployableContainer) suiteContext.getAuthServerInfo().getArquillianContainer().getDeployableContainer(); AbstractQuarkusDeployableContainer container = (AbstractQuarkusDeployableContainer) suiteContext.getAuthServerInfo().getArquillianContainer().getDeployableContainer();
container.setAdditionalBuildArgs(Collections.emptyList()); container.setAdditionalBuildArgs(Collections.emptyList());
container.restartServer(); container.restartServer();
} else { } else {

View file

@ -4,7 +4,7 @@ import org.jboss.arquillian.container.test.api.ContainerController;
import org.jboss.arquillian.test.api.ArquillianResource; import org.jboss.arquillian.test.api.ArquillianResource;
import org.jboss.logging.Logger; import org.jboss.logging.Logger;
import org.keycloak.testsuite.AbstractKeycloakTest; import org.keycloak.testsuite.AbstractKeycloakTest;
import org.keycloak.testsuite.arquillian.containers.KeycloakQuarkusServerDeployableContainer; import org.keycloak.testsuite.arquillian.containers.AbstractQuarkusDeployableContainer;
import org.keycloak.testsuite.util.OAuthClient; import org.keycloak.testsuite.util.OAuthClient;
import java.net.URI; import java.net.URI;
@ -33,7 +33,7 @@ public abstract class AbstractHostnameTest extends AbstractKeycloakTest {
"keycloak.hostname.fixed.alwaysHttps"); "keycloak.hostname.fixed.alwaysHttps");
controller.start(suiteContext.getAuthServerInfo().getQualifier()); controller.start(suiteContext.getAuthServerInfo().getQualifier());
} else if (suiteContext.getAuthServerInfo().isQuarkus()) { } else if (suiteContext.getAuthServerInfo().isQuarkus()) {
KeycloakQuarkusServerDeployableContainer container = (KeycloakQuarkusServerDeployableContainer)suiteContext.getAuthServerInfo().getArquillianContainer().getDeployableContainer(); AbstractQuarkusDeployableContainer container = (AbstractQuarkusDeployableContainer)suiteContext.getAuthServerInfo().getArquillianContainer().getDeployableContainer();
container.resetConfiguration(); container.resetConfiguration();
configureDefault(OAuthClient.AUTH_SERVER_ROOT, false, null); configureDefault(OAuthClient.AUTH_SERVER_ROOT, false, null);
container.restartServer(); container.restartServer();
@ -58,7 +58,7 @@ public abstract class AbstractHostnameTest extends AbstractKeycloakTest {
controller.start(suiteContext.getAuthServerInfo().getQualifier()); controller.start(suiteContext.getAuthServerInfo().getQualifier());
} else if (suiteContext.getAuthServerInfo().isQuarkus()) { } else if (suiteContext.getAuthServerInfo().isQuarkus()) {
controller.stop(suiteContext.getAuthServerInfo().getQualifier()); controller.stop(suiteContext.getAuthServerInfo().getQualifier());
KeycloakQuarkusServerDeployableContainer container = (KeycloakQuarkusServerDeployableContainer)suiteContext.getAuthServerInfo().getArquillianContainer().getDeployableContainer(); AbstractQuarkusDeployableContainer container = (AbstractQuarkusDeployableContainer)suiteContext.getAuthServerInfo().getArquillianContainer().getDeployableContainer();
List<String> additionalArgs = new ArrayList<>(); List<String> additionalArgs = new ArrayList<>();
URI frontendUri = URI.create(frontendUrl); URI frontendUri = URI.create(frontendUrl);
// enable proxy so that we can check headers are taken into account when building urls // enable proxy so that we can check headers are taken into account when building urls

View file

@ -654,6 +654,16 @@
</configuration> </configuration>
</container> </container>
<container qualifier="auth-server-quarkus-embedded" mode="manual" >
<configuration>
<property name="enabled">${auth.server.quarkus.embedded}</property>
<property name="adapterImplClass">
org.keycloak.testsuite.arquillian.containers.KeycloakQuarkusEmbeddedDeployableContainer
</property>
<property name="bindHttpPortOffset">${auth.server.port.offset}</property>
</configuration>
</container>
<!-- Clustering with Quarkus --> <!-- Clustering with Quarkus -->
<group qualifier="auth-server-quarkus-cluster"> <group qualifier="auth-server-quarkus-cluster">
<container qualifier="auth-server-quarkus-backend1" mode="manual" > <container qualifier="auth-server-quarkus-backend1" mode="manual" >

View file

@ -92,6 +92,7 @@
<auth.server.remote>false</auth.server.remote> <auth.server.remote>false</auth.server.remote>
<auth.server.quarkus>false</auth.server.quarkus> <auth.server.quarkus>false</auth.server.quarkus>
<auth.server.quarkus.embedded>false</auth.server.quarkus.embedded>
<auth.server.profile/> <auth.server.profile/>
<auth.server.feature/> <auth.server.feature/>
@ -569,6 +570,8 @@
<auth.server.remote>${auth.server.remote}</auth.server.remote> <auth.server.remote>${auth.server.remote}</auth.server.remote>
<auth.server.quarkus>${auth.server.quarkus}</auth.server.quarkus> <auth.server.quarkus>${auth.server.quarkus}</auth.server.quarkus>
<auth.server.quarkus.embedded>${auth.server.quarkus.embedded}</auth.server.quarkus.embedded>
<jboss.server.config.dir>${auth.server.config.dir}</jboss.server.config.dir>
<adapter.test.props>${adapter.test.props}</adapter.test.props> <adapter.test.props>${adapter.test.props}</adapter.test.props>
<examples.home>${examples.home}</examples.home> <examples.home>${examples.home}</examples.home>
@ -766,6 +769,20 @@
</properties> </properties>
</profile> </profile>
<profile>
<id>auth-server-quarkus-embedded</id>
<properties>
<auth.server>quarkus</auth.server>
<auth.server.quarkus.embedded>true</auth.server.quarkus.embedded>
<auth.server.jboss>false</auth.server.jboss>
<auth.server.undertow>false</auth.server.undertow>
<auth.server.config.dir>${auth.server.home}/conf</auth.server.config.dir>
<auth.server.quarkus.skip.unpack>false</auth.server.quarkus.skip.unpack>
<auth.server.undertow.skip.unpack>true</auth.server.undertow.skip.unpack>
<auth.server.jboss.skip.unpack>true</auth.server.jboss.skip.unpack>
</properties>
</profile>
<profile> <profile>
<id>auth-server-cluster-quarkus</id> <id>auth-server-cluster-quarkus</id>
<properties> <properties>