diff --git a/VERSION b/VERSION index 5fe607230..9b9a24420 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -6.0.1 +6.0.2 diff --git a/deploy/helm/pulse/Chart.yaml b/deploy/helm/pulse/Chart.yaml index 00bf2bc05..046c567e6 100644 --- a/deploy/helm/pulse/Chart.yaml +++ b/deploy/helm/pulse/Chart.yaml @@ -2,9 +2,9 @@ apiVersion: v2 name: pulse description: Helm chart for deploying the Pulse hub and optional Docker monitoring agent. type: application -version: 6.0.1 -appVersion: "6.0.1" -icon: https://raw.githubusercontent.com/rcourtman/Pulse/v6.0.1/docs/images/pulse-logo.svg +version: 6.0.2 +appVersion: "6.0.2" +icon: https://raw.githubusercontent.com/rcourtman/Pulse/v6.0.2/docs/images/pulse-logo.svg keywords: - monitoring - proxmox @@ -32,7 +32,7 @@ annotations: description: Smoke tests with kind cluster deployment artifacthub.io/links: | - name: Documentation - url: https://github.com/rcourtman/Pulse/blob/v6.0.1/docs/KUBERNETES.md + url: https://github.com/rcourtman/Pulse/blob/v6.0.2/docs/KUBERNETES.md - name: Support url: https://github.com/rcourtman/Pulse/discussions artifacthub.io/maintainers: | diff --git a/deploy/helm/pulse/README.md b/deploy/helm/pulse/README.md index f02a1ca4b..34692b14b 100644 --- a/deploy/helm/pulse/README.md +++ b/deploy/helm/pulse/README.md @@ -1,6 +1,6 @@ # pulse -![Version: 6.0.1](https://img.shields.io/badge/Version-6.0.1-informational?style=flat-square) ![Type: application](https://img.shields.io/badge/Type-application-informational?style=flat-square) ![AppVersion: 6.0.1](https://img.shields.io/badge/AppVersion-6.0.1-informational?style=flat-square) +![Version: 6.0.2](https://img.shields.io/badge/Version-6.0.2-informational?style=flat-square) ![Type: application](https://img.shields.io/badge/Type-application-informational?style=flat-square) ![AppVersion: 6.0.2](https://img.shields.io/badge/AppVersion-6.0.2-informational?style=flat-square) Helm chart for deploying the Pulse hub and optional Docker monitoring agent. diff --git a/docker-compose.yml b/docker-compose.yml index 164a4294a..b17ffd3ef 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -2,7 +2,7 @@ version: '3.8' services: pulse: - image: ${PULSE_IMAGE:-rcourtman/pulse:6.0.1} + image: ${PULSE_IMAGE:-rcourtman/pulse:6.0.2} container_name: pulse restart: unless-stopped logging: diff --git a/docs/RELEASE_NOTES.md b/docs/RELEASE_NOTES.md index 8808add12..ffc8f3a3b 100644 --- a/docs/RELEASE_NOTES.md +++ b/docs/RELEASE_NOTES.md @@ -7,8 +7,8 @@ For historical v4 notes that previously lived in this repo, see: `docs/releases/RELEASE_NOTES_v4.md` For the current stable v6 patch packet, see: -- `docs/releases/RELEASE_NOTES_v6.0.1.md` -- `docs/releases/V6_CHANGELOG_v6.0.1.md` +- `docs/releases/RELEASE_NOTES_v6.0.2.md` +- `docs/releases/V6_CHANGELOG_v6.0.2.md` - `docs/UPGRADE_v6.md` For the first stable v6 release packet, see: diff --git a/docs/UPGRADE_v6.md b/docs/UPGRADE_v6.md index 8def0aae8..8ab11dea7 100644 --- a/docs/UPGRADE_v6.md +++ b/docs/UPGRADE_v6.md @@ -4,8 +4,8 @@ This guide covers practical upgrade steps for existing Pulse installs moving to For the current stable v6 packet and rollout references, see: -- `docs/releases/RELEASE_NOTES_v6.0.1.md` -- `docs/releases/V6_CHANGELOG_v6.0.1.md` +- `docs/releases/RELEASE_NOTES_v6.0.2.md` +- `docs/releases/V6_CHANGELOG_v6.0.2.md` - `docs/releases/RELEASE_NOTES_v6.md` - `docs/releases/V6_CHANGELOG.md` diff --git a/docs/release-control/v6/internal/subsystems/deployment-installability.md b/docs/release-control/v6/internal/subsystems/deployment-installability.md index c997e1362..b98352ca6 100644 --- a/docs/release-control/v6/internal/subsystems/deployment-installability.md +++ b/docs/release-control/v6/internal/subsystems/deployment-installability.md @@ -781,10 +781,10 @@ TLS floor in the dynamic config. ## Current State -The active stable `v6.0.1` cut sets the repo-root `VERSION`, repo-root +The active stable `v6.0.2` cut sets the repo-root `VERSION`, repo-root `docker-compose.yml` image default, `scripts/install-docker.sh` fallback, and -Helm chart release metadata to the same `6.0.1` release version. This patch -release uses the stable hotfix path with `rollback_version=v6.0.0`, +Helm chart release metadata to the same `6.0.2` release version. This patch +release uses the stable hotfix path with `rollback_version=v6.0.1`, `hotfix_exception=true`, a release-owner reason, and no fabricated same-version RC tag. The initial GA promotion metadata remains `promoted_from_tag=v6.0.0-rc.7`, `rollback_version=v5.1.35`, @@ -838,8 +838,8 @@ compose image default, standalone installer fallback constant, and packaged Helm metadata. A draft release workflow failure caused by stale image or chart pins is a release-packet blocker until the defaults, tests, and evidence record are refreshed from the new branch head. -For the active stable `v6.0.1` cut, the repo-root compose default and -`scripts/install-docker.sh` fallback must both pin `6.0.1`; the stable +For the active stable `v6.0.2` cut, the repo-root compose default and +`scripts/install-docker.sh` fallback must both pin `6.0.2`; the stable promotion guard remains in force by rejecting leftover `-rc.` defaults when the governed `VERSION` is a stable release. The RC7 packet refresh records `fc10de9b5477613316473267b72b05b6b2b7aaff` diff --git a/docs/releases/RELEASE_NOTES_v6.0.2.md b/docs/releases/RELEASE_NOTES_v6.0.2.md new file mode 100644 index 000000000..76c3a77df --- /dev/null +++ b/docs/releases/RELEASE_NOTES_v6.0.2.md @@ -0,0 +1,25 @@ +# Pulse v6.0.2 Release Notes + +`v6.0.2` is a stable patch release for the Pulse v6 line. It follows +`v6.0.1` and focuses on agent upgrade recovery, release publication, and demo +verification after the GA cutover. + +## Fixes + +- Fixed legacy agent update recovery when an installed agent has connection + details in its running process but no persisted v6 connection state. The + generated update command can now recover the URL and token instead of + failing with "No existing Pulse Agent connection state found." +- Restored release asset validation for stable patch hotfixes so a patch can + publish without inventing a same-version prerelease tag when the explicit + hotfix path is selected. +- Restored demo verification entitlement handling after release updates so the + public demo can recover its fixture entitlement and verify mock mode after + deployment. +- Refreshed Helm chart release metadata and documentation for the stable patch + line. + +## Upgrade Notes + +Use the normal v6 install or update flow for `v6.0.2`. The rollback target for +this patch release is `v6.0.1`. diff --git a/docs/releases/V6_CHANGELOG_v6.0.2.md b/docs/releases/V6_CHANGELOG_v6.0.2.md new file mode 100644 index 000000000..40f85d30f --- /dev/null +++ b/docs/releases/V6_CHANGELOG_v6.0.2.md @@ -0,0 +1,20 @@ +# Pulse v6.0.2 + +_This changelog describes the stable `v6.0.2` patch release compared with +`v6.0.1`._ + +## Fixed + +- Legacy agent updates now recover connection details from the running agent + process when the persisted v6 state file is missing or stale. +- Stable patch release validation now supports the governed hotfix path without + requiring a fabricated same-version RC tag. +- Demo update verification now restores the fixture entitlement before checking + mock-mode access after release deployment. +- Helm chart release metadata now tracks the active stable patch version. + +## Release Metadata + +- Version: `v6.0.2` +- Rollback target: `v6.0.1` +- Promotion path: stable patch hotfix from `pulse/v6-release` diff --git a/scripts/install-docker.sh b/scripts/install-docker.sh index b3a8db593..7ca066396 100755 --- a/scripts/install-docker.sh +++ b/scripts/install-docker.sh @@ -6,7 +6,7 @@ set -euo pipefail SCRIPT_DIR="$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)" DOCKER_IMAGE_REPO="${DOCKER_IMAGE_REPO:-rcourtman/pulse}" -CANONICAL_DEFAULT_PULSE_VERSION="6.0.1" +CANONICAL_DEFAULT_PULSE_VERSION="6.0.2" resolve_default_pulse_version() { if [ -n "${PULSE_IMAGE_VERSION:-}" ]; then diff --git a/scripts/installtests/build_release_assets_test.go b/scripts/installtests/build_release_assets_test.go index 8e0416eef..fcc544634 100644 --- a/scripts/installtests/build_release_assets_test.go +++ b/scripts/installtests/build_release_assets_test.go @@ -673,6 +673,9 @@ func TestDeploymentDefaultsPinVersionedImagesAndHelmDocsChecksum(t *testing.T) { t.Fatalf("Helm Chart.yaml must pin the governed release version, missing %s:\n%s", needle, chart) } } + if previous, ok := previousStablePatchVersion(version); ok && strings.Contains(chart, "v"+previous) { + t.Fatalf("Helm Chart.yaml must not retain the previous stable patch tag v%s:\n%s", previous, chart) + } chartReadmeBytes, err := os.ReadFile(repoFile("deploy", "helm", "pulse", "README.md")) if err != nil { @@ -688,6 +691,9 @@ func TestDeploymentDefaultsPinVersionedImagesAndHelmDocsChecksum(t *testing.T) { t.Fatalf("Helm README.md must reflect the governed release version, missing %s:\n%s", needle, chartReadme) } } + if previous, ok := previousStablePatchVersion(version); ok && strings.Contains(chartReadme, previous) { + t.Fatalf("Helm README.md must not retain the previous stable patch version %s:\n%s", previous, chartReadme) + } helmPagesBytes, err := os.ReadFile(repoFile(".github", "workflows", "helm-pages.yml")) if err != nil { diff --git a/scripts/installtests/install_docker_sh_test.go b/scripts/installtests/install_docker_sh_test.go index ed8838173..37ab3bc5e 100644 --- a/scripts/installtests/install_docker_sh_test.go +++ b/scripts/installtests/install_docker_sh_test.go @@ -1,9 +1,11 @@ package installtests import ( + "fmt" "os" "os/exec" "path/filepath" + "strconv" "strings" "testing" ) @@ -25,6 +27,21 @@ func isPrereleaseVersion(version string) bool { return strings.Contains(version, "-") } +func previousStablePatchVersion(version string) (string, bool) { + if isPrereleaseVersion(version) { + return "", false + } + parts := strings.Split(version, ".") + if len(parts) != 3 { + return "", false + } + patch, err := strconv.Atoi(parts[2]) + if err != nil || patch <= 0 { + return "", false + } + return fmt.Sprintf("%s.%s.%d", parts[0], parts[1], patch-1), true +} + func TestInstallDockerScriptUsesConfiguredImageRepoDefault(t *testing.T) { workDir := t.TempDir() version := currentReleaseVersion(t) @@ -105,6 +122,9 @@ func TestRepoDockerComposeDefaultPinsCurrentVersion(t *testing.T) { if !isPrereleaseVersion(version) && version != "6.0.0" && strings.Contains(text, "rcourtman/pulse:6.0.0") { t.Fatalf("stable patch repo docker-compose.yml must move off the initial GA image tag:\n%s", text) } + if previous, ok := previousStablePatchVersion(version); ok && strings.Contains(text, "rcourtman/pulse:"+previous) { + t.Fatalf("repo docker-compose.yml must not retain the previous stable patch image tag %s:\n%s", previous, text) + } if strings.Contains(text, ":latest") { t.Fatalf("repo docker-compose.yml must not default to a floating latest tag:\n%s", text) } @@ -130,6 +150,9 @@ func TestInstallDockerScriptFallbackPinsCurrentVersion(t *testing.T) { if !isPrereleaseVersion(version) && version != "6.0.0" && strings.Contains(text, `CANONICAL_DEFAULT_PULSE_VERSION="6.0.0"`) { t.Fatalf("stable patch install-docker.sh fallback must move off the initial GA image tag:\n%s", text) } + if previous, ok := previousStablePatchVersion(version); ok && strings.Contains(text, `CANONICAL_DEFAULT_PULSE_VERSION="`+previous+`"`) { + t.Fatalf("install-docker.sh fallback must not retain the previous stable patch version %s:\n%s", previous, text) + } } func runInstallDockerScript(t *testing.T, workDir string, envVars ...string) { diff --git a/scripts/release_control/resolve_release_promotion_test.py b/scripts/release_control/resolve_release_promotion_test.py index 4c539c791..c01ef6484 100644 --- a/scripts/release_control/resolve_release_promotion_test.py +++ b/scripts/release_control/resolve_release_promotion_test.py @@ -111,61 +111,61 @@ class ResolveReleasePromotionTest(unittest.TestCase): def test_stable_patch_hotfix_can_omit_promoted_prerelease(self) -> None: metadata = resolver.resolve_metadata( - version="6.0.1", + version="6.0.2", promoted_from_tag_input="", - rollback_version_input="6.0.0", + rollback_version_input="6.0.1", ga_date_input="", v5_eos_date_input="", hotfix_exception=True, - hotfix_reason_input="Patch release for v6.0.0 upgrade and monitoring fixes.", + hotfix_reason_input="Patch release for v6.0.1 agent upgrade recovery.", release_notes_input="", - tag_exists_fn=lambda tag: tag == "v6.0.0", + tag_exists_fn=lambda tag: tag == "v6.0.1", ) self.assertEqual(metadata["promoted_from_tag"], "") - self.assertEqual(metadata["rollback_tag"], "v6.0.0") - self.assertEqual(metadata["rollback_command"], "./scripts/install.sh --version v6.0.0") + self.assertEqual(metadata["rollback_tag"], "v6.0.1") + self.assertEqual(metadata["rollback_command"], "./scripts/install.sh --version v6.0.1") self.assertEqual(metadata["hotfix_exception"], "true") self.assertEqual( metadata["hotfix_reason"], - "Patch release for v6.0.0 upgrade and monitoring fixes.", + "Patch release for v6.0.1 agent upgrade recovery.", ) self.assertEqual(metadata["soak_hours"], "") def test_stable_patch_without_promoted_tag_requires_hotfix_exception(self) -> None: with self.assertRaisesRegex(ValueError, "Stable promotion requires promoted_from_tag"): resolver.resolve_metadata( - version="6.0.1", + version="6.0.2", promoted_from_tag_input="", - rollback_version_input="6.0.0", + rollback_version_input="6.0.1", ga_date_input="", v5_eos_date_input="", hotfix_exception=False, hotfix_reason_input="", release_notes_input="", - tag_exists_fn=lambda tag: tag == "v6.0.0", + tag_exists_fn=lambda tag: tag == "v6.0.1", ) def test_stable_patch_hotfix_without_promoted_tag_requires_reason(self) -> None: with self.assertRaisesRegex(ValueError, "hotfix_reason is required"): resolver.resolve_metadata( - version="6.0.1", + version="6.0.2", promoted_from_tag_input="", - rollback_version_input="6.0.0", + rollback_version_input="6.0.1", ga_date_input="", v5_eos_date_input="", hotfix_exception=True, hotfix_reason_input="", release_notes_input="", - tag_exists_fn=lambda tag: tag == "v6.0.0", + tag_exists_fn=lambda tag: tag == "v6.0.1", ) def test_stable_hotfix_requires_reason(self) -> None: with self.assertRaisesRegex(ValueError, "hotfix_reason is required"): resolver.resolve_metadata( - version="6.0.1", - promoted_from_tag_input="6.0.1-rc.1", - rollback_version_input="6.0.0", + version="6.0.2", + promoted_from_tag_input="6.0.2-rc.1", + rollback_version_input="6.0.1", ga_date_input="", v5_eos_date_input="", hotfix_exception=True, @@ -181,9 +181,9 @@ class ResolveReleasePromotionTest(unittest.TestCase): def test_stable_rejects_short_soak_without_hotfix(self) -> None: with self.assertRaisesRegex(ValueError, "hours of prerelease soak"): resolver.resolve_metadata( - version="6.0.1", - promoted_from_tag_input="6.0.1-rc.1", - rollback_version_input="6.0.0", + version="6.0.2", + promoted_from_tag_input="6.0.2-rc.1", + rollback_version_input="6.0.1", ga_date_input="", v5_eos_date_input="", hotfix_exception=False,