From 33cb1ad7cd19369c01a628018d219d9cb88eb62a Mon Sep 17 00:00:00 2001 From: Pedro Igor Date: Wed, 11 Jan 2023 18:30:04 -0300 Subject: [PATCH] Support runnning tests using an embedded distribution Closes #16420 --- .../quarkus/deployment/IsIntegrationTest.java | 4 +- .../keycloak/quarkus/runtime/Environment.java | 11 +- .../quarkus/runtime/KeycloakMain.java | 2 - .../runtime/configuration/Configuration.java | 17 +- quarkus/tests/integration/pom.xml | 10 + .../main/java/org/junit/rules/TestRule.java | 6 - .../src/main/java/org/keycloak/Keycloak.java | 237 ++++++++++++ .../integration-arquillian/HOW-TO-RUN.md | 18 +- .../integration-arquillian/tests/base/pom.xml | 58 +++ .../AbstractQuarkusDeployableContainer.java | 355 ++++++++++++++++++ ...oakQuarkusEmbeddedDeployableContainer.java | 56 +++ ...cloakQuarkusServerDeployableContainer.java | 320 +--------------- .../MultipleContainersExtension.java | 1 + .../testsuite/model/StoreProvider.java | 2 +- .../testsuite/util/ContainerAssume.java | 4 +- .../util/SpiProvidersSwitchingUtils.java | 11 +- .../admin/client/ClientSearchTest.java | 6 +- .../admin/group/GroupSearchTest.java | 9 +- .../testsuite/url/AbstractHostnameTest.java | 6 +- .../base/src/test/resources/arquillian.xml | 10 + .../integration-arquillian/tests/pom.xml | 17 + 21 files changed, 810 insertions(+), 350 deletions(-) delete mode 100644 quarkus/tests/integration/src/main/java/org/junit/rules/TestRule.java create mode 100644 quarkus/tests/integration/src/main/java/org/keycloak/Keycloak.java create mode 100644 testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/arquillian/containers/AbstractQuarkusDeployableContainer.java create mode 100644 testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/arquillian/containers/KeycloakQuarkusEmbeddedDeployableContainer.java diff --git a/quarkus/deployment/src/main/java/org/keycloak/quarkus/deployment/IsIntegrationTest.java b/quarkus/deployment/src/main/java/org/keycloak/quarkus/deployment/IsIntegrationTest.java index 19a3522a5b..765074a298 100644 --- a/quarkus/deployment/src/main/java/org/keycloak/quarkus/deployment/IsIntegrationTest.java +++ b/quarkus/deployment/src/main/java/org/keycloak/quarkus/deployment/IsIntegrationTest.java @@ -3,7 +3,7 @@ package org.keycloak.quarkus.deployment; import io.quarkus.deployment.IsTest; 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 { @@ -13,7 +13,7 @@ public class IsIntegrationTest extends IsTest { @Override public boolean getAsBoolean() { - return super.getAsBoolean() && (System.getProperty(LAUNCH_MODE) != null && System.getProperty(LAUNCH_MODE).equals("test")); + return super.getAsBoolean() && Environment.isTestLaunchMode(); } } 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 19e852e207..f4b56839f4 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 @@ -18,7 +18,6 @@ package org.keycloak.quarkus.runtime; 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.FilenameFilter; @@ -177,10 +176,6 @@ public final class Environment { })).collect(Collectors.toMap(File::getName, Function.identity())); } - public static boolean isQuarkusDevMode() { - return ProfileManager.getLaunchMode().equals(LaunchMode.DEVELOPMENT); - } - public static boolean isTestLaunchMode() { return "test".equals(System.getProperty(LAUNCH_MODE)); } @@ -220,7 +215,7 @@ public final class Environment { } public static boolean isDistribution() { - if (isQuarkusDevMode()) { + if (LaunchMode.current().isDevOrTest()) { return false; } return getHomeDir() != null; @@ -233,4 +228,8 @@ public final class Environment { public static boolean isRebuilt() { return Boolean.getBoolean("kc.config.built"); } + + public static void setHomeDir(Path path) { + System.setProperty("kc.home.dir", path.toFile().getAbsolutePath()); + } } 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 d3a1bf2d14..fa08875f3d 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 @@ -38,9 +38,7 @@ import io.quarkus.runtime.Quarkus; import org.jboss.logging.Logger; import org.keycloak.Config; -import org.keycloak.models.KeycloakSession; import org.keycloak.models.KeycloakSessionFactory; -import org.keycloak.models.KeycloakTransactionManager; import org.keycloak.models.utils.KeycloakModelUtils; import org.keycloak.quarkus.runtime.cli.ExecutionExceptionHandler; import org.keycloak.quarkus.runtime.cli.Picocli; diff --git a/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/configuration/Configuration.java b/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/configuration/Configuration.java index 246e5a6cac..b88a3e1929 100644 --- a/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/configuration/Configuration.java +++ b/quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/configuration/Configuration.java @@ -17,7 +17,6 @@ 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.cli.Picocli.ARG_PREFIX; @@ -156,6 +155,22 @@ public final class Configuration { 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) { String value = configSource.getValue("%".concat(getProfileOrDefault("prod").concat(".").concat(name))); diff --git a/quarkus/tests/integration/pom.xml b/quarkus/tests/integration/pom.xml index f941061503..630db55e47 100644 --- a/quarkus/tests/integration/pom.xml +++ b/quarkus/tests/integration/pom.xml @@ -62,6 +62,12 @@ io.quarkus quarkus-junit5-internal + + junit + junit + ${junit.version} + compile + org.keycloak keycloak-quarkus-dist @@ -100,6 +106,10 @@ org.testcontainers mysql + + org.apache.maven.wagon + wagon-http-shared + diff --git a/quarkus/tests/integration/src/main/java/org/junit/rules/TestRule.java b/quarkus/tests/integration/src/main/java/org/junit/rules/TestRule.java deleted file mode 100644 index cd0b384371..0000000000 --- a/quarkus/tests/integration/src/main/java/org/junit/rules/TestRule.java +++ /dev/null @@ -1,6 +0,0 @@ -package org.junit.rules; - -// WORKAROUND: https://github.com/testcontainers/testcontainers-java/issues/970#issuecomment-625044008 -@SuppressWarnings("unused") -public interface TestRule { -} diff --git a/quarkus/tests/integration/src/main/java/org/keycloak/Keycloak.java b/quarkus/tests/integration/src/main/java/org/keycloak/Keycloak.java new file mode 100644 index 0000000000..34ea552bce --- /dev/null +++ b/quarkus/tests/integration/src/main/java/org/keycloak/Keycloak.java @@ -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 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 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 dependencies; + + public Keycloak() { + this(null, Version.VERSION, List.of()); + } + + public Keycloak(Path homeDir, String version, List dependencies) { + this.homeDir = homeDir; + this.dependencies = dependencies; + try { + applicationModel = createApplicationModel(version); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + private Keycloak start(List 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; + } +} diff --git a/testsuite/integration-arquillian/HOW-TO-RUN.md b/testsuite/integration-arquillian/HOW-TO-RUN.md index f179d109e0..715282573b 100644 --- a/testsuite/integration-arquillian/HOW-TO-RUN.md +++ b/testsuite/integration-arquillian/HOW-TO-RUN.md @@ -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 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 ### Arquillian debugging @@ -465,7 +482,6 @@ This is temporary and database configuration should be more integrated with the Activate the following profiles: -* `quarkus` * `auth-server-cluster-quarkus` Then run any cluster test as usual. diff --git a/testsuite/integration-arquillian/tests/base/pom.xml b/testsuite/integration-arquillian/tests/base/pom.xml index 160555f044..3ca796bffa 100644 --- a/testsuite/integration-arquillian/tests/base/pom.xml +++ b/testsuite/integration-arquillian/tests/base/pom.xml @@ -176,6 +176,19 @@ infinispan-server-hotrod ${infinispan.version} + + + + org.keycloak + keycloak-quarkus-integration-tests + ${project.version} + + + * + * + + + @@ -1056,6 +1069,51 @@ + + auth-server-quarkus-embedded + + 1024m + 1024m + 512m + 512m + + + + org.keycloak + keycloak-quarkus-integration-tests + ${project.version} + + + + + org.keycloak + keycloak-crypto-fips1402 + + + + + org.junit.jupiter + junit-jupiter + + + io.quarkus + quarkus-junit5-internal + + + + + org.jboss.logmanager + log4j-jboss-logmanager + 1.3.0.Final + + + net.bytebuddy + byte-buddy + 1.12.18 + + + + diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/arquillian/containers/AbstractQuarkusDeployableContainer.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/arquillian/containers/AbstractQuarkusDeployableContainer.java new file mode 100644 index 0000000000..b537e4e959 --- /dev/null +++ b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/arquillian/containers/AbstractQuarkusDeployableContainer.java @@ -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 { + + private static final Logger log = Logger.getLogger(AbstractQuarkusDeployableContainer.class); + + protected static AtomicBoolean restart = new AtomicBoolean(); + + @Inject + protected Instance suiteContext; + + protected KeycloakQuarkusConfiguration configuration; + protected List additionalBuildArgs = Collections.emptyList(); + + @Override + public Class 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 getArgs() { + return getArgs(new HashMap<>()); + } + + protected List getArgs(Map env) { + List 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 shouldSetUpDb = () -> !restart.get() && !storeProvider.equals(StoreProvider.DEFAULT); + final Supplier 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 configureArgs(List 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 getAdditionalBuildArgs() { + return additionalBuildArgs; + } + + public void setAdditionalBuildArgs(List 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 commands) { + log.debugf("Store '%s' is used.", storeProvider.name()); + storeProvider.addStoreOptions(commands); + } + + private void addFipsOptions(List 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")); + } +} diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/arquillian/containers/KeycloakQuarkusEmbeddedDeployableContainer.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/arquillian/containers/KeycloakQuarkusEmbeddedDeployableContainer.java new file mode 100644 index 0000000000..9d0ff52b7d --- /dev/null +++ b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/arquillian/containers/KeycloakQuarkusEmbeddedDeployableContainer.java @@ -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 configureArgs(List args) { + System.setProperty("quarkus.http.test-port", String.valueOf(configuration.getBindHttpPort())); + System.setProperty("quarkus.http.test-ssl-port", String.valueOf(configuration.getBindHttpsPort())); + return args; + } +} diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/arquillian/containers/KeycloakQuarkusServerDeployableContainer.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/arquillian/containers/KeycloakQuarkusServerDeployableContainer.java index 703033e6eb..055744c3aa 100644 --- a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/arquillian/containers/KeycloakQuarkusServerDeployableContainer.java +++ b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/arquillian/containers/KeycloakQuarkusServerDeployableContainer.java @@ -1,17 +1,7 @@ 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.IOException; -import java.io.InputStream; -import java.net.HttpURLConnection; -import java.net.MalformedURLException; import java.net.URISyntaxException; import java.net.URL; import java.nio.file.FileVisitResult; @@ -20,68 +10,32 @@ import java.nio.file.LinkOption; import java.nio.file.Path; import java.nio.file.Paths; import java.nio.file.SimpleFileVisitor; -import java.nio.file.StandardCopyOption; 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.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.concurrent.CompletableFuture; import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicBoolean; -import java.util.function.Supplier; import java.util.stream.Collectors; 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.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; /** * @author mhajas */ -public class KeycloakQuarkusServerDeployableContainer implements DeployableContainer { +public class KeycloakQuarkusServerDeployableContainer extends AbstractQuarkusDeployableContainer { private static final int DEFAULT_SHUTDOWN_TIMEOUT_SECONDS = 10; private static final Logger log = Logger.getLogger(KeycloakQuarkusServerDeployableContainer.class); - private KeycloakQuarkusConfiguration configuration; private Process container; - private static AtomicBoolean restart = new AtomicBoolean(); private Thread stdoutForwarderThread; - @Inject - private Instance suiteContext; - - private List additionalBuildArgs = Collections.emptyList(); - - @Override - public Class getConfigurationClass() { - return KeycloakQuarkusConfiguration.class; - } - - @Override - public void setup(KeycloakQuarkusConfiguration configuration) { - this.configuration = configuration; - } - @Override public void start() throws LifecycleException { 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 { if (suiteContext.get().isAuthServerMigrationEnabled() && configuration.getImportFile() != null) { final String importFileName = configuration.getImportFile(); @@ -201,224 +111,28 @@ public class KeycloakQuarkusServerDeployableContainer implements DeployableConta return builder.start(); } - private ProcessBuilder getProcessBuilder() { - List commands = new ArrayList<>(); - Map env = new HashMap<>(); + @Override + protected List configureArgs(List args) { + List commands = new ArrayList<>(args); - commands.add(getCommand()); - commands.add("-v"); - commands.add("start"); + commands.add(0, getCommand()); 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 shouldSetUpDb = () -> !restart.get() && !storeProvider.equals(StoreProvider.DEFAULT); - final Supplier 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); - String[] processCommands = commands.toArray(new String[0]); + return commands; + } + private ProcessBuilder getProcessBuilder() { + Map env = new HashMap<>(); + String[] processCommands = getArgs(env).toArray(new String[0]); ProcessBuilder pb = new ProcessBuilder(processCommands); + pb.environment().putAll(env); return pb; } - private void addStorageOptions(StoreProvider storeProvider, List commands) { - log.debugf("Store '%s' is used.", storeProvider.name()); - storeProvider.addStoreOptions(commands); - } - - private void addFipsOptions(List 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() { if (isWindows()) { return configuration.getProvidersPath().resolve("bin").resolve("kc.bat").toString(); @@ -426,14 +140,6 @@ public class KeycloakQuarkusServerDeployableContainer implements DeployableConta return "./kc.sh"; } - public List getAdditionalBuildArgs() { - return additionalBuildArgs; - } - - public void setAdditionalBuildArgs(List newArgs) { - additionalBuildArgs = newArgs; - } - private void destroyDescendantsOnWindows(Process parent, boolean force) { if (!isWindows()) { 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 { if (Files.isDirectory(directory, new LinkOption[0])) { Files.walkFileTree(directory, new SimpleFileVisitor() { diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/arquillian/containers/MultipleContainersExtension.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/arquillian/containers/MultipleContainersExtension.java index 3d336be347..b59e7ba4cf 100644 --- a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/arquillian/containers/MultipleContainersExtension.java +++ b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/arquillian/containers/MultipleContainersExtension.java @@ -44,6 +44,7 @@ public class MultipleContainersExtension implements LoadableExtension { logger.info("Multiple containers extension registering."); builder.service(DeployableContainer.class, KeycloakQuarkusServerDeployableContainer.class) + .service(DeployableContainer.class, KeycloakQuarkusEmbeddedDeployableContainer.class) .service(DeployableContainer.class, InfinispanServerDeployableContainer.class); builder.context(ContainerContextImpl.class).context(DeploymentContextImpl.class); diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/model/StoreProvider.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/model/StoreProvider.java index 09107e681c..5289289d62 100644 --- a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/model/StoreProvider.java +++ b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/model/StoreProvider.java @@ -38,7 +38,7 @@ public enum StoreProvider { public void addStoreOptions(List commands) { commands.add("--storage=" + getAlias()); 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-password=" + System.getProperty("keycloak.map.storage.connectionsJpa.password")); } diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/util/ContainerAssume.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/util/ContainerAssume.java index 0cd0269263..34d73e386f 100644 --- a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/util/ContainerAssume.java +++ b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/util/ContainerAssume.java @@ -57,11 +57,11 @@ public class ContainerAssume { public static void assumeNotAuthServerQuarkus() { 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() { Assume.assumeTrue("Only works on auth-server-quarkus", - AuthServerTestEnricher.AUTH_SERVER_CONTAINER.equals("auth-server-quarkus")); + AuthServerTestEnricher.AUTH_SERVER_CONTAINER.startsWith("auth-server-quarkus")); } } diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/util/SpiProvidersSwitchingUtils.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/util/SpiProvidersSwitchingUtils.java index f4980c5dc5..a95f58e821 100644 --- a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/util/SpiProvidersSwitchingUtils.java +++ b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/util/SpiProvidersSwitchingUtils.java @@ -2,17 +2,12 @@ package org.keycloak.testsuite.util; import org.jboss.arquillian.container.spi.Container; import org.jboss.logging.Logger; -import org.keycloak.testsuite.arquillian.AuthServerTestEnricher; import org.keycloak.testsuite.arquillian.ContainerInfo; import org.keycloak.testsuite.arquillian.SuiteContext; 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.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.Map; import java.util.Optional; @@ -58,8 +53,8 @@ public class SpiProvidersSwitchingUtils { getQuarkusContainer(container).setAdditionalBuildArgs(Collections.emptyList()); } - private KeycloakQuarkusServerDeployableContainer getQuarkusContainer(Container container) { - return (KeycloakQuarkusServerDeployableContainer) container.getDeployableContainer(); + private AbstractQuarkusDeployableContainer getQuarkusContainer(Container container) { + return (AbstractQuarkusDeployableContainer) container.getDeployableContainer(); } /** diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/client/ClientSearchTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/client/ClientSearchTest.java index c01c6bd6c2..6938b7fc1c 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/client/ClientSearchTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/client/ClientSearchTest.java @@ -25,7 +25,7 @@ import org.junit.Before; import org.junit.Test; import org.keycloak.models.ClientProvider; 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.Collections; @@ -147,7 +147,7 @@ public class ClientSearchTest extends AbstractClientTest { .toArray(String[]::new); String s = String.join(",",searchableAttributes); 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 + "\"")); controller.start(suiteContext.getAuthServerInfo().getQualifier()); } else { @@ -165,7 +165,7 @@ public class ClientSearchTest extends AbstractClientTest { System.clearProperty(SEARCHABLE_ATTRS_PROP); controller.start(suiteContext.getAuthServerInfo().getQualifier()); } else if (suiteContext.getAuthServerInfo().isQuarkus()) { - KeycloakQuarkusServerDeployableContainer container = (KeycloakQuarkusServerDeployableContainer) suiteContext.getAuthServerInfo().getArquillianContainer().getDeployableContainer(); + AbstractQuarkusDeployableContainer container = (AbstractQuarkusDeployableContainer) suiteContext.getAuthServerInfo().getArquillianContainer().getDeployableContainer(); container.setAdditionalBuildArgs(Collections.emptyList()); container.restartServer(); } else { diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/group/GroupSearchTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/group/GroupSearchTest.java index e349a3b969..28cbb87452 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/group/GroupSearchTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/group/GroupSearchTest.java @@ -23,11 +23,8 @@ import org.keycloak.admin.client.resource.RealmResource; import org.keycloak.models.GroupProvider; import org.keycloak.representations.idm.GroupRepresentation; import org.keycloak.representations.idm.RealmRepresentation; -import org.keycloak.testsuite.arquillian.AuthServerTestEnricher; -import org.keycloak.testsuite.arquillian.containers.KeycloakQuarkusServerDeployableContainer; +import org.keycloak.testsuite.arquillian.containers.AbstractQuarkusDeployableContainer; 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 { @ArquillianResource @@ -166,7 +163,7 @@ public class GroupSearchTest extends AbstractGroupTest { .toArray(String[]::new); String s = String.join(",", searchableAttributes); controller.stop(suiteContext.getAuthServerInfo().getQualifier()); - KeycloakQuarkusServerDeployableContainer container = (KeycloakQuarkusServerDeployableContainer) suiteContext.getAuthServerInfo().getArquillianContainer().getDeployableContainer(); + AbstractQuarkusDeployableContainer container = (AbstractQuarkusDeployableContainer) suiteContext.getAuthServerInfo().getArquillianContainer().getDeployableContainer(); container.setAdditionalBuildArgs( Collections.singletonList("--spi-group-jpa-searchable-attributes=\"" + s + "\"")); controller.start(suiteContext.getAuthServerInfo().getQualifier()); @@ -184,7 +181,7 @@ public class GroupSearchTest extends AbstractGroupTest { System.clearProperty(SEARCHABLE_ATTRS_PROP); controller.start(suiteContext.getAuthServerInfo().getQualifier()); } else if (suiteContext.getAuthServerInfo().isQuarkus()) { - KeycloakQuarkusServerDeployableContainer container = (KeycloakQuarkusServerDeployableContainer) suiteContext.getAuthServerInfo().getArquillianContainer().getDeployableContainer(); + AbstractQuarkusDeployableContainer container = (AbstractQuarkusDeployableContainer) suiteContext.getAuthServerInfo().getArquillianContainer().getDeployableContainer(); container.setAdditionalBuildArgs(Collections.emptyList()); container.restartServer(); } else { diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/url/AbstractHostnameTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/url/AbstractHostnameTest.java index 14eb877bde..dd226242f7 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/url/AbstractHostnameTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/url/AbstractHostnameTest.java @@ -4,7 +4,7 @@ import org.jboss.arquillian.container.test.api.ContainerController; import org.jboss.arquillian.test.api.ArquillianResource; import org.jboss.logging.Logger; 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 java.net.URI; @@ -33,7 +33,7 @@ public abstract class AbstractHostnameTest extends AbstractKeycloakTest { "keycloak.hostname.fixed.alwaysHttps"); controller.start(suiteContext.getAuthServerInfo().getQualifier()); } else if (suiteContext.getAuthServerInfo().isQuarkus()) { - KeycloakQuarkusServerDeployableContainer container = (KeycloakQuarkusServerDeployableContainer)suiteContext.getAuthServerInfo().getArquillianContainer().getDeployableContainer(); + AbstractQuarkusDeployableContainer container = (AbstractQuarkusDeployableContainer)suiteContext.getAuthServerInfo().getArquillianContainer().getDeployableContainer(); container.resetConfiguration(); configureDefault(OAuthClient.AUTH_SERVER_ROOT, false, null); container.restartServer(); @@ -58,7 +58,7 @@ public abstract class AbstractHostnameTest extends AbstractKeycloakTest { controller.start(suiteContext.getAuthServerInfo().getQualifier()); } else if (suiteContext.getAuthServerInfo().isQuarkus()) { controller.stop(suiteContext.getAuthServerInfo().getQualifier()); - KeycloakQuarkusServerDeployableContainer container = (KeycloakQuarkusServerDeployableContainer)suiteContext.getAuthServerInfo().getArquillianContainer().getDeployableContainer(); + AbstractQuarkusDeployableContainer container = (AbstractQuarkusDeployableContainer)suiteContext.getAuthServerInfo().getArquillianContainer().getDeployableContainer(); List additionalArgs = new ArrayList<>(); URI frontendUri = URI.create(frontendUrl); // enable proxy so that we can check headers are taken into account when building urls diff --git a/testsuite/integration-arquillian/tests/base/src/test/resources/arquillian.xml b/testsuite/integration-arquillian/tests/base/src/test/resources/arquillian.xml index 7fdefad093..511c148524 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/resources/arquillian.xml +++ b/testsuite/integration-arquillian/tests/base/src/test/resources/arquillian.xml @@ -654,6 +654,16 @@ + + + ${auth.server.quarkus.embedded} + + org.keycloak.testsuite.arquillian.containers.KeycloakQuarkusEmbeddedDeployableContainer + + ${auth.server.port.offset} + + + diff --git a/testsuite/integration-arquillian/tests/pom.xml b/testsuite/integration-arquillian/tests/pom.xml index 84ad28e446..c17faf67da 100644 --- a/testsuite/integration-arquillian/tests/pom.xml +++ b/testsuite/integration-arquillian/tests/pom.xml @@ -92,6 +92,7 @@ false false + false @@ -569,6 +570,8 @@ ${auth.server.remote} ${auth.server.quarkus} + ${auth.server.quarkus.embedded} + ${auth.server.config.dir} ${adapter.test.props} ${examples.home} @@ -766,6 +769,20 @@ + + auth-server-quarkus-embedded + + quarkus + true + false + false + ${auth.server.home}/conf + false + true + true + + + auth-server-cluster-quarkus