Skip to content

Commit

Permalink
Add option to remotely debug Keycloak (#118)
Browse files Browse the repository at this point in the history
* Add configuration option `withDebug`
* Add unit test and Javadoc
* Remove version suffix
* Removed dummy port
* use correct hostname

---------

Co-authored-by: Martin Leim <martin.leim@medavis.de>
  • Loading branch information
martinleim and martinleim authored Oct 19, 2023
1 parent 7b00199 commit 5148545
Show file tree
Hide file tree
Showing 2 changed files with 96 additions and 0 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ public abstract class ExtendableKeycloakContainer<SELF extends ExtendableKeycloa

private static final int KEYCLOAK_PORT_HTTP = 8080;
private static final int KEYCLOAK_PORT_HTTPS = 8443;
private static final int KEYCLOAK_PORT_DEBUG = 8787;
private static final Duration DEFAULT_STARTUP_TIMEOUT = Duration.ofMinutes(2);

private static final String KEYCLOAK_ADMIN_USER = "admin";
Expand Down Expand Up @@ -88,6 +89,9 @@ public abstract class ExtendableKeycloakContainer<SELF extends ExtendableKeycloa
private boolean useTls = false;
private boolean disabledCaching = false;
private boolean metricsEnabled = false;
private boolean debugEnabled = false;
private int debugHostPort;
private boolean debugSuspend = false;
private HttpsClientAuth httpsClientAuth = HttpsClientAuth.NONE;

private boolean useVerbose = false;
Expand Down Expand Up @@ -197,6 +201,19 @@ protected void configure() {

commandParts.add("--metrics-enabled=" + metricsEnabled);

if (debugEnabled) {
commandParts.add("--debug");
withEnv("DEBUG_PORT", "*:" + KEYCLOAK_PORT_DEBUG);
if (debugHostPort > 0) {
addFixedExposedPort(debugHostPort, KEYCLOAK_PORT_DEBUG);
} else {
addExposedPort(KEYCLOAK_PORT_DEBUG);
}
if (debugSuspend) {
withEnv("DEBUG_SUSPEND", "y");
}
}

setCommand(commandParts.toArray(new String[0]));
}

Expand Down Expand Up @@ -364,6 +381,30 @@ public SELF withEnabledMetrics() {
return self();
}

/**
* Enable remote debugging in Keycloak and expose it on a random port.
*/
public SELF withDebug() {
return withDebugFixedPort(0, false);
}

/**
* Enable remote debugging in Keycloak and expose it on a fixed port.
*
* @param hostPort The port on the host machine
* @param suspend Control if Keycloak should wait until a debugger is attached
*/
public SELF withDebugFixedPort(int hostPort, boolean suspend) {
return withDebug(hostPort, suspend);
}

private SELF withDebug(int hostPort, boolean suspend) {
this.debugEnabled = true;
this.debugHostPort = hostPort;
this.debugSuspend = suspend;
return self();
}

public Keycloak getKeycloakAdminClient() {
if (useTls) {
return Keycloak.getInstance(getAuthServerUrl(), MASTER_REALM, getAdminUsername(), getAdminPassword(), ADMIN_CLI_CLIENT, buildSslContext());
Expand Down Expand Up @@ -419,6 +460,16 @@ public int getHttpsPort() {
return getMappedPort(KEYCLOAK_PORT_HTTPS);
}

/**
* Get the mapped port for remote debugging. Should only be used if debugging has been enabled.
* @return the mapped port or <code>-1</code> if debugging has not been configured
* @see #withDebug()
* @see #withDebugFixedPort(int, boolean)
*/
public int getDebugPort() {
return debugEnabled ? getMappedPort(KEYCLOAK_PORT_DEBUG) : -1;
}

public String getContextPath() {
return contextPath;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@
import org.keycloak.representations.info.ServerInfoRepresentation;
import org.testcontainers.containers.ContainerLaunchException;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.time.Duration;
import java.time.Instant;

Expand All @@ -15,9 +19,11 @@
import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.endsWith;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.notNullValue;
import static org.hamcrest.Matchers.startsWith;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.Assertions.fail;

/**
* @author Niko Köbler, https://www.n-k.de, @dasniko
Expand Down Expand Up @@ -171,6 +177,26 @@ public void shouldStartKeycloakVerbose() {
}
}

@Test
void shouldOpenRandomDebugPort() throws IOException {
try (KeycloakContainer keycloak = new KeycloakContainer().withDebug()) {
keycloak.start();

testDebugPortAvailable(keycloak.getHost(), keycloak.getDebugPort());
}
}

@Test
void shouldOpenFixedDebugPort() throws IOException {
final int fixedDebugPort = findFreePort();
try (KeycloakContainer keycloak = new KeycloakContainer().withDebugFixedPort(fixedDebugPort, false)) {
keycloak.start();

assertThat(keycloak.getDebugPort(), is(fixedDebugPort));
testDebugPortAvailable(keycloak.getHost(), keycloak.getDebugPort());
}
}

private void checkKeycloakContainerInternals(KeycloakContainer keycloak) {
Keycloak keycloakAdminClient = keycloak.getKeycloakAdminClient();
ServerInfoRepresentation serverInfo = keycloakAdminClient.serverInfo().getInfo();
Expand All @@ -186,4 +212,23 @@ private String getMetricsUrl(String authServerUrl) {
return authServerUrl + "/metrics";
}

private static int findFreePort() {
try (var serverSocket = new ServerSocket(0)) {
return serverSocket.getLocalPort();
} catch (IOException e) {
fail("There is no free port available!");
return -1;
}
}

private void testDebugPortAvailable(final String debugHost, final int debugPort) throws IOException {
try (var debugSocket = new Socket()) {
try {
debugSocket.connect(new InetSocketAddress(debugHost, debugPort));
} catch (IOException e) {
fail("Debug port %d cannot be reached.".formatted(debugPort));
}
}
}

}

0 comments on commit 5148545

Please sign in to comment.