Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion .github/workflows/helm-e2e.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -56,8 +56,12 @@ jobs:
kubectl apply -f manifests/ate-install/kind/prometheus.yaml
- name: Build chart images
run: |
# Pushed under the image's real path (kagent-dev/substrate/<component>):
# the chart composes {registry}/{repository}/{component}, so the local
# registry serves each image where the default repository expects it --
# the same path-preserving rule a production mirror follows.
for component in ateapi atecontroller atelet podcertcontroller atenet; do
KO_DOCKER_REPO="localhost:5001/${component}" \
KO_DOCKER_REPO="localhost:5001/kagent-dev/substrate/${component}" \
./hack/run-tool.sh ko build --bare --tags helm-e2e \
--platform linux/amd64 "./cmd/${component}"
done
Expand Down
35 changes: 35 additions & 0 deletions .github/workflows/helm-verify.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
# Copyright 2026 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

# Enforces the chart checks that were previously developer-run only:
# verify-helm-template's own comment says "Used in CI", but no workflow ran
# it -- so a chart change that broke hack/render-manifests.sh shipped a green
# PR and was caught in review by hand.
name: helm-verify
on:
pull_request:
push:
branches: [main]
permissions:
contents: read
jobs:
helm-verify:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: azure/setup-helm@v4
- name: Lint the chart
run: helm lint charts/substrate charts/substrate-crds
- name: Verify committed manifests match the chart output
run: make verify-helm-template
63 changes: 62 additions & 1 deletion charts/substrate/templates/_helpers.tpl
Original file line number Diff line number Diff line change
Expand Up @@ -149,11 +149,72 @@ are emitted without a tag, letting `ko resolve` supply the digest at build time.
{{- define "substrate.componentImage" -}}
{{- $name := index . 0 -}}
{{- $ctx := index . 1 -}}
{{- $registry := $ctx.Values.image.registry -}}
{{/* image.registry used to carry the full prefix (ghcr.io/kagent-dev/substrate).
It is now the registry host only, joined onto image.repository -- the same
registry/repository split every kagent-family chart uses, so one
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. */}}
{{- /* A scheme'd registry (ko://...) is hack/render-manifests.sh passing an
importpath prefix for `ko resolve` to substitute, same as the "<none>" tag
sentinel below -- unambiguously not the old host+path shape, so the guard
lets it through. */ -}}
{{- if and (contains "/" $ctx.Values.image.registry) (not (contains "://" $ctx.Values.image.registry)) -}}
{{- fail (printf "image.registry (%q) 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." $ctx.Values.image.registry) -}}
{{- end -}}
{{- $registry := printf "%s/%s" (default $ctx.Values.image.registry (($ctx.Values.global).imageRegistry)) $ctx.Values.image.repository -}}
{{- $tag := $ctx.Values.image.tag | default $ctx.Chart.AppVersion -}}
{{- if ne $tag "<none>" -}}
{{- printf "%s/%s:%s" $registry $name $tag -}}
{{- else -}}
{{- printf "%s/%s" $registry $name -}}
{{- end -}}
{{- end -}}

{{/*
Rewrite a full image reference ({registry}/{path}:{tag}) onto global.imageRegistry.

The `images.*` values are single-string references, some digest-pinned, so the
mirror knob has to edit the string. The first path segment is a registry only when
it contains "." or ":" (the containerd rule); otherwise the reference is
docker.io-implied and the mirror is prefixed. The repository path is preserved
either way, so a mirror copies images under their existing paths.

Usage: {{ include "substrate.thirdPartyImage" (list .Values.images.postgres .) }}
*/}}
{{- define "substrate.thirdPartyImage" -}}
{{- $ref := index . 0 -}}
{{- $ctx := index . 1 -}}
{{- $mirror := (($ctx.Values.global).imageRegistry) -}}
{{- if not $mirror -}}
{{- $ref -}}
{{- else -}}
{{- $parts := splitList "/" $ref -}}
{{- $first := first $parts -}}
{{- if and (gt (len $parts) 1) (or (contains "." $first) (contains ":" $first)) -}}
{{- printf "%s/%s" $mirror (join "/" (rest $parts)) -}}
{{- else -}}
{{- printf "%s/%s" $mirror $ref -}}
{{- end -}}
{{- end -}}
{{- end -}}

