Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 14 additions & 1 deletion .github/workflows/sbom-generation.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,17 +19,30 @@
# NOTE: contents: write is required for release-asset upload and for the
# dependency submission API. Fork PR heads run without write and simply skip
# those side effects; the artifact is still produced.
#
# NOTE on the push trigger: it exists so the DEFAULT BRANCH has a dependency
# snapshot. dependency-review compares base...head in the dependency graph; with
# PR-only runs the base commit never has one, so every comparison reports "the
# number of snapshots compared for the base SHA (0) and the head SHA (1) do not
# match" and the whole dependency set reads as newly added. That re-flags
# pre-existing vulnerabilities on every PR instead of only the ones the PR adds.
# Snapshotting pushes to the default branch gives the comparison a real base.
name: SBOM Generation

on:
push:
branches: [main, master, develop]
pull_request:
types: [opened, synchronize, reopened, ready_for_review, closed]
branches: [main, master, develop]
release:
types: [published]

concurrency:
group: sbom-generation-${{ github.event.pull_request.base.repo.full_name || github.repository }}-${{ github.event.pull_request.number || github.event.release.tag_name || github.ref }}
# Final fallback is the SHA, not the ref, so two pushes landing close together
# do not cancel each other: a cancelled push run leaves that commit without a
# snapshot, which is exactly the base-side gap this trigger exists to close.
group: sbom-generation-${{ github.event.pull_request.base.repo.full_name || github.repository }}-${{ github.event.pull_request.number || github.event.release.tag_name || github.sha }}
cancel-in-progress: true

permissions:
Expand Down
54 changes: 54 additions & 0 deletions tests/test_sbom_generation_push_contract.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
"""Contracts for default-branch dependency snapshot generation."""

from __future__ import annotations

from pathlib import Path


WORKFLOW = Path(".github/workflows/sbom-generation.yml")


def _workflow_text() -> str:
"""Return the centrally versioned SBOM workflow as UTF-8 text."""

return WORKFLOW.read_text(encoding="utf-8")


def test_sbom_workflow_snapshots_supported_default_branch_pushes() -> None:
"""Default-branch commits must receive dependency graph snapshots."""

workflow = _workflow_text()

assert "on:\n push:\n branches: [main, master, develop]\n" in workflow
assert " pull_request:\n" in workflow
assert " release:\n" in workflow
assert "dependency-snapshot: true" in workflow


def test_sbom_push_concurrency_is_bound_to_the_commit_sha() -> None:
"""A later default-branch push must not cancel another commit's snapshot."""

workflow = _workflow_text()
group_line = next(
line.strip() for line in workflow.splitlines() if line.strip().startswith("group:")
)

assert "github.event.release.tag_name || github.sha" in group_line
assert "github.event.release.tag_name || github.ref" not in group_line


def test_sbom_job_conditions_keep_push_runs_active_and_closed_prs_inert() -> None:
"""Pushes run the snapshot job while closed-PR events only cancel stale work."""

workflow = _workflow_text()

assert (
"if: github.event_name == 'pull_request' && github.event.action == 'closed'"
in workflow
)
assert (
"if: github.event_name != 'pull_request' || github.event.action != 'closed'"
in workflow
)
assert "generate-sbom:\n" in workflow
assert " contents: write\n" in workflow
Loading