diff --git a/.changeset/five-kiwis-laugh.md b/.changeset/five-kiwis-laugh.md new file mode 100644 index 000000000..84413cef7 --- /dev/null +++ b/.changeset/five-kiwis-laugh.md @@ -0,0 +1,5 @@ +--- +'skuba': minor +--- + +format: Switch Distroless image from `nodejs-debian11` to `nodejs-debian12` diff --git a/docs/deep-dives/arm64.md b/docs/deep-dives/arm64.md index 75813ef2d..cf32d20f8 100644 --- a/docs/deep-dives/arm64.md +++ b/docs/deep-dives/arm64.md @@ -51,11 +51,11 @@ Replace the relevant `--platform` values in your Dockerfile(s), then ensure that you run your builds on AMD64 hardware: ```diff -- FROM --platform=arm64 gcr.io/distroless/nodejs20-debian11 AS runtime -+ FROM --platform=${BUILDPLATFORM:-amd64} gcr.io/distroless/nodejs20-debian11 AS runtime +- FROM --platform=arm64 gcr.io/distroless/nodejs20-debian12 AS runtime ++ FROM --platform=${BUILDPLATFORM:-amd64} gcr.io/distroless/nodejs20-debian12 AS runtime -- FROM --platform=${BUILDPLATFORM:-arm64} gcr.io/distroless/nodejs20-debian11 AS runtime -+ FROM --platform=${BUILDPLATFORM:-amd64} gcr.io/distroless/nodejs20-debian11 AS runtime +- FROM --platform=${BUILDPLATFORM:-arm64} gcr.io/distroless/nodejs20-debian12 AS runtime ++ FROM --platform=${BUILDPLATFORM:-amd64} gcr.io/distroless/nodejs20-debian12 AS runtime ``` For a [Gantry] service, modify the `cpuArchitecture` property in your `gantry.build.yml` and `gantry.apply.yml` resource files: diff --git a/src/cli/__snapshots__/format.int.test.ts.snap b/src/cli/__snapshots__/format.int.test.ts.snap index 59c6a545f..54dee95cc 100644 --- a/src/cli/__snapshots__/format.int.test.ts.snap +++ b/src/cli/__snapshots__/format.int.test.ts.snap @@ -12,7 +12,7 @@ Patch skipped: Add empty exports to Jest files for compliance with TypeScript is Patch skipped: Update Renovate config to support private SEEK packages - owner does not map to a SEEK preset -Patch skipped: Upgrade Node.js Distroless Docker image to -debian11 variant - no Dockerfile found +Patch skipped: Upgrade Node.js Distroless Docker image to -debian12 variant - no Dockerfile found Patch skipped: Add keepAliveTimeout to server listener - no listener file found @@ -79,7 +79,7 @@ Patch skipped: Add empty exports to Jest files for compliance with TypeScript is Patch skipped: Update Renovate config to support private SEEK packages - owner does not map to a SEEK preset -Patch skipped: Upgrade Node.js Distroless Docker image to -debian11 variant - no Dockerfile found +Patch skipped: Upgrade Node.js Distroless Docker image to -debian12 variant - no Dockerfile found Patch skipped: Add keepAliveTimeout to server listener - no listener file found @@ -139,7 +139,7 @@ Patch skipped: Add empty exports to Jest files for compliance with TypeScript is Patch skipped: Update Renovate config to support private SEEK packages - owner does not map to a SEEK preset -Patch skipped: Upgrade Node.js Distroless Docker image to -debian11 variant - no Dockerfile found +Patch skipped: Upgrade Node.js Distroless Docker image to -debian12 variant - no Dockerfile found Patch skipped: Add keepAliveTimeout to server listener - no listener file found @@ -172,7 +172,7 @@ Patch skipped: Add empty exports to Jest files for compliance with TypeScript is Patch skipped: Update Renovate config to support private SEEK packages - owner does not map to a SEEK preset -Patch skipped: Upgrade Node.js Distroless Docker image to -debian11 variant - no Dockerfile found +Patch skipped: Upgrade Node.js Distroless Docker image to -debian12 variant - no Dockerfile found Patch skipped: Add keepAliveTimeout to server listener - no listener file found diff --git a/src/cli/configure/upgrade/patches/7.3.1/index.ts b/src/cli/configure/upgrade/patches/7.3.1/index.ts index b0f815f8a..7004a8de6 100644 --- a/src/cli/configure/upgrade/patches/7.3.1/index.ts +++ b/src/cli/configure/upgrade/patches/7.3.1/index.ts @@ -17,7 +17,7 @@ export const patches: Patches = [ }, { apply: tryPatchDockerfile, - description: 'Upgrade Node.js Distroless Docker image to -debian11 variant', + description: 'Upgrade Node.js Distroless Docker image to -debian12 variant', }, { apply: tryPatchServerListener, diff --git a/src/cli/configure/upgrade/patches/7.3.1/patchDockerfile.test.ts b/src/cli/configure/upgrade/patches/7.3.1/patchDockerfile.test.ts index 7209cc189..900d1ca71 100644 --- a/src/cli/configure/upgrade/patches/7.3.1/patchDockerfile.test.ts +++ b/src/cli/configure/upgrade/patches/7.3.1/patchDockerfile.test.ts @@ -18,9 +18,27 @@ FROM --platform=\${BUILDPLATFORM:-<%- platformName %>} gcr.io/distroless/nodejs: WORKDIR /workdir `; +const dockerfileDebian11 = ` +ARG BASE_IMAGE +ARG BASE_TAG + +FROM --platform=\${BUILDPLATFORM:-<%- platformName %>} gcr.io/distroless/nodejs20-debian11 AS runtime + +WORKDIR /workdir +`; + +const dockerfileNonDistroless = ` +ARG BASE_IMAGE +ARG BASE_TAG + +FROM --platform=\${BUILDPLATFORM:-<%- platformName %>} node:20-alpine AS runtime + +WORKDIR /workdir +`; + describe('tryPatchDockerfile', () => { describe('format mode', () => { - it('patches an existing Dockerfile', async () => { + it('patches a Dockerfile with nodejs:18', async () => { vol.fromJSON({ Dockerfile: dockerfile }); await expect(tryPatchDockerfile('format')).resolves.toEqual({ @@ -33,7 +51,28 @@ describe('tryPatchDockerfile', () => { ARG BASE_IMAGE ARG BASE_TAG - FROM --platform=\${BUILDPLATFORM:-<%- platformName %>} gcr.io/distroless/nodejs18-debian11 AS runtime + FROM --platform=\${BUILDPLATFORM:-<%- platformName %>} gcr.io/distroless/nodejs18-debian12 AS runtime + + WORKDIR /workdir + ", + } + `); + }); + + it('patches a Dockerfile with nodejs18-debian11', async () => { + vol.fromJSON({ Dockerfile: dockerfileDebian11 }); + + await expect(tryPatchDockerfile('format')).resolves.toEqual({ + result: 'apply', + }); + + expect(volToJson()).toMatchInlineSnapshot(` + { + "Dockerfile": " + ARG BASE_IMAGE + ARG BASE_TAG + + FROM --platform=\${BUILDPLATFORM:-<%- platformName %>} gcr.io/distroless/nodejs20-debian12 AS runtime WORKDIR /workdir ", @@ -49,10 +88,20 @@ describe('tryPatchDockerfile', () => { reason: 'no Dockerfile found', }); }); + + it('ignores when a Dockerfile is not distroless', async () => { + vol.fromJSON({ Dockerfile: dockerfileNonDistroless }); + + await expect(tryPatchDockerfile('format')).resolves.toEqual({ + result: 'skip', + }); + + expect(volToJson().Dockerfile).toEqual(dockerfileNonDistroless); // unchanged + }); }); describe('lint mode', () => { - it('patches an existing Dockerfile', async () => { + it('patches a Dockerfile with nodejs:18', async () => { vol.fromJSON({ Dockerfile: dockerfile }); await expect(tryPatchDockerfile('lint')).resolves.toEqual({ @@ -62,6 +111,16 @@ describe('tryPatchDockerfile', () => { expect(volToJson().Dockerfile).toEqual(dockerfile); // unchanged }); + it('patches a Dockerfile with nodejs18-debian11', async () => { + vol.fromJSON({ Dockerfile: dockerfileDebian11 }); + + await expect(tryPatchDockerfile('lint')).resolves.toEqual({ + result: 'apply', + }); + + expect(volToJson().Dockerfile).toEqual(dockerfileDebian11); // unchanged + }); + it('ignores when a Dockerfile is missing', async () => { vol.fromJSON({}); @@ -69,6 +128,18 @@ describe('tryPatchDockerfile', () => { result: 'skip', reason: 'no Dockerfile found', }); + + expect(volToJson()).toEqual({}); // unchanged + }); + + it('ignores when a Dockerfile is not distroless', async () => { + vol.fromJSON({ Dockerfile: dockerfileNonDistroless }); + + await expect(tryPatchDockerfile('format')).resolves.toEqual({ + result: 'skip', + }); + + expect(volToJson().Dockerfile).toEqual(dockerfileNonDistroless); // unchanged }); }); }); diff --git a/src/cli/configure/upgrade/patches/7.3.1/patchDockerfile.ts b/src/cli/configure/upgrade/patches/7.3.1/patchDockerfile.ts index 0b87c645f..60a85e8b5 100644 --- a/src/cli/configure/upgrade/patches/7.3.1/patchDockerfile.ts +++ b/src/cli/configure/upgrade/patches/7.3.1/patchDockerfile.ts @@ -8,8 +8,9 @@ import { createDestinationFileReader } from '../../../analysis/project'; const DOCKERFILE_FILENAME = 'Dockerfile'; -const VERSION_REGEX = /gcr.io\/distroless\/nodejs:(16|18|20)/g; -const VERSION_DEBIAN_REPLACE = 'gcr.io/distroless/nodejs$1-debian11'; +const NON_DEBIAN_REGEX = /gcr.io\/distroless\/nodejs:(18|20)/g; +const DEBIAN_REGEX = /gcr.io\/distroless\/nodejs(18|20)-debian11/g; +const VERSION_DEBIAN_REPLACE = 'gcr.io/distroless/nodejs$1-debian12'; const patchDockerfile = async ( mode: 'format' | 'lint', @@ -23,10 +24,9 @@ const patchDockerfile = async ( return { result: 'skip', reason: 'no Dockerfile found' }; } - const patched = maybeDockerfile.replaceAll( - VERSION_REGEX, - VERSION_DEBIAN_REPLACE, - ); + const patched = maybeDockerfile + .replaceAll(NON_DEBIAN_REGEX, VERSION_DEBIAN_REPLACE) + .replaceAll(DEBIAN_REGEX, VERSION_DEBIAN_REPLACE); if (patched === maybeDockerfile) { return { result: 'skip' }; diff --git a/template/express-rest-api/Dockerfile b/template/express-rest-api/Dockerfile index 98d32ee29..ad06c3dd9 100644 --- a/template/express-rest-api/Dockerfile +++ b/template/express-rest-api/Dockerfile @@ -12,7 +12,7 @@ RUN pnpm install --offline --prod ### -FROM --platform=${BUILDPLATFORM:-<%- platformName %>} gcr.io/distroless/nodejs20-debian11 AS runtime +FROM --platform=${BUILDPLATFORM:-<%- platformName %>} gcr.io/distroless/nodejs20-debian12 AS runtime WORKDIR /workdir diff --git a/template/koa-rest-api/Dockerfile b/template/koa-rest-api/Dockerfile index 37d0a916a..0e6b125be 100644 --- a/template/koa-rest-api/Dockerfile +++ b/template/koa-rest-api/Dockerfile @@ -12,7 +12,7 @@ RUN pnpm install --offline --prod ### -FROM --platform=${BUILDPLATFORM:-<%- platformName %>} gcr.io/distroless/nodejs20-debian11 AS runtime +FROM --platform=${BUILDPLATFORM:-<%- platformName %>} gcr.io/distroless/nodejs20-debian12 AS runtime WORKDIR /workdir