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
27 changes: 27 additions & 0 deletions packages/modules/redpanda/src/redpanda-container.test.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,30 @@
import { setTimeout } from "node:timers/promises";
import type { InspectResult, StartedTestContainer } from "testcontainers";
import { getImage } from "../../../testcontainers/src/utils/test-helper";
import { RedpandaContainer } from "./redpanda-container";
import { assertMessageProducedAndConsumed } from "./test-helper";

const IMAGE = getImage(__dirname);

// Writes each copied file in two parts with a pause between them, as a busy Docker host can.
class SlowCopyRedpandaContainer extends RedpandaContainer {
protected override async containerStarted(
container: StartedTestContainer,
inspectResult: InspectResult
): Promise<void> {
const copyContentToContainer = container.copyContentToContainer.bind(container);
container.copyContentToContainer = async (contentsToCopy) => {
for (const { content, target, mode } of contentsToCopy) {
const [firstLine] = String(content).split("\n", 1);
await copyContentToContainer([{ content: `${firstLine}\n`, target, mode }]);
await setTimeout(1_000);
await copyContentToContainer([{ content, target, mode }]);
}
};
await super.containerStarted(container, inspectResult);
}
}

describe("RedpandaContainer", { timeout: 240_000 }, () => {
it("should connect", async () => {
// connectToKafka {
Expand All @@ -13,6 +34,12 @@ describe("RedpandaContainer", { timeout: 240_000 }, () => {
// }
});

it("should connect when the config reaches the container late and in parts", async () => {
await using container = await new SlowCopyRedpandaContainer(IMAGE).start();

await assertMessageProducedAndConsumed(container);
});

it("should connect to schema registry", async () => {
// connectToSchemaRegistry {
await using container = await new RedpandaContainer(IMAGE).start();
Expand Down
25 changes: 16 additions & 9 deletions packages/modules/redpanda/src/redpanda-container.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,10 @@ const REDPANDA_PORT = 9092;
const REDPANDA_ADMIN_PORT = 9644;
const SCHEMA_REGISTRY_PORT = 8081;
const REST_PROXY_PORT = 8082;
const STARTER_SCRIPT = "/testcontainers_start.sh";
const WAIT_FOR_SCRIPT_MESSAGE = "Waiting for script...";
const REDPANDA_CONFIG = "/etc/redpanda/redpanda.yaml";
const REDPANDA_CONFIG_STAGING = `${REDPANDA_CONFIG}.testcontainers`;
const REDPANDA_CONFIG_MARKER = "# Injected by testcontainers";
const WAIT_FOR_CONFIG_MESSAGE = "Waiting for config...";

export class RedpandaContainer extends GenericContainer {
private originalWaitStrategy: WaitStrategy | undefined;
Expand All @@ -42,29 +44,34 @@ export class RedpandaContainer extends GenericContainer {
}

protected override async beforeContainerCreated(): Promise<void> {
// Change the wait strategy to wait for a log message from a fake starter script
// so that we can put a real starter script in place at that moment
// Change the wait strategy to wait for a log message from a fake starter,
// which starts Redpanda only once the config with the mapped Kafka port is in place
this.originalWaitStrategy = this.waitStrategy;
this.waitStrategy = Wait.forLogMessage(WAIT_FOR_SCRIPT_MESSAGE);
this.waitStrategy = Wait.forLogMessage(WAIT_FOR_CONFIG_MESSAGE);
this.withEntrypoint(["sh"]);
this.withCommand([
"-c",
`echo '${WAIT_FOR_SCRIPT_MESSAGE}'; while [ ! -f ${STARTER_SCRIPT} ]; do sleep 0.1; done; ${STARTER_SCRIPT}`,
`echo '${WAIT_FOR_CONFIG_MESSAGE}'; until grep -q '${REDPANDA_CONFIG_MARKER}' ${REDPANDA_CONFIG}; do sleep 0.1; done; exec rpk redpanda start --mode dev-container --smp=1 --memory=1G`,
]);
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.

protected override async containerStarted(
container: StartedTestContainer,
inspectResult: InspectResult
): Promise<void> {
const command = "#!/bin/bash\nrpk redpanda start --mode dev-container --smp=1 --memory=1G";
await container.copyContentToContainer([{ content: command, target: STARTER_SCRIPT, mode: 0o777 }]);
// Docker writes a copied file in place, so the starter could read the marker before the rest.
// A rename on the same filesystem publishes the whole file at once.
await container.copyContentToContainer([
{
content: this.renderRedpandaFile(container.getHost(), container.getMappedPort(REDPANDA_PORT)),
target: "/etc/redpanda/redpanda.yaml",
target: REDPANDA_CONFIG_STAGING,
},
]);
const { exitCode, output } = await container.exec(["mv", REDPANDA_CONFIG_STAGING, REDPANDA_CONFIG]);

if (exitCode !== 0) {
throw new Error(`Redpanda container configuration failed with exit code ${exitCode}: ${output}`);
}

const client = await getContainerRuntimeClient();
const dockerContainer = client.container.getById(container.getId());
Expand Down
Loading