diff --git a/package-lock.json b/package-lock.json index 292ade439..e4cab896b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -15931,10 +15931,11 @@ } }, "node_modules/protobufjs": { - "version": "7.3.2", - "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.3.2.tgz", - "integrity": "sha512-RXyHaACeqXeqAKGLDl68rQKbmObRsTIn4TYVUUug1KfS47YWCo5MacGITEryugIgZqORCvJWEk4l449POg5Txg==", + "version": "7.4.0", + "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.4.0.tgz", + "integrity": "sha512-mRUWCc3KUU4w1jU8sGxICXH/gNS94DvI1gxqDvBzhj1JpcsimQkYiOJfwsPUykUI5ZaspFbSgmBLER8IrQ3tqw==", "hasInstallScript": true, + "license": "BSD-3-Clause", "dependencies": { "@protobufjs/aspromise": "^1.1.2", "@protobufjs/base64": "^1.1.2", diff --git a/packages/modules/azurecosmosdb/src/azure-cosmosdb-emulator-container.test.ts b/packages/modules/azurecosmosdb/src/azure-cosmosdb-emulator-container.test.ts index de170f08f..f002697ca 100644 --- a/packages/modules/azurecosmosdb/src/azure-cosmosdb-emulator-container.test.ts +++ b/packages/modules/azurecosmosdb/src/azure-cosmosdb-emulator-container.test.ts @@ -1,6 +1,5 @@ import { CosmosClient, PartitionKeyKind } from "@azure/cosmos"; import * as https from "node:https"; -import { expect } from "vitest"; import { AzureCosmosDbEmulatorContainer } from "./azure-cosmosdb-emulator-container"; const IMAGE = "mcr.microsoft.com/cosmosdb/linux/azure-cosmos-emulator:vnext-EN20250228"; diff --git a/packages/modules/hivemq/src/hivemq-container.test.ts b/packages/modules/hivemq/src/hivemq-container.test.ts index 65860cb19..523931f42 100644 --- a/packages/modules/hivemq/src/hivemq-container.test.ts +++ b/packages/modules/hivemq/src/hivemq-container.test.ts @@ -1,5 +1,4 @@ import mqtt from "mqtt"; -import { expect } from "vitest"; import { HiveMQContainer } from "./hivemq-container"; const IMAGE = "hivemq/hivemq-ce:2023.5"; diff --git a/packages/testcontainers/fixtures/docker-compose/docker-compose-with-buildkit/Dockerfile b/packages/testcontainers/fixtures/docker-compose/docker-compose-with-buildkit/Dockerfile new file mode 100644 index 000000000..c683c22c7 --- /dev/null +++ b/packages/testcontainers/fixtures/docker-compose/docker-compose-with-buildkit/Dockerfile @@ -0,0 +1,25 @@ +FROM node:10-alpine + +MAINTAINER Cristian Greco + +EXPOSE 8080 + +RUN --mount=type=tmpfs,target=/buildkit-test \ + echo "BuildKit tmpfs mount is working" > /buildkit-test/success.txt && \ + cat /buildkit-test/success.txt + +RUN apk add --no-cache curl dumb-init libcap openssl + +RUN openssl req -x509 -nodes -days 36500 \ + -subj "/C=CA/ST=QC/O=Company Inc/CN=localhost" \ + -newkey rsa:2048 -keyout /etc/ssl/private/cert.key \ + -out /etc/ssl/certs/cert.crt \ + && chmod 666 /etc/ssl/private/cert.key + +RUN npm init -y && \ + npm install express@4.16.4 + +COPY index.js . + +ENTRYPOINT ["/usr/bin/dumb-init", "--"] +CMD ["node", "index.js"] diff --git a/packages/testcontainers/fixtures/docker-compose/docker-compose-with-buildkit/docker-compose.yml b/packages/testcontainers/fixtures/docker-compose/docker-compose-with-buildkit/docker-compose.yml new file mode 100644 index 000000000..f1a2ff140 --- /dev/null +++ b/packages/testcontainers/fixtures/docker-compose/docker-compose-with-buildkit/docker-compose.yml @@ -0,0 +1,7 @@ +services: + container: + build: + context: . + dockerfile: Dockerfile + ports: + - 8080 diff --git a/packages/testcontainers/fixtures/docker-compose/docker-compose-with-buildkit/index.js b/packages/testcontainers/fixtures/docker-compose/docker-compose-with-buildkit/index.js new file mode 100644 index 000000000..e8dbbebd6 --- /dev/null +++ b/packages/testcontainers/fixtures/docker-compose/docker-compose-with-buildkit/index.js @@ -0,0 +1,62 @@ +const fs = require("fs"); +const http = require("http"); +const https = require("https"); +const express = require("express"); + +const app = express(); + +app.get("/hello-world", (req, res) => { + res.status(200).send("hello-world"); +}); + +app.get("/hello-world-delay", (req, res) => { + setTimeout(() => { + res.status(200).send("hello-world"); + }, 3000); +}); + +app.post("/hello-world-post", (req, res) => { + res.status(200).send("hello-world"); +}); + +app.get("/env", (req, res) => { + res.status(200).json(process.env); +}); + +app.get("/cmd", (req, res) => { + res.status(200).json(process.argv); +}); + +app.get("/auth", (req, res) => { + const auth = req.headers.authorization; + const [, base64Encoded] = auth.split(" "); + const credentials = Buffer.from(base64Encoded, "base64").toString("ascii"); + const [username, password] = credentials.split(":"); + if (username === "user" && password === "pass") { + res.status(200).end(); + } else { + res.status(401).end(); + } +}); + +app.get("/header-or-400/:headerName", (req, res) => { + if (req.headers[req.params["headerName"]] !== undefined) { + res.status(200).end(); + } else { + res.status(400).end(); + } +}); + +const PORT = 8080; +const TLS_PORT = 8443; + +http.createServer(app).listen(PORT, () => console.log(`Listening on port ${PORT}`)); +https + .createServer( + { + key: fs.readFileSync("/etc/ssl/private/cert.key", "utf8"), + cert: fs.readFileSync("/etc/ssl/certs/cert.crt", "utf8"), + }, + app + ) + .listen(TLS_PORT, () => console.log(`Listening on secure port ${TLS_PORT}`)); diff --git a/packages/testcontainers/fixtures/docker/docker-with-buildkit/Dockerfile b/packages/testcontainers/fixtures/docker/docker-with-buildkit/Dockerfile new file mode 100644 index 000000000..c683c22c7 --- /dev/null +++ b/packages/testcontainers/fixtures/docker/docker-with-buildkit/Dockerfile @@ -0,0 +1,25 @@ +FROM node:10-alpine + +MAINTAINER Cristian Greco + +EXPOSE 8080 + +RUN --mount=type=tmpfs,target=/buildkit-test \ + echo "BuildKit tmpfs mount is working" > /buildkit-test/success.txt && \ + cat /buildkit-test/success.txt + +RUN apk add --no-cache curl dumb-init libcap openssl + +RUN openssl req -x509 -nodes -days 36500 \ + -subj "/C=CA/ST=QC/O=Company Inc/CN=localhost" \ + -newkey rsa:2048 -keyout /etc/ssl/private/cert.key \ + -out /etc/ssl/certs/cert.crt \ + && chmod 666 /etc/ssl/private/cert.key + +RUN npm init -y && \ + npm install express@4.16.4 + +COPY index.js . + +ENTRYPOINT ["/usr/bin/dumb-init", "--"] +CMD ["node", "index.js"] diff --git a/packages/testcontainers/fixtures/docker/docker-with-buildkit/index.js b/packages/testcontainers/fixtures/docker/docker-with-buildkit/index.js new file mode 100644 index 000000000..e8dbbebd6 --- /dev/null +++ b/packages/testcontainers/fixtures/docker/docker-with-buildkit/index.js @@ -0,0 +1,62 @@ +const fs = require("fs"); +const http = require("http"); +const https = require("https"); +const express = require("express"); + +const app = express(); + +app.get("/hello-world", (req, res) => { + res.status(200).send("hello-world"); +}); + +app.get("/hello-world-delay", (req, res) => { + setTimeout(() => { + res.status(200).send("hello-world"); + }, 3000); +}); + +app.post("/hello-world-post", (req, res) => { + res.status(200).send("hello-world"); +}); + +app.get("/env", (req, res) => { + res.status(200).json(process.env); +}); + +app.get("/cmd", (req, res) => { + res.status(200).json(process.argv); +}); + +app.get("/auth", (req, res) => { + const auth = req.headers.authorization; + const [, base64Encoded] = auth.split(" "); + const credentials = Buffer.from(base64Encoded, "base64").toString("ascii"); + const [username, password] = credentials.split(":"); + if (username === "user" && password === "pass") { + res.status(200).end(); + } else { + res.status(401).end(); + } +}); + +app.get("/header-or-400/:headerName", (req, res) => { + if (req.headers[req.params["headerName"]] !== undefined) { + res.status(200).end(); + } else { + res.status(400).end(); + } +}); + +const PORT = 8080; +const TLS_PORT = 8443; + +http.createServer(app).listen(PORT, () => console.log(`Listening on port ${PORT}`)); +https + .createServer( + { + key: fs.readFileSync("/etc/ssl/private/cert.key", "utf8"), + cert: fs.readFileSync("/etc/ssl/certs/cert.crt", "utf8"), + }, + app + ) + .listen(TLS_PORT, () => console.log(`Listening on secure port ${TLS_PORT}`)); diff --git a/packages/testcontainers/src/docker-compose-environment/docker-compose-environment.test.ts b/packages/testcontainers/src/docker-compose-environment/docker-compose-environment.test.ts index 48dbf147f..696534412 100644 --- a/packages/testcontainers/src/docker-compose-environment/docker-compose-environment.test.ts +++ b/packages/testcontainers/src/docker-compose-environment/docker-compose-environment.test.ts @@ -1,14 +1,11 @@ import path from "path"; import { RandomUuid } from "../common"; import { randomUuid } from "../common/uuid"; -import { PullPolicy } from "../utils/pull-policy"; import { checkEnvironmentContainerIsHealthy, composeContainerName, - getDockerEventStream, getRunningContainerNames, getVolumeNames, - waitForDockerEvent, } from "../utils/test-helper"; import { Wait } from "../wait-strategies/wait"; import { DockerComposeEnvironment } from "./docker-compose-environment"; @@ -43,32 +40,14 @@ describe("DockerComposeEnvironment", { timeout: 180_000 }, () => { await startedEnvironment.down(); }); - it("should use pull policy", async () => { - const env = new DockerComposeEnvironment(fixtures, "docker-compose-with-many-services.yml"); + it("should work with buildkit features", async () => { + const buildkitFixtures = path.resolve(fixtures, "docker-compose-with-buildkit"); - const startedEnv1 = await env.up(); - const dockerEventStream = await getDockerEventStream(); - const dockerPullEventPromise = waitForDockerEvent(dockerEventStream, "pull"); - const startedEnv2 = await env.withPullPolicy(PullPolicy.alwaysPull()).up(); - await dockerPullEventPromise; + const startedEnvironment = await new DockerComposeEnvironment(buildkitFixtures, "docker-compose.yml").up(); - dockerEventStream.destroy(); - await startedEnv1.stop(); - await startedEnv2.stop(); - }); - - it("should use pull policy for specific service", async () => { - const env = new DockerComposeEnvironment(fixtures, "docker-compose-with-many-services.yml"); - - const startedEnv1 = await env.up(["service_2"]); - const dockerEventStream = await getDockerEventStream(); - const dockerPullEventPromise = waitForDockerEvent(dockerEventStream, "pull"); - const startedEnv2 = await env.withPullPolicy(PullPolicy.alwaysPull()).up(["service_2"]); - await dockerPullEventPromise; + await checkEnvironmentContainerIsHealthy(startedEnvironment, await composeContainerName("container")); - dockerEventStream.destroy(); - await startedEnv1.stop(); - await startedEnv2.stop(); + await startedEnvironment.down(); }); it("should start environment with multiple compose files", async () => { diff --git a/packages/testcontainers/src/generic-container/generic-container-builder.ts b/packages/testcontainers/src/generic-container/generic-container-builder.ts index 18bfb8879..382977255 100644 --- a/packages/testcontainers/src/generic-container/generic-container-builder.ts +++ b/packages/testcontainers/src/generic-container/generic-container-builder.ts @@ -79,6 +79,7 @@ export class GenericContainerBuilder { labels, target: this.target, platform: this.platform, + version: "2", }; if (this.pullPolicy.shouldPull()) { diff --git a/packages/testcontainers/src/generic-container/generic-container-commit.test.ts b/packages/testcontainers/src/generic-container/generic-container-commit.test.ts index 21ab42a2c..3d281f010 100644 --- a/packages/testcontainers/src/generic-container/generic-container-commit.test.ts +++ b/packages/testcontainers/src/generic-container/generic-container-commit.test.ts @@ -1,4 +1,3 @@ -import { expect } from "vitest"; import { RandomUuid } from "../common"; import { getContainerRuntimeClient } from "../container-runtime"; import { getReaper } from "../reaper/reaper"; diff --git a/packages/testcontainers/src/generic-container/generic-container-dockerfile.test.ts b/packages/testcontainers/src/generic-container/generic-container-dockerfile.test.ts index e25e1a819..96bb1f153 100644 --- a/packages/testcontainers/src/generic-container/generic-container-dockerfile.test.ts +++ b/packages/testcontainers/src/generic-container/generic-container-dockerfile.test.ts @@ -1,16 +1,9 @@ import path from "path"; import { RandomUuid } from "../common"; -import { getContainerRuntimeClient, ImageName } from "../container-runtime"; +import { getContainerRuntimeClient } from "../container-runtime"; import { getReaper } from "../reaper/reaper"; import { LABEL_TESTCONTAINERS_SESSION_ID } from "../utils/labels"; -import { PullPolicy } from "../utils/pull-policy"; -import { - checkContainerIsHealthy, - deleteImageByName, - getDockerEventStream, - getImageLabelsByName, - waitForDockerEvent, -} from "../utils/test-helper"; +import { checkContainerIsHealthy, deleteImageByName, getImageLabelsByName } from "../utils/test-helper"; import { Wait } from "../wait-strategies/wait"; import { GenericContainer } from "./generic-container"; @@ -28,6 +21,15 @@ describe("GenericContainer Dockerfile", { timeout: 180_000 }, () => { await startedContainer.stop(); }); + it("should build and start with buildkit", async () => { + const context = path.resolve(fixtures, "docker-with-buildkit"); + const container = await GenericContainer.fromDockerfile(context).build(); + const startedContainer = await container.withExposedPorts(8080).start(); + + await checkContainerIsHealthy(startedContainer); + + await startedContainer.stop(); + }); it("should have a session ID label to be cleaned up by the Reaper", async () => { const context = path.resolve(fixtures, "docker"); const imageName = `${uuidGen.nextUuid()}:${uuidGen.nextUuid()}`; @@ -54,41 +56,6 @@ describe("GenericContainer Dockerfile", { timeout: 180_000 }, () => { await deleteImageByName(imageName); }); - // https://github.com/containers/podman/issues/17779 - if (!process.env.CI_PODMAN) { - it("should use pull policy", async () => { - const dockerfile = path.resolve(fixtures, "docker"); - const containerSpec = GenericContainer.fromDockerfile(dockerfile).withPullPolicy(PullPolicy.alwaysPull()); - - await containerSpec.build(); - const dockerEventStream = await getDockerEventStream(); - const dockerPullEventPromise = waitForDockerEvent(dockerEventStream, "pull"); - await containerSpec.build(); - await dockerPullEventPromise; - - dockerEventStream.destroy(); - }); - - it("should not pull existing image without pull policy", async () => { - const client = await getContainerRuntimeClient(); - await client.image.pull(new ImageName("docker.io", "node", "10-alpine")); - - const dockerfile = path.resolve(fixtures, "docker"); - const containerSpec = GenericContainer.fromDockerfile(dockerfile); - - await containerSpec.build(); - const dockerEventStream = await getDockerEventStream(); - const dockerPullEventPromise = waitForDockerEvent(dockerEventStream, "pull"); - let hasResolved = false; - dockerPullEventPromise.then(() => (hasResolved = true)); - await containerSpec.build(); - - expect(hasResolved).toBeFalsy(); - - dockerEventStream.destroy(); - }); - } - it("should build and start with custom file name", async () => { const context = path.resolve(fixtures, "docker-with-custom-filename"); const container = await GenericContainer.fromDockerfile(context, "Dockerfile-A").build(); diff --git a/packages/testcontainers/src/generic-container/generic-container.test.ts b/packages/testcontainers/src/generic-container/generic-container.test.ts index b96cc5d90..2cfefeb1e 100644 --- a/packages/testcontainers/src/generic-container/generic-container.test.ts +++ b/packages/testcontainers/src/generic-container/generic-container.test.ts @@ -3,12 +3,7 @@ import path from "path"; import { RandomUuid } from "../common"; import { getContainerRuntimeClient } from "../container-runtime"; import { PullPolicy } from "../utils/pull-policy"; -import { - checkContainerIsHealthy, - getDockerEventStream, - getRunningContainerNames, - waitForDockerEvent, -} from "../utils/test-helper"; +import { checkContainerIsHealthy, getRunningContainerNames } from "../utils/test-helper"; import { GenericContainer } from "./generic-container"; describe("GenericContainer", { timeout: 180_000 }, () => { @@ -310,20 +305,6 @@ describe("GenericContainer", { timeout: 180_000 }, () => { await container.stop(); }); - it("should use pull policy", async () => { - const container = new GenericContainer("cristianrgreco/testcontainer:1.1.14").withExposedPorts(8080); - - const startedContainer1 = await container.start(); - const dockerEventStream = await getDockerEventStream(); - const dockerPullEventPromise = waitForDockerEvent(dockerEventStream, "pull"); - const startedContainer2 = await container.withPullPolicy(PullPolicy.alwaysPull()).start(); - await dockerPullEventPromise; - - dockerEventStream.destroy(); - await startedContainer1.stop(); - await startedContainer2.stop(); - }); - it("should set the IPC mode", async () => { const container = await new GenericContainer("cristianrgreco/testcontainer:1.1.14") .withIpcMode("host") @@ -488,7 +469,6 @@ describe("GenericContainer", { timeout: 180_000 }, () => { expect(output).not.toContain("example5.txt"); expect(output).not.toContain("example6.txt"); expect(output).not.toContain("example7.txt"); - expect(output).not.toContain("Dockerfile"); await startedContainer.stop(); }); diff --git a/packages/testcontainers/src/utils/test-helper.ts b/packages/testcontainers/src/utils/test-helper.ts index 82592c981..e4384777d 100644 --- a/packages/testcontainers/src/utils/test-helper.ts +++ b/packages/testcontainers/src/utils/test-helper.ts @@ -94,23 +94,6 @@ export const composeContainerName = async (serviceName: string, index = 1): Prom return `${serviceName}-${index}`; }; -export const waitForDockerEvent = async (eventStream: Readable, eventName: string, times = 1) => { - let currentTimes = 0; - return new Promise((resolve) => { - eventStream.on("data", (data) => { - try { - if (JSON.parse(data).status === eventName) { - if (++currentTimes === times) { - resolve(); - } - } - } catch (err) { - // ignored - } - }); - }); -}; - export async function getImageLabelsByName(imageName: string): Promise<{ [label: string]: string }> { const dockerode = (await getContainerRuntimeClient()).container.dockerode; const imageInfo = await dockerode.getImage(imageName).inspect(); diff --git a/vitest.config.ts b/vitest.config.ts index fb3d8d26d..3e63e1c1c 100644 --- a/vitest.config.ts +++ b/vitest.config.ts @@ -4,7 +4,7 @@ import { defineConfig } from "vitest/config"; export default defineConfig({ test: { globals: true, - silent: "passed-only", + silent: false, mockReset: true, restoreMocks: true, unstubEnvs: true,