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

Add TESTCONTAINERS_RYUK_VERBOSE setting #941

Merged
merged 5 commits into from
Mar 20, 2025
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
25 changes: 13 additions & 12 deletions docs/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,15 +32,16 @@ Configuration of the Docker daemon:

Configuration of Testcontainers and its behaviours:

| Variable | Example | Description |
| ------------------------------------- | -------------------------- | ---------------------------------------- |
| TESTCONTAINERS_HOST_OVERRIDE | tcp://docker:2375 | Docker's host on which ports are exposed |
| TESTCONTAINERS_DOCKER_SOCKET_OVERRIDE | /var/run/docker.sock | Path to Docker's socket used by ryuk |
| TESTCONTAINERS_RYUK_PRIVILEGED | true | Run ryuk as a privileged container |
| TESTCONTAINERS_RYUK_DISABLED | true | Disable ryuk |
| TESTCONTAINERS_RYUK_PORT | 65515 | Set ryuk host port (not recommended) |
| TESTCONTAINERS_SSHD_PORT | 65515 | Set SSHd host port (not recommended) |
| TESTCONTAINERS_HUB_IMAGE_NAME_PREFIX | mycompany.com/registry | Set default image registry |
| RYUK_CONTAINER_IMAGE | testcontainers/ryuk:0.11.0 | Custom image for ryuk |
| SSHD_CONTAINER_IMAGE | testcontainers/sshd:1.1.0 | Custom image for SSHd |
| TESTCONTAINERS_REUSE_ENABLE | true | Enable reusable containers |
| Variable | Example | Description |
| ------------------------------------- | -------------------------- | -------------------------------------------- |
| TESTCONTAINERS_HOST_OVERRIDE | tcp://docker:2375 | Docker's host on which ports are exposed |
| TESTCONTAINERS_DOCKER_SOCKET_OVERRIDE | /var/run/docker.sock | Path to Docker's socket used by ryuk |
| TESTCONTAINERS_RYUK_PRIVILEGED | true | Run ryuk as a privileged container |
| TESTCONTAINERS_RYUK_DISABLED | true | Disable ryuk |
| TESTCONTAINERS_RYUK_PORT | 65515 | Set ryuk host port (not recommended) |
| TESTCONTAINERS_SSHD_PORT | 65515 | Set SSHd host port (not recommended) |
| TESTCONTAINERS_HUB_IMAGE_NAME_PREFIX | mycompany.com/registry | Set default image registry |
| RYUK_CONTAINER_IMAGE | testcontainers/ryuk:0.11.0 | Custom image for ryuk |
| SSHD_CONTAINER_IMAGE | testcontainers/sshd:1.1.0 | Custom image for SSHd |
| TESTCONTAINERS_REUSE_ENABLE | true | Enable reusable containers |
| TESTCONTAINERS_RYUK_VERBOSE | true | Sets RYUK_VERBOSE env var in ryuk container |
30 changes: 30 additions & 0 deletions packages/testcontainers/src/reaper/reaper.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { getContainerRuntimeClient } from "../container-runtime";
import { getReaper } from "./reaper";

// ryuk container is created as global variable
// so it's impossible to test against it in isolation.
// both of this tests work if run manually one after another
// and ensuring that ryuk container does not exist before either test.

describe("Reaper", { timeout: 120_000 }, () => {
it.skip("should create Reaper container without RYUK_VERBOSE env var by default", async () => {
const client = await getContainerRuntimeClient();
const reaper = await getReaper(client);
const reaperContainer = client.container.getById(reaper.reaperContainerId);
const reaperContainerEnv = (await reaperContainer.inspect()).Config.Env;
expect(reaperContainerEnv).not.toContain("RYUK_VERBOSE=true");
expect(reaperContainerEnv).not.toContain("RYUK_VERBOSE=false");
});

it.skip("should propagate TESTCONTAINERS_RYUK_VERBOSE into Reaper container", async () => {
vitest.stubEnv("TESTCONTAINERS_RYUK_VERBOSE", "true");
try {
const client = await getContainerRuntimeClient();
const reaper = await getReaper(client);
const reaperContainer = client.container.getById(reaper.reaperContainerId);
expect((await reaperContainer.inspect()).Config.Env).toContain("RYUK_VERBOSE=true");
} finally {
vitest.unstubAllEnvs();
}
});
});
25 changes: 19 additions & 6 deletions packages/testcontainers/src/reaper/reaper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { Socket } from "net";
import { IntervalRetry, log, RandomUuid, withFileLock } from "../common";
import { ContainerRuntimeClient, ImageName } from "../container-runtime";
import { GenericContainer } from "../generic-container/generic-container";
import { LABEL_TESTCONTAINERS_SESSION_ID } from "../utils/labels";
import { LABEL_TESTCONTAINERS_RYUK, LABEL_TESTCONTAINERS_SESSION_ID } from "../utils/labels";
import { Wait } from "../wait-strategies/wait";

