ci: pin release pipeline actions (Closes #1378)
All checks were successful
CI / lint (pull_request) Successful in 3m11s
CI / build-release (pull_request) Successful in 24s
CI / browser-journey (pull_request) Successful in 5m31s
CI / release-candidate (pull_request) Has been skipped

This commit is contained in:
timmy 2026-08-25 03:57:29 +00:00
parent 9d97b2e84a
commit 22fe5dd1d8
2 changed files with 46 additions and 12 deletions

View File

@ -7,12 +7,15 @@ on:
branches: [main]
workflow_dispatch:
permissions:
contents: read
jobs:
lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0
with: { python-version: "3.11" }
- run: pip install -r requirements.txt
- run: pip install -r requirements-audit.txt
@ -23,7 +26,7 @@ jobs:
runs-on: ubuntu-latest
needs: lint
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- name: Build deterministic release bundle
run: |
SOURCE_DATE_EPOCH="$(git show -s --format=%ct "$GITHUB_SHA")"
@ -33,7 +36,7 @@ jobs:
--commit "$GITHUB_SHA" \
--source-date-epoch "$SOURCE_DATE_EPOCH"
- name: Upload tested release bundle
uses: actions/upload-artifact@v3
uses: actions/upload-artifact@a8a3f3ad30e3422c9c7b888a15615d19a852ae32 # v3.1.3
with:
name: release-bundle
path: dist/
@ -44,11 +47,11 @@ jobs:
env:
STACKCHAIN_RUN_RELEASE_E2E: "1"
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0
with: { python-version: "3.11" }
- name: Download assembled release bundle
uses: actions/download-artifact@v3
uses: actions/download-artifact@9bc31d5ccc31df68ecc42ccf4149144866c47d8a # v3.0.2
with:
name: release-bundle
path: dist
@ -66,9 +69,9 @@ jobs:
permissions:
contents: write
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- name: Download tested release bundle
uses: actions/download-artifact@v3
uses: actions/download-artifact@9bc31d5ccc31df68ecc42ccf4149144866c47d8a # v3.0.2
with:
name: release-bundle
path: dist

View File

@ -1,13 +1,44 @@
from pathlib import Path
import re
WORKFLOW = Path(".gitea/workflows/ci.yml")
ACTION_REFERENCE = re.compile(
r"^\s*- uses: (?P<action>[^\s@]+)@(?P<revision>[^\s#]+)\s+# (?P<version>v\d+(?:\.\d+){1,2})$",
re.MULTILINE,
)
DOWNLOAD_ARTIFACT = (
"actions/download-artifact@9bc31d5ccc31df68ecc42ccf4149144866c47d8a # v3.0.2"
)
def test_ci_uses_gitea_compatible_artifact_action():
text = WORKFLOW.read_text()
assert "actions/upload-artifact@v4" not in text
assert "actions/upload-artifact@v3" in text
assert re.search(
r"actions/upload-artifact@[0-9a-f]{40}\s+# v3\.\d+\.\d+", text
)
def test_ci_pins_every_external_action_to_a_reviewable_commit():
text = WORKFLOW.read_text()
uses_lines = [line for line in text.splitlines() if line.lstrip().startswith("- uses:")]
references = list(ACTION_REFERENCE.finditer(text))
assert len(references) == len(uses_lines)
assert references
for reference in references:
assert re.fullmatch(r"[0-9a-f]{40}", reference["revision"])
def test_ci_grants_repository_write_access_only_to_release_job():
text = WORKFLOW.read_text()
jobs = text[text.index("jobs:") :]
before_release, release = jobs.split(" release-candidate:", 1)
assert "permissions:\n contents: read" in text[: text.index("jobs:")]
assert "contents: write" not in before_release
assert "permissions:\n contents: write" in release
def test_ci_installs_declared_requirements_before_tests():
@ -40,7 +71,7 @@ def test_release_promotion_waits_for_tests_and_bundle():
assert "needs: [lint, build-release, browser-journey]" in release
assert "github.event_name == 'push'" in release
assert "actions/download-artifact@v3" in release
assert DOWNLOAD_ARTIFACT in release
assert (
'python3 scripts/verify_release.py --input-dir dist --commit "$TARGET" --repository .'
in release
@ -54,7 +85,7 @@ def test_release_promotion_waits_for_every_packaged_mobile_journey():
release = text[text.index(" release-candidate:") :]
assert "needs: build-release" in browser
assert "actions/download-artifact@v3" in browser
assert DOWNLOAD_ARTIFACT in browser
assert "pip install -r requirements-e2e.txt" in browser
assert "python3 -m playwright install --with-deps chromium" in browser
assert 'STACKCHAIN_RUN_RELEASE_E2E: "1"' in browser