Skip to content
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
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 |
34 changes: 34 additions & 0 deletions packages/testcontainers/src/reaper/reaper.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { ContainerRuntimeClient, getContainerRuntimeClient } from "../container-runtime";

describe("Reaper", { timeout: 120_000 }, () => {
let client: ContainerRuntimeClient;

const getReaper = async () => await (await import("./reaper")).getReaper(client);

beforeEach(async () => {
vi.resetModules();
vitest.unstubAllEnvs();

client = await getContainerRuntimeClient();
});

it("should create Reaper container without RYUK_VERBOSE env var by default", async () => {
vi.spyOn(client.container, "list").mockResolvedValue([]);
const reaper = await getReaper();

const reaperContainer = client.container.getById(reaper.containerId);
const reaperContainerEnv = (await reaperContainer.inspect()).Config.Env;
expect(reaperContainerEnv).not.toContain("RYUK_VERBOSE=true");
expect(reaperContainerEnv).not.toContain("RYUK_VERBOSE=false");
});

it("should propagate TESTCONTAINERS_RYUK_VERBOSE into Reaper container", async () => {
vitest.stubEnv("TESTCONTAINERS_RYUK_VERBOSE", "true");

vi.spyOn(client.container, "list").mockResolvedValue([]);
const reaper = await getReaper();

const reaperContainer = client.container.getById(reaper.containerId);
expect((await reaperContainer.inspect()).Config.Env).toContain("RYUK_VERBOSE=true");
});
});
22 changes: 15 additions & 7 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 @@ -13,6 +13,8 @@ export const REAPER_IMAGE = process.env["RYUK_CONTAINER_IMAGE"]
export interface Reaper {
sessionId: string;

containerId: string;

addSession(sessionId: string): void;

addComposeProject(projectName: string): void;
Expand All @@ -28,10 +30,10 @@ 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);
return new DisabledReaper(sessionId, "");
} else if (reaperContainer) {
return await useExistingReaper(reaperContainer, sessionId, client.info.containerRuntime.host);
} else {
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, reaperContainer.Id, socket);
}

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, startedContainer.getId(), socket);
}

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

Expand All @@ -148,7 +153,10 @@ class RyukReaper implements Reaper {
}

class DisabledReaper implements Reaper {
constructor(public readonly sessionId: string) {}
constructor(
public readonly sessionId: string,
public readonly containerId: string
) {}

addComposeProject(): void {}

Expand Down
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