export const REAPER_IMAGE = process.env["RYUK_CONTAINER_IMAGE"]
Expand All @@ -16,6 +16,8 @@ export interface Reaper {
addSession(sessionId: string): void;

addComposeProject(projectName: string): void;

get reaperContainerId(): string;
}

let reaper: Reaper;
Expand All @@ -28,7 +30,7 @@ export async function getReaper(client: ContainerRuntimeClient): Promise<Reaper>

reaper = await withFileLock("testcontainers-node.lock", async () => {
const reaperContainer = await findReaperContainer(client);
sessionId = reaperContainer?.Labels["org.testcontainers.session-id"] ?? new RandomUuid().nextUuid();
sessionId = reaperContainer?.Labels[LABEL_TESTCONTAINERS_SESSION_ID] ?? new RandomUuid().nextUuid();

if (process.env.TESTCONTAINERS_RYUK_DISABLED === "true") {
return new DisabledReaper(sessionId);
Expand All @@ -46,7 +48,7 @@ export async function getReaper(client: ContainerRuntimeClient): Promise<Reaper>
async function findReaperContainer(client: ContainerRuntimeClient): Promise<ContainerInfo | undefined> {
const containers = await client.container.list();
return containers.find(
(container) => container.State === "running" && container.Labels["org.testcontainers.ryuk"] === "true"
(container) => container.State === "running" && container.Labels[LABEL_TESTCONTAINERS_RYUK] === "true"
);
}

Expand All @@ -60,7 +62,7 @@ async function useExistingReaper(reaperContainer: ContainerInfo, sessionId: stri

const socket = await connectToReaperSocket(host, reaperPort, reaperContainer.Id);

return new RyukReaper(sessionId, socket);
return new RyukReaper(sessionId, socket, reaperContainer.Id);
}

async function createNewReaper(sessionId: string, remoteSocketPath: string): Promise<Reaper> {
Expand All @@ -76,6 +78,8 @@ async function createNewReaper(sessionId: string, remoteSocketPath: string): Pro
.withBindMounts([{ source: remoteSocketPath, target: "/var/run/docker.sock" }])
.withLabels({ [LABEL_TESTCONTAINERS_SESSION_ID]: sessionId })
.withWaitStrategy(Wait.forLogMessage(/.*Started.*/));
if (process.env["TESTCONTAINERS_RYUK_VERBOSE"])
container.withEnvironment({ RYUK_VERBOSE: process.env["TESTCONTAINERS_RYUK_VERBOSE"] });

if (process.env.TESTCONTAINERS_RYUK_PRIVILEGED === "true") {
container.withPrivilegedMode();
Expand All @@ -89,7 +93,7 @@ async function createNewReaper(sessionId: string, remoteSocketPath: string): Pro
startedContainer.getId()
);

return new RyukReaper(sessionId, socket);
return new RyukReaper(sessionId, socket, startedContainer.getId());
}

async function connectToReaperSocket(host: string, port: number, containerId: string): Promise<Socket> {
Expand Down Expand Up @@ -135,9 +139,14 @@ async function connectToReaperSocket(host: string, port: number, containerId: st
class RyukReaper implements Reaper {
constructor(
public readonly sessionId: string,
private readonly socket: Socket
private readonly socket: Socket,
private readonly containerId: string
) {}

get reaperContainerId() {
return this.containerId;
}

addComposeProject(projectName: string): void {
this.socket.write(`label=com.docker.compose.project=${projectName}\r\n`);
}
Expand All @@ -153,4 +162,8 @@ class DisabledReaper implements Reaper {
addComposeProject(): void {}

addSession(): void {}

get reaperContainerId() {
return "";
}
}
1 change: 1 addition & 0 deletions packages/testcontainers/src/utils/labels.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ export const LABEL_TESTCONTAINERS_VERSION = "org.testcontainers.version";
export const LABEL_TESTCONTAINERS_SESSION_ID = "org.testcontainers.session-id";
export const LABEL_TESTCONTAINERS_SSHD = "org.testcontainers.sshd";
export const LABEL_TESTCONTAINERS_CONTAINER_HASH = "org.testcontainers.container-hash";
export const LABEL_TESTCONTAINERS_RYUK = "org.testcontainers.ryuk";

export function createLabels(): Record<string, string> {
return {
Expand Down