{{/*
imagePullSecrets for a pod spec: the chart's own list merged (union) with
global.imagePullSecrets. Renders nothing when both are empty.
*/}}
{{- define "substrate.imagePullSecrets" -}}
{{- $merged := concat (.Values.imagePullSecrets | default list) (((.Values.global).imagePullSecrets) | default list) | uniq -}}
{{- if $merged -}}
imagePullSecrets:
{{- toYaml $merged | nindent 0 }}
{{- end -}}
{{- end -}}

{{/*
imagePullPolicy: global.imagePullPolicy when set, IfNotPresent otherwise. One
definition so the fallback cannot drift between pods.
*/}}
{{- define "substrate.imagePullPolicy" -}}
{{- ((.Values.global).imagePullPolicy) | default "IfNotPresent" -}}
{{- end -}}
1 change: 1 addition & 0 deletions charts/substrate/templates/ate-api-server.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ spec:
spec:
serviceAccountName: {{ include "substrate.fullname" (list "ate-api-server" .) }}
terminationGracePeriodSeconds: 40
{{- with include "substrate.imagePullSecrets" . }}{{- . | nindent 6 }}{{- end }}
containers:
- name: ate-api-server
image: {{ include "substrate.componentImage" (list "ateapi" .) }}
Expand Down
1 change: 1 addition & 0 deletions charts/substrate/templates/ate-controller.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ spec:
app: ate-controller
spec:
serviceAccountName: {{ include "substrate.fullname" (list "ate-controller" .) }}
{{- with include "substrate.imagePullSecrets" . }}{{- . | nindent 6 }}{{- end }}
containers:
- name: ate-controller
image: {{ include "substrate.componentImage" (list "atecontroller" .) }}
Expand Down
1 change: 1 addition & 0 deletions charts/substrate/templates/atelet.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,7 @@ spec:
prometheus.io/port: "9090"
spec:
serviceAccountName: {{ include "substrate.fullname" (list "atelet" .) }}
{{- with include "substrate.imagePullSecrets" . }}{{- . | nindent 6 }}{{- end }}
containers:
- name: atelet
image: {{ include "substrate.componentImage" (list "atelet" .) }}
Expand Down
3 changes: 2 additions & 1 deletion charts/substrate/templates/atenet-egress.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -96,9 +96,10 @@ spec:
- name: net.ipv4.ip_unprivileged_port_start
value: "0"
terminationGracePeriodSeconds: 60
{{- with include "substrate.imagePullSecrets" . }}{{- . | nindent 6 }}{{- end }}
containers:
- name: agentgateway
image: {{ .Values.images.agentgateway }}
image: {{ include "substrate.thirdPartyImage" (list .Values.images.agentgateway .) }}
args:
- -f
- /etc/agentgateway/config.yaml
Expand Down
3 changes: 2 additions & 1 deletion charts/substrate/templates/atenet-router.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -211,6 +211,7 @@ spec:
prometheus.io/port: "9090"
spec:
serviceAccountName: {{ include "substrate.fullname" (list "atenet-router" .) }}
{{- with include "substrate.imagePullSecrets" . }}{{- . | nindent 6 }}{{- end }}
containers:
- name: atenet-router
image: {{ include "substrate.componentImage" (list "atenet" .) }}
Expand Down Expand Up @@ -258,7 +259,7 @@ spec:
- { name: servicedns-ca, mountPath: /run/servicedns-ca, readOnly: true }
- { name: podidentity, mountPath: /run/podidentity.podcert.ate.dev, readOnly: true }
- name: agentgateway
image: {{ .Values.images.agentgateway }}
image: {{ include "substrate.thirdPartyImage" (list .Values.images.agentgateway .) }}
args:
- "-f"
- "/etc/agentgateway/config.yaml"
Expand Down
1 change: 1 addition & 0 deletions charts/substrate/templates/pod-certificate-controller.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,7 @@ spec:
labels:
app: podcertificate-controller
spec:
{{- with include "substrate.imagePullSecrets" . }}{{- . | nindent 6 }}{{- end }}
containers:
- name: controller
image: {{ include "substrate.componentImage" (list "podcertcontroller" .) }}
Expand Down
5 changes: 3 additions & 2 deletions charts/substrate/templates/postgres.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,7 @@ spec:
initContainers:
- name: tls-reloader
restartPolicy: Always
image: {{ .Values.images.postgres }}
image: {{ include "substrate.thirdPartyImage" (list .Values.images.postgres .) }}
securityContext:
runAsUser: 70
command:
Expand All @@ -145,9 +145,10 @@ spec:
requests:
cpu: 10m
memory: 32Mi
{{- with include "substrate.imagePullSecrets" . }}{{- . | nindent 6 }}{{- end }}
containers:
- name: postgres
image: {{ .Values.images.postgres }}
image: {{ include "substrate.thirdPartyImage" (list .Values.images.postgres .) }}
lifecycle:
postStart:
exec:
Expand Down
8 changes: 5 additions & 3 deletions charts/substrate/templates/rustfs.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -63,10 +63,11 @@ spec:
runAsUser: 10001
runAsGroup: 10001
fsGroup: 10001
{{- with include "substrate.imagePullSecrets" . }}{{- . | nindent 6 }}{{- end }}
containers:
- name: rustfs
image: {{ .Values.images.rustfs }}
imagePullPolicy: IfNotPresent
image: {{ include "substrate.thirdPartyImage" (list .Values.images.rustfs .) }}
imagePullPolicy: {{ include "substrate.imagePullPolicy" . }}
ports:
- containerPort: 9000
name: api
Expand Down Expand Up @@ -103,9 +104,10 @@ spec:
template:
spec:
restartPolicy: OnFailure
{{- with include "substrate.imagePullSecrets" . }}{{- . | nindent 6 }}{{- end }}
containers:
- name: create-bucket
image: {{ .Values.images.awsCli }}
image: {{ include "substrate.thirdPartyImage" (list .Values.images.awsCli .) }}
env:
- name: AWS_ACCESS_KEY_ID
value: {{ .Values.rustfs.accessKey | quote }}
Expand Down
27 changes: 26 additions & 1 deletion charts/substrate/values.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -80,8 +80,33 @@ otel:
enabled: true
endpoint: ""

