mirror of
https://github.com/rcourtman/Pulse.git
synced 2026-07-09 16:00:59 +00:00
Pin deployment defaults and verify Helm docs downloads
This commit is contained in:
parent
9c4bb4a90e
commit
1841c032f6
8 changed files with 146 additions and 11 deletions
8
.github/workflows/helm-pages.yml
vendored
8
.github/workflows/helm-pages.yml
vendored
|
|
@ -107,8 +107,12 @@ jobs:
|
|||
- name: Install helm-docs
|
||||
run: |
|
||||
cd /tmp
|
||||
wget https://github.com/norwoodj/helm-docs/releases/download/v1.14.2/helm-docs_1.14.2_Linux_x86_64.tar.gz
|
||||
tar -xzf helm-docs_1.14.2_Linux_x86_64.tar.gz
|
||||
HELM_DOCS_VERSION="1.14.2"
|
||||
HELM_DOCS_ARCHIVE="helm-docs_${HELM_DOCS_VERSION}_Linux_x86_64.tar.gz"
|
||||
HELM_DOCS_SHA256="a8cf72ada34fad93285ba2a452b38bdc5bd52cc9a571236244ec31022928d6cc"
|
||||
wget "https://github.com/norwoodj/helm-docs/releases/download/v${HELM_DOCS_VERSION}/${HELM_DOCS_ARCHIVE}"
|
||||
printf '%s %s\n' "$HELM_DOCS_SHA256" "$HELM_DOCS_ARCHIVE" | sha256sum --check --
|
||||
tar -xzf "$HELM_DOCS_ARCHIVE"
|
||||
sudo mv helm-docs /usr/local/bin/
|
||||
helm-docs --version
|
||||
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ version: '3.8'
|
|||
|
||||
services:
|
||||
pulse:
|
||||
image: ${PULSE_IMAGE:-rcourtman/pulse:latest}
|
||||
image: ${PULSE_IMAGE:-rcourtman/pulse:6.0.0}
|
||||
container_name: pulse
|
||||
restart: unless-stopped
|
||||
logging:
|
||||
|
|
|
|||
|
|
@ -84,6 +84,8 @@ server-side update execution surfaces.
|
|||
62. `tests/integration/scripts/relay-mobile-token-helper.go`
|
||||
63. `tests/integration/tests/helpers.ts`
|
||||
64. `tests/integration/tests/runtime-defaults.ts`
|
||||
65. `docker-compose.yml`
|
||||
66. `scripts/install-docker.sh`
|
||||
|
||||
## Shared Boundaries
|
||||
|
||||
|
|
@ -96,7 +98,7 @@ server-side update execution surfaces.
|
|||
|
||||
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`, 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`
|
||||
3. Add or change shell installer, Windows installer, container-agent installer, or auto-update script behavior through `scripts/install.sh`, `scripts/install.ps1`, `scripts/install-container-agent.sh`, and `scripts/pulse-auto-update.sh`
|
||||
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`, `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`
|
||||
6. Add or change governed release-promotion workflow inputs, operator-facing promotion metadata, the canonical version file, prerelease feedback intake prompts, artifact publication lineage enforcement, release note or changelog packet composition, or stable-promotion rehearsal summaries through `.github/workflows/create-release.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`, `.github/workflows/update-demo-server.yml`, `.github/ISSUE_TEMPLATE/v6_rc_feedback.yml`, `docs/RELEASE_NOTES.md`, `docs/releases/`, `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/check-workflow-dispatch-inputs.py`, `scripts/release_control/render_release_body.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`, `scripts/trigger-release.sh`, and `scripts/trigger-release-dry-run.sh`
|
||||
|
|
@ -149,6 +151,9 @@ server-side update execution surfaces.
|
|||
in `deploy/helm/pulse/README.md` output must be regenerated from the same
|
||||
chart metadata and release line so published Helm docs, chart version
|
||||
badges, and packaged archive metadata all describe the identical cut.
|
||||
External helper binaries fetched by governed release workflows are part of
|
||||
the same supply-chain boundary and must be checksum-verified before they are
|
||||
executed.
|
||||
8. Add or change operator-facing hosted tenant runtime canary rollout, batch runtime contract reconciliation, canonical hosted route/public URL generation, or control-plane runtime-registry reconciliation through `cmd/pulse-control-plane/main.go`, `internal/cloudcp/docker/manager.go`, `internal/cloudcp/docker/labels.go`, and `internal/cloudcp/tenant_runtime_rollout.go`
|
||||
9. Add or change the canonical hosted staging smoke operator path through `scripts/run_hosted_staging_smoke.sh`, `tests/integration/scripts/bootstrap-hosted-mobile-onboarding.mjs`, `tests/integration/scripts/hosted-mobile-token-runtime.mjs`, `tests/integration/scripts/hosted-tenant-runtime.mjs`, and `tests/integration/scripts/relay-mobile-token-helper.go`
|
||||
|
||||
|
|
@ -209,6 +214,12 @@ so release automation, packaged chart metadata, and chart-runtime smoke no
|
|||
longer depend on unowned `deploy/helm/pulse/` files while the governed
|
||||
release workflows package and publish those artifacts.
|
||||
|
||||
That same lane also owns version-pinned Docker bootstrap defaults. The repo
|
||||
root `docker-compose.yml` sample and `scripts/install-docker.sh` must default
|
||||
to the governed `VERSION` cut instead of floating `:latest`, so self-hosted
|
||||
operators only move to a newer image when they choose a newer explicit tag or
|
||||
override `PULSE_IMAGE`.
|
||||
|
||||
`internal/updates/` is the live deployment and upgrade planner. It owns
|
||||
deployment-type detection, update-plan generation, adapter selection, server
|
||||
update sequencing, and rollback-aware update state for supported Pulse
|
||||
|
|
|
|||
|
|
@ -2389,6 +2389,7 @@
|
|||
".github/workflows/release-dry-run.yml",
|
||||
".github/workflows/update-demo-server.yml",
|
||||
"cmd/pulse-control-plane/main.go",
|
||||
"docker-compose.yml",
|
||||
"Dockerfile",
|
||||
"docs/release-control/v6/internal/PRE_RELEASE_CHECKLIST.md",
|
||||
"docs/release-control/v6/internal/RC_TO_GA_REHEARSAL_TEMPLATE.md",
|
||||
|
|
@ -2419,6 +2420,7 @@
|
|||
"scripts/hot-dev-bg.sh",
|
||||
"scripts/hot-dev.sh",
|
||||
"scripts/install-container-agent.sh",
|
||||
"scripts/install-docker.sh",
|
||||
"scripts/install.ps1",
|
||||
"scripts/install.sh",
|
||||
"scripts/pulse-auto-update.sh",
|
||||
|
|
@ -2448,7 +2450,10 @@
|
|||
"verification": {
|
||||
"allow_same_subsystem_tests": false,
|
||||
"test_prefixes": [],
|
||||
"exact_files": [],
|
||||
"exact_files": [
|
||||
"scripts/installtests/build_release_assets_test.go",
|
||||
"scripts/installtests/install_docker_sh_test.go"
|
||||
],
|
||||
"require_explicit_path_policy_coverage": true,
|
||||
"path_policies": [
|
||||
{
|
||||
|
|
@ -2589,7 +2594,9 @@
|
|||
"label": "deployment script runtime proof",
|
||||
"match_prefixes": [],
|
||||
"match_files": [
|
||||
"docker-compose.yml",
|
||||
"scripts/install-container-agent.sh",
|
||||
"scripts/install-docker.sh",
|
||||
"scripts/install.ps1",
|
||||
"scripts/pulse-auto-update.sh"
|
||||
],
|
||||
|
|
|
|||
|
|
@ -4,8 +4,31 @@
|
|||
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)"
|
||||
DOCKER_IMAGE_REPO="${DOCKER_IMAGE_REPO:-rcourtman/pulse}"
|
||||
DEFAULT_PULSE_IMAGE="${DOCKER_IMAGE_REPO}:latest"
|
||||
CANONICAL_DEFAULT_PULSE_VERSION="6.0.0"
|
||||
|
||||
resolve_default_pulse_version() {
|
||||
if [ -n "${PULSE_IMAGE_VERSION:-}" ]; then
|
||||
printf '%s' "${PULSE_IMAGE_VERSION}"
|
||||
return
|
||||
fi
|
||||
|
||||
for candidate in "${PULSE_VERSION_FILE:-}" "${SCRIPT_DIR}/../VERSION" "${PWD}/VERSION"; do
|
||||
if [ -n "${candidate}" ] && [ -f "${candidate}" ]; then
|
||||
local version
|
||||
version=$(tr -d '\r\n' < "${candidate}" 2>/dev/null || true)
|
||||
if [ -n "${version}" ]; then
|
||||
printf '%s' "${version}"
|
||||
return
|
||||
fi
|
||||
fi
|
||||
done
|
||||
|
||||
printf '%s' "${CANONICAL_DEFAULT_PULSE_VERSION}"
|
||||
}
|
||||
|
||||
DEFAULT_PULSE_IMAGE="${DOCKER_IMAGE_REPO}:$(resolve_default_pulse_version)"
|
||||
PULSE_IMAGE="${PULSE_IMAGE:-${DEFAULT_PULSE_IMAGE}}"
|
||||
PULSE_PORT="${PULSE_PORT:-7655}"
|
||||
|
||||
|
|
|
|||
|
|
@ -199,6 +199,58 @@ func TestReleaseWorkflowsUseSecretSafeAttestedImageBuilds(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestDeploymentDefaultsPinVersionedImagesAndHelmDocsChecksum(t *testing.T) {
|
||||
versionBytes, err := os.ReadFile(repoFile("VERSION"))
|
||||
if err != nil {
|
||||
t.Fatalf("read VERSION: %v", err)
|
||||
}
|
||||
version := strings.TrimSpace(string(versionBytes))
|
||||
if version == "" {
|
||||
t.Fatal("VERSION is empty")
|
||||
}
|
||||
|
||||
composeBytes, err := os.ReadFile(repoFile("docker-compose.yml"))
|
||||
if err != nil {
|
||||
t.Fatalf("read docker-compose.yml: %v", err)
|
||||
}
|
||||
compose := string(composeBytes)
|
||||
if !strings.Contains(compose, "image: ${PULSE_IMAGE:-rcourtman/pulse:"+version+"}") {
|
||||
t.Fatalf("docker-compose.yml must pin the governed release version:\n%s", compose)
|
||||
}
|
||||
if strings.Contains(compose, ":latest") {
|
||||
t.Fatalf("docker-compose.yml must not default to a floating latest tag:\n%s", compose)
|
||||
}
|
||||
|
||||
installDockerBytes, err := os.ReadFile(repoFile("scripts", "install-docker.sh"))
|
||||
if err != nil {
|
||||
t.Fatalf("read install-docker.sh: %v", err)
|
||||
}
|
||||
installDocker := string(installDockerBytes)
|
||||
if !strings.Contains(installDocker, `CANONICAL_DEFAULT_PULSE_VERSION="`+version+`"`) {
|
||||
t.Fatalf("install-docker.sh must pin the governed release version:\n%s", installDocker)
|
||||
}
|
||||
if strings.Contains(installDocker, ":latest") {
|
||||
t.Fatalf("install-docker.sh must not default to a floating latest tag:\n%s", installDocker)
|
||||
}
|
||||
|
||||
helmPagesBytes, err := os.ReadFile(repoFile(".github", "workflows", "helm-pages.yml"))
|
||||
if err != nil {
|
||||
t.Fatalf("read helm-pages.yml: %v", err)
|
||||
}
|
||||
helmPages := string(helmPagesBytes)
|
||||
required := []string{
|
||||
`HELM_DOCS_VERSION="1.14.2"`,
|
||||
`HELM_DOCS_ARCHIVE="helm-docs_${HELM_DOCS_VERSION}_Linux_x86_64.tar.gz"`,
|
||||
`HELM_DOCS_SHA256="a8cf72ada34fad93285ba2a452b38bdc5bd52cc9a571236244ec31022928d6cc"`,
|
||||
`sha256sum --check --`,
|
||||
}
|
||||
for _, needle := range required {
|
||||
if !strings.Contains(helmPages, needle) {
|
||||
t.Fatalf("helm-pages.yml missing checksum-verified helm-docs install step: %s", needle)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestDeployDemoWorkflowFailsClosedForPreviewAndVerifiesFrontendParity(t *testing.T) {
|
||||
workflowBytes, err := os.ReadFile(repoFile(".github", "workflows", "deploy-demo-server.yml"))
|
||||
if err != nil {
|
||||
|
|
|
|||
|
|
@ -8,8 +8,22 @@ import (
|
|||
"testing"
|
||||
)
|
||||
|
||||
func currentReleaseVersion(t *testing.T) string {
|
||||
t.Helper()
|
||||
content, err := os.ReadFile(repoFile("VERSION"))
|
||||
if err != nil {
|
||||
t.Fatalf("read VERSION: %v", err)
|
||||
}
|
||||
version := strings.TrimSpace(string(content))
|
||||
if version == "" {
|
||||
t.Fatal("VERSION is empty")
|
||||
}
|
||||
return version
|
||||
}
|
||||
|
||||
func TestInstallDockerScriptUsesConfiguredImageRepoDefault(t *testing.T) {
|
||||
workDir := t.TempDir()
|
||||
version := currentReleaseVersion(t)
|
||||
runInstallDockerScript(t, workDir, "DOCKER_IMAGE_REPO=example/pulse-enterprise")
|
||||
|
||||
composePath := filepath.Join(workDir, "docker-compose.yml")
|
||||
|
|
@ -18,11 +32,11 @@ func TestInstallDockerScriptUsesConfiguredImageRepoDefault(t *testing.T) {
|
|||
t.Fatalf("read docker-compose.yml: %v", err)
|
||||
}
|
||||
composeText := string(composeContent)
|
||||
if !strings.Contains(composeText, "image: ${PULSE_IMAGE:-example/pulse-enterprise:latest}") {
|
||||
if !strings.Contains(composeText, "image: ${PULSE_IMAGE:-example/pulse-enterprise:"+version+"}") {
|
||||
t.Fatalf("docker-compose.yml missing configured image default:\n%s", composeText)
|
||||
}
|
||||
if strings.Contains(composeText, "rcourtman/pulse:latest") {
|
||||
t.Fatalf("docker-compose.yml still hardcodes upstream image:\n%s", composeText)
|
||||
if strings.Contains(composeText, ":latest") {
|
||||
t.Fatalf("docker-compose.yml must not default to a floating latest tag:\n%s", composeText)
|
||||
}
|
||||
|
||||
envPath := filepath.Join(workDir, ".env")
|
||||
|
|
@ -31,13 +45,14 @@ func TestInstallDockerScriptUsesConfiguredImageRepoDefault(t *testing.T) {
|
|||
t.Fatalf("read .env: %v", err)
|
||||
}
|
||||
envText := string(envContent)
|
||||
if !strings.Contains(envText, "PULSE_IMAGE=example/pulse-enterprise:latest") {
|
||||
if !strings.Contains(envText, "PULSE_IMAGE=example/pulse-enterprise:"+version) {
|
||||
t.Fatalf(".env missing configured image default:\n%s", envText)
|
||||
}
|
||||
}
|
||||
|
||||
func TestInstallDockerScriptPrefersExplicitPulseImage(t *testing.T) {
|
||||
workDir := t.TempDir()
|
||||
version := currentReleaseVersion(t)
|
||||
runInstallDockerScript(
|
||||
t,
|
||||
workDir,
|
||||
|
|
@ -51,7 +66,7 @@ func TestInstallDockerScriptPrefersExplicitPulseImage(t *testing.T) {
|
|||
t.Fatalf("read docker-compose.yml: %v", err)
|
||||
}
|
||||
composeText := string(composeContent)
|
||||
if !strings.Contains(composeText, "image: ${PULSE_IMAGE:-example/pulse-enterprise:latest}") {
|
||||
if !strings.Contains(composeText, "image: ${PULSE_IMAGE:-example/pulse-enterprise:"+version+"}") {
|
||||
t.Fatalf("docker-compose.yml lost configured default image:\n%s", composeText)
|
||||
}
|
||||
|
||||
|
|
@ -66,6 +81,22 @@ func TestInstallDockerScriptPrefersExplicitPulseImage(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestRepoDockerComposeDefaultPinsCurrentVersion(t *testing.T) {
|
||||
version := currentReleaseVersion(t)
|
||||
content, err := os.ReadFile(repoFile("docker-compose.yml"))
|
||||
if err != nil {
|
||||
t.Fatalf("read docker-compose.yml: %v", err)
|
||||
}
|
||||
|
||||
text := string(content)
|
||||
if !strings.Contains(text, "image: ${PULSE_IMAGE:-rcourtman/pulse:"+version+"}") {
|
||||
t.Fatalf("repo docker-compose.yml must pin the current release version:\n%s", text)
|
||||
}
|
||||
if strings.Contains(text, ":latest") {
|
||||
t.Fatalf("repo docker-compose.yml must not default to a floating latest tag:\n%s", text)
|
||||
}
|
||||
}
|
||||
|
||||
func runInstallDockerScript(t *testing.T, workDir string, envVars ...string) {
|
||||
t.Helper()
|
||||
|
||||
|
|
|
|||
|
|
@ -402,6 +402,13 @@ class ReleasePromotionPolicyTest(unittest.TestCase):
|
|||
self.assertIn('git checkout -B "$REQUIRED_BRANCH" "origin/$REQUIRED_BRANCH"', helm_pages)
|
||||
self.assertIn('git pull --rebase origin "$REQUIRED_BRANCH"', helm_pages)
|
||||
self.assertIn('git push origin HEAD:"$REQUIRED_BRANCH"', helm_pages)
|
||||
self.assertIn('HELM_DOCS_VERSION="1.14.2"', helm_pages)
|
||||
self.assertIn('HELM_DOCS_ARCHIVE="helm-docs_${HELM_DOCS_VERSION}_Linux_x86_64.tar.gz"', helm_pages)
|
||||
self.assertIn(
|
||||
'HELM_DOCS_SHA256="a8cf72ada34fad93285ba2a452b38bdc5bd52cc9a571236244ec31022928d6cc"',
|
||||
helm_pages,
|
||||
)
|
||||
self.assertIn('printf \'%s %s\\n\' "$HELM_DOCS_SHA256" "$HELM_DOCS_ARCHIVE" | sha256sum --check --', helm_pages)
|
||||
self.assertNotIn("git pull --rebase origin main", helm_pages)
|
||||
self.assertNotIn("git push origin main", helm_pages)
|
||||
self.assertNotIn("kind load docker-image", helm_pages)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue