Skip to content

Commit 88445b1

Browse files
stubbiclaude
andauthored
fix(chart): render pullable default image tags (#122)
Fixes #113. ## Root cause `release-please-config.json` had a `$.image.tag` updater writing the **bare** version into `values.yaml`: ```yaml image: tag: 0.1.18 # <- unpullable ``` but the release workflow only ever publishes **v-prefixed** tags. `Compute image tags` in `release.yaml` pushes `${TAG}` (= `v0.1.18`), `${MAJOR_MINOR}` (= `0.1`) and `latest` — never a bare `0.1.18`. `_helpers.tpl:16` already had the right fallback: ``` {{- $tag := default (printf "v%s" .Chart.AppVersion) .Values.image.tag -}} ``` It just never fired, because release-please kept the value populated. So this is fixed at the source rather than by hardcoding a `v`. ## Changes | | | |---|---| | `values.yaml` | `image.tag: ""` — chart now tracks `v<appVersion>` via the existing fallback | | `release-please-config.json` | drop the `$.image.tag` updater that repopulated it (`appVersion` stays managed) | | `Chart.yaml` | `artifacthub.io/images` -> `v0.1.18`; the `x-release-please-version` annotation preserves the `v` on future bumps | | `values.yaml` | remove the vestigial `agentImage` block (see below) | | e2e fixtures, relock workflow | `v0.13.0` -> `v0.16.0` | | `hack/check-chart-image-tags.sh` + CI job | regression guard | ## On part 2 of the issue (`agentImage.tag: v0.13.0`) The reported tag is genuinely stale, but the described mechanism is slightly different from the report: a minimal `HermesInstance` does **not** silently render `hermes-agent:v0.13.0`. Nothing reads `agentImage` — it appears in no template and no Go code: ``` $ grep -rn "agentImage" . --exclude-dir=site --exclude-dir=.git charts/hermes-operator/values.yaml:29:agentImage: docs/superpowers/plans/...-plan-3-hermes-runtime.md:4026:agentImage: ``` The agent image comes from `HermesInstance.spec.image`, and the CRD has no default for `tag` plus a CEL rule requiring tag-or-digest, so an instance without an image is *rejected*, not defaulted. The `agentImage` block was therefore dead config advertising a tag that was never published — removed rather than bumped. ## Regression guard `hack/check-chart-image-tags.sh` renders the chart at its defaults and HEADs each `ghcr.io` manifest with an anonymous pull token. Verified against four scenarios: | scenario | exit | |---|---| | A — fixed values, `v0.1.18` released | `0` | | B — the #113 regression (bare `0.1.18`) | `1` | | C — release PR, `appVersion` 0.1.19 not yet tagged | `0` (skipped, format still enforced) | | D — restored | `0` | Scenario C matters: on a release PR the chart legitimately renders a tag that does not exist until the release job runs, so the existence check is exempted for exactly the pending version while the `v`-prefix shape is still enforced. Scenario B is checked by shape, so it fails immediately rather than depending on registry state. `helm lint` passes; rendered operator image is now `ghcr.io/paperclipinc/hermes-operator:v0.1.18`, which resolves. ## Follow-up (not in this PR) - `honchoImage` in `values.yaml` is dead in the same way as `agentImage` was. - The bundle CSV is stale independently of release-please: `containerImage: ...:v0.1.10` and two `image: ...:v0.1.0` entries. I'll file both separately. Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
1 parent 4462cb7 commit 88445b1

8 files changed

Lines changed: 119 additions & 11 deletions

File tree

.github/workflows/agent-image-relock.yaml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,9 @@ on:
1818
workflow_dispatch:
1919
inputs:
2020
hermes_version:
21-
description: "hermes-agent release tag to lock (e.g. v0.13.0)"
21+
description: "hermes-agent release tag to lock (e.g. v0.16.0)"
2222
required: true
23-
default: "v0.13.0"
23+
default: "v0.16.0"
2424
type: string
2525

2626
permissions:

.github/workflows/helm-rbac.yaml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,3 +20,11 @@ jobs:
2020
- uses: actions/checkout@v7
2121
- run: sudo snap install yq
2222
- run: make sync-bundle-rbac-check
23+
24+
chart-image-tags:
25+
name: Chart Image Tags Resolve
26+
runs-on: ubuntu-latest
27+
steps:
28+
- uses: actions/checkout@v7
29+
- uses: azure/setup-helm@v5
30+
- run: bash hack/check-chart-image-tags.sh

charts/hermes-operator/Chart.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ annotations:
6969
size: 10Gi
7070
artifacthub.io/images: |
7171
- name: hermes-operator
72-
image: ghcr.io/paperclipinc/hermes-operator:0.1.18 # x-release-please-version
72+
image: ghcr.io/paperclipinc/hermes-operator:v0.1.18 # x-release-please-version
7373
artifacthub.io/changes: |
7474
- kind: added
7575
description: World-class Artifact Hub metadata (logo, keywords, CRDs, images)

charts/hermes-operator/values.yaml

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
image:
22
repository: ghcr.io/paperclipinc/hermes-operator
3-
tag: 0.1.18
3+
# Leave empty to track the chart's appVersion. The release workflow publishes
4+
# v-prefixed image tags, so _helpers.tpl falls back to "v<appVersion>".
5+
# Pinning a bare version here yields an unpullable reference (see #113).
6+
tag: ""
47
pullPolicy: IfNotPresent
58
logLevel: info
69
createRBAC: true
@@ -23,10 +26,11 @@ webhook:
2326
issuerName: hermes-operator-selfsigned
2427
caBundle: ''
2528
servingCertSecretName: hermes-operator-webhook-server-cert
26-
agentImage:
27-
repository: ghcr.io/paperclipinc/hermes-agent
28-
tag: v0.13.0
29-
pullPolicy: IfNotPresent
29+
# NOTE: there is deliberately no agentImage default here. The agent image is
30+
# chosen per-instance via HermesInstance.spec.image, and the CRD requires an
31+
# explicit tag or digest (pinning a floating tag can silently pull a broken
32+
# upstream build). A chart-level default was vestigial — nothing rendered it —
33+
# and advertised an unpublished tag. See #113.
3034
honchoImage:
3135
repository: ghcr.io/plastic-labs/honcho
3236
tag: 0.1.0

hack/check-chart-image-tags.sh

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
#!/usr/bin/env bash
2+
# Assert that every image reference the chart renders at its own defaults is
3+
# actually pullable from the registry.
4+
#
5+
# Regression guard for #113: release-please used to write a bare version into
6+
# values.yaml `image.tag` while the release workflow only publishes v-prefixed
7+
# tags, so `helm install` at the chart's own defaults produced ImagePullBackOff.
8+
#
9+
# Uses the anonymous registry token + manifest HEAD, so it needs no credentials
10+
# for public images.
11+
set -euo pipefail
12+
13+
CHART_DIR="${CHART_DIR:-charts/hermes-operator}"
14+
APP_VERSION="$(sed -nE 's/^appVersion:[[:space:]]*"?([^"[:space:]]+)"?/\1/p' "${CHART_DIR}/Chart.yaml")"
15+
16+
fail=0
17+
18+
# Resolve a ghcr.io reference to a manifest, following the OCI auth dance.
19+
check_ref() {
20+
local ref="$1"
21+
local repo tag host path token code
22+
23+
host="${ref%%/*}"
24+
path="${ref#*/}"
25+
repo="${path%:*}"
26+
tag="${path##*:}"
27+
28+
if [[ "$host" != "ghcr.io" ]]; then
29+
echo " SKIP $ref (only ghcr.io is checked)"
30+
return 0
31+
fi
32+
33+
# The operator image is only ever published v-prefixed. A bare semver here is
34+
# the #113 regression: it renders an unpullable reference. Catch it by shape,
35+
# so it fails even before the tag would have had a chance to exist.
36+
if [[ "$repo" == "paperclipinc/hermes-operator" && "$tag" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
37+
echo " FAIL $ref (bare version tag; the release workflow publishes v${tag})"
38+
fail=1
39+
return 0
40+
fi
41+
42+
# The operator image is published by the release workflow, which runs *after*
43+
# release-please bumps appVersion. On a release PR (and on main until the
44+
# release job finishes) the chart legitimately renders a tag that does not
45+
# exist yet. Exempt exactly that case from the existence check, but still
46+
# enforce the v-prefix format that #113 was about.
47+
if [[ "$repo" == "paperclipinc/hermes-operator" && "$tag" == "v${APP_VERSION}" ]]; then
48+
if ! git rev-parse -q --verify "refs/tags/v${APP_VERSION}" >/dev/null 2>&1; then
49+
echo " SKIP $ref (v${APP_VERSION} is the pending release; format is correct)"
50+
return 0
51+
fi
52+
fi
53+
54+
token=$(curl -fsSL "https://ghcr.io/token?scope=repository:${repo}:pull&service=ghcr.io" \
55+
| python3 -c 'import sys,json; print(json.load(sys.stdin)["token"])')
56+
57+
code=$(curl -s -o /dev/null -w '%{http_code}' -I \
58+
-H "Authorization: Bearer ${token}" \
59+
-H 'Accept: application/vnd.oci.image.index.v1+json' \
60+
-H 'Accept: application/vnd.oci.image.manifest.v1+json' \
61+
-H 'Accept: application/vnd.docker.distribution.manifest.list.v2+json' \
62+
-H 'Accept: application/vnd.docker.distribution.manifest.v2+json' \
63+
"https://ghcr.io/v2/${repo}/manifests/${tag}")
64+
65+
if [[ "$code" == "200" ]]; then
66+
echo " OK $ref"
67+
else
68+
echo " FAIL $ref (HTTP $code — tag does not resolve)"
69+
fail=1
70+
fi
71+
}
72+
73+
echo "Rendering ${CHART_DIR} at its defaults..."
74+
refs=$(helm template hermes-operator "${CHART_DIR}" \
75+
| grep -oE 'image: "?[a-z0-9./-]+:[A-Za-z0-9._-]+"?' \
76+
| sed -E 's/^image: "?//; s/"?$//' \
77+
| sort -u)
78+
79+
if [[ -z "$refs" ]]; then
80+
echo "ERROR: no image references found in rendered chart — check the grep." >&2
81+
exit 1
82+
fi
83+
84+
echo "Checking rendered image references:"
85+
while IFS= read -r ref; do
86+
[[ -n "$ref" ]] && check_ref "$ref"
87+
done <<<"$refs"
88+
89+
if [[ "$fail" -ne 0 ]]; then
90+
echo
91+
echo "One or more chart default image tags are unpullable." >&2
92+
echo "The release workflow publishes v-prefixed tags (v<version>), so the" >&2
93+
echo "chart must render those — see hack/check-chart-image-tags.sh header." >&2
94+
exit 1
95+
fi
96+
97+
echo "All chart default image tags resolve."

release-please-config.json

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,6 @@
2121
"extra-files": [
2222
{ "type": "yaml", "path": "charts/hermes-operator/Chart.yaml", "jsonpath": "$.version" },
2323
{ "type": "yaml", "path": "charts/hermes-operator/Chart.yaml", "jsonpath": "$.appVersion" },
24-
{ "type": "yaml", "path": "charts/hermes-operator/values.yaml", "jsonpath": "$.image.tag" },
2524
{ "type": "yaml", "path": "bundle/manifests/hermes-operator.clusterserviceversion.yaml", "jsonpath": "$.spec.version" },
2625
{ "type": "generic", "path": "charts/hermes-operator/Chart.yaml" }
2726
]

test/e2e/testdata/hermesinstance-gateways.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ metadata:
2424
spec:
2525
image:
2626
repository: ghcr.io/paperclipinc/hermes-agent
27-
tag: "v0.13.0"
27+
tag: "v0.16.0"
2828
pullPolicy: IfNotPresent
2929
storage:
3030
persistence:

test/e2e/testdata/hermesinstance-tailscale.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ metadata:
1515
spec:
1616
image:
1717
repository: ghcr.io/paperclipinc/hermes-agent
18-
tag: "v0.13.0"
18+
tag: "v0.16.0"
1919
pullPolicy: IfNotPresent
2020
storage:
2121
persistence:

0 commit comments

Comments
 (0)