From e055af9afee3f8433aa7f48fc4e54fcfe2ba6dc4 Mon Sep 17 00:00:00 2001 From: David Date: Wed, 23 Sep 2026 22:46:50 +0200 Subject: [PATCH] fix ci --- .gitea/actions/security/action.yml | 13 +---- .gitea/actions/setup-node/action.yml | 11 ++++ .gitea/actions/setup-trivy/action.yml | 13 +---- .gitea/workflows/cd-main.yml | 33 ++++------- .gitea/workflows/cd-preprod.yml | 30 ++++------ .gitea/workflows/ci.yml | 19 ++---- .gitea/workflows/pr-checks.yml | 23 ++------ docs/CI-CD-SECURITY.md | 21 +++++-- scripts/ci/activate-node.sh | 23 ++++++++ scripts/ci/install-tool.sh | 47 +++++++++++++++ scripts/ci/test_tool_setup.py | 83 +++++++++++++++++++++++++++ scripts/ci/validate-workflows.sh | 2 +- 12 files changed, 219 insertions(+), 99 deletions(-) create mode 100644 .gitea/actions/setup-node/action.yml create mode 100644 scripts/ci/activate-node.sh create mode 100644 scripts/ci/install-tool.sh create mode 100644 scripts/ci/test_tool_setup.py diff --git a/.gitea/actions/security/action.yml b/.gitea/actions/security/action.yml index 556de4a..8b9703f 100644 --- a/.gitea/actions/security/action.yml +++ b/.gitea/actions/security/action.yml @@ -3,20 +3,13 @@ description: Dependency, secrets, infrastructure and workflow checks for Gitea 1 runs: using: composite steps: - - uses: https://github.com/actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4 - with: - node-version: '22' + - uses: ./.gitea/actions/setup-node - uses: ./.gitea/actions/setup-trivy - name: Validate workflows and deployment checks shell: bash run: | - archive="$RUNNER_TEMP/actionlint.tar.gz" - curl --fail --silent --show-error --location --retry 3 --max-time 120 \ - https://github.com/rhysd/actionlint/releases/download/v1.7.12/actionlint_1.7.12_linux_amd64.tar.gz \ - --output "$archive" - echo "8aca8db96f1b94770f1b0d72b6dddcb1ebb8123cb3712530b08cc387b349a3d8 $archive" | sha256sum --check --strict - tar -xzf "$archive" -C "$RUNNER_TEMP" actionlint - ACTIONLINT_BIN="$RUNNER_TEMP/actionlint" bash scripts/ci/validate-workflows.sh + actionlint_bin=$(bash scripts/ci/install-tool.sh actionlint) + ACTIONLINT_BIN="$actionlint_bin" bash scripts/ci/validate-workflows.sh - name: Audit dependencies, secrets and infrastructure shell: bash run: bash scripts/ci/security-audit.sh diff --git a/.gitea/actions/setup-node/action.yml b/.gitea/actions/setup-node/action.yml new file mode 100644 index 0000000..6ed2045 --- /dev/null +++ b/.gitea/actions/setup-node/action.yml @@ -0,0 +1,11 @@ +name: Install and activate Node 22 +description: Set up Node and verify the executable selected by the Gitea runner. +runs: + using: composite + steps: + - uses: https://github.com/actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4 + with: + node-version: '22' + - name: Activate and verify Node 22 + shell: bash + run: bash scripts/ci/activate-node.sh diff --git a/.gitea/actions/setup-trivy/action.yml b/.gitea/actions/setup-trivy/action.yml index d3aa475..83990b6 100644 --- a/.gitea/actions/setup-trivy/action.yml +++ b/.gitea/actions/setup-trivy/action.yml @@ -1,16 +1,9 @@ name: Install verified Trivy -description: Install a pinned scanner with a checked SHA256, without elevated privileges. +description: Install a pinned native scanner with a checked SHA256. runs: using: composite steps: - shell: bash run: | - set -euo pipefail - install_dir="$RUNNER_TEMP/trivy-bin" - mkdir -p "$install_dir" - curl --fail --silent --show-error --location --retry 3 --max-time 120 \ - https://github.com/aquasecurity/trivy/releases/download/v0.74.0/trivy_0.74.0_Linux-64bit.tar.gz \ - --output "$install_dir/trivy.tar.gz" - echo "2ae6fe3ee734b7fdf11335663e18c75ea12dccc76062f09f164a3b0f8be4371a $install_dir/trivy.tar.gz" | sha256sum --check --strict - tar -xzf "$install_dir/trivy.tar.gz" -C "$install_dir" trivy - echo "$install_dir" >> "$GITHUB_PATH" + trivy_bin=$(bash scripts/ci/install-tool.sh trivy) + "$trivy_bin" --version diff --git a/.gitea/workflows/cd-main.yml b/.gitea/workflows/cd-main.yml index bc67649..8f3b941 100644 --- a/.gitea/workflows/cd-main.yml +++ b/.gitea/workflows/cd-main.yml @@ -30,7 +30,6 @@ on: env: REGISTRY: rg.fr-par.scw.cloud/weworkstudio - NODE_VERSION: '22' K8S_NAMESPACE: xpeditis-prod jobs: @@ -54,9 +53,7 @@ jobs: - uses: https://github.com/actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4 with: persist-credentials: false - - uses: https://github.com/actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4 - with: - node-version: ${{ env.NODE_VERSION }} + - uses: ./.gitea/actions/setup-node - run: npm ci --legacy-peer-deps - run: npm run lint -- --no-fix @@ -70,9 +67,7 @@ jobs: - uses: https://github.com/actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4 with: persist-credentials: false - - uses: https://github.com/actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4 - with: - node-version: ${{ env.NODE_VERSION }} + - uses: ./.gitea/actions/setup-node - run: npm ci --legacy-peer-deps - run: npm run lint - run: npm run type-check @@ -88,9 +83,7 @@ jobs: - uses: https://github.com/actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4 with: persist-credentials: false - - uses: https://github.com/actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4 - with: - node-version: ${{ env.NODE_VERSION }} + - uses: ./.gitea/actions/setup-node - run: npm ci --legacy-peer-deps - run: npm test -- --ci --runInBand @@ -105,9 +98,7 @@ jobs: - uses: https://github.com/actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4 with: persist-credentials: false - - uses: https://github.com/actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4 - with: - node-version: ${{ env.NODE_VERSION }} + - uses: ./.gitea/actions/setup-node - run: npm ci --legacy-peer-deps - run: npm test -- --ci --runInBand @@ -149,6 +140,10 @@ jobs: persist-credentials: false # Cet arbre Git est identique à celui de la release preprod vérifiée. ref: ${{ github.sha }} + - uses: https://github.com/docker/setup-qemu-action@c7c53464625b32c7a7e944ae62b3e17d2b600130 # v3 + with: + platforms: amd64,arm64 + cache-image: false - uses: https://github.com/docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f # v3 - uses: https://github.com/docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 # v3 with: @@ -173,7 +168,7 @@ jobs: - name: Contrôle — l'URL de preprod ne doit pas figurer dans le bundle run: | IMAGE="${{ env.REGISTRY }}/xpeditis-frontend@${{ steps.build.outputs.digest }}" - CID=$(docker create "$IMAGE") + CID=$(docker create --platform linux/amd64 "$IMAGE") trap 'docker rm "$CID" >/dev/null' EXIT docker cp "$CID:/app/.next" /tmp/next-check test -d /tmp/next-check @@ -260,14 +255,8 @@ jobs: - name: Installer le client Hetzner run: | - mkdir -p "$RUNNER_TEMP/hcloud-bin" - curl --fail --silent --show-error --location --retry 3 --max-time 120 \ - https://github.com/hetznercloud/cli/releases/download/v1.49.0/hcloud-linux-amd64.tar.gz \ - --output "$RUNNER_TEMP/hcloud.tar.gz" - echo "dc6e5b0e6eaf9ef2baa5473a3eb49a11e80e72cdf2a01fdf7b0af975410e79cc $RUNNER_TEMP/hcloud.tar.gz" | sha256sum --check --strict - tar -xzf "$RUNNER_TEMP/hcloud.tar.gz" -C "$RUNNER_TEMP/hcloud-bin" hcloud - echo "$RUNNER_TEMP/hcloud-bin" >> "$GITHUB_PATH" - "$RUNNER_TEMP/hcloud-bin/hcloud" version + hcloud_bin=$(bash scripts/ci/install-tool.sh hcloud) + "$hcloud_bin" version - name: Ouvrir le port 22 pour l'IP de ce runner env: diff --git a/.gitea/workflows/cd-preprod.yml b/.gitea/workflows/cd-preprod.yml index 1528556..1adc7bc 100644 --- a/.gitea/workflows/cd-preprod.yml +++ b/.gitea/workflows/cd-preprod.yml @@ -19,7 +19,6 @@ on: env: REGISTRY: rg.fr-par.scw.cloud/weworkstudio - NODE_VERSION: '22' jobs: security: @@ -42,9 +41,7 @@ jobs: - uses: https://github.com/actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4 with: persist-credentials: false - - uses: https://github.com/actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4 - with: - node-version: ${{ env.NODE_VERSION }} + - uses: ./.gitea/actions/setup-node - run: npm ci --legacy-peer-deps - run: npm run lint -- --no-fix @@ -58,9 +55,7 @@ jobs: - uses: https://github.com/actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4 with: persist-credentials: false - - uses: https://github.com/actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4 - with: - node-version: ${{ env.NODE_VERSION }} + - uses: ./.gitea/actions/setup-node - run: npm ci --legacy-peer-deps - run: npm run lint - run: npm run type-check @@ -77,9 +72,7 @@ jobs: - uses: https://github.com/actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4 with: persist-credentials: false - - uses: https://github.com/actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4 - with: - node-version: ${{ env.NODE_VERSION }} + - uses: ./.gitea/actions/setup-node - run: npm ci --legacy-peer-deps - run: npm test -- --ci --runInBand @@ -94,9 +87,7 @@ jobs: - uses: https://github.com/actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4 with: persist-credentials: false - - uses: https://github.com/actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4 - with: - node-version: ${{ env.NODE_VERSION }} + - uses: ./.gitea/actions/setup-node - run: npm ci --legacy-peer-deps - run: npm test -- --ci --runInBand @@ -133,9 +124,7 @@ jobs: - uses: https://github.com/actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4 with: persist-credentials: false - - uses: https://github.com/actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4 - with: - node-version: ${{ env.NODE_VERSION }} + - uses: ./.gitea/actions/setup-node - run: npm ci --legacy-peer-deps - name: Run integration tests env: @@ -178,7 +167,8 @@ jobs: run: echo "short=$(echo ${{ github.sha }} | cut -c1-7)" >> $GITHUB_OUTPUT - uses: https://github.com/docker/setup-qemu-action@c7c53464625b32c7a7e944ae62b3e17d2b600130 # v3 with: - platforms: arm64 + platforms: amd64,arm64 + cache-image: false - uses: https://github.com/docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f # v3 - uses: https://github.com/docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 # v3 with: @@ -213,7 +203,8 @@ jobs: run: echo "short=$(echo ${{ github.sha }} | cut -c1-7)" >> $GITHUB_OUTPUT - uses: https://github.com/docker/setup-qemu-action@c7c53464625b32c7a7e944ae62b3e17d2b600130 # v3 with: - platforms: arm64 + platforms: amd64,arm64 + cache-image: false - uses: https://github.com/docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f # v3 - uses: https://github.com/docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 # v3 with: @@ -251,7 +242,8 @@ jobs: run: echo "short=$(echo ${{ github.sha }} | cut -c1-7)" >> $GITHUB_OUTPUT - uses: https://github.com/docker/setup-qemu-action@c7c53464625b32c7a7e944ae62b3e17d2b600130 # v3 with: - platforms: arm64 + platforms: amd64,arm64 + cache-image: false - uses: https://github.com/docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f # v3 - uses: https://github.com/docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 # v3 with: diff --git a/.gitea/workflows/ci.yml b/.gitea/workflows/ci.yml index af83516..61c56cb 100644 --- a/.gitea/workflows/ci.yml +++ b/.gitea/workflows/ci.yml @@ -6,9 +6,6 @@ on: pull_request: branches: [dev] -env: - NODE_VERSION: '22' - jobs: security: name: Security gate @@ -29,9 +26,7 @@ jobs: - uses: https://github.com/actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4 with: persist-credentials: false - - uses: https://github.com/actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4 - with: - node-version: ${{ env.NODE_VERSION }} + - uses: ./.gitea/actions/setup-node - run: npm ci --legacy-peer-deps - run: npm run lint -- --no-fix @@ -45,9 +40,7 @@ jobs: - uses: https://github.com/actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4 with: persist-credentials: false - - uses: https://github.com/actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4 - with: - node-version: ${{ env.NODE_VERSION }} + - uses: ./.gitea/actions/setup-node - run: npm ci --legacy-peer-deps - run: npm run lint - run: npm run type-check @@ -63,9 +56,7 @@ jobs: - uses: https://github.com/actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4 with: persist-credentials: false - - uses: https://github.com/actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4 - with: - node-version: ${{ env.NODE_VERSION }} + - uses: ./.gitea/actions/setup-node - run: npm ci --legacy-peer-deps - run: npm test -- --ci --runInBand @@ -80,9 +71,7 @@ jobs: - uses: https://github.com/actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4 with: persist-credentials: false - - uses: https://github.com/actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4 - with: - node-version: ${{ env.NODE_VERSION }} + - uses: ./.gitea/actions/setup-node - run: npm ci --legacy-peer-deps - run: npm test -- --ci --runInBand diff --git a/.gitea/workflows/pr-checks.yml b/.gitea/workflows/pr-checks.yml index e0d6b59..712122c 100644 --- a/.gitea/workflows/pr-checks.yml +++ b/.gitea/workflows/pr-checks.yml @@ -8,9 +8,6 @@ on: pull_request: branches: [preprod, main] -env: - NODE_VERSION: '22' - jobs: security: name: Security gate @@ -31,9 +28,7 @@ jobs: - uses: https://github.com/actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4 with: persist-credentials: false - - uses: https://github.com/actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4 - with: - node-version: ${{ env.NODE_VERSION }} + - uses: ./.gitea/actions/setup-node - run: npm ci --legacy-peer-deps - run: npm run lint -- --no-fix @@ -47,9 +42,7 @@ jobs: - uses: https://github.com/actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4 with: persist-credentials: false - - uses: https://github.com/actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4 - with: - node-version: ${{ env.NODE_VERSION }} + - uses: ./.gitea/actions/setup-node - run: npm ci --legacy-peer-deps - run: npm run lint - run: npm run type-check @@ -65,9 +58,7 @@ jobs: - uses: https://github.com/actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4 with: persist-credentials: false - - uses: https://github.com/actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4 - with: - node-version: ${{ env.NODE_VERSION }} + - uses: ./.gitea/actions/setup-node - run: npm ci --legacy-peer-deps - run: npm test -- --ci --runInBand @@ -82,9 +73,7 @@ jobs: - uses: https://github.com/actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4 with: persist-credentials: false - - uses: https://github.com/actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4 - with: - node-version: ${{ env.NODE_VERSION }} + - uses: ./.gitea/actions/setup-node - run: npm ci --legacy-peer-deps - run: npm test -- --ci --runInBand @@ -121,9 +110,7 @@ jobs: - uses: https://github.com/actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4 with: persist-credentials: false - - uses: https://github.com/actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4 - with: - node-version: ${{ env.NODE_VERSION }} + - uses: ./.gitea/actions/setup-node - run: npm ci --legacy-peer-deps - name: Run integration tests env: diff --git a/docs/CI-CD-SECURITY.md b/docs/CI-CD-SECURITY.md index 57cd644..154ab7c 100644 --- a/docs/CI-CD-SECURITY.md +++ b/docs/CI-CD-SECURITY.md @@ -2,7 +2,7 @@ La version du serveur `https://gitea.ops.xpeditis.com` a été vérifiée via `/api/v1/version` : **1.22.6**. Les pipelines ciblent Gitea Actions / act_runner, -avec des jobs exécutés dans des conteneurs Linux AMD64. +avec des jobs exécutés dans des conteneurs Linux AMD64 ou ARM64. ## Workflows et contrôles @@ -32,7 +32,7 @@ lorsqu'aucun correctif n'est disponible. Les erreurs de scanner ou de registre échouent aussi : aucune exclusion générale ni échec masqué n'est ajouté. Les installations applicatives utilisent `npm ci`, le runtime est Node 22 et les actions sont épinglées par SHA. Trivy, Actionlint et Hetzner CLI sont téléchargés -avec vérification SHA256. Le lint backend ne réécrit plus les fichiers. +avec détection native AMD64/ARM64 et vérification SHA256. Le lint backend ne réécrit plus les fichiers. Les rapports sont joints aux exécutions **Gitea Actions** avec `actions/upload-artifact` **v3**, compatible avec le protocole de cette version @@ -74,7 +74,7 @@ Gitea 1.22 ignore notamment `concurrency`, `permissions`, `environment`, les dé YAML de jobs et `workflow_dispatch`. Ces paramètres ne sont donc pas présentés comme des protections effectives dans les workflows adaptés. -1. Activer Actions dans le dépôt et disposer d'un runner Docker Linux AMD64 avec +1. Activer Actions dans le dépôt et disposer d'un runner Docker Linux AMD64 ou ARM64 avec le label `ubuntu-latest`, Git, Bash, Python 3, curl, tar, timeout et les outils Docker. Les builds multiarchitecture ont besoin du support QEMU/binfmt. Les services d'intégration sont joints via `postgres` et `redis`, sans ports @@ -142,7 +142,7 @@ ces configurations ne signale plus ce secret après modification. ## Validation -Les 19 tests offline de promotion et santé passent : arbre différent, marqueurs +Les 25 tests offline de promotion et santé passent : arbre différent, marqueurs manquants, panne du registre, digest invalide, mauvaise branche, saisie hostile et échec HTTP sont refusés. Les tests vérifient aussi que les échecs et délais des audits restent bloquants et que le rapport masque les valeurs de secrets. Actionlint valide la syntaxe après normalisation des URLs @@ -152,6 +152,19 @@ d'actions Gitea ; ce contrôle ne remplace pas une exécution avec act_runner. ACTIONLINT_BIN=/chemin/vers/actionlint bash scripts/ci/validate-workflows.sh ``` +La correction du 23 septembre 2026 a aussi été exécutée dans un conteneur Linux +ARM64 : Trivy 0.74.0, Actionlint 1.7.12 et Hetzner CLI 1.49.0 démarrent avec leurs +archives officielles vérifiées. `activate-node.sh` place le Node 22 natif du cache +en tête du PATH et vérifie son exécution ; un Node 18 préinstallé ne peut plus +être sélectionné silencieusement. Node 22.23.2 a été vérifié dans l'étape suivante. +Le téléchargement/affichage de version dans `setup-node` ne suffit pas à confirmer +le runtime utilisé par les étapes shell ; la nouvelle étape affiche le chemin +absolu et la version réellement activés. + +L'installateur commun est `scripts/ci/install-tool.sh`. Chaque architecture a sa +propre archive et son empreinte. QEMU couvre AMD64 et ARM64 dans les builds préprod +et frontend prod, dont la cible reste AMD64 même si le runner est ARM64. + Les installations ont été vérifiées avec `npm ci --dry-run --offline --ignore-scripts --legacy-peer-deps`. Les builds Docker, les tests applicatifs sous Node 22, le transport réel des artefacts et les déploiements sont encore à valider diff --git a/scripts/ci/activate-node.sh b/scripts/ci/activate-node.sh new file mode 100644 index 0000000..7d40355 --- /dev/null +++ b/scripts/ci/activate-node.sh @@ -0,0 +1,23 @@ +#!/usr/bin/env bash +set -euo pipefail +case "$(uname -m)" in + aarch64|arm64) arch=arm64 ;; + x86_64|amd64) arch=x64 ;; + *) echo 'Unsupported Node architecture' >&2; exit 1 ;; +esac +cache="${RUNNER_TOOL_CACHE:-${AGENT_TOOLSDIRECTORY:-/opt/hostedtoolcache}}" +# Select the newest installed Node 22 for this architecture, independent of the +# container's preinstalled Node 18 and setup-node's misleading version output. +node_bin=$(printf '%s\n' "$cache"/node/22.*/"$arch"/bin | sort -Vr | head -n 1) +if [[ ! -x "$node_bin/node" ]]; then + echo "Node 22 is missing from the tool cache: $cache/node (architecture $arch)" >&2 + exit 1 +fi +export PATH="$node_bin:$PATH" +hash -r +node -e 'if (process.versions.node.split(".")[0] !== "22") process.exit(1)' +printf '%s\n' "$node_bin" >> "${GITHUB_PATH:?}" +printf 'PATH=%s\n' "$PATH" >> "${GITHUB_ENV:?}" +printf 'Active Node executable: %s\n' "$(command -v node)" +node --version +npm --version diff --git a/scripts/ci/install-tool.sh b/scripts/ci/install-tool.sh new file mode 100644 index 0000000..d9bc724 --- /dev/null +++ b/scripts/ci/install-tool.sh @@ -0,0 +1,47 @@ +#!/usr/bin/env bash +# Pinned Linux tools for either native architecture; never execute a foreign binary. +set -euo pipefail +tool="${1:?Usage: install-tool.sh trivy|actionlint|hcloud [--print-source]}" +[[ "$(uname -s)" == Linux ]] || { echo 'CI tools require Linux' >&2; exit 1; } +case "$(uname -m)" in + x86_64|amd64) arch=amd64 ;; + aarch64|arm64) arch=arm64 ;; + *) echo 'Unsupported runner architecture' >&2; exit 1 ;; +esac +case "$tool:$arch" in + trivy:amd64) + url=https://github.com/aquasecurity/trivy/releases/download/v0.74.0/trivy_0.74.0_Linux-64bit.tar.gz + sha=2ae6fe3ee734b7fdf11335663e18c75ea12dccc76062f09f164a3b0f8be4371a ;; + trivy:arm64) + url=https://github.com/aquasecurity/trivy/releases/download/v0.74.0/trivy_0.74.0_Linux-ARM64.tar.gz + sha=b94ce1976bbf3c15b514b605ee88be7c6d94a29be2302847ff01cb794d47aad5 ;; + actionlint:amd64) + url=https://github.com/rhysd/actionlint/releases/download/v1.7.12/actionlint_1.7.12_linux_amd64.tar.gz + sha=8aca8db96f1b94770f1b0d72b6dddcb1ebb8123cb3712530b08cc387b349a3d8 ;; + actionlint:arm64) + url=https://github.com/rhysd/actionlint/releases/download/v1.7.12/actionlint_1.7.12_linux_arm64.tar.gz + sha=325e971b6ba9bfa504672e29be93c24981eeb1c07576d730e9f7c8805afff0c6 ;; + hcloud:amd64) + url=https://github.com/hetznercloud/cli/releases/download/v1.49.0/hcloud-linux-amd64.tar.gz + sha=dc6e5b0e6eaf9ef2baa5473a3eb49a11e80e72cdf2a01fdf7b0af975410e79cc ;; + hcloud:arm64) + url=https://github.com/hetznercloud/cli/releases/download/v1.49.0/hcloud-linux-arm64.tar.gz + sha=183dabe0a03b3eb7b93f8d2f2cf91c478de57f9412f189866eda0fdde7baf88c ;; + *) echo "Unsupported tool: $tool" >&2; exit 1 ;; +esac +if [[ "${2:-}" == --print-source ]]; then + printf '%s\n%s\n' "$url" "$sha" + exit 0 +fi +install_dir="${RUNNER_TEMP:?}/ci-tools/$tool" +mkdir -p "$install_dir" +echo "Installing $tool for Linux/$arch" >&2 +curl --fail --silent --show-error --location --retry 3 --max-time 120 \ + "$url" --output "$install_dir/archive.tar.gz" +echo "$sha $install_dir/archive.tar.gz" | sha256sum --check --strict >&2 +tar -xzf "$install_dir/archive.tar.gz" -C "$install_dir" "$tool" +chmod +x "$install_dir/$tool" +printf '%s\n' "$install_dir" >> "${GITHUB_PATH:?}" +# Older act_runner releases can keep the container PATH ahead of add-path entries. +printf 'PATH=%s:%s\n' "$install_dir" "$PATH" >> "${GITHUB_ENV:?}" +printf '%s\n' "$install_dir/$tool" diff --git a/scripts/ci/test_tool_setup.py b/scripts/ci/test_tool_setup.py new file mode 100644 index 0000000..5dd66f0 --- /dev/null +++ b/scripts/ci/test_tool_setup.py @@ -0,0 +1,83 @@ +"""Offline architecture/PATH regressions for Gitea Linux runners.""" +import os +from pathlib import Path +import subprocess +import tempfile +import unittest + +SCRIPTS = Path(__file__).resolve().parent + + +class ToolSetup(unittest.TestCase): + def setUp(self): + self.temp = tempfile.TemporaryDirectory() + self.addCleanup(self.temp.cleanup) + self.root = Path(self.temp.name) + self.bin = self.root / 'bin' + self.bin.mkdir() + self.env = dict(os.environ, PATH=f'{self.bin}:{os.environ["PATH"]}', + FAKE_ARCH='aarch64', FAKE_OS='Linux', + RUNNER_TEMP=str(self.root), RUNNER_TOOL_CACHE=str(self.root / 'cache'), + GITHUB_PATH=str(self.root / 'path'), GITHUB_ENV=str(self.root / 'env')) + self.mock(self.bin / 'uname', 'if [ "$1" = -s ]; then echo "$FAKE_OS"; else echo "$FAKE_ARCH"; fi\n') + + def mock(self, path, body): + path.parent.mkdir(parents=True, exist_ok=True) + path.write_text('#!/bin/sh\n' + body) + path.chmod(0o755) + + def run_script(self, script, *args): + return subprocess.run(['bash', str(SCRIPTS / script), *args], env=self.env, + capture_output=True, text=True) + + def test_all_native_tool_urls_and_checksums(self): + for arch, suffix in [('aarch64', 'arm64'), ('x86_64', 'amd64')]: + self.env['FAKE_ARCH'] = arch + for tool in ['trivy', 'actionlint', 'hcloud']: + with self.subTest(arch=arch, tool=tool): + result = self.run_script('install-tool.sh', tool, '--print-source') + self.assertEqual(result.returncode, 0, result.stderr) + url, checksum = result.stdout.splitlines() + expected = 'Linux-ARM64' if tool == 'trivy' and suffix == 'arm64' else ( + 'Linux-64bit' if tool == 'trivy' else suffix) + self.assertIn(expected, url) + self.assertRegex(checksum, r'^[0-9a-f]{64}$') + + def test_unsupported_architecture_rejected_before_download(self): + self.env['FAKE_ARCH'] = 'armv7l' + self.assertNotEqual(self.run_script('install-tool.sh', 'trivy', '--print-source').returncode, 0) + + def test_non_linux_rejected(self): + self.env['FAKE_OS'] = 'Darwin' + self.assertNotEqual(self.run_script('install-tool.sh', 'trivy', '--print-source').returncode, 0) + + def test_wrong_checksum_cannot_install_or_activate_binary(self): + self.mock(self.bin / 'curl', 'exit 0\n') + self.mock(self.bin / 'sha256sum', 'exit 1\n') + self.mock(self.bin / 'tar', f'touch "{self.root}/extracted"\n') + self.assertNotEqual(self.run_script('install-tool.sh', 'trivy').returncode, 0) + self.assertFalse((self.root / 'extracted').exists()) + self.assertFalse((self.root / 'path').exists()) + + def test_node22_takes_precedence_over_preinstalled_node18(self): + self.mock(self.bin / 'node', 'echo v18.20.8\nexit 1\n') + self.mock(self.bin / 'npm', 'echo old-npm\nexit 1\n') + for version in ['22.9.0', '22.23.2']: + node_bin = self.root / 'cache' / 'node' / version / 'arm64' / 'bin' + self.mock(node_bin / 'node', f'if [ "$1" = -e ]; then exit 0; fi\necho v{version}\n') + self.mock(node_bin / 'npm', 'echo 10.9.0\n') + result = self.run_script('activate-node.sh') + self.assertEqual(result.returncode, 0, result.stderr) + self.assertIn('v22.23.2', result.stdout) + self.assertNotIn('v18.', result.stdout) + self.assertIn('/22.23.2/arm64/bin', (self.root / 'path').read_text()) + self.assertTrue((self.root / 'env').read_text().startswith('PATH=')) + + def test_missing_native_node22_fails(self): + wrong_arch = self.root / 'cache' / 'node' / '22.23.2' / 'x64' / 'bin' + self.mock(wrong_arch / 'node', 'exit 0\n') + self.assertNotEqual(self.run_script('activate-node.sh').returncode, 0) + + +if __name__ == '__main__': + unittest.main() diff --git a/scripts/ci/validate-workflows.sh b/scripts/ci/validate-workflows.sh index 399d9bd..520bd49 100644 --- a/scripts/ci/validate-workflows.sh +++ b/scripts/ci/validate-workflows.sh @@ -8,4 +8,4 @@ for workflow in .gitea/workflows/*.yml; do "$validator" -config-file .gitea/actionlint.yaml -shellcheck='' -stdin-filename "$workflow" - done bash -n scripts/ci/*.sh -python3 scripts/ci/test_release_checks.py +python3 -B -m unittest discover -s scripts/ci -p 'test_*.py'