From 03a637cbc0611b0205a622db108fa34b130929c9 Mon Sep 17 00:00:00 2001 From: g1obalworlds <77709487+g1obalworlds@users.noreply.github.com> Date: Mon, 26 Oct 2026 17:00:22 -0700 Subject: [PATCH] supply-chain: name the repository for the SBOM upload The attest job has no checkout, so `gh release upload` had no git remote to find the repository from and failed with "not a git repository" after the attestations were already published. Pass GH_REPO, as draft-release does. --- .github/workflows/supply-chain.yml | 2 ++ tests/test_supply_chain.py | 29 +++++++++++++++++++++++++++++ 2 files changed, 31 insertions(+) diff --git a/.github/workflows/supply-chain.yml b/.github/workflows/supply-chain.yml index d08d302..47b168c 100644 --- a/.github/workflows/supply-chain.yml +++ b/.github/workflows/supply-chain.yml @@ -72,6 +72,8 @@ jobs: - name: Attach the SBOM to the draft release env: GH_TOKEN: ${{ github.token }} + # No checkout here, so gh has no git remote to find the repository from. + GH_REPO: ${{ github.repository }} VERSION: ${{ inputs.version }} run: | tag="v${VERSION#v}" diff --git a/tests/test_supply_chain.py b/tests/test_supply_chain.py index 420000b..1c53f5c 100644 --- a/tests/test_supply_chain.py +++ b/tests/test_supply_chain.py @@ -1,5 +1,6 @@ """The supply-chain workflow stays opt-in so current release callers keep working.""" +import os from pathlib import Path import subprocess import tempfile @@ -107,6 +108,34 @@ class SupplyChainOptInTests(unittest.TestCase): self.assertIn("::error::no release files were downloaded", result.stdout) self.assertEqual(subjects, {}) + def test_sbom_upload_names_the_repository_without_a_checkout(self): + # The job has no checkout, so gh cannot read the repository from a git remote; like the + # real gh, the fixture fails unless GH_REPO or --repo names it. + steps = self.supply["jobs"]["attest"]["steps"] + self.assertFalse(any(step.get("uses", "").startswith("actions/checkout@") for step in steps)) + attach = next(step for step in steps if step.get("name") == "Attach the SBOM to the draft release") + context = {"${{ github.token }}": "token", "${{ github.repository }}": "storytold/photocraft", + "${{ inputs.version }}": "1.2.3"} + with tempfile.TemporaryDirectory(prefix="supply-chain-test-") as directory: + work = Path(directory) + (work / "bin").mkdir() + gh = work / "bin/gh" + gh.write_text('#!/bin/sh\n' + 'repo="${GH_REPO:-}"\n' + 'for arg; do case "$prev" in --repo|-R) repo="$arg" ;; esac; prev="$arg"; done\n' + 'if [ -z "$repo" ]; then\n' + ' echo "failed to run git: fatal: not a git repository" >&2; exit 1\n' + 'fi\n' + 'echo "$repo $*" > "$GH_LOG"\n') + gh.chmod(0o755) + env = {name: context[value] for name, value in attach["env"].items()} + env.update(PATH=f"{work / 'bin'}{os.pathsep}{os.environ['PATH']}", GH_LOG=str(work / "gh.log")) + result = subprocess.run(["bash", "-e", "-c", attach["run"]], cwd=work, env=env, + text=True, capture_output=True, timeout=15) + self.assertEqual(result.returncode, 0, result) + self.assertEqual((work / "gh.log").read_text().split(), + ["storytold/photocraft", "release", "upload", "v1.2.3", "sbom.cyclonedx.json", "--clobber"]) + if __name__ == "__main__": unittest.main()