mirror of
https://github.com/rcourtman/Pulse.git
synced 2026-09-10 18:45:53 +00:00
Route demo updates over governed Tailscale path
This commit is contained in:
@@ -31,6 +31,13 @@ Required environment secrets:
|
||||
3. **DEMO_SERVER_USER**
|
||||
- The SSH username for the demo server (e.g. `root` or a deploy user with sudo access)
|
||||
|
||||
Required shared secret:
|
||||
|
||||
1. **TS_AUTHKEY**
|
||||
- Tailscale auth key used by the governed demo deploy/update workflows before SSH
|
||||
- Allows GitHub-hosted runners to reach private demo targets such as the stable `pulse-relay` Tailscale host
|
||||
- May be stored as a repository secret or repeated in the selected environment if desired
|
||||
|
||||
Required environment variables:
|
||||
|
||||
1. **DEMO_EXPECTED_HOSTNAME**
|
||||
@@ -68,10 +75,11 @@ Optional environment variables:
|
||||
3. **Service identity guard**: Preview runs default to `pulse-v6-preview` and refuse to target the stable `pulse` service identity
|
||||
4. **Governance check**: Validates the selected tag is reachable from the governed release branch for that version
|
||||
5. **Latest check**: Refuses to update a target unless the published tag is the latest release for that target channel
|
||||
6. **Update**: SSHs to the selected demo host and runs the tag-matched root installer from that exact git tag
|
||||
7. **Host identity check**: Verifies the SSH target reports the governed expected hostname before running installer or deploy steps
|
||||
8. **Verify**: Checks that the new version is running, mock mode is active, and the public demo HTML serves the same frontend entry asset as the target service
|
||||
9. **Cleanup**: Removes SSH key from runner
|
||||
6. **Network attach**: Joins Tailscale before any SSH step so governed demo targets can stay on private hostnames or Tailscale IPs
|
||||
7. **Update**: SSHs to the selected demo host and runs the tag-matched root installer from that exact git tag
|
||||
8. **Host identity check**: Verifies the SSH target reports the governed expected hostname before running installer or deploy steps
|
||||
9. **Verify**: Checks that the new version is running, mock mode is active, and the public demo HTML serves the same frontend entry asset as the target service
|
||||
10. **Cleanup**: Removes SSH key from runner
|
||||
|
||||
### Testing
|
||||
|
||||
@@ -103,6 +111,8 @@ environment without changing the governed release workflow.
|
||||
|
||||
- Uses the same `demo-stable` / `demo-preview-v6` environment contract as the
|
||||
release-driven updater
|
||||
- Joins Tailscale before SSH so governed demo targets can stay on private
|
||||
addresses instead of requiring public runner reachability
|
||||
- Requires `DEMO_EXPECTED_HOSTNAME`, `DEMO_LOCAL_BASE_URL`, and `DEMO_PUBLIC_HEALTH_URL`
|
||||
- Supports optional `DEMO_SERVICE_NAME`, `DEMO_INSTALL_DIR`, `DEMO_TEST_PORT`,
|
||||
`DEMO_AUTH_USER`, and `DEMO_AUTH_PASS`
|
||||
|
||||
@@ -226,6 +226,11 @@ jobs:
|
||||
git show "refs/tags/${TAG}:install.sh" > /tmp/pulse-install.sh
|
||||
chmod +x /tmp/pulse-install.sh
|
||||
|
||||
- name: Tailscale
|
||||
uses: tailscale/github-action@v2
|
||||
with:
|
||||
authkey: ${{ secrets.TS_AUTHKEY }}
|
||||
|
||||
- name: Setup SSH
|
||||
env:
|
||||
DEMO_SERVER_HOST: ${{ secrets.DEMO_SERVER_HOST }}
|
||||
|
||||
+6
-9
@@ -7,26 +7,23 @@
|
||||
## Blocking Facts
|
||||
|
||||
1. No Pulse v6 prerelease has shipped yet.
|
||||
2. The selected remote ref `origin/pulse/v6-release` is still behind the current
|
||||
local governed branch state, so `Release Dry Run` would exercise stale remote
|
||||
control-plane metadata instead of the intended candidate.
|
||||
3. The governed release profile in `docs/release-control/control_plane.json`
|
||||
2. The governed release profile in `docs/release-control/control_plane.json`
|
||||
currently declares both `prerelease_branch` and `stable_branch` as
|
||||
`pulse/v6-release`.
|
||||
4. The active control-plane target is still `v6-rc-stabilization`, not
|
||||
3. The active control-plane target is still `v6-rc-stabilization`, not
|
||||
`v6-ga-promotion`.
|
||||
5. The active local `pulse/v6-release` branch currently reports `VERSION=6.0.0-rc.1`, so the
|
||||
4. The active local `pulse/v6-release` branch currently reports `VERSION=6.0.0-rc.1`, so the
|
||||
working line is still prerelease and there is not yet a governed local stable
|
||||
`6.0.0` candidate.
|
||||
6. There is still no governed `Prerelease-to-GA Rehearsal Record` proving a successful
|
||||
5. There is still no governed `Prerelease-to-GA Rehearsal Record` proving a successful
|
||||
non-publish `Release Dry Run` for the eventual stable `6.0.0` candidate.
|
||||
7. `docs/releases/RELEASE_NOTES_v6.md` and
|
||||
6. `docs/releases/RELEASE_NOTES_v6.md` and
|
||||
`docs/release-control/v6/internal/V5_MAINTENANCE_SUPPORT_POLICY.md` still leave the
|
||||
GA announcement dates as placeholders because no real prerelease lineage or GA-ready
|
||||
rehearsal has locked them yet:
|
||||
- `v6` GA date placeholder: `[v6-ga-date]`
|
||||
- `v5` end-of-support placeholder: `[v5-eos-date]`
|
||||
8. There is still no governed `Release Dry Run` artifact or rehearsal record
|
||||
7. There is still no governed `Release Dry Run` artifact or rehearsal record
|
||||
exercising stable inputs for:
|
||||
- `version=6.0.0`
|
||||
- no governed `promoted_from_tag` exists yet because no prerelease has shipped
|
||||
|
||||
@@ -191,6 +191,11 @@ shell actually updated. That proof
|
||||
must use a deterministic HTML parser for the actual module entry script rather
|
||||
than brittle escaped shell regex or a first-match asset scrape that can fail
|
||||
differently over SSH or select the wrong preloaded chunk.
|
||||
Those same governed demo deploy/update workflows also own the runner-to-host
|
||||
network path. They must establish the canonical Tailscale connectivity step
|
||||
before SSH setup so stable or preview targets may stay on governed private
|
||||
hostnames or Tailscale IPs, rather than silently depending on public SSH
|
||||
reachability from GitHub-hosted runners.
|
||||
Those same governed release workflows also own the operator-facing wording for
|
||||
that promotion metadata. Human-visible workflow inputs, summaries, and error
|
||||
messages must describe the path as a prerelease or preview flow rather than
|
||||
|
||||
@@ -139,6 +139,27 @@ func TestDeployDemoWorkflowFailsClosedForPreviewAndVerifiesFrontendParity(t *tes
|
||||
}
|
||||
}
|
||||
|
||||
func TestUpdateDemoWorkflowUsesGovernedNetworkPath(t *testing.T) {
|
||||
workflowBytes, err := os.ReadFile(repoFile(".github", "workflows", "update-demo-server.yml"))
|
||||
if err != nil {
|
||||
t.Fatalf("read update-demo-server workflow: %v", err)
|
||||
}
|
||||
|
||||
workflow := string(workflowBytes)
|
||||
required := []string{
|
||||
`- name: Tailscale`,
|
||||
`uses: tailscale/github-action@v2`,
|
||||
`authkey: ${{ secrets.TS_AUTHKEY }}`,
|
||||
`Verify target host identity`,
|
||||
`Demo environment points at host $REMOTE_HOSTNAME but expected $DEMO_EXPECTED_HOSTNAME.`,
|
||||
}
|
||||
for _, needle := range required {
|
||||
if !strings.Contains(workflow, needle) {
|
||||
t.Fatalf("update-demo-server workflow missing governed network path: %s", needle)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestDockerfileStagesShippedDocsForEmbeddedFrontendBuild(t *testing.T) {
|
||||
dockerfileBytes, err := os.ReadFile(repoFile("Dockerfile"))
|
||||
if err != nil {
|
||||
|
||||
@@ -231,6 +231,8 @@ class ReleasePromotionPolicyTest(unittest.TestCase):
|
||||
self.assertIn("latest published release for target", demo)
|
||||
self.assertIn('SERVICE_NAME="pulse-v6-preview"', demo)
|
||||
self.assertIn("Preview demo updates must not target the stable pulse service.", demo)
|
||||
self.assertIn("tailscale/github-action@v2", demo)
|
||||
self.assertIn("TS_AUTHKEY", demo)
|
||||
self.assertIn("DEMO_EXPECTED_HOSTNAME", demo)
|
||||
self.assertIn("Verify target host identity", demo)
|
||||
self.assertIn("Demo environment points at host $REMOTE_HOSTNAME but expected $DEMO_EXPECTED_HOSTNAME.", demo)
|
||||
@@ -282,7 +284,6 @@ class ReleasePromotionPolicyTest(unittest.TestCase):
|
||||
blocked = read("docs/release-control/v6/internal/records/rc-to-ga-promotion-readiness-blocked-2026-04-04.md")
|
||||
current_version = read("VERSION").strip()
|
||||
active_target_id = read_json("docs/release-control/control_plane.json")["active_target_id"]
|
||||
self.assertIn("origin/pulse/v6-release", blocked)
|
||||
self.assertIn(f"VERSION={current_version}", blocked)
|
||||
self.assertIn("artifact-owned candidate stable tag", blocked)
|
||||
self.assertIn("artifact-owned promotion channel", blocked)
|
||||
|
||||
Reference in New Issue
Block a user