mirror of
https://github.com/rcourtman/Pulse.git
synced 2026-10-19 18:44:53 +00:00
chore(ci): stop resurrecting the pulse-agent GHCR package
The agent_runtime verify-only build pointed its registry buildcache at
ghcr.io/<owner>/pulse-agent, a package no workflow publishes. A cache ref
creates the package it points at, so every release recreated it and the
repository's Packages sidebar advertised an empty pulse-agent entry that
reads like a pullable agent image sitting beside the real pulse,
pulse-control-plane and pulse-chart/pulse packages. The only release tag it
ever carried is a stray v6.0.0-rc.3.
Move the cache to ghcr.io/<owner>/pulse:agent-buildcache, alongside the
runtime stage's existing pulse:buildcache tag, so it lives in the package
that actually ships and shares backend-builder layers with the runtime
build.
TestAgentBuildCacheDoesNotResurrectPulseAgentPackage locks this in. It scans
every workflow for registry-qualified refs to the pulse-agent package,
collapsing ${{ }} expressions first so an interpolated owner cannot hide one
behind its spaces, and asserts both the new cache target and the chart's
published agent image default. Verified red on a restored buildcache ref and
red again on a ref hidden behind ${{ github.repository_owner }}.
Two stale claims corrected while here. The chart values comment said the old
agent.image default was never published, when it did carry v6.0.0-rc.3. The
deployment-installability contract called rcourtman/pulse the only image
publish-docker.yml pushes, when the same workflow also pushes the MSP
pulse-control-plane image. The contract now also forbids buildcache
references to the pulse-agent package, which is what let this regress.
Deleting the orphaned package itself remains a manual GHCR step.
This commit is contained in:
parent
5d120208b9
commit
7c9a572349
4 changed files with 111 additions and 8 deletions
9
.github/workflows/create-release.yml
vendored
9
.github/workflows/create-release.yml
vendored
|
|
@ -429,8 +429,13 @@ jobs:
|
|||
push: false
|
||||
provenance: mode=max
|
||||
sbom: true
|
||||
cache-from: type=registry,ref=ghcr.io/${{ github.repository_owner }}/pulse-agent:buildcache
|
||||
cache-to: type=registry,ref=ghcr.io/${{ github.repository_owner }}/pulse-agent:buildcache,mode=max
|
||||
# Cache under the published pulse package, not a pulse-agent package.
|
||||
# No workflow pushes a pulse-agent image, so a buildcache ref there
|
||||
# resurrects an empty package in the repo's Packages sidebar that
|
||||
# reads like a pullable agent image. Blobs are also shared with the
|
||||
# runtime build, which uses the same backend-builder layers.
|
||||
cache-from: type=registry,ref=ghcr.io/${{ github.repository_owner }}/pulse:agent-buildcache
|
||||
cache-to: type=registry,ref=ghcr.io/${{ github.repository_owner }}/pulse:agent-buildcache,mode=max
|
||||
build-args: |
|
||||
VERSION=${{ needs.prepare.outputs.tag }}
|
||||
PULSE_LICENSE_PUBLIC_KEY_SHA256=${{ steps.license_key_cache.outputs.sha256 }}
|
||||
|
|
|
|||
|
|
@ -133,7 +133,8 @@ agent:
|
|||
# Defaults to the main Pulse image; the runtime stage now ships an arch-
|
||||
# resolved /usr/local/bin/pulse-agent symlink so the same image can serve
|
||||
# both server and agent workloads. The previous default
|
||||
# (ghcr.io/rcourtman/pulse-agent) was an image that was never published.
|
||||
# (ghcr.io/rcourtman/pulse-agent) is not a maintained image: no release
|
||||
# workflow pushes it, and the only tag it ever carried was v6.0.0-rc.3.
|
||||
repository: rcourtman/pulse
|
||||
tag: ""
|
||||
pullPolicy: IfNotPresent
|
||||
|
|
|
|||
|
|
@ -940,14 +940,22 @@ upgrade, update, release, or artifact-selection behavior.
|
|||
from whatever branch GitHub happened to check out.
|
||||
The chart's `agent.enabled=true` workload must point at an image that is
|
||||
actually published. The default `agent.image.repository` must be the main
|
||||
`rcourtman/pulse` image (which is the only image `publish-docker.yml`
|
||||
pushes); the agent template must override the server ENTRYPOINT via
|
||||
`rcourtman/pulse` image (the only agent-capable image `publish-docker.yml`
|
||||
pushes; the same workflow also pushes the MSP `pulse-control-plane` image,
|
||||
which is not an agent runtime); the agent template must override the server
|
||||
ENTRYPOINT via
|
||||
`agent.command` so the pod runs as a unified agent; and the runtime stage
|
||||
of `Dockerfile` must ship an arch-resolved `/usr/local/bin/pulse-agent`
|
||||
symlink that picks `pulse-agent-linux-{amd64,arm64,armv7}` per `TARGETARCH`
|
||||
so a single command default works across multi-arch nodes. The
|
||||
never-published `ghcr.io/rcourtman/pulse-agent` is forbidden as a chart
|
||||
default. `scripts/validate-release.sh` must assert the
|
||||
so a single command default works across multi-arch nodes. The unmaintained
|
||||
`ghcr.io/rcourtman/pulse-agent` is forbidden as a chart default: no release
|
||||
workflow publishes it, and the only release tag it ever carried is a stray
|
||||
`v6.0.0-rc.3`. No workflow may reference that package at all, buildcache
|
||||
refs included — a `cache-to` there recreates an empty package in the
|
||||
repository's Packages sidebar that reads like a pullable agent image. The
|
||||
`agent_runtime` build cache belongs at
|
||||
`ghcr.io/<owner>/pulse:agent-buildcache`, alongside the runtime stage's own
|
||||
`pulse:buildcache` tag. `scripts/validate-release.sh` must assert the
|
||||
`/usr/local/bin/pulse-agent` symlink exists, points at one of the
|
||||
supported Linux arch binaries, and is executable in the published image.
|
||||
`create-release.yml` must trigger `publish-helm-chart.yml` via an explicit
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ import (
|
|||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
|
|
@ -197,6 +198,94 @@ func shieldsBadgeMessage(value string) string {
|
|||
return strings.ReplaceAll(value, "-", "--")
|
||||
}
|
||||
|
||||
// TestAgentBuildCacheDoesNotResurrectPulseAgentPackage guards the repository's
|
||||
// GHCR package list, which is a user-facing surface. A registry cache ref
|
||||
// creates the package it points at, so pointing the agent_runtime build cache
|
||||
// at ghcr.io/<owner>/pulse-agent recreated an empty package on every release.
|
||||
// It then sat in the repo's Packages sidebar beside pulse, pulse-control-plane
|
||||
// and pulse-chart/pulse, reading like a pullable agent image even though no
|
||||
// workflow publishes it. Only images a release workflow actually pushes may
|
||||
// own a package; the unified agent ships inside the main pulse image.
|
||||
func TestAgentBuildCacheDoesNotResurrectPulseAgentPackage(t *testing.T) {
|
||||
workflowDir := repoFile(".github", "workflows")
|
||||
entries, err := os.ReadDir(workflowDir)
|
||||
if err != nil {
|
||||
t.Fatalf("read workflow dir: %v", err)
|
||||
}
|
||||
|
||||
// Registry-qualified refs only: the release binaries are legitimately named
|
||||
// pulse-agent-<os>-<arch> and must keep matching nothing here. Workflow
|
||||
// expressions are collapsed first so an owner interpolated as
|
||||
// ${{ github.repository_owner }} cannot hide the ref behind its spaces.
|
||||
workflowExpr := regexp.MustCompile(`\$\{\{[^}]*\}\}`)
|
||||
packageRef := regexp.MustCompile(`(?:ghcr\.io|docker\.io)/[^\s"']*/pulse-agent\b|(?:^|\s)rcourtman/pulse-agent\b`)
|
||||
for _, entry := range entries {
|
||||
name := entry.Name()
|
||||
if entry.IsDir() || (!strings.HasSuffix(name, ".yml") && !strings.HasSuffix(name, ".yaml")) {
|
||||
continue
|
||||
}
|
||||
content, err := os.ReadFile(filepath.Join(workflowDir, name))
|
||||
if err != nil {
|
||||
t.Fatalf("read %s: %v", name, err)
|
||||
}
|
||||
for i, line := range strings.Split(string(content), "\n") {
|
||||
if strings.HasPrefix(strings.TrimSpace(line), "#") {
|
||||
continue
|
||||
}
|
||||
if match := packageRef.FindString(workflowExpr.ReplaceAllString(line, "EXPR")); match != "" {
|
||||
t.Fatalf("%s:%d targets the unpublished pulse-agent package (%q); no workflow may reference it, buildcache refs included", name, i+1, strings.TrimSpace(match))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
release, err := os.ReadFile(repoFile(".github", "workflows", "create-release.yml"))
|
||||
if err != nil {
|
||||
t.Fatalf("read create-release.yml: %v", err)
|
||||
}
|
||||
for _, required := range []string{
|
||||
`cache-from: type=registry,ref=ghcr.io/${{ github.repository_owner }}/pulse:agent-buildcache`,
|
||||
`cache-to: type=registry,ref=ghcr.io/${{ github.repository_owner }}/pulse:agent-buildcache,mode=max`,
|
||||
} {
|
||||
if !strings.Contains(string(release), required) {
|
||||
t.Fatalf("create-release.yml must cache the agent_runtime build under the published pulse package; missing %q", required)
|
||||
}
|
||||
}
|
||||
|
||||
values, err := os.ReadFile(repoFile("deploy", "helm", "pulse", "values.yaml"))
|
||||
if err != nil {
|
||||
t.Fatalf("read values.yaml: %v", err)
|
||||
}
|
||||
agentBlock := topLevelYAMLBlock(t, string(values), "agent")
|
||||
if !strings.Contains(agentBlock, "repository: rcourtman/pulse\n") {
|
||||
t.Fatal("chart agent.image.repository must default to the published rcourtman/pulse image")
|
||||
}
|
||||
}
|
||||
|
||||
// topLevelYAMLBlock returns the lines of a top-level mapping key, from the key
|
||||
// itself up to the next unindented key.
|
||||
func topLevelYAMLBlock(t *testing.T, doc string, key string) string {
|
||||
t.Helper()
|
||||
lines := strings.Split(doc, "\n")
|
||||
start := -1
|
||||
for i, line := range lines {
|
||||
if line == key+":" {
|
||||
start = i
|
||||
break
|
||||
}
|
||||
}
|
||||
if start < 0 {
|
||||
t.Fatalf("values.yaml missing top-level %q key", key)
|
||||
}
|
||||
for i := start + 1; i < len(lines); i++ {
|
||||
line := lines[i]
|
||||
if line == "" || strings.HasPrefix(line, " ") || strings.HasPrefix(line, "#") {
|
||||
continue
|
||||
}
|
||||
return strings.Join(lines[start:i], "\n")
|
||||
}
|
||||
return strings.Join(lines[start:], "\n")
|
||||
}
|
||||
|
||||
func TestCreateReleaseUploadsPowerShellInstaller(t *testing.T) {
|
||||
content, err := os.ReadFile(repoFile(".github", "workflows", "create-release.yml"))
|
||||
if err != nil {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue