mirror of
https://github.com/rcourtman/Pulse.git
synced 2026-09-10 02:25:56 +00:00
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:
@@ -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:
|
||||
|
||||
@@ -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`,
|
||||
|
||||
@@ -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(
|
||||
|
||||
Reference in New Issue
Block a user