From 5c8c7704b259fdf9e6afc3960be84f4f90ba1b31 Mon Sep 17 00:00:00 2001 From: Cristian Greco Date: Sun, 23 Mar 2025 21:09:17 +0000 Subject: [PATCH 1/2] Add buildkit support --- docs/features/images.md | 11 ++ package-lock.json | 119 +++++++++--------- .../docker-compose-with-buildkit/Dockerfile | 25 ++++ .../docker-compose.yml | 7 ++ .../docker-compose-with-buildkit/index.js | 62 +++++++++ .../docker/docker-with-buildkit/Dockerfile | 25 ++++ .../docker/docker-with-buildkit/index.js | 62 +++++++++ packages/testcontainers/package.json | 4 +- .../docker-compose-environment.test.ts | 7 ++ .../generic-container-builder.ts | 7 ++ .../generic-container-dockerfile.test.ts | 10 ++ 11 files changed, 280 insertions(+), 59 deletions(-) create mode 100644 packages/testcontainers/fixtures/docker-compose/docker-compose-with-buildkit/Dockerfile create mode 100644 packages/testcontainers/fixtures/docker-compose/docker-compose-with-buildkit/docker-compose.yml create mode 100644 packages/testcontainers/fixtures/docker-compose/docker-compose-with-buildkit/index.js create mode 100644 packages/testcontainers/fixtures/docker/docker-with-buildkit/Dockerfile create mode 100644 packages/testcontainers/fixtures/docker/docker-with-buildkit/index.js diff --git a/docs/features/images.md b/docs/features/images.md index fc4083b82..e4e942846 100644 --- a/docs/features/images.md +++ b/docs/features/images.md @@ -24,6 +24,17 @@ const container = await GenericContainer .build("my-custom-image", { deleteOnExit: false }); ``` +### With buildkit + +```javascript +const { GenericContainer } = require("testcontainers"); + +const container = await GenericContainer + .fromDockerfile("/path/to/build-context") + .withBuildkit() + .build(); +``` + ### With a pull policy Testcontainers will automatically pull an image if it doesn't exist. This is configurable: diff --git a/package-lock.json b/package-lock.json index b93812c97..5b5a2933e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2603,7 +2603,6 @@ "version": "0.7.13", "resolved": "https://registry.npmjs.org/@grpc/proto-loader/-/proto-loader-0.7.13.tgz", "integrity": "sha512-AiXO/bfe9bmxBjxxtYxFAXGZvMaN5s8kO+jBHAJCON8rJoB5YS/D6X7ZNc6XQkuHNmyl4CYaMI1fJ/Gn27RGGw==", - "dev": true, "dependencies": { "lodash.camelcase": "^4.3.0", "long": "^5.0.0", @@ -2961,7 +2960,6 @@ "version": "4.4.2", "resolved": "https://registry.npmjs.org/@js-sdsl/ordered-map/-/ordered-map-4.4.2.tgz", "integrity": "sha512-iUKgm52T8HOE/makSxjqoWhe95ZJA1/G1sYsGev2JDKUSS14KAgg1LHb+Ba+IPow0xflbnSkOsZcO08C7w1gYw==", - "dev": true, "funding": { "type": "opencollective", "url": "https://opencollective.com/js-sdsl" @@ -3777,32 +3775,27 @@ "node_modules/@protobufjs/aspromise": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz", - "integrity": "sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ==", - "dev": true + "integrity": "sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ==" }, "node_modules/@protobufjs/base64": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/@protobufjs/base64/-/base64-1.1.2.tgz", - "integrity": "sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==", - "dev": true + "integrity": "sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==" }, "node_modules/@protobufjs/codegen": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/@protobufjs/codegen/-/codegen-2.0.4.tgz", - "integrity": "sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg==", - "dev": true + "integrity": "sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg==" }, "node_modules/@protobufjs/eventemitter": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@protobufjs/eventemitter/-/eventemitter-1.1.0.tgz", - "integrity": "sha512-j9ednRT81vYJ9OfVuXG6ERSTdEL1xVsNgqpkxMsbIabzSo3goCjDIveeGv5d03om39ML71RdmrGNjG5SReBP/Q==", - "dev": true + "integrity": "sha512-j9ednRT81vYJ9OfVuXG6ERSTdEL1xVsNgqpkxMsbIabzSo3goCjDIveeGv5d03om39ML71RdmrGNjG5SReBP/Q==" }, "node_modules/@protobufjs/fetch": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@protobufjs/fetch/-/fetch-1.1.0.tgz", "integrity": "sha512-lljVXpqXebpsijW71PZaCYeIcE5on1w5DlQy5WH6GLbFryLUrBD4932W/E2BSpfRJWseIL4v/KPgBFxDOIdKpQ==", - "dev": true, "dependencies": { "@protobufjs/aspromise": "^1.1.1", "@protobufjs/inquire": "^1.1.0" @@ -3811,32 +3804,27 @@ "node_modules/@protobufjs/float": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/@protobufjs/float/-/float-1.0.2.tgz", - "integrity": "sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ==", - "dev": true + "integrity": "sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ==" }, "node_modules/@protobufjs/inquire": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@protobufjs/inquire/-/inquire-1.1.0.tgz", - "integrity": "sha512-kdSefcPdruJiFMVSbn801t4vFK7KB/5gd2fYvrxhuJYg8ILrmn9SKSX2tZdV6V+ksulWqS7aXjBcRXl3wHoD9Q==", - "dev": true + "integrity": "sha512-kdSefcPdruJiFMVSbn801t4vFK7KB/5gd2fYvrxhuJYg8ILrmn9SKSX2tZdV6V+ksulWqS7aXjBcRXl3wHoD9Q==" }, "node_modules/@protobufjs/path": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/@protobufjs/path/-/path-1.1.2.tgz", - "integrity": "sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA==", - "dev": true + "integrity": "sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA==" }, "node_modules/@protobufjs/pool": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@protobufjs/pool/-/pool-1.1.0.tgz", - "integrity": "sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw==", - "dev": true + "integrity": "sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw==" }, "node_modules/@protobufjs/utf8": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.0.tgz", - "integrity": "sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw==", - "dev": true + "integrity": "sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw==" }, "node_modules/@qdrant/js-client-rest": { "version": "1.10.0", @@ -5373,9 +5361,10 @@ } }, "node_modules/@types/dockerode": { - "version": "3.3.29", - "resolved": "https://registry.npmjs.org/@types/dockerode/-/dockerode-3.3.29.tgz", - "integrity": "sha512-5PRRq/yt5OT/Jf77ltIdz4EiR9+VLnPF+HpU4xGFwUqmV24Co2HKBNW3w+slqZ1CYchbcDeqJASHDYWzZCcMiQ==", + "version": "3.3.35", + "resolved": "https://registry.npmjs.org/@types/dockerode/-/dockerode-3.3.35.tgz", + "integrity": "sha512-P+DCMASlsH+QaKkDpekKrP5pLls767PPs+/LrlVbKnEnY5tMpEUa2C6U4gRsdFZengOqxdCIqy16R22Q3pLB6Q==", + "license": "MIT", "dependencies": { "@types/docker-modem": "*", "@types/node": "*", @@ -7501,7 +7490,6 @@ "version": "8.0.1", "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", - "dev": true, "dependencies": { "string-width": "^4.2.0", "strip-ansi": "^6.0.1", @@ -7514,14 +7502,12 @@ "node_modules/cliui/node_modules/emoji-regex": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" }, "node_modules/cliui/node_modules/is-fullwidth-code-point": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true, "engines": { "node": ">=8" } @@ -7530,7 +7516,6 @@ "version": "4.2.3", "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", @@ -7544,7 +7529,6 @@ "version": "7.0.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", - "dev": true, "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", @@ -8373,32 +8357,51 @@ } }, "node_modules/docker-modem": { - "version": "3.0.8", - "resolved": "https://registry.npmjs.org/docker-modem/-/docker-modem-3.0.8.tgz", - "integrity": "sha512-f0ReSURdM3pcKPNS30mxOHSbaFLcknGmQjwSfmbcdOw1XWKXVhukM3NJHhr7NpY9BIyyWQb0EBo3KQvvuU5egQ==", + "version": "5.0.6", + "resolved": "https://registry.npmjs.org/docker-modem/-/docker-modem-5.0.6.tgz", + "integrity": "sha512-ens7BiayssQz/uAxGzH8zGXCtiV24rRWXdjNha5V4zSOcxmAZsfGVm/PPFbwQdqEkDnhG+SyR9E3zSHUbOKXBQ==", + "license": "Apache-2.0", "dependencies": { "debug": "^4.1.1", "readable-stream": "^3.5.0", "split-ca": "^1.0.1", - "ssh2": "^1.11.0" + "ssh2": "^1.15.0" }, "engines": { "node": ">= 8.0" } }, "node_modules/dockerode": { - "version": "3.3.5", - "resolved": "https://registry.npmjs.org/dockerode/-/dockerode-3.3.5.tgz", - "integrity": "sha512-/0YNa3ZDNeLr/tSckmD69+Gq+qVNhvKfAHNeZJBnp7EOP6RGKV8ORrJHkUn20So5wU+xxT7+1n5u8PjHbfjbSA==", + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/dockerode/-/dockerode-4.0.4.tgz", + "integrity": "sha512-6GYP/EdzEY50HaOxTVTJ2p+mB5xDHTMJhS+UoGrVyS6VC+iQRh7kZ4FRpUYq6nziby7hPqWhOrFFUFTMUZJJ5w==", + "license": "Apache-2.0", "dependencies": { "@balena/dockerignore": "^1.0.2", - "docker-modem": "^3.0.0", - "tar-fs": "~2.0.1" + "@grpc/grpc-js": "^1.11.1", + "@grpc/proto-loader": "^0.7.13", + "docker-modem": "^5.0.6", + "protobufjs": "^7.3.2", + "tar-fs": "~2.0.1", + "uuid": "^10.0.0" }, "engines": { "node": ">= 8.0" } }, + "node_modules/dockerode/node_modules/@grpc/grpc-js": { + "version": "1.13.1", + "resolved": "https://registry.npmjs.org/@grpc/grpc-js/-/grpc-js-1.13.1.tgz", + "integrity": "sha512-z5nNuIs75S73ZULjPDe5QCNTiCv7FyBZXEVWOyAHtcebnuJf0g1SuueI3U1/z/KK39XyAQRUC+C9ZQJOtgHynA==", + "license": "Apache-2.0", + "dependencies": { + "@grpc/proto-loader": "^0.7.13", + "@js-sdsl/ordered-map": "^4.4.2" + }, + "engines": { + "node": ">=12.10.0" + } + }, "node_modules/dockerode/node_modules/chownr": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", @@ -8430,6 +8433,19 @@ "node": ">=6" } }, + "node_modules/dockerode/node_modules/uuid": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-10.0.0.tgz", + "integrity": "sha512-8XkAphELsDnEGrDxUOHB3RGvXz6TeuYSGEZBOjtTtPm2lwhGBjLgOzLHB63IUWfBpNucQjND6d3AOudO+H3RWQ==", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "license": "MIT", + "bin": { + "uuid": "dist/bin/uuid" + } + }, "node_modules/dom-walk": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/dom-walk/-/dom-walk-0.1.2.tgz", @@ -8804,7 +8820,6 @@ "version": "3.1.1", "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", - "dev": true, "engines": { "node": ">=6" } @@ -9743,7 +9758,6 @@ "version": "2.0.5", "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", - "dev": true, "engines": { "node": "6.* || 8.* || >= 10.*" } @@ -12730,8 +12744,7 @@ "node_modules/lodash.camelcase": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz", - "integrity": "sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA==", - "dev": true + "integrity": "sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA==" }, "node_modules/lodash.clonedeep": { "version": "4.5.0", @@ -12876,8 +12889,7 @@ "node_modules/long": { "version": "5.2.3", "resolved": "https://registry.npmjs.org/long/-/long-5.2.3.tgz", - "integrity": "sha512-lcHwpNoggQTObv5apGNCTdJrO69eHOZMi4BNC+rTLER8iHAqGrUVeLh/irVIM7zTw2bOXA8T6uNPeujwOLg/2Q==", - "dev": true + "integrity": "sha512-lcHwpNoggQTObv5apGNCTdJrO69eHOZMi4BNC+rTLER8iHAqGrUVeLh/irVIM7zTw2bOXA8T6uNPeujwOLg/2Q==" }, "node_modules/loupe": { "version": "3.1.3", @@ -15904,7 +15916,6 @@ "version": "7.3.2", "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.3.2.tgz", "integrity": "sha512-RXyHaACeqXeqAKGLDl68rQKbmObRsTIn4TYVUUug1KfS47YWCo5MacGITEryugIgZqORCvJWEk4l449POg5Txg==", - "dev": true, "hasInstallScript": true, "dependencies": { "@protobufjs/aspromise": "^1.1.2", @@ -16353,7 +16364,6 @@ "version": "2.1.1", "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", - "dev": true, "engines": { "node": ">=0.10.0" } @@ -17164,7 +17174,8 @@ "node_modules/split-ca": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/split-ca/-/split-ca-1.0.1.tgz", - "integrity": "sha512-Q5thBSxp5t8WPTTJQS59LrGqOZqOsrhDGDVm8azCqIBjSBd7nd9o2PM+mDulQQkh8h//4U6hFZnc/mul8t5pWQ==" + "integrity": "sha512-Q5thBSxp5t8WPTTJQS59LrGqOZqOsrhDGDVm8azCqIBjSBd7nd9o2PM+mDulQQkh8h//4U6hFZnc/mul8t5pWQ==", + "license": "ISC" }, "node_modules/split-on-first": { "version": "1.1.0", @@ -19545,7 +19556,6 @@ "version": "5.0.8", "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", - "dev": true, "engines": { "node": ">=10" } @@ -19576,7 +19586,6 @@ "version": "17.7.2", "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", - "dev": true, "dependencies": { "cliui": "^8.0.1", "escalade": "^3.1.1", @@ -19594,7 +19603,6 @@ "version": "21.1.1", "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", - "dev": true, "engines": { "node": ">=12" } @@ -19602,14 +19610,12 @@ "node_modules/yargs/node_modules/emoji-regex": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" }, "node_modules/yargs/node_modules/is-fullwidth-code-point": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true, "engines": { "node": ">=8" } @@ -19618,7 +19624,6 @@ "version": "4.2.3", "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", @@ -20138,13 +20143,13 @@ "license": "MIT", "dependencies": { "@balena/dockerignore": "^1.0.2", - "@types/dockerode": "^3.3.29", + "@types/dockerode": "^3.3.35", "archiver": "^7.0.1", "async-lock": "^1.4.1", "byline": "^5.0.0", "debug": "^4.3.5", "docker-compose": "^0.24.8", - "dockerode": "^3.3.5", + "dockerode": "^4.0.4", "get-port": "^7.1.0", "proper-lockfile": "^4.1.2", "properties-reader": "^2.3.0", 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..cd9441b4a --- /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"] \ No newline at end of file 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..cd9441b4a --- /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"] \ No newline at end of file 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/package.json b/packages/testcontainers/package.json index cfd5af275..1966c46ad 100644 --- a/packages/testcontainers/package.json +++ b/packages/testcontainers/package.json @@ -31,13 +31,13 @@ }, "dependencies": { "@balena/dockerignore": "^1.0.2", - "@types/dockerode": "^3.3.29", + "@types/dockerode": "^3.3.35", "archiver": "^7.0.1", "async-lock": "^1.4.1", "byline": "^5.0.0", "debug": "^4.3.5", "docker-compose": "^0.24.8", - "dockerode": "^3.3.5", + "dockerode": "^4.0.4", "get-port": "^7.1.0", "proper-lockfile": "^4.1.2", "properties-reader": "^2.3.0", 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..7b4dabf77 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 @@ -43,6 +43,13 @@ describe("DockerComposeEnvironment", { timeout: 180_000 }, () => { await startedEnvironment.down(); }); + it("should work with buildkit", async () => { + const buildkitFixtures = path.resolve(fixtures, "docker-compose-with-buildkit"); + const startedEnvironment = await new DockerComposeEnvironment(buildkitFixtures, "docker-compose.yml").up(); + await checkEnvironmentContainerIsHealthy(startedEnvironment, await composeContainerName("container")); + await startedEnvironment.down(); + }); + it("should use pull policy", async () => { const env = new DockerComposeEnvironment(fixtures, "docker-compose-with-many-services.yml"); diff --git a/packages/testcontainers/src/generic-container/generic-container-builder.ts b/packages/testcontainers/src/generic-container/generic-container-builder.ts index 18bfb8879..6d1359f8c 100644 --- a/packages/testcontainers/src/generic-container/generic-container-builder.ts +++ b/packages/testcontainers/src/generic-container/generic-container-builder.ts @@ -17,6 +17,7 @@ export class GenericContainerBuilder { private buildArgs: BuildArgs = {}; private pullPolicy: ImagePullPolicy = PullPolicy.defaultPolicy(); private cache = true; + private buildkit = false; private target?: string; private platform?: string; @@ -41,6 +42,11 @@ export class GenericContainerBuilder { return this; } + public withBuildkit(): this { + this.buildkit = true; + return this; + } + public withPlatform(platform: string): this { this.platform = platform; return this; @@ -79,6 +85,7 @@ export class GenericContainerBuilder { labels, target: this.target, platform: this.platform, + version: this.buildkit ? "2" : "1", }; if (this.pullPolicy.shouldPull()) { 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..c8f74de55 100644 --- a/packages/testcontainers/src/generic-container/generic-container-dockerfile.test.ts +++ b/packages/testcontainers/src/generic-container/generic-container-dockerfile.test.ts @@ -28,6 +28,16 @@ describe("GenericContainer Dockerfile", { timeout: 180_000 }, () => { await startedContainer.stop(); }); + it("should build with buildkit", async () => { + const context = path.resolve(fixtures, "docker-with-buildkit"); + const container = await GenericContainer.fromDockerfile(context).withBuildkit().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()}`; From cb832a88c9e723480c8e00485f190ad4987c42b8 Mon Sep 17 00:00:00 2001 From: Cristian Greco Date: Sun, 23 Mar 2025 22:00:49 +0000 Subject: [PATCH 2/2] Do not run build kit tests with podman --- .../docker-compose-environment.test.ts | 14 ++++++++------ .../generic-container-dockerfile.test.ts | 16 +++++++++------- 2 files changed, 17 insertions(+), 13 deletions(-) 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 7b4dabf77..e59c8dfd3 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 @@ -43,12 +43,14 @@ describe("DockerComposeEnvironment", { timeout: 180_000 }, () => { await startedEnvironment.down(); }); - it("should work with buildkit", async () => { - const buildkitFixtures = path.resolve(fixtures, "docker-compose-with-buildkit"); - const startedEnvironment = await new DockerComposeEnvironment(buildkitFixtures, "docker-compose.yml").up(); - await checkEnvironmentContainerIsHealthy(startedEnvironment, await composeContainerName("container")); - await startedEnvironment.down(); - }); + if (!process.env.CI_PODMAN) { + it("should work with buildkit", async () => { + const buildkitFixtures = path.resolve(fixtures, "docker-compose-with-buildkit"); + const startedEnvironment = await new DockerComposeEnvironment(buildkitFixtures, "docker-compose.yml").up(); + await checkEnvironmentContainerIsHealthy(startedEnvironment, await composeContainerName("container")); + await startedEnvironment.down(); + }); + } it("should use pull policy", async () => { const env = new DockerComposeEnvironment(fixtures, "docker-compose-with-many-services.yml"); 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 c8f74de55..ec14aff4c 100644 --- a/packages/testcontainers/src/generic-container/generic-container-dockerfile.test.ts +++ b/packages/testcontainers/src/generic-container/generic-container-dockerfile.test.ts @@ -28,15 +28,17 @@ describe("GenericContainer Dockerfile", { timeout: 180_000 }, () => { await startedContainer.stop(); }); - it("should build with buildkit", async () => { - const context = path.resolve(fixtures, "docker-with-buildkit"); - const container = await GenericContainer.fromDockerfile(context).withBuildkit().build(); - const startedContainer = await container.withExposedPorts(8080).start(); + if (!process.env.CI_PODMAN) { + it("should build with buildkit", async () => { + const context = path.resolve(fixtures, "docker-with-buildkit"); + const container = await GenericContainer.fromDockerfile(context).withBuildkit().build(); + const startedContainer = await container.withExposedPorts(8080).start(); - await checkContainerIsHealthy(startedContainer); + await checkContainerIsHealthy(startedContainer); - await startedContainer.stop(); - }); + await startedContainer.stop(); + }); + } it("should have a session ID label to be cleaned up by the Reaper", async () => { const context = path.resolve(fixtures, "docker");