diff --git a/.github/workflows/automatic-updates.yml b/.github/workflows/automatic-updates.yml index cf0b4b915..2b7acf1d5 100644 --- a/.github/workflows/automatic-updates.yml +++ b/.github/workflows/automatic-updates.yml @@ -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 ' 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 diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 7ba0c7779..4a0080777 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -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). @@ -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. diff --git a/Dockerfile-alpine.template b/Dockerfile-alpine.template index 298f67387..c1ae4f377 100644 --- a/Dockerfile-alpine.template +++ b/Dockerfile-alpine.template @@ -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)" \ diff --git a/Dockerfile-debian.template b/Dockerfile-debian.template index 23a503dc8..4c5fb79b3 100644 --- a/Dockerfile-debian.template +++ b/Dockerfile-debian.template @@ -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)" \ diff --git a/Dockerfile-slim.template b/Dockerfile-slim.template index 65972bba3..ba8872319 100644 --- a/Dockerfile-slim.template +++ b/Dockerfile-slim.template @@ -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 \ diff --git a/architectures b/architectures deleted file mode 100644 index 3447a5084..000000000 --- a/architectures +++ /dev/null @@ -1,7 +0,0 @@ -bashbrew-arch variants -amd64 alpine3.23,alpine3.24,bookworm,bookworm-slim,trixie,trixie-slim -arm32v6 alpine3.23,alpine3.24 -arm32v7 alpine3.23,alpine3.24,bookworm,bookworm-slim,trixie,trixie-slim -arm64v8 alpine3.23,alpine3.24,bookworm,bookworm-slim,trixie,trixie-slim -ppc64le bookworm,bookworm-slim,trixie,trixie-slim -s390x alpine3.23,alpine3.24,bookworm,bookworm-slim,trixie,trixie-slim diff --git a/build-automation.mjs b/build-automation.mjs index ce6200dd7..85ec7e7dc 100644 --- a/build-automation.mjs +++ b/build-automation.mjs @@ -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=(?\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); } diff --git a/config b/config deleted file mode 100644 index 68ff056a9..000000000 --- a/config +++ /dev/null @@ -1,4 +0,0 @@ -baseuri https://nodejs.org/dist -default_variant trixie -alpine_version 3.23 -debian_versions bookworm bullseye trixie diff --git a/functions.sh b/functions.sh deleted file mode 100755 index f19c5003b..000000000 --- a/functions.sh +++ /dev/null @@ -1,368 +0,0 @@ -#!/usr/bin/env bash -# -# Utility functions -# Don't change this file unless needed -# The GitHub Action for automating new builds rely on this file - -info() { - printf "%s\\n" "$@" -} - -fatal() { - printf "**********\\n" - printf "Fatal Error: %s\\n" "$@" - printf "**********\\n" - exit 1 -} - -# Get system architecture -# -# This is used to get the target architecture for docker image. -# For crossing building, we need a way to specify the target -# architecture manually. -function get_arch() { - local arch - case $(uname -m) in - x86_64) - arch="amd64" - ;; - ppc64le) - arch="ppc64le" - ;; - s390x) - arch="s390x" - ;; - aarch64 | arm64) - arch="arm64" - ;; - armv7l) - arch="arm32v7" - ;; - *) - echo "$0 does not support architecture ${arch:-unknown} ... aborting" - exit 1 - ;; - esac - - echo "${arch}" -} - -# Get corresponding variants based on the architecture. -# All supported variants of each supported architecture are listed in a -# file - 'architectures'. Its format is: -# ,... -# ,... -function get_variants() { - local dir - dir=${1:-.} - shift - - local arch - local availablevariants - local variantsfilter - local variants=() - - arch=$(get_arch) - variantsfilter=("$@") - IFS=' ' read -ra availablevariants <<< "$(grep "^${arch}" "${dir}/architectures" | sed -E 's/'"${arch}"'[[:space:]]*//' | sed -E 's/,/ /g')" - - if [ ${#variantsfilter[@]} -gt 0 ]; then - for variant1 in "${availablevariants[@]}"; do - for variant2 in "${variantsfilter[@]}"; do - if [ "${variant1}" = "${variant2}" ]; then - variants+=("${variant1}") - fi - done - done - - if [ ${#variants[@]} -gt 0 ]; then - echo "${variants[@]}" - fi - else - echo "${availablevariants[@]}" - fi -} - -# Get supported architectures for a specific version and variant -# -# Get default supported architectures from 'architectures'. Then go to the version folder -# to see if there is a local architectures file. The local architectures will override the -# default architectures. This will give us some benefits: -# - a specific version may or may not support some architectures -# - if there is no specialization for a version, just don't provide local architectures -function get_supported_arches() { - local version - local variant - local arches - local lines - local line - version="$1" - shift - variant="$1" - shift - - # Get default supported arches - lines=$(grep "${variant}" "$(dirname "${version}")"/architectures 2> /dev/null | cut -d' ' -f1) - - # Get version specific supported architectures if there is specialized information - if [ -e "${version}"/architectures ]; then - lines=$(grep "${variant}" "${version}"/architectures 2> /dev/null | cut -d' ' -f1) - fi - - while IFS='' read -r line; do - arches+=("${line}") - done <<< "${lines}" - - echo "${arches[@]}" -} - -# Get configuration values from the config file -# -# The configuration entries are simple key/value pairs which are whitespace separated. -function get_config() { - local dir - dir=${1:-.} - shift - - local name - name=${1} - shift - - local value - value=$(grep "^${name}" "${dir}/config" | sed -E 's/'"${name}"'[[:space:]]*//') - echo "${value}" -} - -# Get available versions for a given path -# -# The result is a list of valid versions. -# shellcheck disable=SC2120 -function get_versions() { - shift - - local versions=() - local dirs=("$@") - - local default_variant - default_variant=$(get_config "./" "default_variant") - if [ ${#dirs[@]} -eq 0 ]; then - IFS=' ' read -ra dirs <<< "$(echo "./"*/)" - fi - - for dir in "${dirs[@]}"; do - if [ -e "${dir}/Dockerfile" ] || [ -e "${dir}/${default_variant}/Dockerfile" ]; then - versions+=("${dir#./}") - fi - done - - if [ ${#versions[@]} -gt 0 ]; then - echo "${versions[@]%/}" - fi -} - -function is_alpine() { - local variant - variant=${1} - shift - - if [ "${variant}" = "${variant#alpine}" ]; then - return 1 - fi -} - -function is_debian() { - local variant - variant=$1 - shift - - IFS=' ' read -ra debianVersions <<< "$(get_config "./" "debian_versions")" - for d in "${debianVersions[@]}"; do - if [ "${d}" = "${variant}" ]; then - return 0 - fi - done - return 1 -} - -function is_debian_slim() { - local variant - variant=$1 - shift - - IFS=' ' read -ra debianVersions <<< "$(get_config "./" "debian_versions")" - for d in "${debianVersions[@]}"; do - if [ "${d}-slim" = "${variant}" ]; then - return 0 - fi - done - return 1 -} - -function get_fork_name() { - local version - version=$1 - shift - - IFS='/' read -ra versionparts <<< "${version}" - if [ ${#versionparts[@]} -gt 1 ]; then - echo "${versionparts[0]}" - fi -} - -function get_full_tag() { - local variant - local tag - local full_tag - variant="$1" - shift - tag="$1" - shift - if [ -z "${variant}" ]; then - full_tag="${tag}" - elif [ "${variant}" = "default" ]; then - full_tag="${tag}" - else - full_tag="${tag}-${variant}" - fi - echo "${full_tag}" -} - -function get_full_version() { - local version - version=$1 - shift - - local default_dockerfile - if [ -f "${version}/${default_variant}/Dockerfile" ]; then - default_dockerfile="${version}/${default_variant}/Dockerfile" - else - default_dockerfile="${version}/Dockerfile" - fi - - grep -m1 'ENV NODE_VERSION=' "${default_dockerfile}" | cut -d= -f2 -} - -function get_major_minor_version() { - local version - version=$1 - shift - - local fullversion - fullversion=$(get_full_version "${version}") - - echo "$(echo "${fullversion}" | cut -d'.' -f1).$(echo "${fullversion}" | cut -d'.' -f2)" -} - -function get_path() { - local version - local variant - local path - version="$1" - shift - variant="$1" - shift - - if [ -z "${variant}" ]; then - path="${version}/${variant}" - elif [ "${variant}" = "default" ]; then - path="${version}" - else - path="${version}/${variant}" - fi - echo "${path}" -} - -function get_tag() { - local version - version=$1 - shift - - local versiontype - versiontype=${1:-full} - shift - - local tagversion - if [ "${versiontype}" = full ]; then - tagversion=$(get_full_version "${version}") - elif [ "${versiontype}" = majorminor ]; then - tagversion=$(get_major_minor_version "${version}") - fi - - local tagparts - IFS=' ' read -ra tagparts <<< "$(get_fork_name "${version}") ${tagversion}" - IFS='-' - echo "${tagparts[*]}" - unset IFS -} - -function sort_versions() { - local versions=("$@") - local sorted - local lines - local line - - IFS=$'\n' - lines="${versions[*]}" - unset IFS - - while IFS='' read -r line; do - sorted+=("${line}") - done <<< "$(echo "${lines}" | grep "^[0-9]" | sort -r)" - - while IFS='' read -r line; do - sorted+=("${line}") - done <<< "$(echo "${lines}" | grep -v "^[0-9]" | sort -r)" - - echo "${sorted[@]}" -} - -function commit_range() { - local commit_id_end=${1} - shift - local commit_id_start=${1} - - if [ -z "${commit_id_start}" ]; then - if [ -z "${commit_id_end}" ]; then - echo "HEAD~1..HEAD" - elif [[ "${commit_id_end}" =~ .. ]]; then - echo "${commit_id_end}" - else - echo "${commit_id_end}~1..${commit_id_end}" - fi - else - echo "${commit_id_end}..${commit_id_start}" - fi -} - -function images_updated() { - local commit_range - local versions - local images_changed - - commit_range="$(commit_range "$@")" - - IFS=' ' read -ra versions <<< "$( - IFS=',' - get_versions - )" - images_changed=$(git diff --name-only "${commit_range}" "${versions[@]}") - - if [ -z "${images_changed}" ]; then - return 1 - fi - return 0 -} - -function tests_updated() { - local commit_range - local test_changed - - commit_range="$(commit_range "$@")" - - test_changed=$(git diff --name-only "${commit_range}" test*) - - if [ -z "${test_changed}" ]; then - return 1 - fi - return 0 -} diff --git a/package-lock.json b/package-lock.json index 6d286bcfe..5f45c0672 100644 --- a/package-lock.json +++ b/package-lock.json @@ -6,6 +6,7 @@ "": { "name": "docker-node", "devDependencies": { + "@actions/core": "3.0.1", "@eslint/js": "10.0.1", "doctoc": "2.5.0", "eslint": "10.12.0", @@ -15,6 +16,55 @@ "prettier": "3.9.9" } }, + "node_modules/@actions/core": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@actions/core/-/core-3.0.1.tgz", + "integrity": "sha512-a6d/Nwahm9fliVGRhdhofo40HjHQasUPusmc7vBfyky+7Z+P2A1J68zyFVaNcEclc/Se+eO595oAr5nwEIoIUA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@actions/exec": "^3.0.0", + "@actions/http-client": "^4.0.0" + } + }, + "node_modules/@actions/exec": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@actions/exec/-/exec-3.0.0.tgz", + "integrity": "sha512-6xH/puSoNBXb72VPlZVm7vQ+svQpFyA96qdDBvhB8eNZOE8LtPf9L4oAsfzK/crCL8YZ+19fKYVnM63Sl+Xzlw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@actions/io": "^3.0.2" + } + }, + "node_modules/@actions/http-client": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@actions/http-client/-/http-client-4.0.1.tgz", + "integrity": "sha512-+Nvd1ImaOZBSoPbsUtEhv+1z99H12xzncCkz0a3RuehINE81FZSe2QTj3uvAPTcJX/SCzUQHQ0D1GrPMbrPitg==", + "dev": true, + "license": "MIT", + "dependencies": { + "tunnel": "^0.0.6", + "undici": "^6.23.0" + } + }, + "node_modules/@actions/http-client/node_modules/undici": { + "version": "6.29.0", + "resolved": "https://registry.npmjs.org/undici/-/undici-6.29.0.tgz", + "integrity": "sha512-R+RODBqp6i2pPflGdq+xIOUkl+RNfGgHwoinecKu/JCuf2uO06cOKoDbI2P7Dn6KcswdKwrczbU6IYJ6K8X+wg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18.17" + } + }, + "node_modules/@actions/io": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@actions/io/-/io-3.0.2.tgz", + "integrity": "sha512-nRBchcMM+QK1pdjO7/idu86rbJI5YHUKCvKs0KxnSYbVe3F51UfGxuZX4Qy/fWlp6l7gWFwIkrOzN+oUK03kfw==", + "dev": true, + "license": "MIT" + }, "node_modules/@cacheable/memory": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/@cacheable/memory/-/memory-2.2.0.tgz", @@ -2932,6 +2982,16 @@ "dev": true, "license": "0BSD" }, + "node_modules/tunnel": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/tunnel/-/tunnel-0.0.6.tgz", + "integrity": "sha512-1h/Lnq9yajKY2PEbBadPXj3VxsDDu844OnaAo52UVmIzIvwwtBPIuNvkjuzBlTWpfJyUbG3ez0KSBibQkj4ojg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.6.11 <=0.7.0 || >=0.7.3" + } + }, "node_modules/type-check": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", diff --git a/package.json b/package.json index f7f709cec..8478c8004 100644 --- a/package.json +++ b/package.json @@ -4,6 +4,7 @@ "private": true, "type": "commonjs", "scripts": { + "build": "node build-automation.mjs", "format:toc": "doctoc --update-only .", "format:toc:check": "doctoc --update-only --dryrun .", "format:prettier": "prettier --write .", @@ -15,6 +16,7 @@ "check:markdown-links": "find *.md docs/*.md -print0 | xargs -0 -n1 markdown-link-check -c markdown_link_check_config.json" }, "devDependencies": { + "@actions/core": "3.0.1", "@eslint/js": "10.0.1", "doctoc": "2.5.0", "eslint": "10.12.0", diff --git a/update.sh b/update.sh deleted file mode 100755 index e76eae74e..000000000 --- a/update.sh +++ /dev/null @@ -1,291 +0,0 @@ -#!/usr/bin/env bash - -set -ue - -function usage() { - cat << EOF - - Update the node docker images. - - Usage: - $0 [MAJOR_VERSION(S)] [VARIANT(S)] - - Examples: - - update.sh # Update all images - - update.sh 22,24 # Update all variants of version 22 and 24 - - update.sh 24 alpine3.23,alpine3.24 # Update only alpine3.23 & alpine3.24 variants for version 24 - - update.sh . trixie,trixie-slim # Update only trixie & trixie-slim Debian variants for all versions - - OPTIONS: - -h Show this message - -EOF -} - -while getopts "h" opt; do - case "${opt}" in - h) - usage - exit - ;; - \?) - usage - exit - ;; - esac -done - -if ! command -v jq > /dev/null; then - echo "**jq not installed**" >&2 - echo "Follow the instructions in https://jqlang.org/download/ to install" >&2 - exit 1 -fi - -. functions.sh - -cd "$(cd "${0%/*}" && pwd -P)" - -IFS=',' read -ra versions_arg <<< "${1:-}" -IFS=',' read -ra variant_arg <<< "${2:-}" - -IFS=' ' read -ra versions <<< "$(get_versions .)" -IFS=' ' read -ra update_versions <<< "$(get_versions . "${versions_arg[@]:-}")" -IFS=' ' read -ra update_variants <<< "$(get_variants . "${variant_arg[@]:-}")" -if [ ${#versions[@]} -eq 0 ]; then - fatal "No valid versions found!" -fi - -# Global variables -# Get architecture and use this as target architecture for docker image -# See details in function.sh -# TODO: Should be able to specify target architecture manually -arch=$(get_arch) - -function in_versions_to_update() { - local version=$1 - - if [ "${#update_versions[@]}" -eq 0 ]; then - echo 0 - return - fi - - for version_to_update in "${update_versions[@]}"; do - if [ "${version_to_update}" = "${version}" ]; then - echo 0 - return - fi - done - - echo 1 -} - -function in_variants_to_update() { - local variant=$1 - - if [ "${#update_variants[@]}" -eq 0 ]; then - echo 0 - return - fi - - for variant_to_update in "${update_variants[@]}"; do - if [ "${variant_to_update}" = "${variant}" ]; then - echo 0 - return - fi - done - - echo 1 -} - -function update_node_version() { - - local baseuri=${1} - shift - local version=${1} - shift - local template=${1} - shift - local dockerfile=${1} - shift - local variant="" - if [ $# -eq 1 ]; then - variant=${1} - shift - fi - - nodeVersion="$(curl -sSL --compressed "${baseuri}/index.json" | jq -r '[.[] | select(.version | startswith("v'"${version}"'."))] | first | .version | ltrimstr("v")')" - ( - cp "${template}" "${dockerfile}-tmp" - local fromprefix="" - if [ "${arch}" != "amd64" ] && [ "${arch}" != "arm64" ]; then - fromprefix="${arch}\\/" - fi - - sed -Ei -e 's/^FROM (.*)/FROM '"$fromprefix"'\1/' "${dockerfile}-tmp" - sed -Ei -e 's/^(ENV NODE_VERSION)=.*/\1='"${nodeVersion}"'/' "${dockerfile}-tmp" - - # shellcheck disable=SC1004 - new_line=' \\\ -' - - # Add GPG keys - while read -r line; do - pattern='"\$\{'$(echo "node" | tr '[:lower:]' '[:upper:]')'_KEYS\[@\]\}"' - sed -Ei -e "s/([ \\t]*)(${pattern})/\\1${line}${new_line}\\1\\2/" "${dockerfile}-tmp" - done < "keys/node.keys" - sed -Ei -e "/${pattern}/d" "${dockerfile}-tmp" - - if is_alpine "${variant}"; then - alpine_version="${variant#*alpine}" - sed -Ei -e "s/(alpine:)0.0/\\1${alpine_version}/" "${dockerfile}-tmp" - - alpine_arch='' - local -a arches - arches=$(jq -r ".\"${version}\".variants.\"alpine${alpine_version}\" | @sh" "versions.json") - if [[ "${arches[0]}" == *"amd64"* ]]; then - alpine_arch+='x86_64) ARCH='"'"'x64'"'"' OPENSSL_ARCH=linux-x86_64;; \\\n ' - fi - if [[ "${arches[0]}" == *"arm64v8"* ]]; then - alpine_arch+='aarch64) OPENSSL_ARCH=linux-aarch64;; \\\n ' - fi - if [[ "${arches[0]}" == *"arm32"* ]]; then - alpine_arch+='arm*) OPENSSL_ARCH=linux-armv4;; \\\n ' - fi - if [[ "${arches[0]}" == *"ppc64le"* ]]; then - alpine_arch+='ppc64le) OPENSSL_ARCH=linux-ppc64le;; \\\n ' - fi - if [[ "${arches[0]}" == *"s390x"* ]]; then - alpine_arch+='s390x) OPENSSL_ARCH=linux-s390x;; \\\n ' - fi - # shellcheck disable=SC1003 - alpine_arch+='*) echo "unsupported architecture"; exit 1 ;; \\' - sed -Ei -e "s/\"\\$\{ALPINE_ARCH\[@\]\}\"/${alpine_arch}/" "${dockerfile}-tmp" - elif is_debian "${variant}"; then - sed -Ei -e "s/(buildpack-deps:)name/\\1${variant}/" "${dockerfile}-tmp" - deb_arch='' - local -a arches - arches=$(jq -r ".\"${version}\".variants.\"${variant}\" | @sh" "versions.json") - if [[ "${arches[0]}" == *"amd64"* ]]; then - deb_arch+='amd64) ARCH='"'"'x64'"'"';; \\\n ' - fi - if [[ "${arches[0]}" == *"ppc64le"* ]]; then - deb_arch+='ppc64el) ARCH='"'"'ppc64le'"'"';; \\\n ' - fi - if [[ "${arches[0]}" == *"s390x"* ]]; then - deb_arch+='s390x) ARCH='"'"'s390x'"'"';; \\\n ' - fi - if [[ "${arches[0]}" == *"arm64v8"* ]]; then - deb_arch+='arm64) ARCH='"'"'arm64'"'"';; \\\n ' - fi - if [[ "${arches[0]}" == *"arm32v7"* ]]; then - deb_arch+='armhf) ARCH='"'"'armv7l'"'"';; \\\n ' - fi - # shellcheck disable=SC1003 - deb_arch+='*) echo "unsupported architecture"; exit 1 ;; \\' - sed -Ei -e "s/\"\\$\{DEB_ARCH\[@\]\}\"/${deb_arch}/" "${dockerfile}-tmp" - elif is_debian_slim "${variant}"; then - sed -Ei -e "s/(debian:)name-slim/\\1${variant}/" "${dockerfile}-tmp" - deb_arch='' - local -a arches - arches=$(jq -r ".\"${version}\".variants.\"${variant}\" | @sh" "versions.json") - if [[ "${arches[0]}" == *"amd64"* ]]; then - deb_arch+='amd64) ARCH='"'"'x64'"'"' OPENSSL_ARCH='"'"'linux-x86_64'"'"';; \\\n ' - fi - if [[ "${arches[0]}" == *"ppc64le"* ]]; then - deb_arch+='ppc64el) ARCH='"'"'ppc64le'"'"' OPENSSL_ARCH='"'"'linux-ppc64le'"'"';; \\\n ' - fi - if [[ "${arches[0]}" == *"s390x"* ]]; then - deb_arch+='s390x) ARCH='"'"'s390x'"'"' OPENSSL_ARCH='"'"'linux*-s390x'"'"';; \\\n ' - fi - if [[ "${arches[0]}" == *"arm64v8"* ]]; then - deb_arch+='arm64) ARCH='"'"'arm64'"'"' OPENSSL_ARCH='"'"'linux-aarch64'"'"';; \\\n ' - fi - if [[ "${arches[0]}" == *"arm32v7"* ]]; then - deb_arch+='armhf) ARCH='"'"'armv7l'"'"' OPENSSL_ARCH='"'"'linux-armv4'"'"';; \\\n ' - fi - # shellcheck disable=SC1003 - deb_arch+='*) echo "unsupported architecture"; exit 1 ;; \\' - sed -Ei -e "s/\"\\$\{DEB_ARCH\[@\]\}\"/${deb_arch}/" "${dockerfile}-tmp" - fi - - # Strip out Yarn v1 from Node 26+ images https://github.com/nodejs/docker-node/issues/2407 - # Can be removed from the image templates once Node 24 hits EOL on 2028-04-30 - if [ "${nodeVersion:0:2}" -ge "26" ]; then - sed -Ei -e "/ENV YARN_VERSION/,/rm -rf \/tmp\/\*/d" "${dockerfile}-tmp" - fi - - # Strip out rust/cargo from Alpine images for Node < 26 since these versions don't need rust/cargo for Temporal - # See: https://github.com/nodejs/docker-node/pull/2488 - if [ "${nodeVersion:0:2}" -lt "26" ]; then - sed -Ei -e "/rust/,/cargo/d" "${dockerfile}-tmp" - fi - - if diff -q "${dockerfile}-tmp" "${dockerfile}" > /dev/null; then - echo "${dockerfile} is already up to date!" - else - echo "${dockerfile} updated!" - fi - - # Required for POSIX sed - if [ -f "${dockerfile}-tmp-e" ]; then - rm "${dockerfile}-tmp-e" - fi - - # Guard the move because Alpine sometimes will be missing - if [ -f "${dockerfile}-tmp" ]; then - mv -f "${dockerfile}-tmp" "${dockerfile}" - fi - ) -} - -pids=() - -for version in "${versions[@]}"; do - parentpath=$(dirname "${version}") - versionnum=$(basename "${version}") - baseuri=$(get_config "${parentpath}" "baseuri") - update_version=$(in_versions_to_update "${version}") - - [ "${update_version}" -eq 0 ] && info "Updating version ${version}..." - - # Get supported variants according the target architecture - # See details in function.sh - IFS=' ' read -ra variants <<< "$(get_variants "${parentpath}")" - - if [ -f "${version}/Dockerfile" ]; then - if [ "${update_version}" -eq 0 ]; then - update_node_version "${baseuri}" "${versionnum}" "${parentpath}/Dockerfile.template" "${version}/Dockerfile" & - pids+=($!) - fi - fi - - for variant in "${variants[@]}"; do - # Skip non-docker directories - [ -f "${version}/${variant}/Dockerfile" ] || continue - - update_variant=$(in_variants_to_update "${variant}") - template_file="${parentpath}/Dockerfile-${variant}.template" - - if is_debian "${variant}"; then - template_file="${parentpath}/Dockerfile-debian.template" - elif is_debian_slim "${variant}"; then - template_file="${parentpath}/Dockerfile-slim.template" - elif is_alpine "${variant}"; then - template_file="${parentpath}/Dockerfile-alpine.template" - fi - - cp "${parentpath}/docker-entrypoint.sh" "${version}/${variant}/docker-entrypoint.sh" - if [ "${update_version}" -eq 0 ] && [ "${update_variant}" -eq 0 ]; then - update_node_version "${baseuri}" "${versionnum}" "${template_file}" "${version}/${variant}/Dockerfile" "${variant}" & - pids+=($!) - fi - done -done - -# The reason we explicitly wait on each pid is so the return status of this script is set properly -# if one of the jobs fails. If we just called "wait", the exit status would always be 0 -for pid in "${pids[@]}"; do - wait "$pid" -done - -info "Done!"