mirror of
https://github.com/rcourtman/Pulse.git
synced 2026-07-09 16:00:59 +00:00
Add historical release asset backfill workflow
This commit is contained in:
parent
3308bb733e
commit
16ad67a9b5
9 changed files with 875 additions and 180 deletions
60
.github/workflows/backfill-release-assets.yml
vendored
Normal file
60
.github/workflows/backfill-release-assets.yml
vendored
Normal file
|
|
@ -0,0 +1,60 @@
|
|||
name: Backfill Release Assets
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
tag:
|
||||
description: 'Published release tag to repair (for example v6.0.0-rc.2)'
|
||||
required: true
|
||||
type: string
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
concurrency:
|
||||
group: backfill-release-assets-${{ inputs.tag }}
|
||||
cancel-in-progress: false
|
||||
|
||||
jobs:
|
||||
backfill:
|
||||
runs-on: ubuntu-24.04
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
|
||||
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@40f1582b2485089dde7abd97c1529aa768e1baff # v5
|
||||
with:
|
||||
go-version: '1.25.9'
|
||||
cache: true
|
||||
|
||||
- name: Install Syft
|
||||
run: |
|
||||
set -euo pipefail
|
||||
|
||||
SYFT_VERSION="1.42.4"
|
||||
SYFT_ARCHIVE="syft_${SYFT_VERSION}_linux_amd64.tar.gz"
|
||||
SYFT_SHA256="590650c2743b83f327d1bf9bec64f6f83b7fec504187bb84f500c862bf8f2a0f"
|
||||
TMP_DIR="$(mktemp -d)"
|
||||
trap 'rm -rf "$TMP_DIR"' EXIT
|
||||
|
||||
curl -fsSL "https://github.com/anchore/syft/releases/download/v${SYFT_VERSION}/${SYFT_ARCHIVE}" \
|
||||
-o "${TMP_DIR}/${SYFT_ARCHIVE}"
|
||||
printf '%s %s\n' "${SYFT_SHA256}" "${TMP_DIR}/${SYFT_ARCHIVE}" | sha256sum --check --
|
||||
tar -xzf "${TMP_DIR}/${SYFT_ARCHIVE}" -C "${TMP_DIR}" syft
|
||||
install -m 0755 "${TMP_DIR}/syft" /usr/local/bin/syft
|
||||
syft version
|
||||
|
||||
- name: Backfill published release assets
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
PULSE_UPDATE_SIGNING_KEY: ${{ secrets.PULSE_UPDATE_SIGNING_KEY }}
|
||||
run: |
|
||||
./scripts/backfill-release-assets.sh --tag "${{ inputs.tag }}" --repo "${{ github.repository }}"
|
||||
|
||||
- name: Validate published release packet
|
||||
run: |
|
||||
./scripts/validate-published-release.sh "${{ inputs.tag }}" "${{ github.repository }}"
|
||||
|
|
@ -89,6 +89,9 @@ server-side update execution surfaces.
|
|||
67. `scripts/install-docker.sh`
|
||||
68. `scripts/validate-published-release.sh`
|
||||
69. `scripts/validate-release.sh`
|
||||
70. `scripts/release_asset_common.sh`
|
||||
71. `scripts/backfill-release-assets.sh`
|
||||
72. `.github/workflows/backfill-release-assets.yml`
|
||||
|
||||
## Shared Boundaries
|
||||
|
||||
|
|
@ -100,7 +103,7 @@ server-side update execution surfaces.
|
|||
## Extension Points
|
||||
|
||||
1. Add or change deployment-type detection, update planning, or apply behavior through `internal/updates/`
|
||||
2. Add or change release-build metadata injection, Docker build-context allowlists, release artifact assembly, governed promotion metadata resolution, the canonical version file, operator-facing release packet content, prerelease feedback intake wording, or the canonical in-repo v6 upgrade guide through `scripts/build-release.sh`, `scripts/release_ldflags.sh`, `scripts/check-workflow-dispatch-inputs.py`, `scripts/release_control/render_release_body.py`, `scripts/release_control/resolve_release_promotion.py`, `scripts/release_control/record_rc_to_ga_rehearsal.py`, `scripts/release_control/internal/record_rc_to_ga_rehearsal.py`, `scripts/release_control/release_promotion_policy_support.py`, `.dockerignore`, `Dockerfile`, `.github/ISSUE_TEMPLATE/v6_rc_feedback.yml`, `docs/RELEASE_NOTES.md`, `docs/releases/`, `docs/UPGRADE_v6.md`, `docs/release-control/v6/internal/RELEASE_PROMOTION_POLICY.md`, `docs/release-control/v6/internal/PRE_RELEASE_CHECKLIST.md`, `docs/release-control/v6/internal/RC_TO_GA_REHEARSAL_TEMPLATE.md`, `scripts/validate-release.sh`, `scripts/validate-published-release.sh`, the operator dispatch helpers `scripts/trigger-release.sh` and `scripts/trigger-release-dry-run.sh`, and the governed release workflows `.github/workflows/create-release.yml`, `.github/workflows/deploy-demo-server.yml`, `.github/workflows/helm-pages.yml`, `.github/workflows/publish-docker.yml`, `.github/workflows/publish-helm-chart.yml`, `.github/workflows/promote-floating-tags.yml`, `.github/workflows/release-dry-run.yml`, and `.github/workflows/update-demo-server.yml`
|
||||
2. Add or change release-build metadata injection, Docker build-context allowlists, release artifact assembly, governed promotion metadata resolution, the canonical version file, operator-facing release packet content, prerelease feedback intake wording, historical published-release integrity backfill, or the canonical in-repo v6 upgrade guide through `scripts/build-release.sh`, `scripts/release_asset_common.sh`, `scripts/backfill-release-assets.sh`, `scripts/release_ldflags.sh`, `scripts/check-workflow-dispatch-inputs.py`, `scripts/release_control/render_release_body.py`, `scripts/release_control/resolve_release_promotion.py`, `scripts/release_control/record_rc_to_ga_rehearsal.py`, `scripts/release_control/internal/record_rc_to_ga_rehearsal.py`, `scripts/release_control/release_promotion_policy_support.py`, `.dockerignore`, `Dockerfile`, `.github/ISSUE_TEMPLATE/v6_rc_feedback.yml`, `docs/RELEASE_NOTES.md`, `docs/releases/`, `docs/UPGRADE_v6.md`, `docs/release-control/v6/internal/RELEASE_PROMOTION_POLICY.md`, `docs/release-control/v6/internal/PRE_RELEASE_CHECKLIST.md`, `docs/release-control/v6/internal/RC_TO_GA_REHEARSAL_TEMPLATE.md`, `scripts/validate-release.sh`, `scripts/validate-published-release.sh`, the operator dispatch helpers `scripts/trigger-release.sh` and `scripts/trigger-release-dry-run.sh`, and the governed release workflows `.github/workflows/backfill-release-assets.yml`, `.github/workflows/create-release.yml`, `.github/workflows/deploy-demo-server.yml`, `.github/workflows/helm-pages.yml`, `.github/workflows/publish-docker.yml`, `.github/workflows/publish-helm-chart.yml`, `.github/workflows/promote-floating-tags.yml`, `.github/workflows/release-dry-run.yml`, and `.github/workflows/update-demo-server.yml`
|
||||
3. Add or change shell installer, Docker bootstrap installer, Windows installer, container-agent installer, repo-root compose defaults, or auto-update script behavior through `scripts/install.sh`, `scripts/install-docker.sh`, `scripts/install.ps1`, `scripts/install-container-agent.sh`, `docker-compose.yml`, and `scripts/pulse-auto-update.sh`
|
||||
4. Add or change server update transport through `internal/api/updates.go` and `frontend-modern/src/api/updates.ts`
|
||||
5. Add or change local dev-runtime orchestration, managed ownership, browser-runtime proof wiring, frontend/backend coherence diagnostics, canonical developer entry wrappers, dependency manifest floors, frontend build chunking, or dev-runtime helper control surfaces through `scripts/hot-dev.sh`, `scripts/hot-dev-bg.sh`, `scripts/dev-deploy-agent.sh`, `Makefile`, `package.json`, `package-lock.json`, `frontend-modern/package.json`, `frontend-modern/package-lock.json`, `frontend-modern/vite.config.ts`, `go.mod`, `go.sum`, `scripts/dev-check.sh`, `scripts/toggle-mock.sh`, `scripts/clean-mock-alerts.sh`, `scripts/dev-launchd-setup.sh`, `scripts/dev-launchd-wrapper.sh`, `scripts/run_demo_public_browser_smoke.sh`, `scripts/demo_public_browser_smoke.cjs`, `scripts/com.pulse.hot-dev.plist.template`, `tests/integration/scripts/managed-dev-runtime.mjs`, `tests/integration/playwright.config.ts`, `tests/integration/tests/helpers.ts`, `tests/integration/tests/runtime-defaults.ts`, `tests/integration/README.md`, and `tests/integration/QUICK_START.md`
|
||||
|
|
@ -1049,20 +1052,30 @@ coverage: update transport changes must continue to carry the direct
|
|||
API-contract proof path.
|
||||
That same governed release-promotion boundary now also owns detached agent and
|
||||
installer signatures. `scripts/build-release.sh`,
|
||||
`scripts/release_asset_common.sh`, `scripts/backfill-release-assets.sh`,
|
||||
`scripts/release_update_key.go`, `scripts/render_installers.go`,
|
||||
`scripts/release_ldflags.sh`, `Dockerfile`, `.github/workflows/create-release.yml`,
|
||||
`.github/workflows/publish-docker.yml`, `scripts/validate-release.sh`, and
|
||||
`scripts/validate-published-release.sh` must derive the embedded update trust
|
||||
root and installer SSH trust root from the governed release signing key,
|
||||
render release installers with that pinned SSH verifier, emit both `.sig` and
|
||||
`.sshsig` sidecars for shipped agent binaries and installer assets, emit a
|
||||
standalone SPDX JSON SBOM for the assembled release packet, upload those
|
||||
security artifacts with the matching release packet, and fail validation if
|
||||
any published artifact or `checksums.txt` is missing its `.sshsig` sidecar or
|
||||
if the canonical release-packet SBOM is absent so published RC/stable
|
||||
downloads can keep the updater and installer trust chain fail-closed instead
|
||||
of downgrading to checksum-only trust and can publish a shareable non-image
|
||||
software inventory alongside the signed binaries.
|
||||
`scripts/release_ldflags.sh`, `Dockerfile`,
|
||||
`.github/workflows/backfill-release-assets.yml`,
|
||||
`.github/workflows/create-release.yml`, `.github/workflows/publish-docker.yml`,
|
||||
`scripts/validate-release.sh`, and `scripts/validate-published-release.sh`
|
||||
must derive the embedded update trust root and installer SSH trust root from
|
||||
the governed release signing key, render release installers with that pinned
|
||||
SSH verifier, emit both `.sig` and `.sshsig` sidecars for shipped agent
|
||||
binaries and installer assets, emit a standalone SPDX JSON SBOM for the
|
||||
assembled release packet, upload those security artifacts with the matching
|
||||
release packet, and fail validation if any published artifact or
|
||||
`checksums.txt` is missing its `.sshsig` sidecar or if the canonical
|
||||
release-packet SBOM is absent so published RC/stable downloads can keep the
|
||||
updater and installer trust chain fail-closed instead of downgrading to
|
||||
checksum-only trust and can publish a shareable non-image software inventory
|
||||
alongside the signed binaries.
|
||||
Historical published-release repair must flow through
|
||||
`scripts/backfill-release-assets.sh` and
|
||||
`.github/workflows/backfill-release-assets.yml`, which download the
|
||||
already-published packet and regenerate only the derived integrity assets
|
||||
(`checksums.txt`, `.sha256`, `.sig`, `.sshsig`, and the canonical
|
||||
release-packet SBOM`) from those shipped bytes instead of rebuilding binaries
|
||||
from the current branch tip.
|
||||
The shell-installer boundary now also owns the QNAP boot bootstrap and
|
||||
teardown contract end to end: `scripts/install.sh` must persist the wrapper on
|
||||
the writable data volume, write a flash-backed `autorun.sh` block that waits
|
||||
|
|
|
|||
|
|
@ -2429,6 +2429,7 @@
|
|||
"owned_files": [
|
||||
".dockerignore",
|
||||
".github/ISSUE_TEMPLATE/v6_rc_feedback.yml",
|
||||
".github/workflows/backfill-release-assets.yml",
|
||||
".github/workflows/create-release.yml",
|
||||
".github/workflows/deploy-demo-server.yml",
|
||||
".github/workflows/helm-pages.yml",
|
||||
|
|
@ -2458,6 +2459,7 @@
|
|||
"Makefile",
|
||||
"package-lock.json",
|
||||
"package.json",
|
||||
"scripts/backfill-release-assets.sh",
|
||||
"scripts/build-release.sh",
|
||||
"scripts/check-workflow-dispatch-inputs.py",
|
||||
"scripts/clean-mock-alerts.sh",
|
||||
|
|
@ -2474,6 +2476,7 @@
|
|||
"scripts/install.ps1",
|
||||
"scripts/install.sh",
|
||||
"scripts/pulse-auto-update.sh",
|
||||
"scripts/release_asset_common.sh",
|
||||
"scripts/release_control/internal/record_rc_to_ga_rehearsal.py",
|
||||
"scripts/release_control/record_rc_to_ga_rehearsal.py",
|
||||
"scripts/release_control/release_promotion_policy_support.py",
|
||||
|
|
@ -2555,6 +2558,7 @@
|
|||
],
|
||||
"match_files": [
|
||||
".github/ISSUE_TEMPLATE/v6_rc_feedback.yml",
|
||||
".github/workflows/backfill-release-assets.yml",
|
||||
".github/workflows/create-release.yml",
|
||||
".github/workflows/helm-pages.yml",
|
||||
".github/workflows/promote-floating-tags.yml",
|
||||
|
|
@ -2595,12 +2599,15 @@
|
|||
".dockerignore",
|
||||
".github/workflows/deploy-demo-server.yml",
|
||||
"Dockerfile",
|
||||
"scripts/backfill-release-assets.sh",
|
||||
"scripts/build-release.sh",
|
||||
"scripts/release_asset_common.sh",
|
||||
"scripts/release_ldflags.sh"
|
||||
],
|
||||
"allow_same_subsystem_tests": false,
|
||||
"test_prefixes": [],
|
||||
"exact_files": [
|
||||
"scripts/installtests/backfill_release_assets_test.go",
|
||||
"scripts/installtests/build_release_assets_test.go",
|
||||
"scripts/installtests/release_ldflags_test.go"
|
||||
]
|
||||
|
|
|
|||
244
scripts/backfill-release-assets.sh
Executable file
244
scripts/backfill-release-assets.sh
Executable file
|
|
@ -0,0 +1,244 @@
|
|||
#!/usr/bin/env bash
|
||||
|
||||
# Regenerate integrity metadata for an already-published Pulse release packet
|
||||
# without rebuilding the underlying payload artifacts from the current branch.
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
PULSE_SCRIPTS_DIR="${SCRIPT_DIR}"
|
||||
PULSE_REPO_ROOT="$(cd "${SCRIPT_DIR}/.." && pwd)"
|
||||
cd "${PULSE_REPO_ROOT}"
|
||||
|
||||
source "${SCRIPT_DIR}/release_asset_common.sh"
|
||||
|
||||
usage() {
|
||||
cat <<'EOF' >&2
|
||||
Usage: scripts/backfill-release-assets.sh --tag <tag> [options]
|
||||
|
||||
Options:
|
||||
--repo <owner/repo> GitHub repository (default: rcourtman/Pulse)
|
||||
--release-dir <path> Reuse an existing local asset directory
|
||||
--skip-download Skip gh release download and use --release-dir as-is
|
||||
--skip-upload Skip gh release upload after regenerating metadata
|
||||
|
||||
Examples:
|
||||
scripts/backfill-release-assets.sh --tag v6.0.0-rc.2
|
||||
scripts/backfill-release-assets.sh --tag v6.0.0-rc.2 --release-dir /tmp/release --skip-download --skip-upload
|
||||
EOF
|
||||
exit 1
|
||||
}
|
||||
|
||||
TAG=""
|
||||
REPO="rcourtman/Pulse"
|
||||
RELEASE_DIR=""
|
||||
SKIP_DOWNLOAD="false"
|
||||
SKIP_UPLOAD="false"
|
||||
WORK_DIR=""
|
||||
PAYLOAD_DIR=""
|
||||
RELEASE_PACKET_SBOM=""
|
||||
payload_files=()
|
||||
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case "$1" in
|
||||
--tag)
|
||||
TAG="${2:-}"
|
||||
shift 2
|
||||
;;
|
||||
--repo)
|
||||
REPO="${2:-}"
|
||||
shift 2
|
||||
;;
|
||||
--release-dir)
|
||||
RELEASE_DIR="${2:-}"
|
||||
shift 2
|
||||
;;
|
||||
--skip-download)
|
||||
SKIP_DOWNLOAD="true"
|
||||
shift
|
||||
;;
|
||||
--skip-upload)
|
||||
SKIP_UPLOAD="true"
|
||||
shift
|
||||
;;
|
||||
*)
|
||||
usage
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
[[ -n "${TAG}" ]] || usage
|
||||
|
||||
RELEASE_PACKET_SBOM="pulse-${TAG}-release.sbom.spdx.json"
|
||||
|
||||
cleanup() {
|
||||
pulse_release_cleanup_signing_state
|
||||
if [[ -n "${PAYLOAD_DIR}" && -d "${PAYLOAD_DIR}" ]]; then
|
||||
rm -rf "${PAYLOAD_DIR}"
|
||||
fi
|
||||
if [[ -n "${WORK_DIR}" && -d "${WORK_DIR}" ]]; then
|
||||
rm -rf "${WORK_DIR}"
|
||||
fi
|
||||
}
|
||||
trap cleanup EXIT
|
||||
|
||||
ensure_release_is_published() {
|
||||
local metadata=""
|
||||
metadata="$(gh release view "${TAG}" -R "${REPO}" --json isDraft,tagName)"
|
||||
if [[ "$(printf '%s' "${metadata}" | jq -r '.isDraft')" != "false" ]]; then
|
||||
echo "Error: ${TAG} is still a draft release; use the normal release pipeline instead of historical backfill." >&2
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
download_release_assets() {
|
||||
if [[ -z "${RELEASE_DIR}" ]]; then
|
||||
WORK_DIR="$(mktemp -d)"
|
||||
RELEASE_DIR="${WORK_DIR}/release"
|
||||
fi
|
||||
mkdir -p "${RELEASE_DIR}"
|
||||
echo "Downloading published assets for ${TAG} from ${REPO}..."
|
||||
gh release download "${TAG}" -R "${REPO}" --dir "${RELEASE_DIR}" --clobber
|
||||
}
|
||||
|
||||
verify_existing_checksums() {
|
||||
local checksums_path="${RELEASE_DIR}/checksums.txt"
|
||||
local status=0
|
||||
|
||||
if [[ ! -f "${checksums_path}" ]]; then
|
||||
echo "Error: ${checksums_path} is missing." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
while read -r checksum filename _; do
|
||||
[[ -n "${checksum:-}" ]] || continue
|
||||
[[ "${checksum}" =~ ^# ]] && continue
|
||||
|
||||
if [[ -z "${filename:-}" ]]; then
|
||||
echo "Malformed checksums.txt line: ${checksum}" >&2
|
||||
status=1
|
||||
continue
|
||||
fi
|
||||
|
||||
if [[ ! -f "${RELEASE_DIR}/${filename}" ]]; then
|
||||
echo "Missing published artifact ${filename}" >&2
|
||||
status=1
|
||||
continue
|
||||
fi
|
||||
|
||||
local actual_checksum=""
|
||||
actual_checksum="$(sha256sum "${RELEASE_DIR}/${filename}" | awk '{print $1}')"
|
||||
if [[ "${actual_checksum}" != "${checksum}" ]]; then
|
||||
echo "Checksum mismatch for ${filename}: expected ${checksum}, got ${actual_checksum}" >&2
|
||||
status=1
|
||||
fi
|
||||
|
||||
local sha_path="${RELEASE_DIR}/${filename}.sha256"
|
||||
local expected_line="${checksum} ${filename}"
|
||||
if [[ ! -f "${sha_path}" ]]; then
|
||||
echo "Missing ${filename}.sha256" >&2
|
||||
status=1
|
||||
continue
|
||||
fi
|
||||
|
||||
local sha_content=""
|
||||
sha_content="$(tr -d '\r' < "${sha_path}" | sed 's/[[:space:]]*$//')"
|
||||
if [[ "${sha_content}" != "${expected_line}" ]]; then
|
||||
echo "${filename}.sha256 content mismatch (expected '${expected_line}', got '${sha_content}')" >&2
|
||||
status=1
|
||||
fi
|
||||
done < "${checksums_path}"
|
||||
|
||||
if [[ "${status}" -ne 0 ]]; then
|
||||
echo "Error: published release packet for ${TAG} failed integrity verification before backfill." >&2
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
collect_payload_files() {
|
||||
payload_files=()
|
||||
|
||||
while read -r checksum filename _; do
|
||||
[[ -n "${checksum:-}" ]] || continue
|
||||
[[ "${checksum}" =~ ^# ]] && continue
|
||||
[[ -n "${filename:-}" ]] || continue
|
||||
|
||||
if [[ "${filename}" == "${RELEASE_PACKET_SBOM}" ]]; then
|
||||
continue
|
||||
fi
|
||||
|
||||
payload_files+=( "${filename}" )
|
||||
done < "${RELEASE_DIR}/checksums.txt"
|
||||
|
||||
if [[ ${#payload_files[@]} -eq 0 ]]; then
|
||||
echo "Error: ${TAG} does not contain any payload artifacts in checksums.txt." >&2
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
stage_payload_assets() {
|
||||
PAYLOAD_DIR="$(mktemp -d)"
|
||||
|
||||
local filename=""
|
||||
for filename in "${payload_files[@]}"; do
|
||||
mkdir -p "${PAYLOAD_DIR}/$(dirname "${filename}")"
|
||||
cp "${RELEASE_DIR}/${filename}" "${PAYLOAD_DIR}/${filename}"
|
||||
done
|
||||
}
|
||||
|
||||
rebuild_release_packet_integrity() {
|
||||
rm -f \
|
||||
"${RELEASE_DIR}/${RELEASE_PACKET_SBOM}" \
|
||||
"${RELEASE_DIR}/${RELEASE_PACKET_SBOM}.sig" \
|
||||
"${RELEASE_DIR}/${RELEASE_PACKET_SBOM}.sshsig"
|
||||
|
||||
pulse_release_generate_packet_sbom "${PAYLOAD_DIR}" "${RELEASE_PACKET_SBOM}"
|
||||
cp "${PAYLOAD_DIR}/${RELEASE_PACKET_SBOM}" "${RELEASE_DIR}/${RELEASE_PACKET_SBOM}"
|
||||
|
||||
local -a checksum_files=("${payload_files[@]}" "${RELEASE_PACKET_SBOM}")
|
||||
pulse_release_write_checksums_and_signatures "${RELEASE_DIR}" "${checksum_files[@]}"
|
||||
}
|
||||
|
||||
upload_release_assets() {
|
||||
echo "Uploading regenerated integrity assets for ${TAG}..."
|
||||
gh release upload "${TAG}" "${RELEASE_DIR}/checksums.txt" --clobber
|
||||
if compgen -G "${RELEASE_DIR}/*.sha256" > /dev/null; then
|
||||
gh release upload "${TAG}" "${RELEASE_DIR}"/*.sha256 --clobber
|
||||
fi
|
||||
if compgen -G "${RELEASE_DIR}/*.sig" > /dev/null; then
|
||||
gh release upload "${TAG}" "${RELEASE_DIR}"/*.sig --clobber
|
||||
fi
|
||||
if compgen -G "${RELEASE_DIR}/*.sshsig" > /dev/null; then
|
||||
gh release upload "${TAG}" "${RELEASE_DIR}"/*.sshsig --clobber
|
||||
fi
|
||||
gh release upload "${TAG}" "${RELEASE_DIR}/${RELEASE_PACKET_SBOM}" --clobber
|
||||
}
|
||||
|
||||
if [[ "${SKIP_DOWNLOAD}" == "true" ]]; then
|
||||
[[ -n "${RELEASE_DIR}" ]] || {
|
||||
echo "Error: --skip-download requires --release-dir." >&2
|
||||
exit 1
|
||||
}
|
||||
else
|
||||
ensure_release_is_published
|
||||
download_release_assets
|
||||
fi
|
||||
|
||||
[[ -d "${RELEASE_DIR}" ]] || {
|
||||
echo "Error: release directory ${RELEASE_DIR} does not exist." >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
pulse_release_prepare_signing_state "pulse-installer" "pulse-install"
|
||||
verify_existing_checksums
|
||||
collect_payload_files
|
||||
stage_payload_assets
|
||||
rebuild_release_packet_integrity
|
||||
|
||||
if [[ "${SKIP_UPLOAD}" == "true" ]]; then
|
||||
echo "Skipped GitHub upload; regenerated assets are in ${RELEASE_DIR}"
|
||||
else
|
||||
upload_release_assets
|
||||
fi
|
||||
|
||||
echo "Historical release asset backfill completed for ${TAG}."
|
||||
|
|
@ -5,6 +5,13 @@
|
|||
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
PULSE_SCRIPTS_DIR="${SCRIPT_DIR}"
|
||||
PULSE_REPO_ROOT="$(cd "${SCRIPT_DIR}/.." && pwd)"
|
||||
cd "${PULSE_REPO_ROOT}"
|
||||
|
||||
source "${SCRIPT_DIR}/release_asset_common.sh"
|
||||
|
||||
# Prefer the pinned toolchain from go.mod (toolchain directive).
|
||||
# If /usr/local/go exists (typical in CI images), prepend it to PATH.
|
||||
if [ -x /usr/local/go/bin/go ]; then
|
||||
|
|
@ -30,8 +37,6 @@ VERSION=${1:-$(cat VERSION)}
|
|||
BUILD_DIR="build"
|
||||
RELEASE_DIR="release"
|
||||
RENDERED_INSTALLERS_DIR="${BUILD_DIR}/rendered-installers"
|
||||
INSTALLER_SSH_SIGNER_IDENTITY="pulse-installer"
|
||||
INSTALLER_SSH_SIGNER_NAMESPACE="pulse-install"
|
||||
RELEASE_PACKET_SBOM="pulse-v${VERSION}-release.sbom.spdx.json"
|
||||
|
||||
echo "Building Pulse v${VERSION}..."
|
||||
|
|
@ -76,36 +81,10 @@ fi
|
|||
# Require update signing for release-grade agent and installer verification.
|
||||
# Explicitly opt out with PULSE_ALLOW_MISSING_UPDATE_SIGNING_KEY=true for local-only debugging.
|
||||
update_ldflags_args=()
|
||||
update_ssh_public_key=""
|
||||
update_ssh_private_key_file=""
|
||||
if [[ -z "${PULSE_UPDATE_SIGNING_KEY:-}" ]]; then
|
||||
if [[ "${PULSE_ALLOW_MISSING_UPDATE_SIGNING_KEY:-false}" == "true" ]]; then
|
||||
echo "Warning: PULSE_UPDATE_SIGNING_KEY not set; continuing because PULSE_ALLOW_MISSING_UPDATE_SIGNING_KEY=true."
|
||||
else
|
||||
echo "Error: PULSE_UPDATE_SIGNING_KEY is required for release builds." >&2
|
||||
echo "Set PULSE_ALLOW_MISSING_UPDATE_SIGNING_KEY=true only for local non-release debugging." >&2
|
||||
exit 1
|
||||
fi
|
||||
else
|
||||
update_public_key=$(go run ./scripts/release_update_key.go public-key --private-key "${PULSE_UPDATE_SIGNING_KEY}")
|
||||
if [[ -z "${update_public_key}" ]]; then
|
||||
echo "Error: failed to derive update signing public key." >&2
|
||||
exit 1
|
||||
fi
|
||||
if ! command -v ssh-keygen >/dev/null 2>&1; then
|
||||
echo "Error: ssh-keygen is required to sign installer release assets." >&2
|
||||
exit 1
|
||||
fi
|
||||
update_ssh_public_key=$(go run ./scripts/release_update_key.go public-key-ssh --private-key "${PULSE_UPDATE_SIGNING_KEY}" --comment "${INSTALLER_SSH_SIGNER_IDENTITY}")
|
||||
if [[ -z "${update_ssh_public_key}" ]]; then
|
||||
echo "Error: failed to derive installer SSH signing public key." >&2
|
||||
exit 1
|
||||
fi
|
||||
update_ssh_private_key_file=$(mktemp)
|
||||
trap 'rm -f "${update_ssh_private_key_file}"' EXIT
|
||||
go run ./scripts/release_update_key.go openssh-private-key --private-key "${PULSE_UPDATE_SIGNING_KEY}" --comment "${INSTALLER_SSH_SIGNER_IDENTITY}" > "${update_ssh_private_key_file}"
|
||||
chmod 600 "${update_ssh_private_key_file}"
|
||||
update_ldflags_args=(--update-public-keys "${update_public_key}")
|
||||
pulse_release_prepare_signing_state "pulse-installer" "pulse-install"
|
||||
trap 'pulse_release_cleanup_signing_state' EXIT
|
||||
if [[ -n "${PULSE_RELEASE_UPDATE_PUBLIC_KEY:-}" ]]; then
|
||||
update_ldflags_args=(--update-public-keys "${PULSE_RELEASE_UPDATE_PUBLIC_KEY}")
|
||||
fi
|
||||
|
||||
render_release_installers() {
|
||||
|
|
@ -114,49 +93,7 @@ render_release_installers() {
|
|||
go run ./scripts/render_installers.go \
|
||||
--source-dir ./scripts \
|
||||
--output-dir "${output_dir}" \
|
||||
--installer-ssh-public-key "${update_ssh_public_key}"
|
||||
}
|
||||
|
||||
sign_release_file() {
|
||||
local file="$1"
|
||||
if [[ -z "${PULSE_UPDATE_SIGNING_KEY:-}" ]]; then
|
||||
return 0
|
||||
fi
|
||||
rm -f "${file}.sig" "${file}.sshsig"
|
||||
ssh-keygen -q -Y sign \
|
||||
-f "${update_ssh_private_key_file}" \
|
||||
-n "${INSTALLER_SSH_SIGNER_NAMESPACE}" \
|
||||
"${file}" >/dev/null
|
||||
mv "${file}.sig" "${file}.sshsig"
|
||||
go run ./scripts/release_update_key.go sign --private-key "${PULSE_UPDATE_SIGNING_KEY}" --file "${file}" > "${file}.sig"
|
||||
}
|
||||
|
||||
sign_directory_release_assets() {
|
||||
local dir="$1"
|
||||
if [[ -z "${PULSE_UPDATE_SIGNING_KEY:-}" ]]; then
|
||||
return 0
|
||||
fi
|
||||
while IFS= read -r -d '' file; do
|
||||
sign_release_file "${file}"
|
||||
done < <(find "${dir}" -maxdepth 1 -type f ! -name '*.sig' ! -name '*.sshsig' -print0)
|
||||
}
|
||||
|
||||
generate_release_packet_sbom() {
|
||||
local tmp_sbom=""
|
||||
if ! command -v syft >/dev/null 2>&1; then
|
||||
if [[ "${PULSE_ALLOW_MISSING_RELEASE_SBOM_TOOL:-false}" == "true" ]]; then
|
||||
echo "Warning: syft not installed; skipping release-packet SBOM because PULSE_ALLOW_MISSING_RELEASE_SBOM_TOOL=true."
|
||||
return 0
|
||||
fi
|
||||
echo "Error: syft is required to generate the release-packet SBOM." >&2
|
||||
echo "Install syft or set PULSE_ALLOW_MISSING_RELEASE_SBOM_TOOL=true only for local non-release debugging." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
tmp_sbom="$(mktemp "${BUILD_DIR}/release-packet-sbom.XXXXXX")"
|
||||
echo "Generating release-packet SBOM ${RELEASE_PACKET_SBOM}..."
|
||||
syft "dir:${RELEASE_DIR}" -o "spdx-json=${tmp_sbom}"
|
||||
mv "${tmp_sbom}" "${RELEASE_DIR}/${RELEASE_PACKET_SBOM}"
|
||||
--installer-ssh-public-key "${PULSE_RELEASE_UPDATE_SSH_PUBLIC_KEY}"
|
||||
}
|
||||
|
||||
# Clean previous builds
|
||||
|
|
@ -280,9 +217,9 @@ for build_name in "${build_order[@]}"; do
|
|||
chmod 755 "$staging_dir/scripts/"*.sh
|
||||
chmod 755 "$staging_dir/scripts/"*.ps1 2>/dev/null || true
|
||||
echo "$VERSION" > "$staging_dir/VERSION"
|
||||
sign_directory_release_assets "$staging_dir/bin"
|
||||
sign_directory_release_assets "$staging_dir/scripts"
|
||||
sign_release_file "$staging_dir/VERSION"
|
||||
pulse_release_sign_directory_assets "$staging_dir/bin"
|
||||
pulse_release_sign_directory_assets "$staging_dir/scripts"
|
||||
pulse_release_sign_file "$staging_dir/VERSION"
|
||||
|
||||
# Create tarball from staging directory
|
||||
cd "$staging_dir"
|
||||
|
|
@ -364,9 +301,9 @@ chmod +x "$universal_dir/bin/pulse-agent"
|
|||
|
||||
# Add VERSION file
|
||||
echo "$VERSION" > "$universal_dir/VERSION"
|
||||
sign_directory_release_assets "$universal_dir/bin"
|
||||
sign_directory_release_assets "$universal_dir/scripts"
|
||||
sign_release_file "$universal_dir/VERSION"
|
||||
pulse_release_sign_directory_assets "$universal_dir/bin"
|
||||
pulse_release_sign_directory_assets "$universal_dir/scripts"
|
||||
pulse_release_sign_file "$universal_dir/VERSION"
|
||||
|
||||
# Package standalone unified agent binaries (all platforms)
|
||||
# Linux
|
||||
|
|
@ -446,76 +383,9 @@ cp "${RENDERED_INSTALLERS_DIR}/install.sh" "$RELEASE_DIR/install.sh"
|
|||
cp scripts/install-docker.sh "$RELEASE_DIR/"
|
||||
cp scripts/pulse-auto-update.sh "$RELEASE_DIR/"
|
||||
|
||||
generate_release_packet_sbom
|
||||
|
||||
# Generate checksums (include tarballs, zip files, helm chart, and install.sh)
|
||||
cd "$RELEASE_DIR"
|
||||
shopt -s nullglob extglob
|
||||
checksum_files=()
|
||||
# Match all tarballs, zip files, exe files, and install scripts
|
||||
if compgen -G "pulse-*.tar.gz" > /dev/null; then
|
||||
checksum_files+=( pulse-*.tar.gz )
|
||||
fi
|
||||
if compgen -G "pulse-*.tgz" > /dev/null; then
|
||||
checksum_files+=( pulse-*.tgz )
|
||||
fi
|
||||
if compgen -G "pulse-*.zip" > /dev/null; then
|
||||
checksum_files+=( pulse-*.zip )
|
||||
fi
|
||||
if compgen -G "pulse-*.sbom.spdx.json" > /dev/null; then
|
||||
checksum_files+=( pulse-*.sbom.spdx.json )
|
||||
fi
|
||||
if compgen -G "pulse-agent-linux-*" > /dev/null; then
|
||||
checksum_files+=( pulse-agent-linux-* )
|
||||
fi
|
||||
if compgen -G "pulse-agent-freebsd-*" > /dev/null; then
|
||||
checksum_files+=( pulse-agent-freebsd-* )
|
||||
fi
|
||||
if compgen -G "pulse-*.exe" > /dev/null; then
|
||||
checksum_files+=( pulse-*.exe )
|
||||
fi
|
||||
if [ -f "install.sh" ]; then
|
||||
checksum_files+=( install.sh )
|
||||
fi
|
||||
if [ -f "install-docker.sh" ]; then
|
||||
checksum_files+=( install-docker.sh )
|
||||
fi
|
||||
if [ -f "pulse-auto-update.sh" ]; then
|
||||
checksum_files+=( pulse-auto-update.sh )
|
||||
fi
|
||||
if compgen -G "install*.ps1" > /dev/null; then
|
||||
checksum_files+=( install*.ps1 )
|
||||
fi
|
||||
if [ ${#checksum_files[@]} -eq 0 ]; then
|
||||
echo "Warning: no release artifacts found to checksum."
|
||||
else
|
||||
# Generate checksums from a single sha256sum run for deterministic results (prevents #671 checksum mismatches)
|
||||
checksum_output="$(sha256sum "${checksum_files[@]}" | sort -k 2)"
|
||||
printf '%s\n' "$checksum_output" > checksums.txt
|
||||
|
||||
# Emit per-file .sha256 artifacts for backward compatibility while legacy installers transition off them
|
||||
while read -r checksum filename; do
|
||||
printf '%s %s\n' "$checksum" "$filename" > "${filename}.sha256"
|
||||
done <<< "$checksum_output"
|
||||
|
||||
for artifact in "${checksum_files[@]}" checksums.txt; do
|
||||
sign_release_file "${artifact}"
|
||||
done
|
||||
|
||||
if [ -n "${SIGNING_KEY_ID:-}" ]; then
|
||||
if command -v gpg >/dev/null 2>&1; then
|
||||
echo "Signing checksums with GPG key ${SIGNING_KEY_ID}..."
|
||||
gpg --batch --yes --detach-sign --armor \
|
||||
--local-user "${SIGNING_KEY_ID}" \
|
||||
--output checksums.txt.asc \
|
||||
checksums.txt
|
||||
else
|
||||
echo "SIGNING_KEY_ID is set but gpg is not installed; skipping signature."
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
shopt -u nullglob
|
||||
cd ..
|
||||
pulse_release_generate_packet_sbom "${RELEASE_DIR}" "${RELEASE_PACKET_SBOM}"
|
||||
mapfile -t checksum_files < <(pulse_release_collect_checksum_files "${RELEASE_DIR}")
|
||||
pulse_release_write_checksums_and_signatures "${RELEASE_DIR}" "${checksum_files[@]}"
|
||||
|
||||
echo
|
||||
echo "Release build complete!"
|
||||
|
|
|
|||
174
scripts/installtests/backfill_release_assets_test.go
Normal file
174
scripts/installtests/backfill_release_assets_test.go
Normal file
|
|
@ -0,0 +1,174 @@
|
|||
package installtests
|
||||
|
||||
import (
|
||||
"crypto/ed25519"
|
||||
"crypto/rand"
|
||||
"crypto/sha256"
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestBackfillReleaseAssetsRewritesIntegrityMetadataLocally(t *testing.T) {
|
||||
if _, err := exec.LookPath("bash"); err != nil {
|
||||
t.Skip("bash not installed")
|
||||
}
|
||||
if _, err := exec.LookPath("go"); err != nil {
|
||||
t.Skip("go not installed")
|
||||
}
|
||||
if _, err := exec.LookPath("ssh-keygen"); err != nil {
|
||||
t.Skip("ssh-keygen not installed")
|
||||
}
|
||||
|
||||
tag := "v0.0.0-test"
|
||||
releaseDir := t.TempDir()
|
||||
|
||||
writeReleaseFile(t, releaseDir, "pulse-v0.0.0-test.tar.gz", "payload")
|
||||
writeReleaseFile(t, releaseDir, "install.sh", "#!/bin/sh\necho test\n")
|
||||
writeHistoricalChecksums(t, releaseDir, []string{
|
||||
"install.sh",
|
||||
"pulse-v0.0.0-test.tar.gz",
|
||||
})
|
||||
|
||||
syftStub := filepath.Join(t.TempDir(), "syft")
|
||||
if err := os.WriteFile(syftStub, []byte(`#!/bin/sh
|
||||
set -eu
|
||||
out=""
|
||||
for arg in "$@"; do
|
||||
case "$arg" in
|
||||
spdx-json=*)
|
||||
out=${arg#spdx-json=}
|
||||
;;
|
||||
esac
|
||||
done
|
||||
[ -n "$out" ] || exit 1
|
||||
printf '{"spdxVersion":"SPDX-2.3","files":[]}\n' > "$out"
|
||||
`), 0o755); err != nil {
|
||||
t.Fatalf("write syft stub: %v", err)
|
||||
}
|
||||
|
||||
_, privateKey, err := ed25519.GenerateKey(rand.Reader)
|
||||
if err != nil {
|
||||
t.Fatalf("generate signing key: %v", err)
|
||||
}
|
||||
encodedPrivateKey := base64.StdEncoding.EncodeToString(privateKey)
|
||||
|
||||
runBackfill := func() {
|
||||
cmd := exec.Command("bash", "scripts/backfill-release-assets.sh",
|
||||
"--tag", tag,
|
||||
"--release-dir", releaseDir,
|
||||
"--skip-download",
|
||||
"--skip-upload",
|
||||
)
|
||||
repoRootCmd := exec.Command("go", "env", "GOMOD")
|
||||
gomodOutput, err := repoRootCmd.Output()
|
||||
if err != nil {
|
||||
t.Fatalf("resolve repo root via go env GOMOD: %v", err)
|
||||
}
|
||||
repoRoot := filepath.Dir(strings.TrimSpace(string(gomodOutput)))
|
||||
repoRoot, err = filepath.Abs(repoRoot)
|
||||
if err != nil {
|
||||
t.Fatalf("resolve repo root: %v", err)
|
||||
}
|
||||
cmd.Dir = repoRoot
|
||||
cmd.Env = append(os.Environ(),
|
||||
"PULSE_UPDATE_SIGNING_KEY="+encodedPrivateKey,
|
||||
"PULSE_RELEASE_SBOM_TOOL="+syftStub,
|
||||
)
|
||||
output, err := cmd.CombinedOutput()
|
||||
if err != nil {
|
||||
t.Fatalf("backfill-release-assets.sh failed: %v\n%s", err, output)
|
||||
}
|
||||
}
|
||||
|
||||
runBackfill()
|
||||
runBackfill()
|
||||
|
||||
checksumsPath := filepath.Join(releaseDir, "checksums.txt")
|
||||
checksumsBytes, err := os.ReadFile(checksumsPath)
|
||||
if err != nil {
|
||||
t.Fatalf("read checksums.txt: %v", err)
|
||||
}
|
||||
|
||||
releaseSBOM := "pulse-v0.0.0-test-release.sbom.spdx.json"
|
||||
lines := strings.Split(strings.TrimSpace(string(checksumsBytes)), "\n")
|
||||
if len(lines) != 3 {
|
||||
t.Fatalf("expected 3 checksummed artifacts after backfill, got %d:\n%s", len(lines), checksumsBytes)
|
||||
}
|
||||
|
||||
if _, err := os.Stat(filepath.Join(releaseDir, releaseSBOM)); err != nil {
|
||||
t.Fatalf("missing generated release SBOM: %v", err)
|
||||
}
|
||||
if _, err := os.Stat(filepath.Join(releaseDir, "checksums.txt.sshsig")); err != nil {
|
||||
t.Fatalf("missing checksums.txt.sshsig: %v", err)
|
||||
}
|
||||
if _, err := os.Stat(filepath.Join(releaseDir, "checksums.txt.sig")); err != nil {
|
||||
t.Fatalf("missing checksums.txt.sig: %v", err)
|
||||
}
|
||||
|
||||
for _, line := range lines {
|
||||
fields := strings.Fields(line)
|
||||
if len(fields) != 2 {
|
||||
t.Fatalf("malformed checksums line %q", line)
|
||||
}
|
||||
|
||||
sum := fields[0]
|
||||
filename := fields[1]
|
||||
artifactPath := filepath.Join(releaseDir, filename)
|
||||
data, err := os.ReadFile(artifactPath)
|
||||
if err != nil {
|
||||
t.Fatalf("read %s: %v", filename, err)
|
||||
}
|
||||
actualSum := fmt.Sprintf("%x", sha256.Sum256(data))
|
||||
if actualSum != sum {
|
||||
t.Fatalf("checksum mismatch for %s: got %s want %s", filename, actualSum, sum)
|
||||
}
|
||||
|
||||
shaFileBytes, err := os.ReadFile(artifactPath + ".sha256")
|
||||
if err != nil {
|
||||
t.Fatalf("read %s.sha256: %v", filename, err)
|
||||
}
|
||||
if strings.TrimSpace(string(shaFileBytes)) != line {
|
||||
t.Fatalf("unexpected %s.sha256 contents: %q", filename, shaFileBytes)
|
||||
}
|
||||
|
||||
if _, err := os.Stat(artifactPath + ".sig"); err != nil {
|
||||
t.Fatalf("missing %s.sig: %v", filename, err)
|
||||
}
|
||||
if _, err := os.Stat(artifactPath + ".sshsig"); err != nil {
|
||||
t.Fatalf("missing %s.sshsig: %v", filename, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func writeHistoricalChecksums(t *testing.T, releaseDir string, filenames []string) {
|
||||
t.Helper()
|
||||
|
||||
lines := make([]string, 0, len(filenames))
|
||||
for _, filename := range filenames {
|
||||
data, err := os.ReadFile(filepath.Join(releaseDir, filename))
|
||||
if err != nil {
|
||||
t.Fatalf("read %s: %v", filename, err)
|
||||
}
|
||||
line := fmt.Sprintf("%x %s", sha256.Sum256(data), filename)
|
||||
lines = append(lines, line)
|
||||
if err := os.WriteFile(filepath.Join(releaseDir, filename+".sha256"), []byte(line+"\n"), 0o644); err != nil {
|
||||
t.Fatalf("write %s.sha256: %v", filename, err)
|
||||
}
|
||||
}
|
||||
|
||||
if err := os.WriteFile(filepath.Join(releaseDir, "checksums.txt"), []byte(strings.Join(lines, "\n")+"\n"), 0o644); err != nil {
|
||||
t.Fatalf("write checksums.txt: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func writeReleaseFile(t *testing.T, releaseDir, name, content string) {
|
||||
t.Helper()
|
||||
if err := os.WriteFile(filepath.Join(releaseDir, name), []byte(content), 0o755); err != nil {
|
||||
t.Fatalf("write %s: %v", name, err)
|
||||
}
|
||||
}
|
||||
|
|
@ -15,6 +15,10 @@ func TestBuildReleaseUsesV6InstallScripts(t *testing.T) {
|
|||
|
||||
script := string(content)
|
||||
required := []string{
|
||||
`SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"`,
|
||||
`PULSE_REPO_ROOT="$(cd "${SCRIPT_DIR}/.." && pwd)"`,
|
||||
`cd "${PULSE_REPO_ROOT}"`,
|
||||
`source "${SCRIPT_DIR}/release_asset_common.sh"`,
|
||||
`RENDERED_INSTALLERS_DIR="${BUILD_DIR}/rendered-installers"`,
|
||||
`go run ./scripts/render_installers.go \`,
|
||||
`cp "${RENDERED_INSTALLERS_DIR}/install.sh" "$RELEASE_DIR/install.sh"`,
|
||||
|
|
@ -38,23 +42,43 @@ func TestBuildReleaseUsesV6InstallScripts(t *testing.T) {
|
|||
requiredScriptWiring := []string{
|
||||
`agent_ldflags="$(./scripts/release_ldflags.sh agent --version "v${VERSION}" "${update_ldflags_args[@]}")"`,
|
||||
`server_ldflags="$(./scripts/release_ldflags.sh server --version "v${VERSION}" --build-time "${build_time}" --git-commit "${git_commit}" "${license_ldflags_args[@]}" "${update_ldflags_args[@]}")"`,
|
||||
`PULSE_UPDATE_SIGNING_KEY`,
|
||||
`RELEASE_PACKET_SBOM="pulse-v${VERSION}-release.sbom.spdx.json"`,
|
||||
`generate_release_packet_sbom() {`,
|
||||
`syft "dir:${RELEASE_DIR}" -o "spdx-json=${tmp_sbom}"`,
|
||||
`generate_release_packet_sbom`,
|
||||
`pulse-*.sbom.spdx.json`,
|
||||
`go run ./scripts/release_update_key.go public-key --private-key "${PULSE_UPDATE_SIGNING_KEY}"`,
|
||||
`go run ./scripts/release_update_key.go public-key-ssh --private-key "${PULSE_UPDATE_SIGNING_KEY}"`,
|
||||
`go run ./scripts/release_update_key.go openssh-private-key --private-key "${PULSE_UPDATE_SIGNING_KEY}"`,
|
||||
`ssh-keygen -q -Y sign`,
|
||||
`sign_release_file "${artifact}"`,
|
||||
`pulse_release_prepare_signing_state "pulse-installer" "pulse-install"`,
|
||||
`trap 'pulse_release_cleanup_signing_state' EXIT`,
|
||||
`--installer-ssh-public-key "${PULSE_RELEASE_UPDATE_SSH_PUBLIC_KEY}"`,
|
||||
`pulse_release_generate_packet_sbom "${RELEASE_DIR}" "${RELEASE_PACKET_SBOM}"`,
|
||||
`mapfile -t checksum_files < <(pulse_release_collect_checksum_files "${RELEASE_DIR}")`,
|
||||
`pulse_release_write_checksums_and_signatures "${RELEASE_DIR}" "${checksum_files[@]}"`,
|
||||
}
|
||||
for _, needle := range requiredScriptWiring {
|
||||
if !strings.Contains(script, needle) {
|
||||
t.Fatalf("build-release.sh missing canonical ldflags wiring: %s", needle)
|
||||
}
|
||||
}
|
||||
|
||||
helperBytes, err := os.ReadFile(repoFile("scripts", "release_asset_common.sh"))
|
||||
if err != nil {
|
||||
t.Fatalf("read release_asset_common.sh: %v", err)
|
||||
}
|
||||
helper := string(helperBytes)
|
||||
helperRequired := []string{
|
||||
`: "${PULSE_SCRIPTS_DIR:=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)}"`,
|
||||
`: "${PULSE_REPO_ROOT:=$(cd "${PULSE_SCRIPTS_DIR}/.." && pwd)}"`,
|
||||
`go -C "${PULSE_REPO_ROOT}" run "${PULSE_SCRIPTS_DIR}/release_update_key.go" "$@"`,
|
||||
`pulse_release_go_run_update_key public-key --private-key "${PULSE_UPDATE_SIGNING_KEY}"`,
|
||||
`pulse_release_go_run_update_key public-key-ssh --private-key "${PULSE_UPDATE_SIGNING_KEY}"`,
|
||||
`pulse_release_go_run_update_key openssh-private-key --private-key "${PULSE_UPDATE_SIGNING_KEY}"`,
|
||||
`pulse_release_go_run_update_key sign --private-key "${PULSE_UPDATE_SIGNING_KEY}" --file "${absolute_file}"`,
|
||||
`ssh-keygen -q -Y sign`,
|
||||
`"${resolved_tool}" "dir:${release_dir}" -o "spdx-json=${tmp_sbom}"`,
|
||||
`if compgen -G "pulse-*.sbom.spdx.json" > /dev/null; then`,
|
||||
`find . -maxdepth 1 -type f \( -name '*.sig' -o -name '*.sshsig' \) -delete`,
|
||||
}
|
||||
for _, needle := range helperRequired {
|
||||
if !strings.Contains(helper, needle) {
|
||||
t.Fatalf("release_asset_common.sh missing canonical release asset wiring: %s", needle)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestCreateReleaseUploadsPowerShellInstaller(t *testing.T) {
|
||||
|
|
@ -105,6 +129,61 @@ func TestCreateReleaseUploadsPowerShellInstaller(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestBackfillReleaseWorkflowRepairsPublishedAssetsWithoutRebuilds(t *testing.T) {
|
||||
scriptBytes, err := os.ReadFile(repoFile("scripts", "backfill-release-assets.sh"))
|
||||
if err != nil {
|
||||
t.Fatalf("read backfill-release-assets.sh: %v", err)
|
||||
}
|
||||
script := string(scriptBytes)
|
||||
scriptRequired := []string{
|
||||
`SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"`,
|
||||
`PULSE_REPO_ROOT="$(cd "${SCRIPT_DIR}/.." && pwd)"`,
|
||||
`cd "${PULSE_REPO_ROOT}"`,
|
||||
`source "${SCRIPT_DIR}/release_asset_common.sh"`,
|
||||
`gh release view "${TAG}" -R "${REPO}" --json isDraft,tagName`,
|
||||
`Error: ${TAG} is still a draft release; use the normal release pipeline instead of historical backfill.`,
|
||||
`gh release download "${TAG}" -R "${REPO}" --dir "${RELEASE_DIR}" --clobber`,
|
||||
`pulse_release_prepare_signing_state "pulse-installer" "pulse-install"`,
|
||||
`pulse_release_generate_packet_sbom "${PAYLOAD_DIR}" "${RELEASE_PACKET_SBOM}"`,
|
||||
`pulse_release_write_checksums_and_signatures "${RELEASE_DIR}" "${checksum_files[@]}"`,
|
||||
`gh release upload "${TAG}" "${RELEASE_DIR}/checksums.txt" --clobber`,
|
||||
`gh release upload "${TAG}" "${RELEASE_DIR}"/*.sha256 --clobber`,
|
||||
`gh release upload "${TAG}" "${RELEASE_DIR}"/*.sig --clobber`,
|
||||
`gh release upload "${TAG}" "${RELEASE_DIR}"/*.sshsig --clobber`,
|
||||
`gh release upload "${TAG}" "${RELEASE_DIR}/${RELEASE_PACKET_SBOM}" --clobber`,
|
||||
}
|
||||
for _, needle := range scriptRequired {
|
||||
if !strings.Contains(script, needle) {
|
||||
t.Fatalf("backfill-release-assets.sh missing required historical backfill step: %s", needle)
|
||||
}
|
||||
}
|
||||
|
||||
workflowBytes, err := os.ReadFile(repoFile(".github", "workflows", "backfill-release-assets.yml"))
|
||||
if err != nil {
|
||||
t.Fatalf("read backfill-release-assets.yml: %v", err)
|
||||
}
|
||||
workflow := string(workflowBytes)
|
||||
workflowRequired := []string{
|
||||
`name: Backfill Release Assets`,
|
||||
`workflow_dispatch:`,
|
||||
`contents: write`,
|
||||
`runs-on: ubuntu-24.04`,
|
||||
`uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4`,
|
||||
`uses: actions/setup-go@40f1582b2485089dde7abd97c1529aa768e1baff # v5`,
|
||||
`SYFT_VERSION="1.42.4"`,
|
||||
`SYFT_ARCHIVE="syft_${SYFT_VERSION}_linux_amd64.tar.gz"`,
|
||||
`SYFT_SHA256="590650c2743b83f327d1bf9bec64f6f83b7fec504187bb84f500c862bf8f2a0f"`,
|
||||
`./scripts/backfill-release-assets.sh --tag "${{ inputs.tag }}" --repo "${{ github.repository }}"`,
|
||||
`PULSE_UPDATE_SIGNING_KEY: ${{ secrets.PULSE_UPDATE_SIGNING_KEY }}`,
|
||||
`./scripts/validate-published-release.sh "${{ inputs.tag }}" "${{ github.repository }}"`,
|
||||
}
|
||||
for _, needle := range workflowRequired {
|
||||
if !strings.Contains(workflow, needle) {
|
||||
t.Fatalf("backfill-release-assets.yml missing required release-repair step: %s", needle)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestReleaseValidationRequiresSignedSidecars(t *testing.T) {
|
||||
localValidatorBytes, err := os.ReadFile(repoFile("scripts", "validate-release.sh"))
|
||||
if err != nil {
|
||||
|
|
@ -160,12 +239,18 @@ func TestReleaseValidationRequiresSignedSidecars(t *testing.T) {
|
|||
contractRequired := []string{
|
||||
"`scripts/validate-release.sh`",
|
||||
"`scripts/validate-published-release.sh`",
|
||||
"`scripts/backfill-release-assets.sh`",
|
||||
"`.github/workflows/backfill-release-assets.yml`",
|
||||
"`scripts/validate-release.sh`, and",
|
||||
"`scripts/validate-published-release.sh` must derive the embedded update trust",
|
||||
"`scripts/release_asset_common.sh`",
|
||||
"must derive the embedded update trust root",
|
||||
"standalone SPDX JSON SBOM",
|
||||
"already-published packet",
|
||||
"derived integrity assets",
|
||||
"and fail validation if",
|
||||
"published artifact or `checksums.txt` is missing its `.sshsig` sidecar",
|
||||
"canonical release-packet SBOM is absent",
|
||||
"published artifact or",
|
||||
"`checksums.txt` is missing its `.sshsig` sidecar",
|
||||
"release-packet SBOM is absent",
|
||||
}
|
||||
for _, needle := range contractRequired {
|
||||
if !strings.Contains(contract, needle) {
|
||||
|
|
|
|||
215
scripts/release_asset_common.sh
Normal file
215
scripts/release_asset_common.sh
Normal file
|
|
@ -0,0 +1,215 @@
|
|||
#!/usr/bin/env bash
|
||||
|
||||
# Shared helpers for Pulse release asset assembly and integrity metadata.
|
||||
|
||||
: "${PULSE_SCRIPTS_DIR:=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)}"
|
||||
: "${PULSE_REPO_ROOT:=$(cd "${PULSE_SCRIPTS_DIR}/.." && pwd)}"
|
||||
|
||||
pulse_release_go_run_update_key() {
|
||||
go -C "${PULSE_REPO_ROOT}" run "${PULSE_SCRIPTS_DIR}/release_update_key.go" "$@"
|
||||
}
|
||||
|
||||
pulse_release_prepare_signing_state() {
|
||||
local signer_identity="${1:-pulse-installer}"
|
||||
local signer_namespace="${2:-pulse-install}"
|
||||
|
||||
PULSE_RELEASE_SIGNER_IDENTITY="${signer_identity}"
|
||||
PULSE_RELEASE_SIGNER_NAMESPACE="${signer_namespace}"
|
||||
PULSE_RELEASE_UPDATE_PUBLIC_KEY=""
|
||||
PULSE_RELEASE_UPDATE_SSH_PUBLIC_KEY=""
|
||||
PULSE_RELEASE_UPDATE_SSH_PRIVATE_KEY_FILE=""
|
||||
|
||||
if [[ -z "${PULSE_UPDATE_SIGNING_KEY:-}" ]]; then
|
||||
if [[ "${PULSE_ALLOW_MISSING_UPDATE_SIGNING_KEY:-false}" == "true" ]]; then
|
||||
echo "Warning: PULSE_UPDATE_SIGNING_KEY not set; continuing because PULSE_ALLOW_MISSING_UPDATE_SIGNING_KEY=true."
|
||||
return 0
|
||||
fi
|
||||
echo "Error: PULSE_UPDATE_SIGNING_KEY is required for release builds." >&2
|
||||
echo "Set PULSE_ALLOW_MISSING_UPDATE_SIGNING_KEY=true only for local non-release debugging." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
PULSE_RELEASE_UPDATE_PUBLIC_KEY="$(pulse_release_go_run_update_key public-key --private-key "${PULSE_UPDATE_SIGNING_KEY}")"
|
||||
if [[ -z "${PULSE_RELEASE_UPDATE_PUBLIC_KEY}" ]]; then
|
||||
echo "Error: failed to derive update signing public key." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if ! command -v ssh-keygen >/dev/null 2>&1; then
|
||||
echo "Error: ssh-keygen is required to sign installer release assets." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
PULSE_RELEASE_UPDATE_SSH_PUBLIC_KEY="$(pulse_release_go_run_update_key public-key-ssh --private-key "${PULSE_UPDATE_SIGNING_KEY}" --comment "${PULSE_RELEASE_SIGNER_IDENTITY}")"
|
||||
if [[ -z "${PULSE_RELEASE_UPDATE_SSH_PUBLIC_KEY}" ]]; then
|
||||
echo "Error: failed to derive installer SSH signing public key." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
PULSE_RELEASE_UPDATE_SSH_PRIVATE_KEY_FILE="$(mktemp)"
|
||||
pulse_release_go_run_update_key openssh-private-key --private-key "${PULSE_UPDATE_SIGNING_KEY}" --comment "${PULSE_RELEASE_SIGNER_IDENTITY}" > "${PULSE_RELEASE_UPDATE_SSH_PRIVATE_KEY_FILE}"
|
||||
chmod 600 "${PULSE_RELEASE_UPDATE_SSH_PRIVATE_KEY_FILE}"
|
||||
}
|
||||
|
||||
pulse_release_cleanup_signing_state() {
|
||||
if [[ -n "${PULSE_RELEASE_UPDATE_SSH_PRIVATE_KEY_FILE:-}" && -f "${PULSE_RELEASE_UPDATE_SSH_PRIVATE_KEY_FILE}" ]]; then
|
||||
rm -f "${PULSE_RELEASE_UPDATE_SSH_PRIVATE_KEY_FILE}"
|
||||
fi
|
||||
}
|
||||
|
||||
pulse_release_sign_file() {
|
||||
local file="$1"
|
||||
local absolute_file="${file}"
|
||||
|
||||
if [[ -z "${PULSE_UPDATE_SIGNING_KEY:-}" ]]; then
|
||||
return 0
|
||||
fi
|
||||
|
||||
if [[ "${absolute_file}" != /* ]]; then
|
||||
absolute_file="$(pwd)/${file}"
|
||||
fi
|
||||
|
||||
rm -f "${absolute_file}.sig" "${absolute_file}.sshsig"
|
||||
ssh-keygen -q -Y sign \
|
||||
-f "${PULSE_RELEASE_UPDATE_SSH_PRIVATE_KEY_FILE}" \
|
||||
-n "${PULSE_RELEASE_SIGNER_NAMESPACE}" \
|
||||
"${absolute_file}" >/dev/null
|
||||
mv "${absolute_file}.sig" "${absolute_file}.sshsig"
|
||||
pulse_release_go_run_update_key sign --private-key "${PULSE_UPDATE_SIGNING_KEY}" --file "${absolute_file}" > "${absolute_file}.sig"
|
||||
}
|
||||
|
||||
pulse_release_sign_directory_assets() {
|
||||
local dir="$1"
|
||||
|
||||
if [[ -z "${PULSE_UPDATE_SIGNING_KEY:-}" ]]; then
|
||||
return 0
|
||||
fi
|
||||
|
||||
while IFS= read -r -d '' file; do
|
||||
pulse_release_sign_file "${file}"
|
||||
done < <(find "${dir}" -maxdepth 1 -type f ! -name '*.sig' ! -name '*.sshsig' -print0)
|
||||
}
|
||||
|
||||
pulse_release_generate_packet_sbom() {
|
||||
local release_dir="$1"
|
||||
local output_name="$2"
|
||||
local sbom_tool="${PULSE_RELEASE_SBOM_TOOL:-syft}"
|
||||
local tmp_base="${BUILD_DIR:-${release_dir}}"
|
||||
local resolved_tool=""
|
||||
local tmp_sbom=""
|
||||
|
||||
if [[ "${sbom_tool}" == */* ]]; then
|
||||
resolved_tool="${sbom_tool}"
|
||||
else
|
||||
resolved_tool="$(command -v "${sbom_tool}" || true)"
|
||||
fi
|
||||
|
||||
if [[ -z "${resolved_tool}" || ! -x "${resolved_tool}" ]]; then
|
||||
if [[ "${PULSE_ALLOW_MISSING_RELEASE_SBOM_TOOL:-false}" == "true" ]]; then
|
||||
echo "Warning: syft not installed; skipping release-packet SBOM because PULSE_ALLOW_MISSING_RELEASE_SBOM_TOOL=true."
|
||||
return 0
|
||||
fi
|
||||
echo "Error: syft is required to generate the release-packet SBOM." >&2
|
||||
echo "Install syft or set PULSE_ALLOW_MISSING_RELEASE_SBOM_TOOL=true only for local non-release debugging." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
mkdir -p "${tmp_base}"
|
||||
tmp_sbom="$(mktemp "${tmp_base}/release-packet-sbom.XXXXXX")"
|
||||
echo "Generating release-packet SBOM ${output_name}..."
|
||||
"${resolved_tool}" "dir:${release_dir}" -o "spdx-json=${tmp_sbom}"
|
||||
mv "${tmp_sbom}" "${release_dir}/${output_name}"
|
||||
}
|
||||
|
||||
pulse_release_collect_checksum_files() {
|
||||
local release_dir="$1"
|
||||
|
||||
(
|
||||
cd "${release_dir}"
|
||||
shopt -s nullglob extglob
|
||||
local -a checksum_files=()
|
||||
|
||||
if compgen -G "pulse-*.tar.gz" > /dev/null; then
|
||||
checksum_files+=( pulse-*.tar.gz )
|
||||
fi
|
||||
if compgen -G "pulse-*.tgz" > /dev/null; then
|
||||
checksum_files+=( pulse-*.tgz )
|
||||
fi
|
||||
if compgen -G "pulse-*.zip" > /dev/null; then
|
||||
checksum_files+=( pulse-*.zip )
|
||||
fi
|
||||
if compgen -G "pulse-*.sbom.spdx.json" > /dev/null; then
|
||||
checksum_files+=( pulse-*.sbom.spdx.json )
|
||||
fi
|
||||
if compgen -G "pulse-agent-linux-*" > /dev/null; then
|
||||
checksum_files+=( pulse-agent-linux-* )
|
||||
fi
|
||||
if compgen -G "pulse-agent-freebsd-*" > /dev/null; then
|
||||
checksum_files+=( pulse-agent-freebsd-* )
|
||||
fi
|
||||
if compgen -G "pulse-*.exe" > /dev/null; then
|
||||
checksum_files+=( pulse-*.exe )
|
||||
fi
|
||||
if [[ -f "install.sh" ]]; then
|
||||
checksum_files+=( install.sh )
|
||||
fi
|
||||
if [[ -f "install-docker.sh" ]]; then
|
||||
checksum_files+=( install-docker.sh )
|
||||
fi
|
||||
if [[ -f "pulse-auto-update.sh" ]]; then
|
||||
checksum_files+=( pulse-auto-update.sh )
|
||||
fi
|
||||
if compgen -G "install*.ps1" > /dev/null; then
|
||||
checksum_files+=( install*.ps1 )
|
||||
fi
|
||||
|
||||
if [[ ${#checksum_files[@]} -gt 0 ]]; then
|
||||
printf '%s\n' "${checksum_files[@]}" | sort -u
|
||||
fi
|
||||
)
|
||||
}
|
||||
|
||||
pulse_release_write_checksums_and_signatures() {
|
||||
local release_dir="$1"
|
||||
shift
|
||||
local -a checksum_files=("$@")
|
||||
|
||||
(
|
||||
cd "${release_dir}"
|
||||
|
||||
rm -f checksums.txt checksums.txt.sig checksums.txt.sshsig
|
||||
find . -maxdepth 1 -type f -name '*.sha256' -delete
|
||||
find . -maxdepth 1 -type f \( -name '*.sig' -o -name '*.sshsig' \) -delete
|
||||
|
||||
if [[ ${#checksum_files[@]} -eq 0 ]]; then
|
||||
echo "Warning: no release artifacts found to checksum."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
local checksum_output=""
|
||||
checksum_output="$(sha256sum "${checksum_files[@]}" | sort -k 2)"
|
||||
printf '%s\n' "${checksum_output}" > checksums.txt
|
||||
|
||||
while read -r checksum filename; do
|
||||
[[ -n "${checksum:-}" && -n "${filename:-}" ]] || continue
|
||||
printf '%s %s\n' "${checksum}" "${filename}" > "${filename}.sha256"
|
||||
done <<< "${checksum_output}"
|
||||
|
||||
local artifact=""
|
||||
for artifact in "${checksum_files[@]}" checksums.txt; do
|
||||
pulse_release_sign_file "${artifact}"
|
||||
done
|
||||
|
||||
if [[ -n "${SIGNING_KEY_ID:-}" ]]; then
|
||||
if command -v gpg >/dev/null 2>&1; then
|
||||
echo "Signing checksums with GPG key ${SIGNING_KEY_ID}..."
|
||||
gpg --batch --yes --detach-sign --armor \
|
||||
--local-user "${SIGNING_KEY_ID}" \
|
||||
--output checksums.txt.asc \
|
||||
checksums.txt
|
||||
else
|
||||
echo "SIGNING_KEY_ID is set but gpg is not installed; skipping signature."
|
||||
fi
|
||||
fi
|
||||
)
|
||||
}
|
||||
|
|
@ -283,9 +283,36 @@ class ReleasePromotionPolicyTest(unittest.TestCase):
|
|||
self.assertIn("uses: actions/attest@59d89421af93a897026c735860bf21b6eb4f7b26 # v4", content)
|
||||
self.assertIn("subject-path: release/*", content)
|
||||
build_script = read("scripts/build-release.sh")
|
||||
release_asset_helper = read("scripts/release_asset_common.sh")
|
||||
backfill_script = read("scripts/backfill-release-assets.sh")
|
||||
backfill_workflow = read(".github/workflows/backfill-release-assets.yml")
|
||||
self.assertIn('RELEASE_PACKET_SBOM="pulse-v${VERSION}-release.sbom.spdx.json"', build_script)
|
||||
self.assertIn('syft "dir:${RELEASE_DIR}" -o "spdx-json=${tmp_sbom}"', build_script)
|
||||
self.assertIn('pulse-*.sbom.spdx.json', build_script)
|
||||
self.assertIn('SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"', build_script)
|
||||
self.assertIn('PULSE_REPO_ROOT="$(cd "${SCRIPT_DIR}/.." && pwd)"', build_script)
|
||||
self.assertIn('cd "${PULSE_REPO_ROOT}"', build_script)
|
||||
self.assertIn('source "${SCRIPT_DIR}/release_asset_common.sh"', build_script)
|
||||
self.assertIn('pulse_release_prepare_signing_state "pulse-installer" "pulse-install"', build_script)
|
||||
self.assertIn('pulse_release_generate_packet_sbom "${RELEASE_DIR}" "${RELEASE_PACKET_SBOM}"', build_script)
|
||||
self.assertIn('pulse_release_write_checksums_and_signatures "${RELEASE_DIR}" "${checksum_files[@]}"', build_script)
|
||||
self.assertIn(': "${PULSE_SCRIPTS_DIR:=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)}"', release_asset_helper)
|
||||
self.assertIn(': "${PULSE_REPO_ROOT:=$(cd "${PULSE_SCRIPTS_DIR}/.." && pwd)}"', release_asset_helper)
|
||||
self.assertIn('go -C "${PULSE_REPO_ROOT}" run "${PULSE_SCRIPTS_DIR}/release_update_key.go" "$@"', release_asset_helper)
|
||||
self.assertIn('pulse_release_go_run_update_key public-key --private-key "${PULSE_UPDATE_SIGNING_KEY}"', release_asset_helper)
|
||||
self.assertIn('pulse_release_go_run_update_key public-key-ssh --private-key "${PULSE_UPDATE_SIGNING_KEY}"', release_asset_helper)
|
||||
self.assertIn('pulse_release_go_run_update_key openssh-private-key --private-key "${PULSE_UPDATE_SIGNING_KEY}"', release_asset_helper)
|
||||
self.assertIn('pulse_release_go_run_update_key sign --private-key "${PULSE_UPDATE_SIGNING_KEY}" --file "${file}"', release_asset_helper)
|
||||
self.assertIn('"${resolved_tool}" "dir:${release_dir}" -o "spdx-json=${tmp_sbom}"', release_asset_helper)
|
||||
self.assertIn('find . -maxdepth 1 -type f \\( -name \'*.sig\' -o -name \'*.sshsig\' \\) -delete', release_asset_helper)
|
||||
self.assertIn('source "${SCRIPT_DIR}/release_asset_common.sh"', backfill_script)
|
||||
self.assertIn('gh release download "${TAG}" -R "${REPO}" --dir "${RELEASE_DIR}" --clobber', backfill_script)
|
||||
self.assertIn('pulse_release_generate_packet_sbom "${PAYLOAD_DIR}" "${RELEASE_PACKET_SBOM}"', backfill_script)
|
||||
self.assertIn('pulse_release_write_checksums_and_signatures "${RELEASE_DIR}" "${checksum_files[@]}"', backfill_script)
|
||||
self.assertIn('gh release upload "${TAG}" "${RELEASE_DIR}/${RELEASE_PACKET_SBOM}" --clobber', backfill_script)
|
||||
self.assertIn("name: Backfill Release Assets", backfill_workflow)
|
||||
self.assertIn("workflow_dispatch:", backfill_workflow)
|
||||
self.assertIn('SYFT_VERSION="1.42.4"', backfill_workflow)
|
||||
self.assertIn('./scripts/backfill-release-assets.sh --tag "${{ inputs.tag }}" --repo "${{ github.repository }}"', backfill_workflow)
|
||||
self.assertIn('./scripts/validate-published-release.sh "${{ inputs.tag }}" "${{ github.repository }}"', backfill_workflow)
|
||||
self.assertIn("pulse_license_public_key=${{ secrets.PULSE_LICENSE_PUBLIC_KEY }}", content)
|
||||
self.assertIn("pulse_update_signing_key=${{ secrets.PULSE_UPDATE_SIGNING_KEY }}", content)
|
||||
self.assertIn("--secret id=pulse_license_public_key,env=PULSE_LICENSE_PUBLIC_KEY", content)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue