mirror of
https://github.com/rcourtman/Pulse.git
synced 2026-09-10 10:35:51 +00:00
Merge pull request #1956 from rcourtman/maintainer/20260907T121525Z-release-v6.4
Reject v6.4 release dispatches when source identity drifts
This commit is contained in:
@@ -9,6 +9,10 @@ on:
|
||||
description: 'Version number (e.g., 4.30.0)'
|
||||
required: true
|
||||
type: string
|
||||
expected_source_sha:
|
||||
description: 'Exact 40-character commit SHA admitted for this release'
|
||||
required: true
|
||||
type: string
|
||||
release_notes:
|
||||
description: 'Release notes (markdown)'
|
||||
required: true
|
||||
@@ -108,6 +112,22 @@ jobs:
|
||||
visual_capture_count: ${{ steps.visual_plan.outputs.capture_count }}
|
||||
visual_comparison_tag: ${{ steps.visual_plan.outputs.comparison_tag }}
|
||||
steps:
|
||||
- name: Verify admitted source commit
|
||||
env:
|
||||
EXPECTED_SOURCE_SHA: ${{ inputs.expected_source_sha }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
if [[ ! "${EXPECTED_SOURCE_SHA}" =~ ^[0-9a-f]{40}$ ]]; then
|
||||
echo "::error::expected_source_sha must be an exact 40-character commit SHA"
|
||||
exit 1
|
||||
fi
|
||||
if [[ "${GITHUB_SHA}" != "${EXPECTED_SOURCE_SHA}" || \
|
||||
"${GITHUB_WORKFLOW_SHA}" != "${EXPECTED_SOURCE_SHA}" ]]; then
|
||||
echo "::error::Release dispatch expected ${EXPECTED_SOURCE_SHA}, but GitHub resolved source ${GITHUB_SHA} and workflow ${GITHUB_WORKFLOW_SHA}."
|
||||
exit 1
|
||||
fi
|
||||
echo "[OK] Release dispatch is bound to ${EXPECTED_SOURCE_SHA}"
|
||||
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
with:
|
||||
|
||||
@@ -2168,6 +2168,13 @@ artifact-selection behaviour.
|
||||
trigger, promotion resolver, rendered release body, current upgrade guide,
|
||||
or current release packet that routes systemd/LXC rollback through the
|
||||
Unified Agent installer, and must retain explicit Docker image guidance.
|
||||
17. Bind every publishing release dispatch to the exact commit admitted by the
|
||||
caller. `.github/workflows/create-release.yml` must require a full
|
||||
40-character `expected_source_sha` and, before checkout, reject the run
|
||||
unless both `GITHUB_SHA` and `GITHUB_WORKFLOW_SHA` equal that commit.
|
||||
`scripts/trigger-release.sh` and `scripts/trigger-stable-patch.sh` must send
|
||||
the exact remote candidate SHA they already verified; branch ancestry or a
|
||||
later branch tip is not equivalent release admission.
|
||||
|
||||
## Current State
|
||||
|
||||
|
||||
@@ -933,6 +933,10 @@ func TestCreateReleaseUploadsPowerShellInstaller(t *testing.T) {
|
||||
convergenceWorkflow := string(convergenceContent)
|
||||
required := []string{
|
||||
`historical_asset_backfill_only:`,
|
||||
`expected_source_sha:`,
|
||||
`EXPECTED_SOURCE_SHA: ${{ inputs.expected_source_sha }}`,
|
||||
`"${GITHUB_SHA}" != "${EXPECTED_SOURCE_SHA}"`,
|
||||
`"${GITHUB_WORKFLOW_SHA}" != "${EXPECTED_SOURCE_SHA}"`,
|
||||
`description: 'Repair an already-published release packet in place without rebuilding binaries'`,
|
||||
`SYFT_VERSION="1.42.4"`,
|
||||
`SYFT_ARCHIVE="syft_${SYFT_VERSION}_linux_amd64.tar.gz"`,
|
||||
|
||||
@@ -1595,6 +1595,31 @@ class ReleasePromotionPolicyTest(unittest.TestCase):
|
||||
self.assertIn("did not produce a valid promotion metadata envelope", workflow)
|
||||
self.assertIn("Do not use this artifact to clear", workflow)
|
||||
|
||||
def test_release_dispatch_rejects_source_or_workflow_drift_before_checkout(self) -> None:
|
||||
workflow = yaml.safe_load(read(".github/workflows/create-release.yml"))
|
||||
steps = workflow["jobs"]["prepare"]["steps"]
|
||||
self.assertEqual(steps[0]["name"], "Verify admitted source commit")
|
||||
self.assertIn("actions/checkout@", steps[1]["uses"])
|
||||
expected = "a" * 40
|
||||
other = "b" * 40
|
||||
for admitted, source, workflow_sha, succeeds in (
|
||||
(expected, expected, expected, True),
|
||||
(expected, other, expected, False),
|
||||
(expected, expected, other, False),
|
||||
(expected, other, other, False),
|
||||
("", expected, expected, False),
|
||||
("main", expected, expected, False),
|
||||
("a" * 39, expected, expected, False),
|
||||
):
|
||||
with self.subTest(admitted=admitted, source=source, workflow=workflow_sha):
|
||||
result = subprocess.run(
|
||||
["bash", "-c", steps[0]["run"]],
|
||||
env={"PATH": os.defpath, "EXPECTED_SOURCE_SHA": admitted,
|
||||
"GITHUB_SHA": source, "GITHUB_WORKFLOW_SHA": workflow_sha},
|
||||
capture_output=True, text=True, check=False,
|
||||
)
|
||||
self.assertEqual(result.returncode == 0, succeeds, result.stdout + result.stderr)
|
||||
|
||||
def test_release_workflow_enforces_rc_lineage_soak_and_v5_notice(self) -> None:
|
||||
content = read(".github/workflows/create-release.yml")
|
||||
update_demo_workflow = read(".github/workflows/update-demo-server.yml")
|
||||
@@ -1694,6 +1719,10 @@ class ReleasePromotionPolicyTest(unittest.TestCase):
|
||||
self.assertIn("build_rollback_section", renderer)
|
||||
self.assertIn("promotion metadata out of customer notes", renderer)
|
||||
self.assertIn("historical_asset_backfill_only:", content)
|
||||
self.assertIn("expected_source_sha:", content)
|
||||
self.assertIn('EXPECTED_SOURCE_SHA: ${{ inputs.expected_source_sha }}', content)
|
||||
self.assertIn('"${GITHUB_SHA}" != "${EXPECTED_SOURCE_SHA}"', content)
|
||||
self.assertIn('"${GITHUB_WORKFLOW_SHA}" != "${EXPECTED_SOURCE_SHA}"', content)
|
||||
self.assertIn("Repair an already-published release packet in place without rebuilding binaries", content)
|
||||
self.assertIn("draft: true", content)
|
||||
self.assertIn("activate_release:", content)
|
||||
|
||||
@@ -120,6 +120,7 @@ python3 scripts/check-workflow-dispatch-inputs.py \
|
||||
--workflow-path .github/workflows/create-release.yml \
|
||||
--branch "$CURRENT_BRANCH" \
|
||||
--require version \
|
||||
--require expected_source_sha \
|
||||
--require release_notes \
|
||||
--require release_screenshot_plan \
|
||||
--require promoted_from_tag \
|
||||
@@ -372,6 +373,7 @@ echo "Triggering release workflow..."
|
||||
if [ -n "$NOTES_FILE" ]; then
|
||||
jq -n \
|
||||
--arg version "$VERSION" \
|
||||
--arg expected_source_sha "$LOCAL" \
|
||||
--rawfile release_notes "$NOTES_FILE" \
|
||||
--rawfile release_screenshot_plan "$VISUAL_PLAN_FILE" \
|
||||
--arg rollback_version "$ROLLBACK_VERSION" \
|
||||
@@ -387,6 +389,7 @@ if [ -n "$NOTES_FILE" ]; then
|
||||
--arg mobile_release_evidence "$MOBILE_RELEASE_EVIDENCE" \
|
||||
'{
|
||||
version: $version,
|
||||
expected_source_sha: $expected_source_sha,
|
||||
release_notes: $release_notes,
|
||||
release_screenshot_plan: $release_screenshot_plan,
|
||||
rollback_version: $rollback_version,
|
||||
|
||||
@@ -203,6 +203,7 @@ else
|
||||
--workflow-path .github/workflows/create-release.yml \
|
||||
--branch "$CURRENT_BRANCH" \
|
||||
--require version \
|
||||
--require expected_source_sha \
|
||||
--require release_notes \
|
||||
--require release_screenshot_plan \
|
||||
--require promoted_from_tag \
|
||||
@@ -219,6 +220,7 @@ else
|
||||
|
||||
jq -n \
|
||||
--arg version "$VERSION" \
|
||||
--arg expected_source_sha "$LOCAL_SHA" \
|
||||
--rawfile release_notes "$NOTES_FILE" \
|
||||
--rawfile release_screenshot_plan "$VISUAL_PLAN_FILE" \
|
||||
--arg promoted_from_tag "" \
|
||||
@@ -234,6 +236,7 @@ else
|
||||
--arg mobile_release_evidence "$MOBILE_RELEASE_EVIDENCE" \
|
||||
'{
|
||||
version: $version,
|
||||
expected_source_sha: $expected_source_sha,
|
||||
release_notes: $release_notes,
|
||||
release_screenshot_plan: $release_screenshot_plan,
|
||||
promoted_from_tag: $promoted_from_tag,
|
||||
|
||||
Reference in New Issue
Block a user