Skip to content

feat(helm): global image values, and the registry/repository split - #38

Open
jjamroga wants to merge 1 commit into
mainfrom
jjamroga/helm-global-values
Open

jjamroga wants to merge 1 commit into
mainfrom
jjamroga/helm-global-values

Conversation

@jjamroga

@jjamroga jjamroga commented Sep 14, 2026

Copy link
Copy Markdown
Collaborator

What this gives operators

This PR adds a global: block for install-wide image settings. Helm passes global.* down from a parent chart, so a parent that vendors substrate sets each value once.

  • global.imageRegistry — for air-gapped and private-registry installs. Mirror the images, then set this one value. Every image the chart renders pulls from the mirror. Component images resolve as <your-registry>/kagent-dev/substrate/<component>. The single-string images.* references (postgres, rustfs, aws-cli, agentgateway) have their registry segment replaced, digests preserved. Repository paths are identical on every registry, so a mirror copies each image under its existing path.
  • global.imagePullSecrets — the pull secret that mirror needs. The chart merges it (union) into every pod spec. The chart previously had no pull-secret surface at all: a private mirror was unusable regardless of the registry value. A chart-level imagePullSecrets list is added alongside the global.
  • global.imagePullPolicy — one pull policy for the containers that previously hardcoded IfNotPresent.

The breaking change

image.registry was one string that carried a path: ghcr.io/kagent-dev/substrate. It is now split. image.registry holds the registry host. A new image.repository holds the path prefix ahead of each component name. The other kagent-family charts use this same shape. The shared shape is what makes one mirror value work across all of them.

Who this breaks: values files that relocated the component images by overriding image.registry with a full prefix.

How the break stays safe: it cannot fail silently. A naive render would join the new default repository onto the old-shape override. The doubled prefix would fail only at pod start, as ImagePullBackOff. Instead, the render fails at template time. The error names the fix:

image.registry ("my.reg/custom/prefix") carries a path. It is now the registry
host only: keep the path in image.repository, e.g. registry: ghcr.io,
repository: kagent-dev/substrate.

The operator sees one loud helm upgrade error with the migration in it. No pod fails to pull. Operators who do not override image.registry are untouched. A default render is byte-identical to main.

A boundary, stated

The images.* values stay single strings, because several are digest-pinned third-party references with no natural registry/repository split. For those, the mirror override replaces the registry segment: the first path segment is treated as a registry only when it contains a . or a : (the containerd rule); otherwise the mirror is prefixed. The rewrite activates only when global.imageRegistry is set. Without it, every reference renders exactly as written.

Verification

A default render is byte-identical to main. Verified by render: the mirror value redirects all 9 images with repository paths and digests preserved; the old-shape image.registry fails at template time with the message above; the merged pull secrets land on all 9 pod specs; the pull-policy fallback fires.

🤖 Generated with Claude Code

Comment thread charts/substrate/templates/_helpers.tpl Outdated
global.imageRegistry value redirects them all. A values file still carrying
a path in registry would render a doubled prefix that fails only at pod
start, so it fails the render here instead and names the split. */}}
{{- if contains "/" $ctx.Values.image.registry -}}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This guard seems like it might reject the repository's own manifest renderer. hack/render-manifests.sh:63 still passes image.registry=ko://github.com/agent-substrate/substrate/cmd with image.tag=<none>. Its Helm invocation succeeds on main, but fails for me here with image.registry (...) carries a path, so both make helm-template and make helm-template-check stop before generating/checking manifests. I think that script might need to be updated.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch — fixed in the latest push. Two parts: hack/render-manifests.sh now passes the split (image.registry=ko://github.com, image.repository=agent-substrate/substrate/cmd), and the guard permits a scheme'd registry (://), since a ko importpath prefix is unambiguously not the old host+path shape — same idea as the existing <none> tag sentinel this script already relies on. Verified the composed refs are byte-identical to main's renderer output for all five components, and the guard still rejects a real old-shape value like my.reg/custom/prefix.

@jjamroga
jjamroga force-pushed the jjamroga/helm-global-values branch from 7fc41f7 to ed9f853 Compare September 14, 2026 19:28
global.imageRegistry redirects every image at once for air-gapped
mirrors. Component images now resolve through the same
registry/repository split every kagent-family chart uses:
image.registry was one string carrying its path
(ghcr.io/kagent-dev/substrate) and is now the registry host only,
joined onto image.repository, so one global value (which overrides
image.registry) redirects the whole family. This is a breaking change
for values files that put a full prefix in image.registry: rendered
silently they would produce a doubled prefix failing only at pod start,
so the render fails instead, naming the split. A default render is
byte-identical to main.

Single-string images.* references (postgres, rustfs, aws-cli,
agentgateway) have their registry segment replaced by the containerd
rule (first path segment with a dot or colon), preserving repository
paths either way. global.imagePullSecrets merges (union) into every pod
spec, which previously had no pull-secret surface at all.
global.imagePullPolicy replaces the hardcoded IfNotPresent values as a
fallback, via substrate.imagePullPolicy.

Verified: a default render is byte-identical to main; the mirror knob
redirects all 9 images with paths preserved; the old-shape registry
fails loudly at template time; pull secrets land on all 9 pod specs;
the pullPolicy fallback fires.

Signed-off-by: Jonathan Jamroga <jjamroga@gmail.com>
@jjamroga
jjamroga force-pushed the jjamroga/helm-global-values branch from ed9f853 to 238b46b Compare September 14, 2026 20:00
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants