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
314 changes: 179 additions & 135 deletions .github/workflows/sbom-diff-and-risk-ci.yml
Original file line number Diff line number Diff line change
@@ -1,164 +1,208 @@
name: sbom-diff-and-risk-ci
run-name: sbom-diff-and-risk ci / ${{ github.event_name }} / ${{ github.ref_name }}

on:
workflow_dispatch:
push:
# Version tags provide a minimal release-build scaffold without changing publishing.
tags:
- "v*"
paths:
- ".github/workflows/sbom-diff-and-risk-ci.yml"
- "tools/sbom-diff-and-risk/**"
pull_request:
paths:
- ".github/workflows/sbom-diff-and-risk-ci.yml"
- "tools/sbom-diff-and-risk/**"

permissions: {}

env:
name: sbom-diff-and-risk-ci
run-name: sbom-diff-and-risk ci / ${{ github.event_name }} / ${{ github.ref_name }}
on:
workflow_dispatch:
push:
# Version tags provide a minimal release-build scaffold without changing publishing.
tags:
- "v*"
paths:
- ".github/workflows/sbom-diff-and-risk-ci.yml"
- "tools/sbom-diff-and-risk/**"
pull_request:
paths:
- ".github/workflows/sbom-diff-and-risk-ci.yml"
- "tools/sbom-diff-and-risk/**"
permissions: {}
env:
SBOM_DIFF_RISK_PYTHON_VERSION: "3.11"
SBOM_DIFF_RISK_DIST_ARTIFACT_NAME: sbom-diff-and-risk-dist
SBOM_DIFF_RISK_CHECKSUM_MANIFEST: sbom-diff-and-risk-SHA256SUMS.txt
SBOM_DIFF_RISK_RELEASE_TITLE_PREFIX: sbom-diff-and-risk

jobs:
test:
runs-on: ubuntu-latest
permissions:
contents: read
defaults:
run:
working-directory: tools/sbom-diff-and-risk
steps:
- name: Check out repository
uses: actions/checkout@v6

- name: Set up Python
uses: actions/setup-python@v6
with:
python-version: ${{ env.SBOM_DIFF_RISK_PYTHON_VERSION }}

- name: Upgrade pip
run: python -m pip install --upgrade pip

- name: Install project
run: python -m pip install -e .[dev]

- name: Run test suite
run: python -m pytest

- name: CLI smoke test
shell: bash
run: |
tmpdir="$(mktemp -d)"
python -m sbom_diff_risk.cli compare \
--before examples/cdx_before.json \
--after examples/cdx_after.json \
--format auto \
--out-json "$tmpdir/report.json" \
--out-md "$tmpdir/report.md"
test -f "$tmpdir/report.json"
test -f "$tmpdir/report.md"
diff -u examples/sample-report.json "$tmpdir/report.json"
diff -u examples/sample-report.md "$tmpdir/report.md"

build-and-attest:
# Keep provenance publication on trusted non-PR runs so consumers verify
# workflow-produced wheel/sdist artifacts from this repository workflow.
if: github.event_name != 'pull_request'
needs: test
runs-on: ubuntu-latest
permissions:
contents: read
id-token: write
attestations: write
defaults:
run:
working-directory: tools/sbom-diff-and-risk
steps:
- name: Check out repository
uses: actions/checkout@v6

- name: Set up Python
uses: actions/setup-python@v6
with:
python-version: ${{ env.SBOM_DIFF_RISK_PYTHON_VERSION }}

- name: Upgrade pip
run: python -m pip install --upgrade pip

- name: Install build tooling
run: python -m pip install build

- name: Build distributable artifacts
run: python -m build

jobs:
test:
runs-on: ubuntu-latest
permissions:
contents: read
defaults:
run:
working-directory: tools/sbom-diff-and-risk
steps:
- name: Check out repository
uses: actions/checkout@v6

- name: Set up Python
uses: actions/setup-python@v6
with:
python-version: ${{ env.SBOM_DIFF_RISK_PYTHON_VERSION }}

- name: Upgrade pip
run: python -m pip install --upgrade pip

- name: Install project
run: python -m pip install -e .[dev]

- name: Run test suite
run: python -m pytest

- name: CLI smoke test
- name: Generate SHA256 checksum manifest
shell: bash
run: |
tmpdir="$(mktemp -d)"
python -m sbom_diff_risk.cli compare \
--before examples/cdx_before.json \
--after examples/cdx_after.json \
--format auto \
--out-json "$tmpdir/report.json" \
--out-md "$tmpdir/report.md"
test -f "$tmpdir/report.json"
test -f "$tmpdir/report.md"
diff -u examples/sample-report.json "$tmpdir/report.json"
diff -u examples/sample-report.md "$tmpdir/report.md"

