From 25b30374852bb71f0a2bdadbf8f37f604c2c7980 Mon Sep 17 00:00:00 2001 From: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com> Date: Sun, 19 Apr 2026 07:53:06 -0400 Subject: [PATCH] fix(ci): pnpm sbom generation (#27337) (#27339) Signed-off-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com> --- .github/workflows/release.yaml | 33 ++++-- .gitignore | 2 + docs/operator-manual/upgrading/3.4-3.5.md | 8 ++ hack/generate-ui-pnpm-sbom.sh | 127 ++++++++++++++++++++++ 4 files changed, 158 insertions(+), 12 deletions(-) create mode 100755 hack/generate-ui-pnpm-sbom.sh diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index 4acd04cbe1..019de2f413 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -243,28 +243,37 @@ jobs: SPDX_GEN_VERSION: v0.0.13 # defines the sigs.k8s.io/bom version to use. SIGS_BOM_VERSION: v0.2.1 - # comma delimited list of project relative folders to inspect for package - # managers (gomod, pnpm, npm). - PROJECT_FOLDERS: '.,./ui' # full qualified name of the docker image to be inspected DOCKER_IMAGE: ${{ needs.setup-variables.outputs.quay_image_name }} run: | + set -euo pipefail pnpm install --dir ./ui --frozen-lockfile go install github.com/spdx/spdx-sbom-generator/cmd/generator@$SPDX_GEN_VERSION go install sigs.k8s.io/bom/cmd/bom@$SIGS_BOM_VERSION - # Generate SPDX for project dependencies analyzing package managers - for folder in $(echo $PROJECT_FOLDERS | sed "s/,/ /g") - do - generator -p $folder -o /tmp - done + generator -p . -o /tmp - # Generate SPDX for binaries analyzing the docker image - if [[ ! -z $DOCKER_IMAGE ]]; then - bom generate -o /tmp/bom-docker-image.spdx -i $DOCKER_IMAGE + # When ui/ should use in-repo pnpm for `pnpm sbom` (11+): + # 1. In ui/package.json set "packageManager" to a pnpm 11+ release (e.g. pnpm@11.0.0), then from ./ui run + # `pnpm install` and commit the resulting ui/pnpm-lock.yaml so release CI's pnpm/action-setup matches. + # 2. Delete hack/generate-ui-pnpm-sbom.sh and remove the ./hack/generate-ui-pnpm-sbom.sh line below. + # 3. Uncomment: + # pnpm --dir ./ui sbom --sbom-format spdx --prod > /tmp/bom-ui-pnpm.spdx.json + ./hack/generate-ui-pnpm-sbom.sh --write /tmp/bom-ui-pnpm.spdx.json + + if [[ -n "${DOCKER_IMAGE:-}" ]]; then + bom generate -o /tmp/bom-docker-image.spdx -i "${DOCKER_IMAGE}" fi - cd /tmp && tar -zcf sbom.tar.gz *.spdx + cd /tmp + shopt -s nullglob + spdx_files=( *.spdx ) + shopt -u nullglob + if [[ ${#spdx_files[@]} -eq 0 ]]; then + echo "No .spdx files produced under /tmp" + exit 1 + fi + tar -zcf sbom.tar.gz "${spdx_files[@]}" bom-ui-pnpm.spdx.json - name: Generate SBOM hash shell: bash diff --git a/.gitignore b/.gitignore index ff37d6d26f..61fd691de6 100644 --- a/.gitignore +++ b/.gitignore @@ -16,6 +16,8 @@ coverage.out test-results .scannerwork .scratch +# pnpm SBOM helper (hack/generate-ui-pnpm-sbom.sh) download cache — remove this line when that script is deleted. +hack/.cache/ node_modules/ .kube/ ./test/cmp/*.sock diff --git a/docs/operator-manual/upgrading/3.4-3.5.md b/docs/operator-manual/upgrading/3.4-3.5.md index d56a00b5ca..dd634d8094 100644 --- a/docs/operator-manual/upgrading/3.4-3.5.md +++ b/docs/operator-manual/upgrading/3.4-3.5.md @@ -38,3 +38,11 @@ No action is required for users who do not have impersonation enabled. ## Helm Upgraded ## Custom Healthchecks Added + +## Other Changes + +### Release `sbom.tar.gz` contents + +For normal GitHub releases, **`sbom.tar.gz`** still includes **`bom-go-mod.spdx`** (Go dependencies, tag-value SPDX from `spdx-sbom-generator`) and **`bom-docker-image.spdx`** (the published release image, tag-value SPDX from `sigs.k8s.io/bom`). The UI dependency list is now **`bom-ui-pnpm.spdx.json`**: **SPDX 2.3 JSON** from **`pnpm sbom`**, replacing the old tag-value **`./ui`** output from `spdx-sbom-generator`. + +If you consume this archive with tooling that only looked at **`*.spdx`** files, extend it to handle **`bom-ui-pnpm.spdx.json`** as well, or verify **`sbom.tar.gz`** using **`argocd-sbom.intoto.jsonl`** without depending on a fixed internal file list. diff --git a/hack/generate-ui-pnpm-sbom.sh b/hack/generate-ui-pnpm-sbom.sh new file mode 100755 index 0000000000..e2cf4ca174 --- /dev/null +++ b/hack/generate-ui-pnpm-sbom.sh @@ -0,0 +1,127 @@ +#!/usr/bin/env bash +# Generate SPDX 2.3 JSON for ui/ using a pinned pnpm standalone that includes `pnpm sbom`. +# Supports Linux (amd64, arm64) and macOS (Intel, Apple Silicon). Used locally and in +# .github/workflows/release.yaml (see --write for CI output path). +# +# CLEANUP once ui/package.json `packageManager` is pnpm 11+ and ui/pnpm-lock.yaml is refreshed (`pnpm install` in ./ui): +# - Delete this script; in .github/workflows/release.yaml use `pnpm sbom` (see comments there). The export of +# COREPACK_ENABLE_STRICT=0 below goes away with this file. +# ============================================================================= +set -euo pipefail + +ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +readonly PNPM_SBOM_RELEASE_TAG='v11.0.0-rc.0' +CACHE_ROOT="${ROOT}/hack/.cache/pnpm-sbom" + +# ui/package.json pins pnpm 10.x; standalone pnpm 11 otherwise exec-switches to that version (no `sbom`). +export COREPACK_ENABLE_STRICT=0 + +write_out="" +sbom_args=() +while [[ $# -gt 0 ]]; do + case "$1" in + -o|--write|--output) + if [[ $# -lt 2 ]]; then + echo "usage: $0 [--write PATH] [extra args for pnpm sbom...]" >&2 + exit 1 + fi + write_out="$2" + shift 2 + ;; + -h|--help) + echo "usage: $0 [--write PATH] [extra args for pnpm sbom...]" >&2 + echo " Default: write SPDX JSON to stdout." >&2 + echo " --write PATH: write SPDX JSON to PATH (for CI)." >&2 + exit 0 + ;; + *) + sbom_args+=("$1") + shift + ;; + esac +done + +os="$(uname -s | tr '[:upper:]' '[:lower:]')" +arch="$(uname -m)" +tarball="" +expected_sha="" +case "${os}-${arch}" in + darwin-arm64) + tarball="pnpm-macos-arm64.tar.gz" + # https://github.com/pnpm/pnpm/releases/download/v11.0.0-rc.0/pnpm-macos-arm64.tar.gz + expected_sha='ff7e95be75a27c793fbcdf49c4f9e0c8adfc54e214c7aea4b1306445f31e5391' + ;; + darwin-x86_64) + tarball="pnpm-macos-x64.tar.gz" + # https://github.com/pnpm/pnpm/releases/download/v11.0.0-rc.0/pnpm-macos-x64.tar.gz + expected_sha='13fa24a2e0e25af7837acf13d84515fd5a4daab582ac271b01c0b574388ce0bd' + ;; + linux-x86_64) + tarball="pnpm-linux-x64.tar.gz" + # https://github.com/pnpm/pnpm/releases/download/v11.0.0-rc.0/pnpm-linux-x64.tar.gz + expected_sha='fe82b94125a6b743456b869e823611a8837b545f2535e4602578e4c9fdb5742a' + ;; + linux-aarch64|linux-arm64) + tarball="pnpm-linux-arm64.tar.gz" + # https://github.com/pnpm/pnpm/releases/download/v11.0.0-rc.0/pnpm-linux-arm64.tar.gz + expected_sha='69ad2d528f4a2c00fd42541a80c13491c57e66b2765b2d9d89829aeb0f6482be' + ;; + *) + echo "Unsupported OS/arch for pinned pnpm SBOM helper: ${os}-${arch}" >&2 + exit 1 + ;; +esac + +sha256_file() { + local f="$1" + if command -v sha256sum >/dev/null 2>&1; then + sha256sum "${f}" | awk '{print $1}' + else + shasum -a 256 "${f}" | awk '{print $1}' + fi +} + +CACHE="${CACHE_ROOT}/${PNPM_SBOM_RELEASE_TAG}" +mkdir -p "${CACHE}" +TAR_PATH="${CACHE}/${tarball}" + +if [[ ! -f "${TAR_PATH}" ]]; then + echo "Downloading ${PNPM_SBOM_RELEASE_TAG}/${tarball} ..." >&2 + curl -fsSL "https://github.com/pnpm/pnpm/releases/download/${PNPM_SBOM_RELEASE_TAG}/${tarball}" \ + -o "${TAR_PATH}.part" + actual="$(sha256_file "${TAR_PATH}.part")" + if [[ "${actual}" != "${expected_sha}" ]]; then + echo "SHA256 mismatch for ${tarball}: expected ${expected_sha}, got ${actual}" >&2 + exit 1 + fi + mv "${TAR_PATH}.part" "${TAR_PATH}" +fi + +actual="$(sha256_file "${TAR_PATH}")" +if [[ "${actual}" != "${expected_sha}" ]]; then + echo "SHA256 mismatch for cached ${tarball}: expected ${expected_sha}, got ${actual}" >&2 + exit 1 +fi + +if [[ ! -x "${CACHE}/pnpm" ]]; then + tar -xzf "${TAR_PATH}" -C "${CACHE}" + chmod +x "${CACHE}/pnpm" +fi + +echo "Using ${CACHE}/pnpm ($("${CACHE}/pnpm" --version))" >&2 + +run_sbom() { + # With `set -u`, expanding an empty "${sbom_args[@]}" is an error on some bash builds. + if [[ ${#sbom_args[@]} -gt 0 ]]; then + (cd "${ROOT}" && "${CACHE}/pnpm" --dir ./ui sbom --sbom-format spdx --prod "${sbom_args[@]}") + else + (cd "${ROOT}" && "${CACHE}/pnpm" --dir ./ui sbom --sbom-format spdx --prod) + fi +} + +if [[ -n "${write_out}" ]]; then + run_sbom >"${write_out}" + test -s "${write_out}" +else + run_sbom +fi