Skip to content
Draft
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
23 changes: 12 additions & 11 deletions .github/workflows/automatic-updates.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,26 +15,27 @@ jobs:
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1

- name: Run automation script
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
id: updt
- name: Set up Node.js
uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v7.1.0
with:
result-encoding: string
script: |
const { default: script } = await import(`${process.env.GITHUB_WORKSPACE}/build-automation.mjs`);
return script(github);
node-version-file: .node-version
cache: npm
- name: Install dependencies
run: npm ci

- name: Run automation script
run: npm run build

- name: Create update PR
id: cpr
if: steps.updt.outputs.result != ''
if: env.NODE_PR_TITLE != ''
uses: peter-evans/create-pull-request@5f6978faf089d4d20b00c7766989d076bb2fc7f1 # v8.1.1
with:
token: ${{ secrets.GH_API_TOKEN }}
author: 'Node.js GitHub Bot <nodejs-github-bot@users.noreply.github.com>'
branch: update-branch
base: main
commit-message: 'feat: Node.js ${{ steps.updt.outputs.result }}'
title: 'feat: Node.js ${{ steps.updt.outputs.result }}'
commit-message: '${{ env.NODE_PR_TITLE }}'
title: '${{ env.NODE_PR_TITLE }}'
delete-branch: true
team-reviewers: |
nodejs/docker
4 changes: 2 additions & 2 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -149,7 +149,7 @@ After the end of Debian LTS support, the corresponding Debian release is dropped
### Image Creation Automation