build-and-attest:
# Keep provenance publication on trusted non-PR runs so consumers verify
# workflow-produced wheel/sdist artifacts from this repository workflow.
if: github.event_name != 'pull_request'
needs: test
runs-on: ubuntu-latest
permissions:
contents: read
id-token: write
attestations: write
defaults:
run:
working-directory: tools/sbom-diff-and-risk
steps:
- name: Check out repository
uses: actions/checkout@v6

- name: Set up Python
uses: actions/setup-python@v6
with:
python-version: ${{ env.SBOM_DIFF_RISK_PYTHON_VERSION }}
set -euo pipefail
shopt -s nullglob

- name: Upgrade pip
run: python -m pip install --upgrade pip
cd dist
artifacts=( *.tar.gz *.whl )
IFS=$'\n'
artifacts=( $(printf '%s\n' "${artifacts[@]}" | LC_ALL=C sort) )
unset IFS

- name: Install build tooling
run: python -m pip install build
if [ "${#artifacts[@]}" -ne 2 ]; then
echo "Expected exactly one source distribution and one wheel in dist/." >&2
printf 'Found %s artifact(s):\n' "${#artifacts[@]}" >&2
printf ' %s\n' "${artifacts[@]}" >&2
exit 1
fi

- name: Build distributable artifacts
run: python -m build
sha256sum "${artifacts[@]}" > "${SBOM_DIFF_RISK_CHECKSUM_MANIFEST}"
grep -E ' sbom_diff_and_risk-.+\.tar\.gz$' "${SBOM_DIFF_RISK_CHECKSUM_MANIFEST}"
grep -E ' sbom_diff_and_risk-.+\.whl$' "${SBOM_DIFF_RISK_CHECKSUM_MANIFEST}"
cat "${SBOM_DIFF_RISK_CHECKSUM_MANIFEST}"