# Values under `global` are visible to this chart and to every subchart. A parent
# chart or an operator sets one value here instead of one value per chart.
global:
# -- Mirror registry that overrides where every image is pulled from. This is
# the air-gap knob. It wins over image.registry for component images. Each
# `images.*` reference has its registry segment replaced. Repository paths are
# preserved, so a mirror only has to copy images under their existing paths.
# For control without the override, leave this unset and set image.registry.
imageRegistry: ""
# -- Pull secrets merged (union) into each pod's own imagePullSecrets list.
imagePullSecrets: []
# -- Fallback imagePullPolicy where a container does not set one.
imagePullPolicy: ""

# Pull secrets for every pod this chart renders. Merged with global.imagePullSecrets.
imagePullSecrets: []

image:
registry: ghcr.io/kagent-dev/substrate
# Registry host for the component images, and nothing else. To change
# environments, change only this value or global.imageRegistry, which overrides
# it. A path inside `registry` fails the render, and the error names this
# split.
registry: ghcr.io
# Image path prefix under the registry, ahead of each component name. The path
# is identical on every registry that serves the images. A mirror copies the
# images under this same path.
repository: kagent-dev/substrate
tag: ""

images:
Expand Down
3 changes: 2 additions & 1 deletion hack/render-manifests.sh
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,8 @@ helm template substrate "${CHART_DIR}" \
--namespace ate-system \
--set auth.mode=mtls \
--set createNamespace=true \
--set image.registry=ko://github.com/agent-substrate/substrate/cmd \
--set image.registry=ko://github.com \
--set image.repository=agent-substrate/substrate/cmd \
--set image.tag="<none>" \
> "${TMP_DIR}/all.yaml"

Expand Down
Loading