Prepare v6.0.3 patch release

This commit is contained in:
rcourtman
2026-07-05 11:07:27 +01:00
parent 314429b879
commit c384d5e0c8
16 changed files with 175 additions and 31 deletions
+1 -1
View File
@@ -1 +1 @@
6.0.2
6.0.3
+4 -4
View File
@@ -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.2
appVersion: "6.0.2"
icon: https://raw.githubusercontent.com/rcourtman/Pulse/v6.0.2/docs/images/pulse-logo.svg
version: 6.0.3
appVersion: "6.0.3"
icon: https://raw.githubusercontent.com/rcourtman/Pulse/v6.0.3/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.2/docs/KUBERNETES.md
url: https://github.com/rcourtman/Pulse/blob/v6.0.3/docs/KUBERNETES.md
- name: Support
url: https://github.com/rcourtman/Pulse/discussions
artifacthub.io/maintainers: |
+1 -1
View File
@@ -1,6 +1,6 @@
# pulse
![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)
![Version: 6.0.3](https://img.shields.io/badge/Version-6.0.3-informational?style=flat-square) ![Type: application](https://img.shields.io/badge/Type-application-informational?style=flat-square) ![AppVersion: 6.0.3](https://img.shields.io/badge/AppVersion-6.0.3-informational?style=flat-square)
Helm chart for deploying the Pulse hub and optional Docker monitoring agent.
+1 -1
View File
@@ -2,7 +2,7 @@ version: '3.8'
services:
pulse:
image: ${PULSE_IMAGE:-rcourtman/pulse:6.0.2}
image: ${PULSE_IMAGE:-rcourtman/pulse:6.0.3}
container_name: pulse
restart: unless-stopped
logging:
+2
View File
@@ -7,6 +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.3.md`
- `docs/releases/V6_CHANGELOG_v6.0.3.md`
- `docs/releases/RELEASE_NOTES_v6.0.2.md`
- `docs/releases/V6_CHANGELOG_v6.0.2.md`
- `docs/UPGRADE_v6.md`
+2 -2
View File
@@ -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.2.md`
- `docs/releases/V6_CHANGELOG_v6.0.2.md`
- `docs/releases/RELEASE_NOTES_v6.0.3.md`
- `docs/releases/V6_CHANGELOG_v6.0.3.md`
- `docs/releases/RELEASE_NOTES_v6.md`
- `docs/releases/V6_CHANGELOG.md`
@@ -793,10 +793,10 @@ TLS floor in the dynamic config.
## Current State
The active stable `v6.0.2` cut sets the repo-root `VERSION`, repo-root
The active stable `v6.0.3` 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.2` release version. This patch
release uses the stable hotfix path with `rollback_version=v6.0.1`,
Helm chart release metadata to the same `6.0.3` release version. This patch
release uses the stable hotfix path with `rollback_version=v6.0.2`,
`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`,
@@ -850,8 +850,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.2` cut, the repo-root compose default and
`scripts/install-docker.sh` fallback must both pin `6.0.2`; the stable
For the active stable `v6.0.3` cut, the repo-root compose default and
`scripts/install-docker.sh` fallback must both pin `6.0.3`; 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`
+32
View File
@@ -0,0 +1,32 @@
# Pulse v6.0.3 Release Notes
`v6.0.3` is a stable patch release for the Pulse v6 line. It follows
`v6.0.2` and focuses on the remaining v5-to-v6 agent upgrade failure reported
after the first two v6 patch releases.
## Fixes
- Fixed v5 agent update recovery when the generated update command supplies an
explicit Pulse URL but the old running v5 agent process is still the only
source for the token and enabled telemetry scopes. The installer now merges
the explicit URL with the recovered process state instead of failing with
"No existing Pulse Agent connection state found."
- Treated incomplete saved v6 agent state as recoverable during `--update`.
This keeps the update path on the legacy recovery branch whenever any
required connection detail is still missing.
- Allowed carrier-grade NAT / overlay network addresses in `100.64.0.0/10` as
local-network HTTP and WebSocket targets for Pulse agent connections. This
fixes deployments that legitimately use addresses such as
`http://100.100.100.5:7655` while keeping public non-local addresses on the
HTTPS-required path.
- Refreshed Docker, Helm, and installer release metadata for the stable patch
line.
## Upgrade Notes
Use the normal v6 install or update flow for `v6.0.3`. The rollback target for
this patch release is `v6.0.2`.
If a v5.1.35 agent upgrade failed on `v6.0.0`, `v6.0.1`, or `v6.0.2`, rerun the
current update command from **Settings -> Infrastructure** after updating the
Pulse server to `v6.0.3`.
+21
View File
@@ -0,0 +1,21 @@
# Pulse v6.0.3
_This changelog describes the stable `v6.0.3` patch release compared with
`v6.0.2`._
## Fixed
- Legacy v5 agent updates now recover complete connection state when saved v6
state is missing or incomplete.
- Agent update commands now merge explicit Pulse URLs with token and scope
details recovered from the running v5 agent process.
- Agent URL validation now treats `100.64.0.0/10` carrier-grade NAT and overlay
addresses as local network targets for HTTP and WebSocket URLs.
- Docker, Helm, and installer release metadata now track the active stable
patch version.
## Release Metadata
- Version: `v6.0.3`
- Rollback target: `v6.0.2`
- Promotion path: stable patch hotfix from `pulse/v6-release`
@@ -88,7 +88,7 @@ describe('ConnectionsAPI', () => {
lastError: null,
source: 'agent',
agentVersion: '6.0.0',
expectedAgentVersion: '6.0.2',
expectedAgentVersion: '6.0.3',
agentUpdateAvailable: true,
capabilities: { supportsPause: false, supportsScope: false, supportsTest: false },
},
@@ -99,7 +99,7 @@ describe('ConnectionsAPI', () => {
expect(result.connections[0]).toMatchObject({
agentVersion: '6.0.0',
expectedAgentVersion: '6.0.2',
expectedAgentVersion: '6.0.3',
agentUpdateAvailable: true,
});
});
@@ -906,7 +906,7 @@ describe('InfrastructureWorkspace', () => {
surfaces: ['host'],
scope: { host: true } as any,
source: 'agent',
agentVersion: '6.0.2',
agentVersion: '6.0.3',
agentIdentity: {
hostname: 'tower',
platform: 'linux',
@@ -970,7 +970,7 @@ describe('InfrastructureWorkspace', () => {
await waitFor(() => expect(screen.getByRole('dialog')).toBeInTheDocument());
expect(screen.getByText('Pulse Agent version')).toBeInTheDocument();
expect(screen.getByText('6.0.2')).toBeInTheDocument();
expect(screen.getByText('6.0.3')).toBeInTheDocument();
expect(screen.getByText('Operating system')).toBeInTheDocument();
expect(screen.getAllByText('Unraid 7.1.0').length).toBeGreaterThan(0);
expect(screen.getByText('Reported hostname')).toBeInTheDocument();
@@ -996,7 +996,7 @@ describe('InfrastructureWorkspace', () => {
scope: { host: true } as any,
lastSeen: new Date().toISOString(),
agentVersion: '6.0.0',
expectedAgentVersion: '6.0.2',
expectedAgentVersion: '6.0.3',
agentUpdateAvailable: true,
capabilities: { supportsPause: true, supportsScope: true, supportsTest: true },
});
@@ -1042,7 +1042,7 @@ describe('InfrastructureWorkspace', () => {
expect(screen.getByText('Pulse Agent augmentation')).toBeInTheDocument();
expect(screen.getByText('zeus-agent')).toBeInTheDocument();
expect(screen.getByText('Update available')).toBeInTheDocument();
expect(screen.getByText('6.0.0 -> 6.0.2')).toBeInTheDocument();
expect(screen.getByText('6.0.0 -> 6.0.3')).toBeInTheDocument();
expect(screen.getByRole('button', { name: /Copy uninstall command/i })).toBeInTheDocument();
});
+8 -8
View File
@@ -330,11 +330,11 @@ func TestBuildConnections_AgentVersionUpdateAvailability(t *testing.T) {
now := time.Now()
in := aggregatorInputs{
hosts: []models.Host{
{ID: "current", Hostname: "current", LastSeen: now, AgentVersion: "6.0.2"},
{ID: "current", Hostname: "current", LastSeen: now, AgentVersion: "6.0.3"},
{ID: "outdated", Hostname: "outdated", LastSeen: now, AgentVersion: "6.0.0"},
{ID: "unknown", Hostname: "unknown", LastSeen: now},
},
expectedAgentVersion: "6.0.2",
expectedAgentVersion: "6.0.3",
now: now,
}
@@ -344,14 +344,14 @@ func TestBuildConnections_AgentVersionUpdateAvailability(t *testing.T) {
byID[connection.ID] = connection
}
if byID["agent:current"].AgentVersion != "6.0.2" {
t.Fatalf("current agent version = %q, want %q", byID["agent:current"].AgentVersion, "6.0.2")
if byID["agent:current"].AgentVersion != "6.0.3" {
t.Fatalf("current agent version = %q, want %q", byID["agent:current"].AgentVersion, "6.0.3")
}
if byID["agent:current"].ExpectedAgentVersion != "6.0.2" {
if byID["agent:current"].ExpectedAgentVersion != "6.0.3" {
t.Fatalf(
"current expected agent version = %q, want %q",
byID["agent:current"].ExpectedAgentVersion,
"6.0.2",
"6.0.3",
)
}
if byID["agent:current"].AgentUpdateAvailable {
@@ -387,7 +387,7 @@ func TestBuildConnections_AgentFleetGovernance(t *testing.T) {
ID: "current",
Hostname: "current",
LastSeen: now,
AgentVersion: "6.0.2",
AgentVersion: "6.0.3",
CommandsEnabled: true,
},
{
@@ -404,7 +404,7 @@ func TestBuildConnections_AgentFleetGovernance(t *testing.T) {
agentDesiredConfigs: map[string]connectionAgentDesiredConfig{
"current": currentDesiredConfig,
},
expectedAgentVersion: "6.0.2",
expectedAgentVersion: "6.0.3",
now: now,
}
+2 -2
View File
@@ -14549,7 +14549,7 @@ func TestContract_AgentConnectionPayloadIncludesVersionFields(t *testing.T) {
CommandsEnabled: true,
},
AgentVersion: "6.0.0",
ExpectedAgentVersion: "6.0.2",
ExpectedAgentVersion: "6.0.3",
AgentUpdateAvailable: true,
Fleet: ConnectionFleetGovernance{
EnrollmentState: fleetStateEnrolled,
@@ -14583,7 +14583,7 @@ func TestContract_AgentConnectionPayloadIncludesVersionFields(t *testing.T) {
t.Fatalf("agent platform must remain runtime platform, got profile id %q", conn.AgentIdentity.Platform)
}
want := `{"id":"agent:host-1","type":"agent","name":"host-1","address":"host-1","hostAliases":["host-1","192.168.0.2"],"state":"active","enabled":true,"surfaces":["host"],"scope":{"host":true},"lastSeen":"2026-04-22T12:00:00Z","source":"agent","agentIdentity":{"hostname":"host-1","platform":"linux","hostProfile":"unraid","osName":"Unraid","osVersion":"7.1.0","kernelVersion":"6.12.0","architecture":"x86_64","reportIp":"192.168.0.2","commandsEnabled":true},"agentVersion":"6.0.0","expectedAgentVersion":"6.0.2","agentUpdateAvailable":true,"fleet":{"enrollmentState":"enrolled","livenessState":"active","versionDrift":"behind","adapterHealth":"healthy","configRollout":"reported","credentialStatus":"verified","updateStatus":"update-available","remoteControl":"enabled","commandPolicy":{"status":"enabled","desired":"enabled","applied":"enabled","enforcement":"in-sync","reason":"agent command execution matches the desired enabled policy"}},"capabilities":{"supportsPause":false,"supportsScope":false,"supportsTest":false}}`
want := `{"id":"agent:host-1","type":"agent","name":"host-1","address":"host-1","hostAliases":["host-1","192.168.0.2"],"state":"active","enabled":true,"surfaces":["host"],"scope":{"host":true},"lastSeen":"2026-04-22T12:00:00Z","source":"agent","agentIdentity":{"hostname":"host-1","platform":"linux","hostProfile":"unraid","osName":"Unraid","osVersion":"7.1.0","kernelVersion":"6.12.0","architecture":"x86_64","reportIp":"192.168.0.2","commandsEnabled":true},"agentVersion":"6.0.0","expectedAgentVersion":"6.0.3","agentUpdateAvailable":true,"fleet":{"enrollmentState":"enrolled","livenessState":"active","versionDrift":"behind","adapterHealth":"healthy","configRollout":"reported","credentialStatus":"verified","updateStatus":"update-available","remoteControl":"enabled","commandPolicy":{"status":"enabled","desired":"enabled","applied":"enabled","enforcement":"in-sync","reason":"agent command execution matches the desired enabled policy"}},"capabilities":{"supportsPause":false,"supportsScope":false,"supportsTest":false}}`
assertJSONSnapshot(t, body, want)
}
+1 -1
View File
@@ -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.2"
CANONICAL_DEFAULT_PULSE_VERSION="6.0.3"
resolve_default_pulse_version() {
if [ -n "${PULSE_IMAGE_VERSION:-}" ]; then
@@ -240,6 +240,60 @@ func TestCreateReleaseUploadsPowerShellInstaller(t *testing.T) {
}
}
func TestCurrentStablePatchReleasePacketTracksInstallMetadata(t *testing.T) {
version := currentReleaseVersion(t)
if isPrereleaseVersion(version) {
t.Skip("current release is a prerelease")
}
previous, ok := previousStablePatchVersion(version)
if !ok {
t.Skip("current release is not a stable patch release")
}
releaseNotesPath := repoFile("docs", "releases", "RELEASE_NOTES_v"+version+".md")
changelogPath := repoFile("docs", "releases", "V6_CHANGELOG_v"+version+".md")
assertFileContainsAllNormalized(t, releaseNotesPath,
"`v"+version+"` is a stable patch release",
"`v"+previous+"`",
"rollback target for this patch release is `v"+previous+"`",
)
assertFileContainsAll(t, changelogPath,
"Version: `v"+version+"`",
"Rollback target: `v"+previous+"`",
"Promotion path: stable patch hotfix from `pulse/v6-release`",
)
assertFileContainsAll(t, repoFile("docs", "RELEASE_NOTES.md"),
"docs/releases/RELEASE_NOTES_v"+version+".md",
"docs/releases/V6_CHANGELOG_v"+version+".md",
)
assertFileContainsAll(t, repoFile("docs", "UPGRADE_v6.md"),
"docs/releases/RELEASE_NOTES_v"+version+".md",
"docs/releases/V6_CHANGELOG_v"+version+".md",
)
assertFileContainsAll(t, repoFile("deploy", "helm", "pulse", "Chart.yaml"),
"version: "+version,
`appVersion: "`+version+`"`,
"raw.githubusercontent.com/rcourtman/Pulse/v"+version+"/docs/images/pulse-logo.svg",
"blob/v"+version+"/docs/KUBERNETES.md",
)
assertFileContainsAll(t, repoFile("deploy", "helm", "pulse", "README.md"),
"Version-"+version+"-informational",
"AppVersion-"+version+"-informational",
)
assertFileContainsAll(t, repoFile("docker-compose.yml"),
"image: ${PULSE_IMAGE:-rcourtman/pulse:"+version+"}",
)
assertFileContainsAll(t, repoFile("scripts", "install-docker.sh"),
`CANONICAL_DEFAULT_PULSE_VERSION="`+version+`"`,
)
assertFileContainsAllNormalized(t, repoFile("docs", "release-control", "v6", "internal", "subsystems", "deployment-installability.md"),
"The active stable `v"+version+"` 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 `"+version+"` release version.",
"This patch release uses the stable hotfix path with `rollback_version=v"+previous+"`, `hotfix_exception=true`, a release-owner reason, and no fabricated same-version RC tag.",
"For the active stable `v"+version+"` cut, the repo-root compose default and `scripts/install-docker.sh` fallback must both pin `"+version+"`",
)
}
func TestBackfillReleaseWorkflowRepairsPublishedAssetsWithoutRebuilds(t *testing.T) {
scriptBytes, err := os.ReadFile(repoFile("scripts", "backfill-release-assets.sh"))
if err != nil {
@@ -1297,6 +1351,24 @@ func assertFileContainsAll(t *testing.T, path string, required ...string) {
}
}
func assertFileContainsAllNormalized(t *testing.T, path string, required ...string) {
t.Helper()
content, err := os.ReadFile(path)
if err != nil {
t.Fatalf("read %s: %v", path, err)
}
s := normalizedInstallTestWhitespace(string(content))
for _, needle := range required {
if !strings.Contains(s, normalizedInstallTestWhitespace(needle)) {
t.Fatalf("%s missing required normalized substring: %s", path, needle)
}
}
}
func normalizedInstallTestWhitespace(text string) string {
return strings.Join(strings.Fields(text), " ")
}
func workflowJobBlock(t *testing.T, workflow, job string) string {
t.Helper()
@@ -155,6 +155,23 @@ func TestInstallDockerScriptFallbackPinsCurrentVersion(t *testing.T) {
}
}
func TestInstallDockerProofTracksStablePatchReleaseContract(t *testing.T) {
version := currentReleaseVersion(t)
if isPrereleaseVersion(version) {
t.Skip("current release is a prerelease")
}
previous, ok := previousStablePatchVersion(version)
if !ok {
t.Skip("current release is not a stable patch release")
}
assertFileContainsAllNormalized(t, repoFile("docs", "release-control", "v6", "internal", "subsystems", "deployment-installability.md"),
"The active stable `v"+version+"` 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 `"+version+"` release version.",
"This patch release uses the stable hotfix path with `rollback_version=v"+previous+"`, `hotfix_exception=true`, a release-owner reason, and no fabricated same-version RC tag.",
"For the active stable `v"+version+"` cut, the repo-root compose default and `scripts/install-docker.sh` fallback must both pin `"+version+"`",
)
}
func runInstallDockerScript(t *testing.T, workDir string, envVars ...string) {
t.Helper()