diff --git a/.github/scripts/verify_main_ci_success.sh b/.github/scripts/verify_main_ci_success.sh new file mode 100755 index 0000000..1aa5238 --- /dev/null +++ b/.github/scripts/verify_main_ci_success.sh @@ -0,0 +1,35 @@ +#!/usr/bin/env bash +set -euo pipefail + +workflow="${CI_WORKFLOW:-ci.yml}" +branch="${VERIFY_BRANCH:-main}" +repository="${GITHUB_REPOSITORY:?GITHUB_REPOSITORY is required}" + +if ! command -v gh >/dev/null 2>&1; then + echo "gh CLI is required to verify main CI before publish." >&2 + exit 1 +fi + +if [ -z "${GH_TOKEN:-}" ] && [ -z "${GITHUB_TOKEN:-}" ]; then + echo "GH_TOKEN or GITHUB_TOKEN is required to verify main CI before publish." >&2 + exit 1 +fi + +export GH_TOKEN="${GH_TOKEN:-${GITHUB_TOKEN}}" + +read -r status conclusion </dev/null || echo " missing") +EOF + +if [ "${status}" != "completed" ] || [ "${conclusion}" != "success" ]; then + echo "Latest ${branch} CI (${workflow}) must be completed with success before publish; got status=${status:-unknown} conclusion=${conclusion:-unknown}." >&2 + exit 1 +fi + +echo "Verified latest ${branch} ${workflow} run succeeded." diff --git a/.github/workflows/publish-hk-snapshot-artifacts.yml b/.github/workflows/publish-hk-snapshot-artifacts.yml index a806d73..9264942 100644 --- a/.github/workflows/publish-hk-snapshot-artifacts.yml +++ b/.github/workflows/publish-hk-snapshot-artifacts.yml @@ -104,6 +104,7 @@ jobs: runs-on: ubuntu-latest timeout-minutes: 60 permissions: + actions: read contents: read id-token: write env: @@ -136,6 +137,12 @@ jobs: - name: Checkout uses: actions/checkout@v6 + - name: Verify main CI succeeded before publish + if: github.event_name == 'workflow_dispatch' && inputs.execute_publish == true + env: + GH_TOKEN: ${{ github.token }} + run: bash .github/scripts/verify_main_ci_success.sh + - name: Set up Python uses: actions/setup-python@v6 with: diff --git a/tests/test_publish_workflow_ci_gate.py b/tests/test_publish_workflow_ci_gate.py new file mode 100644 index 0000000..b381ceb --- /dev/null +++ b/tests/test_publish_workflow_ci_gate.py @@ -0,0 +1,28 @@ +"""Workflow config tests for publish CI gating.""" + +from __future__ import annotations + +from pathlib import Path + + +ROOT = Path(__file__).resolve().parents[1] +PUBLISH_WORKFLOW = ROOT / ".github/workflows/publish-hk-snapshot-artifacts.yml" +VERIFY_SCRIPT = ROOT / ".github/scripts/verify_main_ci_success.sh" + + +def test_publish_hk_snapshot_artifacts_requires_main_ci_before_live_publish() -> None: + workflow = PUBLISH_WORKFLOW.read_text(encoding="utf-8") + + assert "actions: read" in workflow + assert "Verify main CI succeeded before publish" in workflow + assert "bash .github/scripts/verify_main_ci_success.sh" in workflow + assert "inputs.execute_publish == true" in workflow + + +def test_verify_main_ci_success_script_checks_latest_main_run() -> None: + script = VERIFY_SCRIPT.read_text(encoding="utf-8") + + assert 'workflow="${CI_WORKFLOW:-ci.yml}"' in script + assert 'branch="${VERIFY_BRANCH:-main}"' in script + assert "gh run list" in script + assert 'conclusion}" != "success"' in script