Skip to content

Commit 90f6708

Browse files
authored
improvement(helm): hygiene pass — CI gating, strict values schema, ESO v1 default (#5939)
* improvement(helm): hygiene pass — CI gating, strict values schema, ESO v1 default, ci values Closes the gaps from a best-practices audit of the chart (template-level conformance was already clean: full label set, 82 unit tests, kubeconform- valid renders): - new Helm Chart workflow gates every chart change: helm lint, the 82 helm-unittest cases, kubeconform validation of default + all-components renders (k8s 1.29 strict, CRD catalog), and a render of all 10 example values files - helm/sim/ci/ values files (chart-testing convention) so the chart lints and templates cleanly out of the box with dummy secrets - values.schema.json declares all 30 top-level keys (16 were invisible) and sets root additionalProperties: false, so top-level typos fail fast - externalSecrets.apiVersion defaults to v1: current ESO releases removed the v1beta1 compatibility path in 2026, so the old default produced rejected manifests on new installs; NOTES/values comments updated - wait-for-postgres init container gets requests/limits (the only container in the chart without them; broke ResourceQuota'd namespaces) - drop the telemetry Prometheus scrape config for app/realtime — neither exposes /metrics, so it was dead config that also rendered a realtime target with realtime disabled - chart 1.2.0 with README upgrade notes * fix(helm): version-agnostic schema-error assertions in the secret-length suite Helm v4 phrases schema rejections as 'minLength: got N, want 32' while v3 says 'String length must be greater than or equal to 32'. The suite grepped for 'minLength' only, so it passed on local helm v4 and failed on CI's v3.16.4 — the enforcement itself works on both. Patterns now assert the key name plus either wording. Caught by the new Helm Chart workflow on its very first run. * improvement(helm): kind install test + chart version-bump gate in CI Benchmarked against the flagship OSS charts (ingress-nginx, argo-cd, kube-prometheus-stack, grafana, bitnami, cert-manager): sim already exceeds most of them on validation rigor (strict schema — 4 of 6 ship none; 82 unit tests vs argo-cd's zero; kubeconform manifest validation none of them run), but every top community chart repo actually installs the chart on a kind cluster in chart CI — the one majority practice we lacked. Adds: - install job: kind cluster, helm install with ci/default + a new small-footprint ci/kind-values.yaml overlay (default app requests of 4Gi can't schedule on a CI node), --wait, then the chart's helm test hook, with pod/event/log diagnostics on failure - version-bump job (PR-only): fails when helm/sim/** changes without a Chart.yaml version increment — argo/kps/grafana all enforce this * fix(helm): declare naming overrides in the strict schema; SHA-pin CI actions - nameOverride/fullnameOverride are consumed by sim.name/sim.fullname but were never in values.yaml, so root additionalProperties: false rejected Helm's standard naming overrides — both now declared (a helper-wide sweep confirmed they were the only template-read keys missing), with a smoke regression test that installs under the strict schema and asserts the override lands in resource names - CI supply-chain hardening: checkout/setup-helm/kind-action pinned to full commit SHAs (tag comments retained) and the kubeconform archive verified against its published sha256 * fix(helm): immutable unittest runner and fail-closed version gate in CI - helm-unittest runs via the project's official docker image pinned by immutable sha256 digest instead of a plugin install from a mutable git tag - the version-bump gate fetches the full base ref (a --depth=1 fetch could leave no merge base), computes the merge-base and diff outside the if so any git failure fails the job instead of falling into the skip branch * fix(ci): run the helm-unittest container as the runner UID The digest-pinned image runs as a non-root user that cannot write into the runner-owned bind mount (it creates tests/__snapshot__, absent from the checkout since empty dirs aren't tracked). Standard bind-mount pattern: --user "$(id -u):$(id -g)" with HOME=/tmp for helm's cache. * fix(helm): appVersion points at a real GHCR tag (v0.7.44) The kind install job caught this on its first full run: the default image tag (Chart.AppVersion 0.6.73) returns 404 on GHCR for all three images — the registry's tags are v-prefixed — so an unpinned default install could never pull. Updated appVersion to v0.7.44 (verified 200 for simstudio, realtime, and migrations manifests), stale values comment refreshed, and an upgrade note added. The CI kind values deliberately stay tag-free so the job keeps exercising the true default path.
1 parent f0f9870 commit 90f6708

17 files changed

Lines changed: 395 additions & 27 deletions

.github/workflows/helm.yml

Lines changed: 157 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,157 @@
1+
name: Helm Chart
2+
3+
on:
4+
push:
5+
branches: [main, staging, dev]
6+
paths:
7+
- 'helm/sim/**'
8+
- '.github/workflows/helm.yml'
9+
pull_request:
10+
branches: [main, staging, dev]
11+
paths:
12+
- 'helm/sim/**'
13+
- '.github/workflows/helm.yml'
14+
15+
concurrency:
16+
group: helm-${{ github.ref }}
17+
cancel-in-progress: true
18+
19+
permissions:
20+
contents: read
21+
22+
jobs:
23+
chart:
24+
name: Lint, test, and validate chart
25+
runs-on: ${{ (vars.CI_PROVIDER == '' || vars.CI_PROVIDER == 'blacksmith') && 'blacksmith-2vcpu-ubuntu-2404' || 'ubuntu-latest' }}
26+
timeout-minutes: 15
27+
steps:
28+
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4
29+
30+
- name: Set up Helm
31+
uses: azure/setup-helm@1a275c3b69536ee54be43f2070a358922e12c8d4 # v4
32+
with:
33+
version: v3.16.4
34+
35+
- name: Helm lint
36+
run: helm lint helm/sim --values helm/sim/ci/default-values.yaml
37+
38+
- name: Helm unit tests
39+
run: |
40+
# Official helm-unittest image, pinned by immutable digest (tag 3.17.3-0.8.2).
41+
# Run as the runner's UID so the container can write into the bind
42+
# mount (it creates tests/__snapshot__), with a writable HOME for helm.
43+
docker run --rm --user "$(id -u):$(id -g)" -e HOME=/tmp \
44+
-v "$PWD/helm/sim:/apps" \
45+
helmunittest/helm-unittest@sha256:b653db7d5665bc6cec677b15c5eaa1c0377c0de8ac4eb1df58b924478baa21e1 .
46+
47+
- name: Install kubeconform
48+
run: |
49+
curl -sSL -o /tmp/kubeconform.tar.gz \
50+
https://github.com/yannh/kubeconform/releases/download/v0.6.7/kubeconform-linux-amd64.tar.gz
51+
echo "95f14e87aa28c09d5941f11bd024c1d02fdc0303ccaa23f61cef67bc92619d73 /tmp/kubeconform.tar.gz" | sha256sum -c -
52+
tar -xzf /tmp/kubeconform.tar.gz -C /tmp kubeconform
53+
54+
- name: Render and validate manifests (default configuration)
55+
run: |
56+
helm template sim helm/sim --namespace sim \
57+
--values helm/sim/ci/default-values.yaml \
58+
| /tmp/kubeconform -strict -summary \
59+
-kubernetes-version 1.29.0 \
60+
-schema-location default \
61+
-schema-location 'https://raw.githubusercontent.com/datreeio/CRDs-catalog/main/{{.Group}}/{{.ResourceKind}}_{{.ResourceAPIVersion}}.json'
62+
63+
- name: Render and validate manifests (all components enabled)
64+
run: |
65+
helm template sim helm/sim --namespace sim \
66+
--values helm/sim/ci/full-values.yaml \
67+
| /tmp/kubeconform -strict -summary \
68+
-kubernetes-version 1.29.0 \
69+
-schema-location default \
70+
-schema-location 'https://raw.githubusercontent.com/datreeio/CRDs-catalog/main/{{.Group}}/{{.ResourceKind}}_{{.ResourceAPIVersion}}.json'
71+
72+
- name: Render every example values file
73+
run: |
74+
set -euo pipefail
75+
for f in helm/sim/examples/values-*.yaml; do
76+
echo "--- $f"
77+
# Examples intentionally omit secrets (their headers document the
78+
# required --set flags), so supply the CI dummies alongside each.
79+
helm template sim helm/sim --namespace sim \
80+
--values "$f" \
81+
--values helm/sim/ci/default-values.yaml \
82+
--set copilot.postgresql.auth.password=ci-dummy-password \
83+
--set copilot.server.env.AGENT_API_DB_ENCRYPTION_KEY=cicicicicicicicicicicicicicicicicicicicicicicicicicicicicicicici \
84+
--set copilot.server.env.INTERNAL_API_SECRET=cicicicicicicicicicicicicicicicicicicicicicicicicicicicicicicici \
85+
--set copilot.server.env.LICENSE_KEY=ci-dummy-license \
86+
--set copilot.server.env.SIM_BASE_URL=https://ci.example.com \
87+
--set copilot.server.env.SIM_AGENT_API_KEY=ci-dummy-agent-key \
88+
--set copilot.server.env.REDIS_URL=redis://ci-redis:6379 \
89+
--set copilot.server.env.OPENAI_API_KEY_1=ci-dummy-openai-key \
90+
--set externalDatabase.password=ci-dummy-password > /dev/null
91+
done
92+
93+
version-bump:
94+
name: Chart version bumped
95+
if: github.event_name == 'pull_request'
96+
runs-on: ${{ (vars.CI_PROVIDER == '' || vars.CI_PROVIDER == 'blacksmith') && 'blacksmith-2vcpu-ubuntu-2404' || 'ubuntu-latest' }}
97+
timeout-minutes: 5
98+
steps:
99+
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4
100+
with:
101+
fetch-depth: 0
102+
- name: Require a Chart.yaml version bump when chart content changes
103+
run: |
104+
set -euo pipefail
105+
base="origin/${{ github.base_ref }}"
106+
git fetch origin "${{ github.base_ref }}"
107+
merge_base=$(git merge-base "$base" HEAD)
108+
changed=$(git diff --name-only "$merge_base" HEAD)
109+
if echo "$changed" | grep -q '^helm/sim/'; then
110+
base_version=$(git show "$merge_base:helm/sim/Chart.yaml" | awk '/^version:/ {print $2}')
111+
head_version=$(awk '/^version:/ {print $2}' helm/sim/Chart.yaml)
112+
echo "base=$base_version head=$head_version"
113+
if [ "$base_version" = "$head_version" ]; then
114+
echo "::error::helm/sim/** changed but Chart.yaml version did not (still $head_version). Bump it per SemVer."
115+
exit 1
116+
fi
117+
else
118+
echo "No chart changes; skipping."
119+
fi
120+
121+
install:
122+
name: Install on kind and run helm test
123+
needs: chart
124+
runs-on: ${{ (vars.CI_PROVIDER == '' || vars.CI_PROVIDER == 'blacksmith') && 'blacksmith-4vcpu-ubuntu-2404' || 'ubuntu-latest' }}
125+
timeout-minutes: 25
126+
steps:
127+
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4
128+
129+
- name: Set up Helm
130+
uses: azure/setup-helm@1a275c3b69536ee54be43f2070a358922e12c8d4 # v4
131+
with:
132+
version: v3.16.4
133+
134+
- name: Create kind cluster
135+
uses: helm/kind-action@ef37e7f390d99f746eb8b610417061a60e82a6cc # v1
136+
with:
137+
version: v0.24.0
138+
139+
- name: Install chart
140+
run: |
141+
helm install sim helm/sim \
142+
--namespace sim --create-namespace \
143+
--values helm/sim/ci/default-values.yaml \
144+
--values helm/sim/ci/kind-values.yaml \
145+
--wait --timeout 15m
146+
147+
- name: Diagnostics on failure
148+
if: failure()
149+
run: |
150+
kubectl -n sim get pods -o wide || true
151+
kubectl -n sim get events --sort-by=.lastTimestamp | tail -40 || true
152+
kubectl -n sim describe pods | tail -100 || true
153+
kubectl -n sim logs deploy/sim-app -c migrations --tail=50 || true
154+
kubectl -n sim logs deploy/sim-app --tail=80 || true
155+
156+
- name: Run helm test
157+
run: helm test sim --namespace sim --timeout 5m

helm/sim/Chart.yaml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,8 @@ apiVersion: v2
22
name: sim
33
description: A Helm chart for Sim - the open-source AI workspace where teams build, deploy, and manage AI agents
44
type: application
5-
version: 1.1.0
6-
appVersion: "0.6.73"
5+
version: 1.2.0
6+
appVersion: "v0.7.44"
77
kubeVersion: ">=1.25.0-0"
88
home: https://sim.ai
99
icon: https://raw.githubusercontent.com/simstudioai/sim/main/apps/sim/public/logo/primary/primary.svg

helm/sim/README.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -467,6 +467,13 @@ kubectl --namespace sim logs deploy/sim-app -c migrations
467467

468468
---
469469

470+
## Upgrading to 1.2.0
471+
472+
* `appVersion` (the default image tag when `image.tag` is unset) is now `v0.7.44` — the previous `0.6.73` referenced a tag that does not exist on GHCR, so an unpinned default install could not pull images. Production installs should still pin `image.tag` explicitly.
473+
* `externalSecrets.apiVersion` now defaults to `"v1"` — current External Secrets Operator releases no longer serve `v1beta1` (removed upstream in 2026). Set `externalSecrets.apiVersion: "v1beta1"` only if you still run ESO < 0.17.
474+
* `values.schema.json` now declares every top-level key and rejects unknown top-level keys, so a typo like `networkPolciy:` fails fast at install time instead of being silently ignored. If an upgrade suddenly fails schema validation, check your values file for stray top-level keys.
475+
* The opt-in telemetry collector no longer ships a Prometheus scrape config for the app/realtime services (they expose no `/metrics` endpoint); OTLP ingestion is unchanged.
476+
470477
## Upgrading to 1.1.0
471478

472479
No action is required for working configurations. Notes:

helm/sim/ci/default-values.yaml

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
# CI-only values: the minimum required secrets so `helm lint`/`helm template`
2+
# render the DEFAULT configuration. Dummy values — never use in a deployment.
3+
app:
4+
env:
5+
BETTER_AUTH_SECRET: "cicicicicicicicicicicicicicicicicicicicicicicicicicicicicicicici"
6+
ENCRYPTION_KEY: "cicicicicicicicicicicicicicicicicicicicicicicicicicicicicicicici"
7+
INTERNAL_API_SECRET: "cicicicicicicicicicicicicicicicicicicicicicicicicicicicicicicici"
8+
CRON_SECRET: "cicicicicicicicicicicicicicicicicicicicicicicicicicicicicicicici"
9+
10+
postgresql:
11+
auth:
12+
password: "ci-dummy-password"

helm/sim/ci/full-values.yaml

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
# CI-only values: every optional component enabled so the full template surface
2+
# renders and validates. Dummy values — never use in a deployment.
3+
app:
4+
env:
5+
BETTER_AUTH_SECRET: "cicicicicicicicicicicicicicicicicicicicicicicicicicicicicicicici"
6+
ENCRYPTION_KEY: "cicicicicicicicicicicicicicicicicicicicicicicicicicicicicicicici"
7+
INTERNAL_API_SECRET: "cicicicicicicicicicicicicicicicicicicicicicicicicicicicicicicici"
8+
CRON_SECRET: "cicicicicicicicicicicicicicicicicicicicicicicicicicicicicicicici"
9+
API_ENCRYPTION_KEY: "cicicicicicicicicicicicicicicicicicicicicicicicicicicicicicicici"
10+
11+
postgresql:
12+
auth:
13+
password: "ci-dummy-password"
14+
tls:
15+
enabled: true
16+
17+
certManager:
18+
enabled: true
19+
20+
ingress:
21+
enabled: true
22+
app:
23+
host: ci.example.com
24+
paths: [{ path: /, pathType: Prefix }]
25+
realtime:
26+
host: ci-ws.example.com
27+
paths: [{ path: /, pathType: Prefix }]
28+
tls:
29+
enabled: true
30+
31+
networkPolicy:
32+
enabled: true
33+
34+
autoscaling:
35+
enabled: true
36+
37+
monitoring:
38+
serviceMonitor:
39+
enabled: true
40+
41+
telemetry:
42+
enabled: true
43+
jaeger:
44+
enabled: true
45+
prometheus:
46+
enabled: true
47+
otlp:
48+
enabled: true
49+
50+
sharedStorage:
51+
enabled: true
52+
53+
ollama:
54+
enabled: true
55+
56+
pii:
57+
enabled: true
58+
59+
copilot:
60+
enabled: true
61+
postgresql:
62+
auth:
63+
password: "ci-dummy-password"
64+
server:
65+
env:
66+
AGENT_API_DB_ENCRYPTION_KEY: "cicicicicicicicicicicicicicicicicicicicicicicicicicicicicicicici"
67+
INTERNAL_API_SECRET: "cicicicicicicicicicicicicicicicicicicicicicicicicicicicicicicici"
68+
LICENSE_KEY: "ci-dummy-license"
69+
SIM_BASE_URL: "https://ci.example.com"
70+
SIM_AGENT_API_KEY: "ci-dummy-agent-key"
71+
REDIS_URL: "redis://ci-redis:6379"
72+
OPENAI_API_KEY_1: "ci-dummy-openai-key"

helm/sim/ci/kind-values.yaml

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
# CI-only overlay for the kind install test: shrink resource requests so the
2+
# default configuration schedules on a small CI runner. Layered on top of
3+
# ci/default-values.yaml. Dummy sizing — never use in a deployment.
4+
app:
5+
resources:
6+
requests:
7+
cpu: 200m
8+
memory: 512Mi
9+
limits:
10+
cpu: 1000m
11+
memory: 2Gi
12+
13+
realtime:
14+
resources:
15+
requests:
16+
cpu: 50m
17+
memory: 128Mi
18+
limits:
19+
cpu: 500m
20+
memory: 512Mi
21+
22+
migrations:
23+
resources:
24+
requests:
25+
cpu: 100m
26+
memory: 256Mi
27+
limits:
28+
cpu: 500m
29+
memory: 1Gi
30+
31+
postgresql:
32+
resources:
33+
requests:
34+
cpu: 100m
35+
memory: 256Mi
36+
limits:
37+
memory: 1Gi
38+
persistence:
39+
size: 2Gi

helm/sim/templates/NOTES.txt

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -86,9 +86,9 @@ Your release is named {{ .Release.Name }} in namespace {{ .Release.Namespace }}.
8686

8787
5. Upgrade notes (read before upgrading from a chart version released before this one):
8888

89-
* externalSecrets.apiVersion default is "v1beta1" (was "v1"). v1beta1 is
90-
supported by every ESO release from v0.7+ through current. If you're on
91-
ESO v0.17+ and want the graduated v1 API, set externalSecrets.apiVersion: "v1".
89+
* externalSecrets.apiVersion defaults to "v1". Current ESO releases no
90+
longer serve v1beta1 (the compatibility path was removed in 2026) — set
91+
externalSecrets.apiVersion: "v1beta1" only if you still run ESO < 0.17.
9292
* networkPolicy.egress remains a list of custom egress rules (unchanged).
9393
Cloud-metadata CIDR blocking is now configured via networkPolicy.egressExceptCidrs
9494
(defaults to AWS/GCP/Azure IMDS + ECS task metadata).

helm/sim/templates/external-secret-app.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
# - a string: treated as the remoteRef.key (legacy form)
1010
# - a map: passed through as the remoteRef block (e.g. {key, property,
1111
# version, decodingStrategy, conversionStrategy, metadataPolicy})
12-
apiVersion: external-secrets.io/{{ .Values.externalSecrets.apiVersion | default "v1beta1" }}
12+
apiVersion: external-secrets.io/{{ .Values.externalSecrets.apiVersion | default "v1" }}
1313
kind: ExternalSecret
1414
metadata:
1515
name: {{ include "sim.fullname" . }}-app-secrets

helm/sim/templates/external-secret-copilot.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
# template rendering if a required key (or any non-empty copilot.server.env key) is
77
# missing a matching remoteRefs.copilot entry, so a misconfiguration surfaces at
88
# `helm template` time instead of a container starting with an empty value.
9-
apiVersion: external-secrets.io/{{ .Values.externalSecrets.apiVersion | default "v1beta1" }}
9+
apiVersion: external-secrets.io/{{ .Values.externalSecrets.apiVersion | default "v1" }}
1010
kind: ExternalSecret
1111
metadata:
1212
name: {{ include "sim.copilot.envSecretName" . }}

helm/sim/templates/external-secret-external-db.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{{- if and .Values.externalSecrets.enabled .Values.externalDatabase.enabled .Values.externalSecrets.remoteRefs.externalDatabase.password }}
22
# ExternalSecret for external database password (syncs from external secret managers)
3-
apiVersion: external-secrets.io/{{ .Values.externalSecrets.apiVersion | default "v1beta1" }}
3+
apiVersion: external-secrets.io/{{ .Values.externalSecrets.apiVersion | default "v1" }}
44
kind: ExternalSecret
55
metadata:
66
name: {{ include "sim.fullname" . }}-external-db-secret

0 commit comments

Comments
 (0)