fix(helm): omit empty repository segment in bundled image path#2120
Merged
EItanya merged 4 commits intoJul 2, 2026
Merged
Conversation
Contributor
There was a problem hiding this comment.
Pull request overview
Fixes Helm chart rendering for the bundled PostgreSQL image when database.postgres.bundled.image.repository is empty, avoiding invalid Docker references with a double slash (registry//name:tag) that can lead to ImagePullBackOff.
Changes:
- Update
kagent.postgresql.imagehelper to build the image path from non-empty segments and join with/. - Add a Helm unittest case to cover the empty-repository scenario and ensure
//is not present.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 1 comment.
| File | Description |
|---|---|
| helm/kagent/templates/_helpers.tpl | Build image reference by compacting empty path segments before joining, preventing registry//name:tag. |
| helm/kagent/tests/postgresql_test.yaml | Add regression test validating docker.io/postgres:... output when repository is empty. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Signed-off-by: Mike Spinks <mikespinks@gmail.com>
c1ddfef to
fb7add8
Compare
This was referenced Jul 1, 2026
EItanya
pushed a commit
that referenced
this pull request
Jul 1, 2026
## What
Make the grafana-mcp chart render a valid image reference when
`image.registry` is left empty, so the registry can be omitted (e.g. to
pull from a mirror or let the runtime resolve the default registry)
without producing a leading-slash, unpullable reference.
## Why
The image is built inline in `templates/deployment.yaml`:
```gotemplate
image: "{{ .Values.image.registry }}/{{ .Values.image.repository }}:{{ .Values.image.tag }}"
```
The `/` between registry and repository is written unconditionally. If
`image.registry` is set to `""`, the result has a leading slash and is
not a valid image reference, so the pod enters `ImagePullBackOff`:
```
/grafana:latest # invalid reference
```
There is currently no way to omit the registry and let the container
runtime resolve the repository against its default registry / a
configured mirror.
Omitting the registry is a configuration users legitimately need:
- **Air-gapped / mirrored environments.** Registry mirrors are
configured at the container runtime, which rewrites the host — so chart
values should carry only `repository:tag`.
- **Registry rewriting.** Admission controllers (e.g. Kyverno) prepend
an internal registry, so users leave `registry: ""`.
- **Consistency.** Other charts let you omit the registry; grafana-mcp
silently breaks with `ImagePullBackOff`, which reads like a bug.
> Note: the default `registry: mcp` is intentional and unchanged —
`mcp/grafana` resolves to `docker.io/mcp/grafana` (Docker Hub's `mcp`
namespace). This PR only makes an *empty* registry valid; the default
path still renders `mcp/grafana:latest`.
> This is the same 2-segment `registry/repository:tag` leading-slash
shape as the querydoc chart (#2124), and distinct from the
bundled-PostgreSQL `registry//name:tag` double-slash bug (#2120), which
uses a 3-segment shape. All fixed independently.
## Fix
Introduce a `grafana-mcp.image` helper (the chart currently has none)
that builds the path from non-empty segments via `compact` + `join`, and
call it from the deployment:
```gotemplate
{{- define "grafana-mcp.image" -}}
{{- $img := .Values.image -}}
{{- $parts := compact (list $img.registry $img.repository) -}}
{{- printf "%s:%s" (join "/" $parts) $img.tag -}}
{{- end -}}
```
```gotemplate
# templates/deployment.yaml
image: "{{ include "grafana-mcp.image" . }}"
```
Rendered results:
| registry | repository | tag | result |
|---|---|---|---|
| `mcp` | `grafana` | `latest` | `mcp/grafana:latest` (unchanged) |
| `""` | `grafana` | `latest` | `grafana:latest` (fixed) |
## Tests
Added two cases to `helm/tools/grafana-mcp/tests/deployment_test.yaml`
(developed test-first): a default-image regression guard (the suite had
no image assertion), and the empty-registry path.
```yaml
- it: should render the default container image
template: deployment.yaml
asserts:
- equal:
path: spec.template.spec.containers[0].image
value: mcp/grafana:latest
- it: should omit empty registry segment in image
template: deployment.yaml
set:
image:
registry: ""
asserts:
- equal:
path: spec.template.spec.containers[0].image
value: grafana:latest
- notMatchRegex:
path: spec.template.spec.containers[0].image
pattern: "^/"
```
The empty-registry test fails without this change (`/grafana:latest`)
and passes with it — revert the `deployment.yaml`/`_helpers.tpl` hunks
to reproduce.
## How to verify
```bash
make helm-tools # generate grafana-mcp Chart.yaml
helm unittest helm/tools/grafana-mcp # full suite green (incl. new cases)
helm lint helm/tools/grafana-mcp
# test-independent reproduction:
helm template t helm/tools/grafana-mcp --set image.registry="" | grep image:
# before: /grafana:latest
# after: grafana:latest
```
## Scope
Limited to the grafana-mcp chart's image construction. Empty
`repository` is not made valid here — omitting it leaves no image to
pull. This PR only makes `image.registry` optional.
## Checklist
- [x] Commit is DCO signed-off (`git commit -s`).
- [x] `helm unittest helm/tools/grafana-mcp` green; `helm lint
helm/tools/grafana-mcp` clean.
- [x] Default image still renders `mcp/grafana:latest` (regression
guard).
- [x] Diff limited to `templates/_helpers.tpl`,
`templates/deployment.yaml`, and `tests/deployment_test.yaml` (generated
chart files excluded).
- [x] Test fails without the fix, passes with it.
Signed-off-by: Mike Spinks <mikespinks@gmail.com>
EItanya
pushed a commit
that referenced
this pull request
Jul 1, 2026
## What
Make the querydoc (doc2vec) chart render a valid image reference when
`image.registry` is left empty, so the registry can be omitted (e.g. to
pull from Docker Hub or a mirror) without producing a leading-slash,
unpullable reference.
## Why
The image is built inline in `templates/deployment.yaml`:
```gotemplate
image: "{{ .Values.image.registry }}/{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}"
```
The `/` between registry and repository is written unconditionally. If
`image.registry` is set to `""`, the result has a leading slash and is
not a valid image reference, so the pod enters `ImagePullBackOff`:
```
/kagent-dev/doc2vec/mcp:1.1.10 # invalid reference
```
There is currently no way to omit the registry and let the container
runtime resolve the repository against its default registry / a
configured mirror.
Omitting the registry is a configuration users legitimately need:
- **Docker Hub.** A bare `org/image` reference resolves to Docker Hub,
so users mirroring the image there want `kagent-dev/doc2vec/mcp:tag`,
not `ghcr.io/...`.
- **Air-gapped / mirrored environments.** Registry mirrors are
configured at the container runtime, which rewrites the host — so chart
values should carry only `repository:tag`.
- **Registry rewriting.** Admission controllers (e.g. Kyverno) prepend
an internal registry, so users leave `registry: ""`.
- **Consistency.** Other charts let you omit the registry; querydoc
silently breaks with `ImagePullBackOff`, which reads like a bug.
> Note: this is a different issue from the bundled-PostgreSQL
`registry//name:tag`
> double-slash bug (#2120). That helper uses a 3-segment
`registry/repository/name:tag`
> shape; querydoc uses a 2-segment `registry/repository:tag` shape with
no separate
> image name, so the failure here is a leading slash from an empty
registry, not a
> double slash from an empty repository. They are fixed independently.
## Fix
Introduce a `querydoc.image` helper (the chart currently has none) that
builds the path from non-empty segments via `compact` + `join`, and call
it from the deployment:
```gotemplate
{{- define "querydoc.image" -}}
{{- $img := .Values.image -}}
{{- $parts := compact (list $img.registry $img.repository) -}}
{{- printf "%s:%s" (join "/" $parts) ($img.tag | default .Chart.AppVersion) -}}
{{- end -}}
```
```gotemplate
# templates/deployment.yaml
image: "{{ include "querydoc.image" . }}"
```
Rendered results:
| registry | repository | tag | result |
|---|---|---|---|
| `ghcr.io` | `kagent-dev/doc2vec/mcp` | `1.1.10` |
`ghcr.io/kagent-dev/doc2vec/mcp:1.1.10` (unchanged) |
| `ghcr.io` | `kagent-dev/doc2vec/mcp` | `v2.0.0` |
`ghcr.io/kagent-dev/doc2vec/mcp:v2.0.0` (unchanged) |
| `""` | `kagent-dev/doc2vec/mcp` | `1.1.10` |
`kagent-dev/doc2vec/mcp:1.1.10` (fixed) |
## Tests
Added one case to `helm/tools/querydoc/tests/deployment_test.yaml` for
the empty-registry path (developed test-first). The existing "should
have correct container image" and "should use custom image tag when set"
tests already guard the default and custom-tag paths, so no regression
test was duplicated.
```yaml
- it: should omit empty registry segment in image
template: deployment.yaml
set:
image:
registry: ""
asserts:
- equal:
path: spec.template.spec.containers[0].image
value: kagent-dev/doc2vec/mcp:1.1.10
- notMatchRegex:
path: spec.template.spec.containers[0].image
pattern: "^/"
```
The test fails without this change (`/kagent-dev/doc2vec/mcp:1.1.10`)
and passes with it — revert the `deployment.yaml`/`_helpers.tpl` hunks
to reproduce.
## How to verify
```bash
make helm-tools # generate querydoc Chart.yaml
helm unittest helm/tools/querydoc # full suite green (incl. new case)
helm lint helm/tools/querydoc
# test-independent reproduction:
helm template t helm/tools/querydoc --set image.registry="" | grep image:
# before: /kagent-dev/doc2vec/mcp:1.1.10
# after: kagent-dev/doc2vec/mcp:1.1.10
```
## Scope
Limited to the querydoc chart's image construction. Empty `repository`
is not made valid here, because the repository carries the full image
path and there is no separate image-name segment — omitting it leaves no
image to pull. This PR only makes
`image.registry` optional.
## Checklist
- [x] Commit is DCO signed-off (`git commit -s`).
- [x] `helm unittest helm/tools/querydoc` green; `helm lint
helm/tools/querydoc` clean.
- [x] Diff limited to `templates/_helpers.tpl`,
`templates/deployment.yaml`, and
`tests/deployment_test.yaml` (generated chart files excluded).
- [x] Test fails without the fix, passes with it.
Signed-off-by: Mike Spinks <mikespinks@gmail.com>
EItanya
approved these changes
Jul 1, 2026
EItanya
approved these changes
Jul 2, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
Fix the bundled-PostgreSQL image helper so an empty
image.repositoryrenders a valid reference (registry/name:tag) instead ofregistry//name:tag, which is an invalid image reference and sends the pod intoImagePullBackOff. I observed this on AKS with Helm Chart 0.9.9.Why
templates/_helpers.tplbuilds the image with aprintfthat always emits the sameregistry/repository/name:tagshape — it writes a/between every segment whether or not that segment is empty:printf "%s/%s/%s:%s"always inserts a/between segments, so an emptyrepository(a valid configuration for images that live at the root of a registry) leaves an empty path segment and renders a double slash:The
kagent.postgresql.imagehelper is unchanged acrossv0.9.9,v0.9.10,v0.10.0-beta1, andmain(theprintfline is identical at each), so the issue affects the current stable line and the next (v0.10.x) release line.Fix
Build the path as a list, drop empty segments with
compact, thenjoinwith/. This needs noif/else: an emptyregistryand/orrepositoryis simply left out, so leavingrepositoryblank produces a valid reference (the supported way to pull an image from the root of a registry) instead of a broken one:Rendered results:
docker.iolibrarypostgresdocker.io/library/postgres:tag(unchanged)docker.io""postgresdocker.io/postgres:tag(fixed)""""postgrespostgres:tagTests
Added one case to
helm/kagent/tests/postgresql_test.yamlcovering the empty-repository path (developed test-first). The existingdefault pgvector imageandcustom imagetests already guard the non-empty path, so no regression test wasduplicated.
The test fails without this change (
docker.io//postgres:18.3-alpine) and passes with it — revert the_helpers.tplhunk to reproduce.How to verify
Scope
Limited to the bundled-PostgreSQL helper (
kagent.postgresql.image), where the//template bug is confirmed. The doc2vec/QueryDoc MCP image lives in a separate chart (helm/tools/querydoc) and is assembled controller-side fromIMAGE_REGISTRY/IMAGE_REPOSITORY/IMAGE_TAGenv vars, so it is intentionally not changed here. If it exhibits the same//symptom at deploy time, it should be addressed separately (likely in the controller join logic), not in this chart.A backport to the
v0.9.xstable line may be worthwhile since the bug is present there — maintainers' call.Checklist
git commit -s).helm unittest helm/kagentgreen;helm lint helm/kagentclean.templates/_helpers.tplandtests/postgresql_test.yaml(generated
Chart.yaml/Chart.lock/charts/*.tgz/dist/excluded).