diff --git a/.github/workflows/publish.yaml b/.github/workflows/publish.yaml index ebb99f1f..28473fe5 100644 --- a/.github/workflows/publish.yaml +++ b/.github/workflows/publish.yaml @@ -18,20 +18,54 @@ jobs: id-token: write steps: - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - name: Set up Python - uses: actions/setup-python@65d7f2d534ac1bc67fcd62888c5f4f3d2cb2b236 # v4.7.1 + uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0 with: python-version: '3.x' - name: Install dependencies run: | python -m pip install --upgrade pip - pip install build + pip install build spdx3-validate - name: Build package run: python -m build + - name: Embed SBOM into the wheel + id: pitloom + uses: bact/pitloom@4842922f65fb7ed1c5e51507c0fb253c62d97960 # v0.16.2 + with: + embed-wheel: "dist/*.whl" + extras: "content-type" + content-type: "true" + artifact-name: "sbom" + + - name: Extract and validate embedded SBOM + env: + SBOM_PATH: ${{ steps.pitloom.outputs.sbom-path }} + run: | + set -euo pipefail + whl=$(ls dist/*.whl) + sbom_basename=$(basename "${SBOM_PATH}") + + entry="" + while IFS= read -r line; do + case "${line}" in + *".dist-info/sboms/${sbom_basename}") entry="${line}"; break ;; + esac + done < <(unzip -Z1 "${whl}") + + if [ -z "${entry}" ]; then + echo "::error::${sbom_basename} not found under */.dist-info/sboms/ in ${whl}" + exit 1 + fi + echo "Found: ${entry}" + + mkdir -p extracted-sbom + unzip -p "${whl}" "${entry}" > "extracted-sbom/${sbom_basename}" + spdx3-validate --json "extracted-sbom/${sbom_basename}" + - name: Publish distribution to PyPI uses: pypa/gh-action-pypi-publish@76f52bc884231f62b9a034ebfe128415bbaabdfc # release/v1 diff --git a/.github/workflows/test-sbom.yaml b/.github/workflows/test-sbom.yaml new file mode 100644 index 00000000..6e23d8a9 --- /dev/null +++ b/.github/workflows/test-sbom.yaml @@ -0,0 +1,73 @@ +--- +# Tests that the SBOM generation and embedding step works correctly on every PR. +name: Test SBOM generation + +on: + push: + paths-ignore: &ignore-paths + - "docs/**" + - "**/*.md" + - "LICENSE*" + pull_request: + paths-ignore: *ignore-paths + +permissions: + contents: read + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + test-sbom: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + + - name: Setup Python + uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0 + with: + python-version: "3.x" + cache: pip + + - name: Install build tooling + run: | + python -m pip install --upgrade pip + pip install build spdx3-validate + + - name: Build wheel + run: python -m build --wheel + + - name: Embed SBOM into the wheel + id: pitloom + uses: bact/pitloom@4842922f65fb7ed1c5e51507c0fb253c62d97960 # v0.16.2 + with: + embed-wheel: "dist/*.whl" + extras: "content-type" + content-type: "true" + artifact-name: "sbom" + + - name: Extract and validate embedded SBOM + env: + SBOM_PATH: ${{ steps.pitloom.outputs.sbom-path }} + run: | + set -euo pipefail + whl=$(ls dist/*.whl) + sbom_basename=$(basename "${SBOM_PATH}") + + entry="" + while IFS= read -r line; do + case "${line}" in + *".dist-info/sboms/${sbom_basename}") entry="${line}"; break ;; + esac + done < <(unzip -Z1 "${whl}") + + if [ -z "${entry}" ]; then + echo "::error::${sbom_basename} not found under */.dist-info/sboms/ in ${whl}" + exit 1 + fi + echo "Found: ${entry}" + + mkdir -p extracted-sbom + unzip -p "${whl}" "${entry}" > "extracted-sbom/${sbom_basename}" + spdx3-validate --json "extracted-sbom/${sbom_basename}" diff --git a/pyproject.toml b/pyproject.toml index ca21367b..a71f869d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -78,6 +78,11 @@ warn_redundant_casts = true # warn_unreachable = true # warn_unused_ignores = true +[[tool.pitloom.creator]] +name = "Joshua Watt" +email = "JPEWhacker@gmail.com" +type = "person" + [tool.pytest.ini_options] addopts = [ "--import-mode=importlib",