Merge pull request #1840 from rcourtman/claude/helm-publish-checkout-order

Fix the release preflight worker and Helm publish, and map v6.4.3 to release/v6.4
This commit is contained in:
rcourtman
2026-09-02 10:10:19 +01:00
committed by GitHub
7 changed files with 91 additions and 10 deletions
+7 -7
View File
@@ -55,6 +55,13 @@ jobs:
id-token: write
attestations: write
steps:
- name: Checkout repository
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
fetch-depth: 0
fetch-tags: true
- name: Determine chart version
id: versions
env:
@@ -93,13 +100,6 @@ jobs:
python3 scripts/write_github_output.py release_tag "$RELEASE_TAG"
echo "is_prerelease=$IS_PRERELEASE" >> "$GITHUB_OUTPUT"
- name: Checkout repository
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
fetch-depth: 0
fetch-tags: true
- name: Set up Helm
uses: azure/setup-helm@dda3372f752e03dde6b3237bc9431cdc2f7a02a2 # v5.0.0
with:
+5
View File
@@ -18,6 +18,11 @@
"prerelease_branch": "release/v6.3.2",
"stable_branch": "release/v6.3.2"
},
{
"version_prefix": "6.4.3",
"prerelease_branch": "release/v6.4",
"stable_branch": "release/v6.4"
},
{
"version_prefix": "6.5.",
"prerelease_branch": "release/v6.5",
@@ -279,6 +279,14 @@ without the other lanes changing the candidate underneath it.
v6.2.0, v6.3.0, and v6.4.0. They remain recorded and bounded; the train
does not continue the practice. An exception requires active customer
harm and is recorded in the release notes.
7. The `v6.4.3` patch line predates the first train and is the first line
released under the train's branch rule. `release/v6.4` was created from
`main` at the exact-SHA-qualified commit `56e51e622e` on 2026-09-02 and is
declared in `control_plane.json` with the version prefix `6.4.3`, so the
release workflow refuses a `v6.4.3` dispatch from any other branch and a
moving `main` can no longer invalidate the compiler's exact-SHA binding
between dispatch and compilation, which is what failed run 33579042375.
Earlier `6.4.x` versions keep their historical `main` mapping.
## Paid Pro Artifact Lineage
@@ -948,6 +948,15 @@ artifact-selection behaviour.
Worker startup must compare the complete `go`-prefixed toolchain identity
from `go.mod` with `go env GOVERSION` so a formatting mismatch cannot reject
an otherwise exact toolchain or conceal a real version drift.
The worker must activate an installed `mise` toolchain before its tool
checks, because it runs over a non-login ssh shell where profile hooks do
not load; it must leave `GOTMPDIR` unset by default so unix-socket test
fixtures resolve under `/tmp` exactly as on GitHub runners; and it must
publish the smoke stacks on a host port pair it has verified free
(honouring `PULSE_RELEASE_PREFLIGHT_E2E_PORT`), probe health and update
status on that port, and hand Playwright the same base URL, because a
worker may also host long-running Pulse instances on `7655` and `17655`
and a port collision fails the smoke only after every other stage passed.
Race-instrumented Go builds must place `GOTMPDIR` under the worker's
persistent run directory and remove that bounded scratch directory on exit;
a small WSL `/tmp` tmpfs must not turn release qualification into a false
@@ -1947,6 +1956,11 @@ artifact-selection behaviour.
`release` and `workflow_dispatch` triggers, and its chart-version
resolver must prefer inputs over the release-event tag when inputs are
present so all three entry paths converge on the same identity.
Because that resolver writes its outputs through
`scripts/write_github_output.py`, the workflow must check out the
repository before the resolver step runs; de41ea1883 introduced the
script call ahead of the checkout and every chart publish failed until
the v6.4.3-rc.1 run exposed it.
`helm-pages.yml` must not treat chart-releaser's "no chart changes
detected" no-op as a successful Pages publication for a newly published
release version. A successful Pages workflow must create or update the
@@ -3189,6 +3189,14 @@ func TestPublishHelmChartReachableViaWorkflowCall(t *testing.T) {
if !(attestationLogin < chartPush && chartPush < attestation) {
t.Fatal("publish-helm-chart.yml must authenticate the OCI attestation client before pushing and attesting the chart")
}
// The chart-version resolver writes its outputs through
// scripts/write_github_output.py, so the repository must already be
// checked out when it runs (de41ea1883 broke every chart publish this way).
checkout := strings.Index(workflow, "- name: Checkout repository")
chartVersion := strings.Index(workflow, "- name: Determine chart version")
if !(checkout >= 0 && chartVersion > checkout) {
t.Fatal("publish-helm-chart.yml must check out the repository before the chart-version resolver runs scripts/write_github_output.py")
}
for _, forbidden := range []string{
`versions/latest/restore`,
`-f visibility=public`,
+33 -3
View File
@@ -44,6 +44,35 @@ RUN_DIR="${WORKER_ROOT}/tmp/${RUN_ID}"
GO_TMP_DIR="${PULSE_RELEASE_PREFLIGHT_GO_TMP_DIR:-}"
TIMINGS_FILE="${RUN_DIR}/timings.tsv"
TEST_DATA_DIR="${WORKER_ROOT}/test-data/${PROFILE}"
# The smoke stacks publish the Pulse server and agent ports on the host. A
# worker may also host long-running Pulse instances (pulse-dev keeps the
# dogfood instance on 7655 and a second instance on 17655), and a collision
# fails the smoke only after every other stage has passed. Honour an explicit
# override, otherwise take the first candidate pair with both ports free.
smoke_port_pair_is_free() {
local listeners
listeners="$(ss -Hltn 2>/dev/null | awk '{print $4}' | sed 's/.*://' | sort -u)"
! printf '%s\n' "$listeners" | grep -qx "$1" && ! printf '%s\n' "$listeners" | grep -qx "$2"
}
if [ -n "${PULSE_RELEASE_PREFLIGHT_E2E_PORT:-}" ]; then
PULSE_E2E_PORT="$PULSE_RELEASE_PREFLIGHT_E2E_PORT"
PULSE_E2E_AGENT_PORT="${PULSE_RELEASE_PREFLIGHT_E2E_AGENT_PORT:-$((PULSE_E2E_PORT + 1))}"
else
PULSE_E2E_PORT=""
for candidate in 27655 28655 29655 31655; do
if smoke_port_pair_is_free "$candidate" "$((candidate + 1))"; then
PULSE_E2E_PORT="$candidate"
PULSE_E2E_AGENT_PORT="$((candidate + 1))"
break
fi
done
if [ -z "$PULSE_E2E_PORT" ]; then
echo "Error: no free host port pair for the smoke stack; set PULSE_RELEASE_PREFLIGHT_E2E_PORT." >&2
exit 3
fi
fi
export PULSE_E2E_PORT PULSE_E2E_AGENT_PORT
export PULSE_E2E_BASE_URL="http://localhost:${PULSE_E2E_PORT}"
# The worker is invoked over a non-login ssh shell, so /etc/profile.d is not
# sourced and an infra-managed mise toolchain (Node 24, Go) would be shadowed by
@@ -236,6 +265,7 @@ run_playwright() {
--env CI=true \
--env HOME=/tmp \
--env "PULSE_E2E_DIAGNOSTIC=${PULSE_E2E_DIAGNOSTIC:-}" \
--env "PLAYWRIGHT_BASE_URL=${PULSE_E2E_BASE_URL}" \
--volume "$REPOSITORY_DIR/tests/integration:/work" \
--workdir /work \
"$PLAYWRIGHT_IMAGE" \
@@ -250,10 +280,10 @@ run_rehearsal_smoke() {
export MOCK_STALE_RELEASE=false
export PULSE_E2E_DIAGNOSTIC=1
docker compose -f docker-compose.test.yml up -d --wait
timeout 60 sh -c 'until curl -fsS http://localhost:7655/api/health >/dev/null; do sleep 2; done'
timeout 60 sh -c 'until curl -fsS ${PULSE_E2E_BASE_URL}/api/health >/dev/null; do sleep 2; done'
run_playwright tests/00-diagnostic.spec.ts --project=chromium --reporter=list
local status
status="$(curl -s -o "$RUN_DIR/update-status.json" -w '%{http_code}' http://localhost:7655/api/updates/status || true)"
status="$(curl -s -o "$RUN_DIR/update-status.json" -w '%{http_code}' ${PULSE_E2E_BASE_URL}/api/updates/status || true)"
case "$status" in
200|401|403) ;;
*)
@@ -275,7 +305,7 @@ run_release_smoke() {
docker compose -f docker-compose.test.yml up -d
timeout 60 sh -c 'until docker inspect --format="{{json .State.Health.Status}}" pulse-mock-github | grep -q healthy; do sleep 2; done'
timeout 60 sh -c 'until docker inspect --format="{{json .State.Health.Status}}" pulse-test-server | grep -q healthy; do sleep 2; done'
timeout 60 sh -c 'until curl -fsS http://localhost:7655/api/health >/dev/null; do sleep 2; done'
timeout 60 sh -c 'until curl -fsS ${PULSE_E2E_BASE_URL}/api/health >/dev/null; do sleep 2; done'
run_playwright tests/95-release-smoke.spec.ts --project=chromium --reporter=list
docker compose -f docker-compose.test.yml down -v
}
@@ -248,6 +248,22 @@ class ReleasePreflightTest(unittest.TestCase):
)
self.assertIn("is not reachable from a fetched origin branch", runner)
def test_worker_matches_ci_toolchain_and_publishes_smoke_on_a_free_port(self) -> None:
worker = (ROOT / "scripts/release-preflight-worker.sh").read_text()
# Non-login ssh shells never source profile hooks, so an installed mise
# toolchain must be activated explicitly or a stale system Node wins.
self.assertIn('"$HOME/.local/bin/mise" activate bash --shims', worker)
# GitHub runners leave GOTMPDIR unset; unix-socket fixtures sit at the
# sun_path limit under /tmp, so the worker must not nest test temp dirs.
self.assertIn('GO_TMP_DIR="${PULSE_RELEASE_PREFLIGHT_GO_TMP_DIR:-}"', worker)
# Smoke stacks publish on a verified-free host port pair and every probe
# and the browser use that port; a worker may host Pulse on 7655.
self.assertIn("smoke_port_pair_is_free", worker)
self.assertIn("PULSE_RELEASE_PREFLIGHT_E2E_PORT", worker)
self.assertIn('export PULSE_E2E_BASE_URL="http://localhost:${PULSE_E2E_PORT}"', worker)
self.assertIn('--env "PLAYWRIGHT_BASE_URL=${PULSE_E2E_BASE_URL}"', worker)
self.assertNotIn("localhost:7655", worker)
def test_worker_serializes_resource_intensive_test_suites(self) -> None:
worker = (ROOT / "scripts/release-preflight-worker.sh").read_text()
scheduling_block = re.search(