Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Make Keycloak DevService work with @QuarkusIntegrationTest and container launches #21999

Merged
merged 1 commit into from
Dec 8, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,24 @@ public static String configureSharedNetwork(GenericContainer<?> container, Strin
// When a shared network is requested for the launched containers, we need to configure
// the container to use it. We also need to create a hostname that will be applied to the returned
// URL
container.setNetwork(Network.SHARED);

var tccl = Thread.currentThread().getContextClassLoader();
if (tccl.getName().contains("Deployment")) {
// we need to use the shared network loaded from the Augmentation ClassLoader because that ClassLoader
// is what the test launching process (that has access to the curated application) has access to
// FIXME: This is an ugly hack, but there is not much we can do...
try {
Class<?> networkClass = tccl.getParent()
.loadClass("org.testcontainers.containers.Network");
Object sharedNetwork = networkClass.getField("SHARED").get(null);
container.setNetwork((Network) sharedNetwork);
} catch (Exception e) {
throw new IllegalStateException("Unable to obtain SHARED network from testcontainers", e);
}
} else {
container.setNetwork(Network.SHARED);
}

String hostName = hostNamePrefix + "-" + Base58.randomString(5);
container.setNetworkAliases(Collections.singletonList(hostName));

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@
import java.io.Closeable;
import java.io.IOException;
import java.io.InputStream;
import java.io.UncheckedIOException;
import java.net.MalformedURLException;
import java.net.ServerSocket;
import java.net.URI;
import java.net.URL;
import java.nio.file.Files;
Expand Down Expand Up @@ -72,6 +74,8 @@ public class KeycloakDevServicesProcessor {
private static final String CONFIG_PREFIX = "quarkus.oidc.";
private static final String TENANT_ENABLED_CONFIG_KEY = CONFIG_PREFIX + "tenant-enabled";
private static final String AUTH_SERVER_URL_CONFIG_KEY = CONFIG_PREFIX + "auth-server-url";
// avoid the Quarkus prefix in order to prevent warnings when the application starts in container integration tests
private static final String CLIENT_AUTH_SERVER_URL_CONFIG_KEY = "client." + CONFIG_PREFIX + "auth-server-url";
sberyozkin marked this conversation as resolved.
Show resolved Hide resolved
private static final String APPLICATION_TYPE_CONFIG_KEY = CONFIG_PREFIX + "application-type";
private static final String CLIENT_ID_CONFIG_KEY = CONFIG_PREFIX + "client-id";
private static final String CLIENT_SECRET_CONFIG_KEY = CONFIG_PREFIX + "credentials.secret";
Expand All @@ -84,6 +88,7 @@ public class KeycloakDevServicesProcessor {

private static final String KEYCLOAK_ADMIN_USER = "admin";
private static final String KEYCLOAK_ADMIN_PASSWORD = "admin";
private static final String KEYCLOAK_FRONTEND_URL = "KEYCLOAK_FRONTEND_URL";

// Properties recognized by Wildfly-powered Keycloak
private static final String KEYCLOAK_WILDFLY_USER_PROP = "KEYCLOAK_USER";
Expand All @@ -110,7 +115,8 @@ public class KeycloakDevServicesProcessor {

private static volatile List<Closeable> closeables;
private static volatile boolean first = true;
private static volatile String capturedKeycloakUrl;
private static volatile String capturedKeycloakInternalURL;
private static volatile String capturedKeycloakHostURL;
private static volatile FileTime capturedRealmFileLastModifiedDate;
private final IsDockerWorking isDockerWorking = new IsDockerWorking(true);
private static volatile KeycloakDevServicesConfigBuildItem existingDevServiceConfig;
Expand Down Expand Up @@ -158,7 +164,7 @@ public KeycloakDevServicesConfigBuildItem startKeycloakContainer(
}
closeables = null;
capturedDevServicesConfiguration = null;
capturedKeycloakUrl = null;
capturedKeycloakInternalURL = null;
existingDevServiceConfig = null;
}
capturedDevServicesConfiguration = currentDevServicesConfiguration;
Expand Down Expand Up @@ -206,7 +212,8 @@ public void run() {
closeBuildItem.addCloseTask(closeTask, true);
}

capturedKeycloakUrl = startResult.url + (startResult.keycloakX ? "" : "/auth");
capturedKeycloakInternalURL = startResult.internalURL;
capturedKeycloakHostURL = startResult.hostURL;
if (vertxInstance == null) {
vertxInstance = Vertx.vertx();
}
Expand All @@ -222,29 +229,37 @@ public void run() {
startResult.realmNameToUse, devServices);
}

private String startURL(String host, Integer port, boolean isKeyCloakX) {
return "http://" + host + ":" + port + (isKeyCloakX ? "" : "/auth");
}

private KeycloakDevServicesConfigBuildItem prepareConfiguration(boolean createRealm, String realmNameToUse,
BuildProducer<DevServicesConfigResultBuildItem> devServices) {
final String realmName = realmNameToUse != null ? realmNameToUse : getDefaultRealmName();
final String authServerUrl = capturedKeycloakUrl + "/realms/" + realmName;
final String authServerInternalUrl = realmsURL(capturedKeycloakInternalURL, realmName);

String clientAuthServerUrl = capturedKeycloakHostURL != null ? realmsURL(capturedKeycloakHostURL, realmName)
: realmsURL(capturedKeycloakInternalURL, realmName);

String oidcClientId = getOidcClientId();
String oidcClientSecret = getOidcClientSecret();
String oidcApplicationType = getOidcApplicationType();
Map<String, String> users = getUsers(capturedDevServicesConfiguration.users, createRealm);

if (createRealm) {
createRealm(capturedKeycloakUrl, users, oidcClientId, oidcClientSecret);
createRealm(capturedKeycloakInternalURL, users, oidcClientId, oidcClientSecret);
}
devServices.produce(new DevServicesConfigResultBuildItem(KEYCLOAK_URL_KEY, capturedKeycloakUrl));
devServices.produce(new DevServicesConfigResultBuildItem(AUTH_SERVER_URL_CONFIG_KEY, authServerUrl));
devServices.produce(new DevServicesConfigResultBuildItem(KEYCLOAK_URL_KEY, capturedKeycloakInternalURL));
devServices.produce(new DevServicesConfigResultBuildItem(AUTH_SERVER_URL_CONFIG_KEY, authServerInternalUrl));
devServices.produce(new DevServicesConfigResultBuildItem(CLIENT_AUTH_SERVER_URL_CONFIG_KEY, clientAuthServerUrl));
devServices.produce(new DevServicesConfigResultBuildItem(APPLICATION_TYPE_CONFIG_KEY, oidcApplicationType));
devServices.produce(new DevServicesConfigResultBuildItem(CLIENT_ID_CONFIG_KEY, oidcClientId));
devServices.produce(new DevServicesConfigResultBuildItem(CLIENT_SECRET_CONFIG_KEY, oidcClientSecret));

Map<String, Object> configProperties = new HashMap<>();
configProperties.put(KEYCLOAK_URL_KEY, capturedKeycloakUrl);
configProperties.put(KEYCLOAK_URL_KEY, capturedKeycloakInternalURL);
configProperties.put(KEYCLOAK_REALM_KEY, realmName);
configProperties.put(AUTH_SERVER_URL_CONFIG_KEY, authServerUrl);
configProperties.put(AUTH_SERVER_URL_CONFIG_KEY, authServerInternalUrl);
configProperties.put(APPLICATION_TYPE_CONFIG_KEY, oidcApplicationType);
configProperties.put(CLIENT_ID_CONFIG_KEY, oidcClientId);
configProperties.put(CLIENT_SECRET_CONFIG_KEY, oidcClientSecret);
Expand All @@ -254,11 +269,15 @@ private KeycloakDevServicesConfigBuildItem prepareConfiguration(boolean createRe
return existingDevServiceConfig;
}

private String realmsURL(String baseURL, String realmName) {
return baseURL + "/realms/" + realmName;
}

private String getDefaultRealmName() {
return capturedDevServicesConfiguration.realmName.orElse("quarkus");
}

private StartResult startContainer(boolean useSharedContainer, Optional<Duration> timeout) {
private StartResult startContainer(boolean useSharedNetwork, Optional<Duration> timeout) {
if (!capturedDevServicesConfiguration.enabled) {
// explicitly disabled
LOG.debug("Not starting Dev Services for Keycloak as it has been disabled in the config");
Expand Down Expand Up @@ -290,7 +309,7 @@ private StartResult startContainer(boolean useSharedContainer, Optional<Duration

QuarkusOidcContainer oidcContainer = new QuarkusOidcContainer(dockerImageName,
capturedDevServicesConfiguration.port,
useSharedContainer,
useSharedNetwork,
capturedDevServicesConfiguration.realmName,
capturedDevServicesConfiguration.realmPath,
capturedDevServicesConfiguration.serviceName,
Expand All @@ -300,8 +319,11 @@ private StartResult startContainer(boolean useSharedContainer, Optional<Duration
timeout.ifPresent(oidcContainer::withStartupTimeout);
oidcContainer.start();

String url = "http://" + oidcContainer.getHost() + ":" + oidcContainer.getPort();
return new StartResult(url,
return new StartResult(
startURL(oidcContainer.getHost(), oidcContainer.getPort(), oidcContainer.keycloakX),
oidcContainer.useSharedNetwork
? startURL("localhost", oidcContainer.fixedExposedPort.getAsInt(), oidcContainer.keycloakX)
: null,
!oidcContainer.realmFileExists,
oidcContainer.realmNameToUse,
new Closeable() {
Expand All @@ -311,14 +333,15 @@ public void close() {

LOG.info("Dev Services for Keycloak shut down.");
}
},
oidcContainer.keycloakX,
false);
});
};

return maybeContainerAddress
.map(containerAddress -> new StartResult(getSharedContainerUrl(containerAddress), false,
null, null, isKeycloakX(dockerImageName), true))
.map(containerAddress -> new StartResult(
getSharedContainerUrl(containerAddress),
getSharedContainerUrl(containerAddress), // TODO: this probably needs to be addressed
false,
null, null))
.orElseGet(defaultKeycloakContainerSupplier);
}

Expand All @@ -334,21 +357,20 @@ private String getSharedContainerUrl(ContainerAddress containerAddress) {
}

private static class StartResult {
private final String url;
private final String internalURL;
private final String hostURL;
private final boolean createDefaultRealm;
private String realmNameToUse;
private final String realmNameToUse;
private final Closeable closeable;
private final boolean keycloakX;
private final boolean shared;

public StartResult(String url, boolean createDefaultRealm, String realmNameToUse, Closeable closeable,
boolean keycloakX, boolean shared) {
this.url = url;
public StartResult(String internalURL, String hostURL, boolean createDefaultRealm,
String realmNameToUse,
Closeable closeable) {
this.internalURL = internalURL;
this.hostURL = hostURL;
this.createDefaultRealm = createDefaultRealm;
this.realmNameToUse = realmNameToUse;
this.closeable = closeable;
this.keycloakX = keycloakX;
this.shared = shared;
}
}

Expand All @@ -363,20 +385,29 @@ private static class QuarkusOidcContainer extends GenericContainer {
private boolean realmFileExists;
private String hostName = null;
private String realmNameToUse;
private boolean keycloakX;
private final boolean keycloakX;

public QuarkusOidcContainer(DockerImageName dockerImageName, OptionalInt fixedExposedPort, boolean useSharedNetwork,
Optional<String> configuredRealmName, Optional<String> realmPath, String containerLabelValue,
boolean sharedContainer, Optional<String> javaOpts) {
super(dockerImageName);
this.fixedExposedPort = fixedExposedPort;

this.useSharedNetwork = useSharedNetwork;
this.configuredRealmName = configuredRealmName;
this.realmPath = realmPath;
this.containerLabelValue = containerLabelValue;
this.sharedContainer = sharedContainer;
this.javaOpts = javaOpts;
this.keycloakX = isKeycloakX(dockerImageName);

if (sharedContainer && fixedExposedPort.isEmpty()) {
sberyozkin marked this conversation as resolved.
Show resolved Hide resolved
// We need to know the port we are exposing when using the shared network, in order to be able to tell
// Keycloak what the client URL is. This is necessary in order for Keycloak to create the proper 'issuer'
// when creating tokens
fixedExposedPort = OptionalInt.of(findRandomPort());
}

this.fixedExposedPort = fixedExposedPort;
}

@Override
Expand All @@ -385,11 +416,12 @@ protected void configure() {

if (useSharedNetwork) {
hostName = ConfigureUtil.configureSharedNetwork(this, "keycloak");
addEnv(KEYCLOAK_FRONTEND_URL, "http://localhost:" + fixedExposedPort.getAsInt());
}

if (fixedExposedPort.isPresent()) {
addFixedExposedPort(fixedExposedPort.getAsInt(), KEYCLOAK_PORT);
} else {
if (fixedExposedPort.isPresent()) {
addFixedExposedPort(fixedExposedPort.getAsInt(), KEYCLOAK_PORT);
}
// we always add this one in order to avoid dumb warning messages from the wait strategy...
addExposedPort(KEYCLOAK_PORT);
}

Expand Down Expand Up @@ -445,7 +477,15 @@ protected void configure() {
}

LOG.infof("Using %s powered Keycloak distribution", keycloakX ? "Quarkus" : "WildFly");
super.setWaitStrategy(Wait.forHttp(keycloakX ? "/" : "/auth").forPort(KEYCLOAK_PORT));
super.setWaitStrategy(Wait.forLogMessage(".*Keycloak.*started.*", 1));
sberyozkin marked this conversation as resolved.
Show resolved Hide resolved
}

private Integer findRandomPort() {
try (ServerSocket socket = new ServerSocket(0)) {
return socket.getLocalPort();
} catch (IOException e) {
throw new UncheckedIOException(e);
}
}

private String getRealmNameFromRealmFile(URI uri, String realmPath) {
Expand Down
Loading