- Every 15 minutes, the [workflow](https://github.com/nodejs/docker-node/blob/main/.github/workflows/automatic-updates.yml) within the [nodejs/docker-node](https://github.com/nodejs/docker-node) repo [checks](https://github.com/nodejs/docker-node/blob/main/build-automation.mjs) for new versions of Node.js [published to the website's `index.json` file](https://nodejs.org/download/release/index.json).
- If found, the [update script](https://github.com/nodejs/docker-node/blob/main/update.sh) runs
- If found, the [update script](https://github.com/nodejs/docker-node/blob/main/build-automation.mjs) runs
- The workflow opens a pull request either automatically via [nodejs-github-bot](https://github.com/nodejs-github-bot) or in some cases manually, such as when there is a new major release.
- Another [workflow](https://github.com/nodejs/docker-node/blob/main/.github/workflows/official-pr.yml) detects the merger of these pull requests and opens a pull request to [docker-library/official-images](https://github.com/docker-library/official-images).
- The official images are built and published according to [docker's process](https://github.com/docker-library/faq#an-images-source-changed-in-git-now-what), resulting in the new images being available on [Docker Hub](https://hub.docker.com/_/node).
Expand All @@ -167,7 +167,7 @@ and your suggestion to resolve it.
To set up a version update pull request, see the [Pull Requests](#pull-requests) section above and follow these instructions:

1. Create a `version-update` branch
1. Run `./update.sh`. You can see additional options by using the built-in help documentation with `./update.sh -h`. This script will automatically update the appropriate files with the latest versions and checksums.
1. Run `npm run build`. This script will automatically update the appropriate files with the latest versions.
1. Commit the modified files to the `version-update` branch and push the branch to your fork.
1. Create a pull request.

Expand Down
2 changes: 1 addition & 1 deletion Dockerfile-alpine.template
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ RUN addgroup -g 1000 node \
curl \
&& ARCH= OPENSSL_ARCH='linux*' && alpineArch="$(apk --print-arch)" \
&& case "${alpineArch##*-}" in \
"${ALPINE_ARCH[@]}"
"${SUPPORTED_ARCH[@]}"
esac \
# use pre-existing gpg directory, see https://github.com/nodejs/docker-node/pull/1895#issuecomment-1550389150
&& export GNUPGHOME="$(mktemp -d)" \
Expand Down
2 changes: 1 addition & 1 deletion Dockerfile-debian.template
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ ENV NODE_VERSION=0.0.0

RUN ARCH= && dpkgArch="$(dpkg --print-architecture)" \
&& case "${dpkgArch##*-}" in \
"${DEB_ARCH[@]}"
"${SUPPORTED_ARCH[@]}"
esac \
# use pre-existing gpg directory, see https://github.com/nodejs/docker-node/pull/1895#issuecomment-1550389150
&& export GNUPGHOME="$(mktemp -d)" \
Expand Down
2 changes: 1 addition & 1 deletion Dockerfile-slim.template
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ ENV NODE_VERSION=0.0.0

RUN ARCH= OPENSSL_ARCH= && dpkgArch="$(dpkg --print-architecture)" \
&& case "${dpkgArch##*-}" in \
"${DEB_ARCH[@]}"
"${SUPPORTED_ARCH[@]}"
esac \
&& set -ex \
&& apt-get update && apt-get install -y ca-certificates curl wget gnupg dirmngr xz-utils libatomic1 --no-install-recommends \
Expand Down
7 changes: 0 additions & 7 deletions architectures

This file was deleted.

237 changes: 140 additions & 97 deletions build-automation.mjs
Original file line number Diff line number Diff line change
@@ -1,107 +1,150 @@
import { promisify } from 'util';

import child_process from 'child_process';

const exec = promisify(child_process.exec);

// a function that queries the Node.js release website for new versions,
// compare the available ones with the ones we use in this repo
// and returns whether we should update or not
const checkIfThereAreNewVersions = async (github) => {
try {
const { stdout: versionsOutput } = await exec(
'. ./functions.sh && get_versions',
{ shell: 'bash' },
);

const supportedVersions = versionsOutput.trim().split(' ');

let latestSupportedVersions = {};

for (let supportedVersion of supportedVersions) {
const { stdout } = await exec(`ls ${supportedVersion}`);

const { stdout: fullVersionOutput } = await exec(
`. ./functions.sh && get_full_version ./${supportedVersion}/${stdout.trim().split('\n')[0]}`,
{ shell: 'bash' },
);

console.log(fullVersionOutput);

latestSupportedVersions[supportedVersion] = {
fullVersion: fullVersionOutput.trim(),
};
}

const { data: availableVersionsJson } = await github.request(
'https://nodejs.org/download/release/index.json',
import { readFileSync, writeFileSync } from 'node:fs';
import { join, resolve } from 'node:path';
import { exportVariable } from '@actions/core';

const supportedVersions = JSON.parse(readFileSync('versions.json'));

const nodeKeys = readFileSync(resolve(import.meta.dirname, 'keys', 'node.keys'))
.toString()
.trim()
.split('\n');

const updatedVersions = [];

try {
console.log(
`Found major versions in versions.json: ${Object.keys(supportedVersions).join(', ')}`,
);

console.log('Grabbing Index.json files');
const availableVersions = await fetch(
'https://nodejs.org/download/release/index.json',
);
const officialIndexJson = await availableVersions.json();

for (let supportedVersion in supportedVersions) {
console.log(`Checking for updates for ${supportedVersion}`);
let latestVersion = officialIndexJson.find((indexVersion) =>
indexVersion.version.startsWith(`v${supportedVersion}`),
);

// filter only more recent versions of availableVersionsJson for each major version in latestSupportedVersions' keys
// e.g. if latestSupportedVersions = { "12": "12.22.10", "14": "14.19.0", "16": "16.14.0", "17": "17.5.0" }
// and availableVersions = ["Node.js 12.22.10", "Node.js 12.24.0", "Node.js 14.19.0", "Node.js 14.22.0", "Node.js 16.14.0", "Node.js 16.16.0", "Node.js 17.5.0", "Node.js 17.8.0"]
// return { "12": "12.24.0", "14": "14.22.0", "16": "16.16.0", "17": "17.8.0" }

let filteredNewerVersions = {};
console.log(`\tLatest version: ${latestVersion.version}
\tReleased: ${latestVersion.date}
\tSecurity release: ${latestVersion.security}`);

for (let variant in supportedVersions[supportedVersion].variants) {
const dockerfilePath = join('.', supportedVersion, variant, 'Dockerfile');
const currentDockerfile = readFileSync(dockerfilePath, 'utf-8');

const localVersion =
'v' +
currentDockerfile.match(/NODE_VERSION=(?<version>\d*\.\d*\.\d)/).groups[
'version'
];

const arches = supportedVersions[supportedVersion].variants[variant];

let baseImage;
let archString = '';
let dockerfileTemplate;
if (variant.startsWith('alpine')) {
dockerfileTemplate = readFileSync(
resolve(import.meta.dirname, 'Dockerfile-alpine.template'),
).toString();

baseImage = `alpine:${variant.replace(/alpine/, '')}`;
if (arches.includes('amd64'))
archString += `x86_64) ARCH='x64' OPENSSL_ARCH=linux-x86_64;; \\\n `;
if (arches.includes('arm64v8'))
archString += `aarch64) OPENSSL_ARCH=linux-aarch64;; \\\n `;
if (arches.includes('arm32v6') || arches.includes('arm32v7'))
archString += `arm*) OPENSSL_ARCH=linux-armv4;; \\\n `;
if (arches.includes('ppc64le'))
archString += `ppc64le) OPENSSL_ARCH=linux-ppc64le;; \\\n `;
if (arches.includes('s390x'))
archString += `s390x) OPENSSL_ARCH=linux-s390x;; \\\n `;
archString += '*) echo "unsupported architecture"; exit 1 ;; \\';
} else if (variant.endsWith('-slim')) {
dockerfileTemplate = readFileSync(
resolve(import.meta.dirname, 'Dockerfile-slim.template'),
).toString();
baseImage = `debian:${variant}`;
if (arches.includes('amd64'))
archString += `amd64) ARCH='x64' OPENSSL_ARCH='linux-x86_64';; \\\n `;
if (arches.includes('ppc64le'))
archString += `ppc64el) ARCH='ppc64le' OPENSSL_ARCH='linux-ppc64le';; \\\n `;
if (arches.includes('s390x'))
archString += `s390x) ARCH='s390x' OPENSSL_ARCH='linux*-s390x';; \\\n `;
if (arches.includes('arm64v8'))
archString += `arm64) ARCH='arm64' OPENSSL_ARCH='linux-aarch64';; \\\n `;
if (arches.includes('arm32v7'))
archString += `armhf) ARCH='armv7l' OPENSSL_ARCH='linux-armv4';; \\\n `;
archString += '*) echo "unsupported architecture"; exit 1 ;; \\';
} else {
dockerfileTemplate = readFileSync(
resolve(import.meta.dirname, 'Dockerfile-debian.template'),
).toString();
baseImage = `buildpack-deps:${variant}`;
if (arches.includes('amd64'))
archString += `amd64) ARCH='x64';; \\\n `;
if (arches.includes('ppc64le'))
archString += `ppc64el) ARCH='ppc64le';; \\\n `;
if (arches.includes('s390x'))
archString += `s390x) ARCH='s390x';; \\\n `;
if (arches.includes('arm64v8'))
archString += `arm64) ARCH='arm64';; \\\n `;
if (arches.includes('arm32v7'))
archString += `armhf) ARCH='armv7l';; \\\n `;
archString += '*) echo "unsupported architecture"; exit 1 ;; \\';
}

for (let availableVersion of availableVersionsJson) {
const [availableMajor, availableMinor, availablePatch] =
availableVersion.version.split('v')[1].split('.');
if (latestSupportedVersions[availableMajor] == null) {
continue;
let dockerfileOutput = dockerfileTemplate
.replace(/^FROM.+$/m, `FROM ${baseImage}`)
.replace(
/^ENV NODE_VERSION=.+$/m,
`ENV NODE_VERSION=${latestVersion.version.substring(1)}`,
)
.replace(
/^(\s*)"\${NODE_KEYS\[@]}".*$/m,
((keys) => keys.map((key) => `$1${key} \\`).join('\n'))(nodeKeys),
)
.replace(/"\$\{SUPPORTED_ARCH\[@\]\}"/s, archString);

if (parseInt(supportedVersion, 10) >= 26) {
dockerfileOutput = dockerfileOutput.replace(
/ENV YARN_VERSION.*\*\n/s,
'',
);
} else {
// Strip out rust and cargo packages for Node.js < 26
dockerfileOutput = dockerfileOutput.replace(
/\s{4}rust \\.*cargo \\\s*/s,
'',
);
}
// eslint-disable-next-line no-unused-vars
const [_latestMajor, latestMinor, latestPatch] =
latestSupportedVersions[availableMajor].fullVersion.split('.');
if (
latestSupportedVersions[availableMajor] &&
(Number(availableMinor) > Number(latestMinor) ||
(availableMinor === latestMinor &&
Number(availablePatch) > Number(latestPatch)))
) {
filteredNewerVersions[availableMajor] = {
fullVersion: `${availableMajor}.${availableMinor}.${availablePatch}`,
};
if (dockerfileOutput === currentDockerfile) {
console.log(`\t${variant}: everything up to date!`);
} else if (latestVersion.version !== localVersion) {
console.log(`\t${variant}: updating to ${latestVersion.version}`);
updatedVersions.push(latestVersion.version);
writeFileSync(dockerfilePath, dockerfileOutput);
} else {
console.warn(
`\t${variant}: non-version changes detected, but file not updated .`,
);
}
}

return {
shouldUpdate:
Object.keys(filteredNewerVersions).length > 0 &&
JSON.stringify(filteredNewerVersions) !==
JSON.stringify(latestSupportedVersions),
versions: filteredNewerVersions,
};
} catch (error) {
console.error(error);
process.exit(1);
}
};

export default async function (github) {
// if there are no new versions, exit gracefully
// if there are new versions, run update.sh
const { shouldUpdate, versions } = await checkIfThereAreNewVersions(github);

if (!shouldUpdate) {
console.log('No new versions found. No update required.');
process.exit(0);
if (updatedVersions.length !== 0) {
// Let the GitHub Action library set the GITHUB_ENV file rather than manually handling it
exportVariable(
'NODE_PR_TITLE',
`feat: Node.js ${[...new Set(updatedVersions)].join(', ')}`,
);
} else {
let updatedVersions = [];
for (const [version, newVersion] of Object.entries(versions)) {
const { stdout } = await exec(`./update.sh ${version}`);
console.log(stdout);
updatedVersions.push(newVersion.fullVersion);
}

if (updatedVersions.length === 0) {
console.log('No versions with musl builds were updated.');
process.exit(0);
}

const { stdout } = await exec(`git diff`);
console.log(stdout);

return updatedVersions.join(', ');
console.log('No Pull Request will be created.');
}
} catch (error) {
console.error(error);
process.exit(1);
}
4 changes: 0 additions & 4 deletions config

This file was deleted.

Loading
Loading