From 187e266e9ed6284b489c6a3759052febd4a4208a Mon Sep 17 00:00:00 2001 From: Waleed Latif Date: Fri, 24 Jul 2026 12:53:29 -0700 Subject: [PATCH 1/7] =?UTF-8?q?improvement(helm):=20hygiene=20pass=20?= =?UTF-8?q?=E2=80=94=20CI=20gating,=20strict=20values=20schema,=20ESO=20v1?= =?UTF-8?q?=20default,=20ci=20values?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- .github/workflows/helm.yml | 86 +++++++++++++++++++ helm/sim/Chart.yaml | 2 +- helm/sim/README.md | 6 ++ helm/sim/ci/default-values.yaml | 12 +++ helm/sim/ci/full-values.yaml | 72 ++++++++++++++++ helm/sim/templates/NOTES.txt | 6 +- helm/sim/templates/external-secret-app.yaml | 2 +- .../templates/external-secret-copilot.yaml | 2 +- .../external-secret-external-db.yaml | 2 +- .../templates/external-secret-postgresql.yaml | 2 +- .../sim/templates/job-copilot-migrations.yaml | 7 ++ helm/sim/templates/telemetry.yaml | 12 +-- helm/sim/values.schema.json | 65 ++++++++++++++ helm/sim/values.yaml | 7 +- 14 files changed, 261 insertions(+), 22 deletions(-) create mode 100644 .github/workflows/helm.yml create mode 100644 helm/sim/ci/default-values.yaml create mode 100644 helm/sim/ci/full-values.yaml diff --git a/.github/workflows/helm.yml b/.github/workflows/helm.yml new file mode 100644 index 00000000000..813c2b4c5d3 --- /dev/null +++ b/.github/workflows/helm.yml @@ -0,0 +1,86 @@ +name: Helm Chart + +on: + push: + branches: [main, staging, dev] + paths: + - 'helm/sim/**' + - '.github/workflows/helm.yml' + pull_request: + branches: [main, staging, dev] + paths: + - 'helm/sim/**' + - '.github/workflows/helm.yml' + +concurrency: + group: helm-${{ github.ref }} + cancel-in-progress: true + +permissions: + contents: read + +jobs: + chart: + name: Lint, test, and validate chart + runs-on: ${{ (vars.CI_PROVIDER == '' || vars.CI_PROVIDER == 'blacksmith') && 'blacksmith-2vcpu-ubuntu-2404' || 'ubuntu-latest' }} + timeout-minutes: 15 + steps: + - uses: actions/checkout@v4 + + - name: Set up Helm + uses: azure/setup-helm@v4 + with: + version: v3.16.4 + + - name: Helm lint + run: helm lint helm/sim --values helm/sim/ci/default-values.yaml + + - name: Helm unit tests + run: | + helm plugin install https://github.com/helm-unittest/helm-unittest --version v0.8.2 + helm unittest helm/sim + + - name: Install kubeconform + run: | + curl -sSL -o /tmp/kubeconform.tar.gz \ + https://github.com/yannh/kubeconform/releases/download/v0.6.7/kubeconform-linux-amd64.tar.gz + tar -xzf /tmp/kubeconform.tar.gz -C /tmp kubeconform + + - name: Render and validate manifests (default configuration) + run: | + helm template sim helm/sim --namespace sim \ + --values helm/sim/ci/default-values.yaml \ + | /tmp/kubeconform -strict -summary \ + -kubernetes-version 1.29.0 \ + -schema-location default \ + -schema-location 'https://raw.githubusercontent.com/datreeio/CRDs-catalog/main/{{.Group}}/{{.ResourceKind}}_{{.ResourceAPIVersion}}.json' + + - name: Render and validate manifests (all components enabled) + run: | + helm template sim helm/sim --namespace sim \ + --values helm/sim/ci/full-values.yaml \ + | /tmp/kubeconform -strict -summary \ + -kubernetes-version 1.29.0 \ + -schema-location default \ + -schema-location 'https://raw.githubusercontent.com/datreeio/CRDs-catalog/main/{{.Group}}/{{.ResourceKind}}_{{.ResourceAPIVersion}}.json' + + - name: Render every example values file + run: | + set -euo pipefail + for f in helm/sim/examples/values-*.yaml; do + echo "--- $f" + # Examples intentionally omit secrets (their headers document the + # required --set flags), so supply the CI dummies alongside each. + helm template sim helm/sim --namespace sim \ + --values "$f" \ + --values helm/sim/ci/default-values.yaml \ + --set copilot.postgresql.auth.password=ci-dummy-password \ + --set copilot.server.env.AGENT_API_DB_ENCRYPTION_KEY=cicicicicicicicicicicicicicicicicicicicicicicicicicicicicicicici \ + --set copilot.server.env.INTERNAL_API_SECRET=cicicicicicicicicicicicicicicicicicicicicicicicicicicicicicicici \ + --set copilot.server.env.LICENSE_KEY=ci-dummy-license \ + --set copilot.server.env.SIM_BASE_URL=https://ci.example.com \ + --set copilot.server.env.SIM_AGENT_API_KEY=ci-dummy-agent-key \ + --set copilot.server.env.REDIS_URL=redis://ci-redis:6379 \ + --set copilot.server.env.OPENAI_API_KEY_1=ci-dummy-openai-key \ + --set externalDatabase.password=ci-dummy-password > /dev/null + done diff --git a/helm/sim/Chart.yaml b/helm/sim/Chart.yaml index c4c8b2ca7c1..9694998376d 100644 --- a/helm/sim/Chart.yaml +++ b/helm/sim/Chart.yaml @@ -2,7 +2,7 @@ apiVersion: v2 name: sim description: A Helm chart for Sim - the open-source AI workspace where teams build, deploy, and manage AI agents type: application -version: 1.1.0 +version: 1.2.0 appVersion: "0.6.73" kubeVersion: ">=1.25.0-0" home: https://sim.ai diff --git a/helm/sim/README.md b/helm/sim/README.md index 5b02a288567..a8db664369a 100644 --- a/helm/sim/README.md +++ b/helm/sim/README.md @@ -467,6 +467,12 @@ kubectl --namespace sim logs deploy/sim-app -c migrations --- +## Upgrading to 1.2.0 + +* `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. +* `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. +* 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. + ## Upgrading to 1.1.0 No action is required for working configurations. Notes: diff --git a/helm/sim/ci/default-values.yaml b/helm/sim/ci/default-values.yaml new file mode 100644 index 00000000000..f27cd58a49b --- /dev/null +++ b/helm/sim/ci/default-values.yaml @@ -0,0 +1,12 @@ +# CI-only values: the minimum required secrets so `helm lint`/`helm template` +# render the DEFAULT configuration. Dummy values — never use in a deployment. +app: + env: + BETTER_AUTH_SECRET: "cicicicicicicicicicicicicicicicicicicicicicicicicicicicicicicici" + ENCRYPTION_KEY: "cicicicicicicicicicicicicicicicicicicicicicicicicicicicicicicici" + INTERNAL_API_SECRET: "cicicicicicicicicicicicicicicicicicicicicicicicicicicicicicicici" + CRON_SECRET: "cicicicicicicicicicicicicicicicicicicicicicicicicicicicicicicici" + +postgresql: + auth: + password: "ci-dummy-password" diff --git a/helm/sim/ci/full-values.yaml b/helm/sim/ci/full-values.yaml new file mode 100644 index 00000000000..ca980273414 --- /dev/null +++ b/helm/sim/ci/full-values.yaml @@ -0,0 +1,72 @@ +# CI-only values: every optional component enabled so the full template surface +# renders and validates. Dummy values — never use in a deployment. +app: + env: + BETTER_AUTH_SECRET: "cicicicicicicicicicicicicicicicicicicicicicicicicicicicicicicici" + ENCRYPTION_KEY: "cicicicicicicicicicicicicicicicicicicicicicicicicicicicicicicici" + INTERNAL_API_SECRET: "cicicicicicicicicicicicicicicicicicicicicicicicicicicicicicicici" + CRON_SECRET: "cicicicicicicicicicicicicicicicicicicicicicicicicicicicicicicici" + API_ENCRYPTION_KEY: "cicicicicicicicicicicicicicicicicicicicicicicicicicicicicicicici" + +postgresql: + auth: + password: "ci-dummy-password" + tls: + enabled: true + +certManager: + enabled: true + +ingress: + enabled: true + app: + host: ci.example.com + paths: [{ path: /, pathType: Prefix }] + realtime: + host: ci-ws.example.com + paths: [{ path: /, pathType: Prefix }] + tls: + enabled: true + +networkPolicy: + enabled: true + +autoscaling: + enabled: true + +monitoring: + serviceMonitor: + enabled: true + +telemetry: + enabled: true + jaeger: + enabled: true + prometheus: + enabled: true + otlp: + enabled: true + +sharedStorage: + enabled: true + +ollama: + enabled: true + +pii: + enabled: true + +copilot: + enabled: true + postgresql: + auth: + password: "ci-dummy-password" + server: + env: + AGENT_API_DB_ENCRYPTION_KEY: "cicicicicicicicicicicicicicicicicicicicicicicicicicicicicicicici" + INTERNAL_API_SECRET: "cicicicicicicicicicicicicicicicicicicicicicicicicicicicicicicici" + LICENSE_KEY: "ci-dummy-license" + SIM_BASE_URL: "https://ci.example.com" + SIM_AGENT_API_KEY: "ci-dummy-agent-key" + REDIS_URL: "redis://ci-redis:6379" + OPENAI_API_KEY_1: "ci-dummy-openai-key" diff --git a/helm/sim/templates/NOTES.txt b/helm/sim/templates/NOTES.txt index 29be6c39bd6..c3f3e211f8a 100644 --- a/helm/sim/templates/NOTES.txt +++ b/helm/sim/templates/NOTES.txt @@ -86,9 +86,9 @@ Your release is named {{ .Release.Name }} in namespace {{ .Release.Namespace }}. 5. Upgrade notes (read before upgrading from a chart version released before this one): - * externalSecrets.apiVersion default is "v1beta1" (was "v1"). v1beta1 is - supported by every ESO release from v0.7+ through current. If you're on - ESO v0.17+ and want the graduated v1 API, set externalSecrets.apiVersion: "v1". + * externalSecrets.apiVersion defaults to "v1". Current ESO releases no + longer serve v1beta1 (the compatibility path was removed in 2026) — set + externalSecrets.apiVersion: "v1beta1" only if you still run ESO < 0.17. * networkPolicy.egress remains a list of custom egress rules (unchanged). Cloud-metadata CIDR blocking is now configured via networkPolicy.egressExceptCidrs (defaults to AWS/GCP/Azure IMDS + ECS task metadata). diff --git a/helm/sim/templates/external-secret-app.yaml b/helm/sim/templates/external-secret-app.yaml index 826291e4eba..ea82d6a1f6e 100644 --- a/helm/sim/templates/external-secret-app.yaml +++ b/helm/sim/templates/external-secret-app.yaml @@ -9,7 +9,7 @@ # - a string: treated as the remoteRef.key (legacy form) # - a map: passed through as the remoteRef block (e.g. {key, property, # version, decodingStrategy, conversionStrategy, metadataPolicy}) -apiVersion: external-secrets.io/{{ .Values.externalSecrets.apiVersion | default "v1beta1" }} +apiVersion: external-secrets.io/{{ .Values.externalSecrets.apiVersion | default "v1" }} kind: ExternalSecret metadata: name: {{ include "sim.fullname" . }}-app-secrets diff --git a/helm/sim/templates/external-secret-copilot.yaml b/helm/sim/templates/external-secret-copilot.yaml index a7b78da0310..93cf7fb3bc8 100644 --- a/helm/sim/templates/external-secret-copilot.yaml +++ b/helm/sim/templates/external-secret-copilot.yaml @@ -6,7 +6,7 @@ # template rendering if a required key (or any non-empty copilot.server.env key) is # missing a matching remoteRefs.copilot entry, so a misconfiguration surfaces at # `helm template` time instead of a container starting with an empty value. -apiVersion: external-secrets.io/{{ .Values.externalSecrets.apiVersion | default "v1beta1" }} +apiVersion: external-secrets.io/{{ .Values.externalSecrets.apiVersion | default "v1" }} kind: ExternalSecret metadata: name: {{ include "sim.copilot.envSecretName" . }} diff --git a/helm/sim/templates/external-secret-external-db.yaml b/helm/sim/templates/external-secret-external-db.yaml index 1d2ac705e82..d1cde291aac 100644 --- a/helm/sim/templates/external-secret-external-db.yaml +++ b/helm/sim/templates/external-secret-external-db.yaml @@ -1,6 +1,6 @@ {{- if and .Values.externalSecrets.enabled .Values.externalDatabase.enabled .Values.externalSecrets.remoteRefs.externalDatabase.password }} # ExternalSecret for external database password (syncs from external secret managers) -apiVersion: external-secrets.io/{{ .Values.externalSecrets.apiVersion | default "v1beta1" }} +apiVersion: external-secrets.io/{{ .Values.externalSecrets.apiVersion | default "v1" }} kind: ExternalSecret metadata: name: {{ include "sim.fullname" . }}-external-db-secret diff --git a/helm/sim/templates/external-secret-postgresql.yaml b/helm/sim/templates/external-secret-postgresql.yaml index 7f55bff5424..d8796077d92 100644 --- a/helm/sim/templates/external-secret-postgresql.yaml +++ b/helm/sim/templates/external-secret-postgresql.yaml @@ -1,6 +1,6 @@ {{- if and .Values.externalSecrets.enabled .Values.postgresql.enabled .Values.externalSecrets.remoteRefs.postgresql.password }} # ExternalSecret for PostgreSQL password (syncs from external secret managers) -apiVersion: external-secrets.io/{{ .Values.externalSecrets.apiVersion | default "v1beta1" }} +apiVersion: external-secrets.io/{{ .Values.externalSecrets.apiVersion | default "v1" }} kind: ExternalSecret metadata: name: {{ include "sim.fullname" . }}-postgresql-secret diff --git a/helm/sim/templates/job-copilot-migrations.yaml b/helm/sim/templates/job-copilot-migrations.yaml index 60a2bbe663f..862ef02e919 100644 --- a/helm/sim/templates/job-copilot-migrations.yaml +++ b/helm/sim/templates/job-copilot-migrations.yaml @@ -45,6 +45,13 @@ spec: sleep 2 done echo "Copilot PostgreSQL is ready!" + resources: + requests: + cpu: 10m + memory: 16Mi + limits: + cpu: 100m + memory: 64Mi envFrom: - secretRef: name: {{ include "sim.fullname" . }}-copilot-postgresql-secret diff --git a/helm/sim/templates/telemetry.yaml b/helm/sim/templates/telemetry.yaml index 761c5155784..bd0627d4284 100644 --- a/helm/sim/templates/telemetry.yaml +++ b/helm/sim/templates/telemetry.yaml @@ -18,16 +18,6 @@ data: endpoint: 0.0.0.0:4317 http: endpoint: 0.0.0.0:4318 - prometheus: - config: - scrape_configs: - - job_name: 'sim-app' - static_configs: - - targets: ['{{ include "sim.fullname" . }}-app:{{ .Values.app.service.port }}'] - - job_name: 'sim-realtime' - static_configs: - - targets: ['{{ include "sim.fullname" . }}-realtime:{{ .Values.realtime.service.port }}'] - processors: batch: timeout: 1s @@ -83,7 +73,7 @@ data: - otlp {{- end }} metrics: - receivers: [otlp, prometheus] + receivers: [otlp] processors: [memory_limiter, batch] exporters: - logging diff --git a/helm/sim/values.schema.json b/helm/sim/values.schema.json index fc26f3c48c8..1a9cdb61f6e 100644 --- a/helm/sim/values.schema.json +++ b/helm/sim/values.schema.json @@ -1190,8 +1190,73 @@ } } } + }, + "affinity": { + "type": "object", + "description": "Pod affinity/anti-affinity rules applied to app and realtime pods" + }, + "branding": { + "type": "object", + "description": "Whitelabel branding configuration (logos, names, colors)" + }, + "certManager": { + "type": "object", + "description": "Chart-managed cert-manager issuers (self-signed bootstrap + CA) for postgresql.tls" + }, + "cronjobs": { + "type": "object", + "description": "Scheduled jobs (schedule execution, polling, reconciliation)" + }, + "extraEnvVars": { + "type": "array", + "description": "Additional environment variables added to app and realtime containers" + }, + "extraVolumeMounts": { + "type": "array", + "description": "Additional volume mounts added to app and realtime containers" + }, + "extraVolumes": { + "type": "array", + "description": "Additional volumes added to app and realtime pods" + }, + "ingressInternal": { + "type": "object", + "description": "Secondary internal ingress configuration" + }, + "migrations": { + "type": "object", + "description": "Database migrations init-container configuration" + }, + "monitoring": { + "type": "object", + "description": "Prometheus Operator ServiceMonitor configuration" + }, + "networkPolicy": { + "type": "object", + "description": "NetworkPolicy toggles and custom ingress/egress rules" + }, + "podAnnotations": { + "type": "object", + "description": "Annotations added to app and realtime pods" + }, + "podDisruptionBudget": { + "type": "object", + "description": "PodDisruptionBudget configuration for app and realtime" + }, + "podLabels": { + "type": "object", + "description": "Labels added to app and realtime pods" + }, + "serviceAccount": { + "type": "object", + "description": "ServiceAccount creation, name, and annotations (e.g. Workload Identity)" + }, + "tolerations": { + "type": "array", + "description": "Tolerations applied to app and realtime pods" } }, + "additionalProperties": false, "allOf": [ { "if": { diff --git a/helm/sim/values.yaml b/helm/sim/values.yaml index 314c4edb38f..9741f794801 100644 --- a/helm/sim/values.yaml +++ b/helm/sim/values.yaml @@ -1729,9 +1729,10 @@ externalSecrets: enabled: false # ESO API version. Default "v1beta1" — supported by every ESO release from - # v0.7+ (mid-2023) through current. Set to "v1" only when targeting ESO - # v0.17+ clusters where the v1 API has graduated. - apiVersion: "v1beta1" + # Current ESO releases serve only external-secrets.io/v1 (the v1beta1 + # compatibility path was removed in 2026). Set to "v1beta1" only for + # clusters still running ESO < 0.17. + apiVersion: "v1" # How often to sync secrets from the external store refreshInterval: "1h" From f01cef2f84c194c3f00543e4dbd57c4f7126d9d3 Mon Sep 17 00:00:00 2001 From: Waleed Latif Date: Fri, 24 Jul 2026 13:09:26 -0700 Subject: [PATCH 2/7] fix(helm): version-agnostic schema-error assertions in the secret-length suite MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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. --- helm/sim/tests/schema-secret-length_test.yaml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/helm/sim/tests/schema-secret-length_test.yaml b/helm/sim/tests/schema-secret-length_test.yaml index b2255d93853..f9b24c9b13c 100644 --- a/helm/sim/tests/schema-secret-length_test.yaml +++ b/helm/sim/tests/schema-secret-length_test.yaml @@ -13,7 +13,7 @@ tests: postgresql.auth.password: xxxxxxxx asserts: - failedTemplate: - errorPattern: "BETTER_AUTH_SECRET.*minLength" + errorPattern: "BETTER_AUTH_SECRET.*(minLength|greater than or equal to 32)" - it: rejects an ENCRYPTION_KEY shorter than 32 characters set: @@ -24,7 +24,7 @@ tests: postgresql.auth.password: xxxxxxxx asserts: - failedTemplate: - errorPattern: "ENCRYPTION_KEY.*minLength" + errorPattern: "ENCRYPTION_KEY.*(minLength|greater than or equal to 32)" - it: rejects a postgresql.auth.password shorter than 8 characters set: @@ -35,7 +35,7 @@ tests: postgresql.auth.password: short asserts: - failedTemplate: - errorPattern: "password.*minLength" + errorPattern: "password.*(minLength|greater than or equal to 8)" - it: accepts an empty BETTER_AUTH_SECRET (deferred to existingSecret/ESO) templates: From 065e389765acccfd6e7c37b6656b4843c07beee0 Mon Sep 17 00:00:00 2001 From: Waleed Latif Date: Fri, 24 Jul 2026 13:19:54 -0700 Subject: [PATCH 3/7] improvement(helm): kind install test + chart version-bump gate in CI MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- .github/workflows/helm.yml | 64 ++++++++++++++++++++++++++++++++++++ helm/sim/ci/kind-values.yaml | 39 ++++++++++++++++++++++ 2 files changed, 103 insertions(+) create mode 100644 helm/sim/ci/kind-values.yaml diff --git a/.github/workflows/helm.yml b/.github/workflows/helm.yml index 813c2b4c5d3..7cd30750cf3 100644 --- a/.github/workflows/helm.yml +++ b/.github/workflows/helm.yml @@ -84,3 +84,67 @@ jobs: --set copilot.server.env.OPENAI_API_KEY_1=ci-dummy-openai-key \ --set externalDatabase.password=ci-dummy-password > /dev/null done + + version-bump: + name: Chart version bumped + if: github.event_name == 'pull_request' + runs-on: ${{ (vars.CI_PROVIDER == '' || vars.CI_PROVIDER == 'blacksmith') && 'blacksmith-2vcpu-ubuntu-2404' || 'ubuntu-latest' }} + timeout-minutes: 5 + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + - name: Require a Chart.yaml version bump when chart content changes + run: | + set -euo pipefail + base="origin/${{ github.base_ref }}" + git fetch origin "${{ github.base_ref }}" --depth=1 + if git diff --name-only "$base"...HEAD | grep -q '^helm/sim/'; then + base_version=$(git show "$base:helm/sim/Chart.yaml" | awk '/^version:/ {print $2}') + head_version=$(awk '/^version:/ {print $2}' helm/sim/Chart.yaml) + echo "base=$base_version head=$head_version" + if [ "$base_version" = "$head_version" ]; then + echo "::error::helm/sim/** changed but Chart.yaml version did not (still $head_version). Bump it per SemVer." + exit 1 + fi + else + echo "No chart changes; skipping." + fi + + install: + name: Install on kind and run helm test + needs: chart + runs-on: ${{ (vars.CI_PROVIDER == '' || vars.CI_PROVIDER == 'blacksmith') && 'blacksmith-4vcpu-ubuntu-2404' || 'ubuntu-latest' }} + timeout-minutes: 25 + steps: + - uses: actions/checkout@v4 + + - name: Set up Helm + uses: azure/setup-helm@v4 + with: + version: v3.16.4 + + - name: Create kind cluster + uses: helm/kind-action@v1 + with: + version: v0.24.0 + + - name: Install chart + run: | + helm install sim helm/sim \ + --namespace sim --create-namespace \ + --values helm/sim/ci/default-values.yaml \ + --values helm/sim/ci/kind-values.yaml \ + --wait --timeout 15m + + - name: Diagnostics on failure + if: failure() + run: | + kubectl -n sim get pods -o wide || true + kubectl -n sim get events --sort-by=.lastTimestamp | tail -40 || true + kubectl -n sim describe pods | tail -100 || true + kubectl -n sim logs deploy/sim-app -c migrations --tail=50 || true + kubectl -n sim logs deploy/sim-app --tail=80 || true + + - name: Run helm test + run: helm test sim --namespace sim --timeout 5m diff --git a/helm/sim/ci/kind-values.yaml b/helm/sim/ci/kind-values.yaml new file mode 100644 index 00000000000..cf3185d63d6 --- /dev/null +++ b/helm/sim/ci/kind-values.yaml @@ -0,0 +1,39 @@ +# CI-only overlay for the kind install test: shrink resource requests so the +# default configuration schedules on a small CI runner. Layered on top of +# ci/default-values.yaml. Dummy sizing — never use in a deployment. +app: + resources: + requests: + cpu: 200m + memory: 512Mi + limits: + cpu: 1000m + memory: 2Gi + +realtime: + resources: + requests: + cpu: 50m + memory: 128Mi + limits: + cpu: 500m + memory: 512Mi + +migrations: + resources: + requests: + cpu: 100m + memory: 256Mi + limits: + cpu: 500m + memory: 1Gi + +postgresql: + resources: + requests: + cpu: 100m + memory: 256Mi + limits: + memory: 1Gi + persistence: + size: 2Gi From f9a9964a52203f5e3b730e0ada14e10e81127242 Mon Sep 17 00:00:00 2001 From: Waleed Latif Date: Fri, 24 Jul 2026 13:23:50 -0700 Subject: [PATCH 4/7] fix(helm): declare naming overrides in the strict schema; SHA-pin CI actions MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 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 --- .github/workflows/helm.yml | 13 +++++++------ helm/sim/tests/smoke_test.yaml | 10 ++++++++++ helm/sim/values.schema.json | 8 ++++++++ 3 files changed, 25 insertions(+), 6 deletions(-) diff --git a/.github/workflows/helm.yml b/.github/workflows/helm.yml index 7cd30750cf3..27974d1f8dc 100644 --- a/.github/workflows/helm.yml +++ b/.github/workflows/helm.yml @@ -25,10 +25,10 @@ jobs: runs-on: ${{ (vars.CI_PROVIDER == '' || vars.CI_PROVIDER == 'blacksmith') && 'blacksmith-2vcpu-ubuntu-2404' || 'ubuntu-latest' }} timeout-minutes: 15 steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4 - name: Set up Helm - uses: azure/setup-helm@v4 + uses: azure/setup-helm@1a275c3b69536ee54be43f2070a358922e12c8d4 # v4 with: version: v3.16.4 @@ -44,6 +44,7 @@ jobs: run: | curl -sSL -o /tmp/kubeconform.tar.gz \ https://github.com/yannh/kubeconform/releases/download/v0.6.7/kubeconform-linux-amd64.tar.gz + echo "95f14e87aa28c09d5941f11bd024c1d02fdc0303ccaa23f61cef67bc92619d73 /tmp/kubeconform.tar.gz" | sha256sum -c - tar -xzf /tmp/kubeconform.tar.gz -C /tmp kubeconform - name: Render and validate manifests (default configuration) @@ -91,7 +92,7 @@ jobs: runs-on: ${{ (vars.CI_PROVIDER == '' || vars.CI_PROVIDER == 'blacksmith') && 'blacksmith-2vcpu-ubuntu-2404' || 'ubuntu-latest' }} timeout-minutes: 5 steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4 with: fetch-depth: 0 - name: Require a Chart.yaml version bump when chart content changes @@ -117,15 +118,15 @@ jobs: runs-on: ${{ (vars.CI_PROVIDER == '' || vars.CI_PROVIDER == 'blacksmith') && 'blacksmith-4vcpu-ubuntu-2404' || 'ubuntu-latest' }} timeout-minutes: 25 steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4 - name: Set up Helm - uses: azure/setup-helm@v4 + uses: azure/setup-helm@1a275c3b69536ee54be43f2070a358922e12c8d4 # v4 with: version: v3.16.4 - name: Create kind cluster - uses: helm/kind-action@v1 + uses: helm/kind-action@ef37e7f390d99f746eb8b610417061a60e82a6cc # v1 with: version: v0.24.0 diff --git a/helm/sim/tests/smoke_test.yaml b/helm/sim/tests/smoke_test.yaml index 4b95fd12f2d..23c035c0acf 100644 --- a/helm/sim/tests/smoke_test.yaml +++ b/helm/sim/tests/smoke_test.yaml @@ -33,3 +33,13 @@ tests: asserts: - isKind: { of: Secret } - equal: { path: metadata.name, value: t-sim-app-secrets } + + - it: accepts the standard Helm naming overrides under the strict schema + set: + nameOverride: acme + fullnameOverride: acme-sim + asserts: + - matchRegex: + path: metadata.name + pattern: "^acme-sim" + template: deployment-app.yaml diff --git a/helm/sim/values.schema.json b/helm/sim/values.schema.json index 1a9cdb61f6e..9088f9bd6f1 100644 --- a/helm/sim/values.schema.json +++ b/helm/sim/values.schema.json @@ -1191,6 +1191,14 @@ } } }, + "nameOverride": { + "type": "string", + "description": "Override the chart name used in resource names" + }, + "fullnameOverride": { + "type": "string", + "description": "Override the fully qualified release name used in resource names" + }, "affinity": { "type": "object", "description": "Pod affinity/anti-affinity rules applied to app and realtime pods" From e4b34ca6c6119925d6c700e49740b3db4f5c27a6 Mon Sep 17 00:00:00 2001 From: Waleed Latif Date: Fri, 24 Jul 2026 13:35:44 -0700 Subject: [PATCH 5/7] 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 --- .github/workflows/helm.yml | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/.github/workflows/helm.yml b/.github/workflows/helm.yml index 27974d1f8dc..11478e7b152 100644 --- a/.github/workflows/helm.yml +++ b/.github/workflows/helm.yml @@ -37,8 +37,9 @@ jobs: - name: Helm unit tests run: | - helm plugin install https://github.com/helm-unittest/helm-unittest --version v0.8.2 - helm unittest helm/sim + # Official helm-unittest image, pinned by immutable digest (tag 3.17.3-0.8.2) + docker run --rm -v "$PWD/helm/sim:/apps" \ + helmunittest/helm-unittest@sha256:b653db7d5665bc6cec677b15c5eaa1c0377c0de8ac4eb1df58b924478baa21e1 . - name: Install kubeconform run: | @@ -99,9 +100,11 @@ jobs: run: | set -euo pipefail base="origin/${{ github.base_ref }}" - git fetch origin "${{ github.base_ref }}" --depth=1 - if git diff --name-only "$base"...HEAD | grep -q '^helm/sim/'; then - base_version=$(git show "$base:helm/sim/Chart.yaml" | awk '/^version:/ {print $2}') + git fetch origin "${{ github.base_ref }}" + merge_base=$(git merge-base "$base" HEAD) + changed=$(git diff --name-only "$merge_base" HEAD) + if echo "$changed" | grep -q '^helm/sim/'; then + base_version=$(git show "$merge_base:helm/sim/Chart.yaml" | awk '/^version:/ {print $2}') head_version=$(awk '/^version:/ {print $2}' helm/sim/Chart.yaml) echo "base=$base_version head=$head_version" if [ "$base_version" = "$head_version" ]; then From 3b273c2594081dd904c7ec48d181d0e42ce2c7bb Mon Sep 17 00:00:00 2001 From: Waleed Latif Date: Fri, 24 Jul 2026 13:47:50 -0700 Subject: [PATCH 6/7] 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. --- .github/workflows/helm.yml | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/.github/workflows/helm.yml b/.github/workflows/helm.yml index 11478e7b152..11944217283 100644 --- a/.github/workflows/helm.yml +++ b/.github/workflows/helm.yml @@ -37,8 +37,11 @@ jobs: - name: Helm unit tests run: | - # Official helm-unittest image, pinned by immutable digest (tag 3.17.3-0.8.2) - docker run --rm -v "$PWD/helm/sim:/apps" \ + # Official helm-unittest image, pinned by immutable digest (tag 3.17.3-0.8.2). + # Run as the runner's UID so the container can write into the bind + # mount (it creates tests/__snapshot__), with a writable HOME for helm. + docker run --rm --user "$(id -u):$(id -g)" -e HOME=/tmp \ + -v "$PWD/helm/sim:/apps" \ helmunittest/helm-unittest@sha256:b653db7d5665bc6cec677b15c5eaa1c0377c0de8ac4eb1df58b924478baa21e1 . - name: Install kubeconform From 1cfaf0f50a1a9906ce49359d508b046838717d72 Mon Sep 17 00:00:00 2001 From: Waleed Latif Date: Fri, 24 Jul 2026 14:09:26 -0700 Subject: [PATCH 7/7] fix(helm): appVersion points at a real GHCR tag (v0.7.44) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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. --- helm/sim/Chart.yaml | 2 +- helm/sim/README.md | 1 + helm/sim/values.yaml | 2 +- 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/helm/sim/Chart.yaml b/helm/sim/Chart.yaml index 9694998376d..9550ef6d9c1 100644 --- a/helm/sim/Chart.yaml +++ b/helm/sim/Chart.yaml @@ -3,7 +3,7 @@ name: sim description: A Helm chart for Sim - the open-source AI workspace where teams build, deploy, and manage AI agents type: application version: 1.2.0 -appVersion: "0.6.73" +appVersion: "v0.7.44" kubeVersion: ">=1.25.0-0" home: https://sim.ai icon: https://raw.githubusercontent.com/simstudioai/sim/main/apps/sim/public/logo/primary/primary.svg diff --git a/helm/sim/README.md b/helm/sim/README.md index a8db664369a..e48eaeb4220 100644 --- a/helm/sim/README.md +++ b/helm/sim/README.md @@ -469,6 +469,7 @@ kubectl --namespace sim logs deploy/sim-app -c migrations ## Upgrading to 1.2.0 +* `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. * `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. * `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. * 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. diff --git a/helm/sim/values.yaml b/helm/sim/values.yaml index 9741f794801..5c62ee6def7 100644 --- a/helm/sim/values.yaml +++ b/helm/sim/values.yaml @@ -20,7 +20,7 @@ app: # Image configuration image: repository: simstudioai/simstudio - # tag defaults to Chart.AppVersion. Override with a release tag (e.g. "0.6.73") or pin via image.digest. + # tag defaults to Chart.AppVersion. Override with a release tag (e.g. "v0.7.44") or pin via image.digest. tag: "" # Optional image digest pin: "sha256:..." — when set, overrides tag. digest: ""