- name: Upload wheel and source distribution artifact
- name: Upload distribution artifact and checksum manifest
uses: actions/upload-artifact@v7
with:
name: ${{ env.SBOM_DIFF_RISK_DIST_ARTIFACT_NAME }}
path: |
tools/sbom-diff-and-risk/dist/*.whl
tools/sbom-diff-and-risk/dist/*.tar.gz
tools/sbom-diff-and-risk/dist/${{ env.SBOM_DIFF_RISK_CHECKSUM_MANIFEST }}
if-no-files-found: error

- name: Generate artifact attestation for built distributions
uses: actions/attest@v4
with:
subject-path: ${{ github.workspace }}/tools/sbom-diff-and-risk/dist/*

publish-release-assets:
# Publish the exact built wheel/sdist bytes from this run as release assets.
if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/v')
needs: build-and-attest
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- name: Check out repository
uses: actions/checkout@v6
with:
fetch-depth: 0

- name: Download built distribution artifact
uses: actions/download-artifact@v8
with:
name: ${{ env.SBOM_DIFF_RISK_DIST_ARTIFACT_NAME }}
path: release-assets

- name: Publish release assets from CI-built distributions
shell: bash
env:
GH_TOKEN: ${{ github.token }}
GH_REPO: ${{ github.repository }}
RELEASE_TAG: ${{ github.ref_name }}
RELEASE_TITLE_PREFIX: ${{ env.SBOM_DIFF_RISK_RELEASE_TITLE_PREFIX }}
run: |
subject-path: |
${{ github.workspace }}/tools/sbom-diff-and-risk/dist/*.whl
${{ github.workspace }}/tools/sbom-diff-and-risk/dist/*.tar.gz

publish-release-assets:
# Publish the exact built wheel/sdist bytes and checksum manifest from this run.
if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/v')
needs: build-and-attest
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- name: Check out repository
uses: actions/checkout@v6
with:
fetch-depth: 0

- name: Download built distribution artifact and checksum manifest
uses: actions/download-artifact@v8
with:
name: ${{ env.SBOM_DIFF_RISK_DIST_ARTIFACT_NAME }}
path: release-assets

- name: Publish release assets from CI-built distributions
shell: bash
env:
GH_TOKEN: ${{ github.token }}
GH_REPO: ${{ github.repository }}
RELEASE_TAG: ${{ github.ref_name }}
RELEASE_TITLE_PREFIX: ${{ env.SBOM_DIFF_RISK_RELEASE_TITLE_PREFIX }}
run: |
set -euo pipefail
shopt -s nullglob
assets=(release-assets/*.whl release-assets/*.tar.gz)
if [ "${#assets[@]}" -eq 0 ]; then
echo "No release assets found in release-assets/" >&2
IFS=$'\n'
assets=( $(printf '%s\n' "${assets[@]}" | LC_ALL=C sort) )
unset IFS
checksum_manifest="release-assets/${SBOM_DIFF_RISK_CHECKSUM_MANIFEST}"

if [ "${#assets[@]}" -ne 2 ]; then
echo "Expected exactly one wheel and one source distribution in release-assets/." >&2
printf 'Found %s artifact(s):\n' "${#assets[@]}" >&2
printf ' %s\n' "${assets[@]}" >&2
exit 1
fi

title="${RELEASE_TITLE_PREFIX} ${RELEASE_TAG}"

if gh release view "${RELEASE_TAG}" --repo "${GH_REPO}" >/dev/null 2>&1; then
is_draft="$(gh release view "${RELEASE_TAG}" --repo "${GH_REPO}" --json isDraft -q .isDraft)"
if [ "${is_draft}" != "true" ]; then
echo "Release ${RELEASE_TAG} already exists and is published; leaving assets unchanged."
exit 0
fi
else
gh release create "${RELEASE_TAG}" \
--repo "${GH_REPO}" \
--draft \
--verify-tag \
--title "${title}" \
--notes "Release assets for ${RELEASE_TAG}. See docs/release-provenance.md for provenance verification guidance."
if [ ! -f "${checksum_manifest}" ]; then
echo "Missing checksum manifest: ${checksum_manifest}" >&2
exit 1
fi

gh release upload "${RELEASE_TAG}" "${assets[@]}" --repo "${GH_REPO}" --clobber
gh release edit "${RELEASE_TAG}" --repo "${GH_REPO}" --draft=false --title "${title}"
grep -E ' sbom_diff_and_risk-.+\.tar\.gz$' "${checksum_manifest}"
grep -E ' sbom_diff_and_risk-.+\.whl$' "${checksum_manifest}"
assets+=( "${checksum_manifest}" )

title="${RELEASE_TITLE_PREFIX} ${RELEASE_TAG}"

if gh release view "${RELEASE_TAG}" --repo "${GH_REPO}" >/dev/null 2>&1; then
is_draft="$(gh release view "${RELEASE_TAG}" --repo "${GH_REPO}" --json isDraft -q .isDraft)"
if [ "${is_draft}" != "true" ]; then
echo "Release ${RELEASE_TAG} already exists and is published; leaving assets unchanged."
exit 0
fi
else
gh release create "${RELEASE_TAG}" \
--repo "${GH_REPO}" \
--draft \
--verify-tag \
--title "${title}" \
--notes "Release assets for ${RELEASE_TAG}. See docs/release-provenance.md for provenance verification guidance."
fi

gh release upload "${RELEASE_TAG}" "${assets[@]}" --repo "${GH_REPO}"
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Keep release upload idempotent for draft reruns

The publish step now calls gh release upload without --clobber, which makes tag-job reruns fail when a draft release already contains assets with the same filenames (for example after a partial prior run). GitHub CLI documents --clobber as the way to overwrite same-name assets, so removing it turns a previously recoverable rerun path into a hard failure before gh release edit can publish the draft.

Useful? React with 👍 / 👎.

gh release edit "${RELEASE_TAG}" --repo "${GH_REPO}" --draft=false --title "${title}"
7 changes: 4 additions & 3 deletions tools/sbom-diff-and-risk/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -239,9 +239,10 @@ This section is about verifying `sbom-diff-and-risk` itself. If you want the sho
This repository also records provenance for `sbom-diff-and-risk` itself by generating GitHub artifact attestations for the wheel and source distribution produced by the `sbom-diff-and-risk-ci` workflow.

- the attested files are the wheel and source distribution built by `python -m build` from `tools/sbom-diff-and-risk`
- the build files are uploaded together as the `sbom-diff-and-risk-dist` workflow artifact
- version-tag runs also publish those same built files as GitHub Release assets for the matching tag
- only trusted non-PR runs publish the attestation
- the build files are uploaded together as the `sbom-diff-and-risk-dist` workflow artifact
- version-tag runs also publish those same built files as GitHub Release assets for the matching tag
- releases produced by the updated workflow include `sbom-diff-and-risk-SHA256SUMS.txt` for local SHA256 verification of downloaded wheel and source distribution files
- only trusted non-PR runs publish the attestation
- consumers can verify workflow-built artifacts with `gh attestation verify`
- consumers can verify immutable releases and downloaded release assets with `gh release verify` and `gh release verify-asset`
- this complements the tool's analysis of third-party supply-chain inputs, but it does not replace that analysis
Expand Down
Loading