Skip to content

Commit acfe18a

Browse files
Add integrations-hub Helm chart for Kubernetes deployment (#614)
* Add integrations-hub Helm chart for Kubernetes deployment - Add integrations-hub chart with deployment, service, and service account templates - Configure environment variables for Next.js, PostgreSQL, Auth.js, and OpenHands auth - Add health/ready probe endpoints at /api/health and /api/ready - Support both GCP Cloud SQL and in-cluster PostgreSQL configurations - Add integrations-hub as optional dependency in openhands chart - Add ingress template for integrations-hub in openhands chart - Update GitHub workflows to include integrations-hub chart publishing Co-authored-by: openhands <openhands@all-hands.dev> * Update integrations-hub Helm chart for Python/FastAPI backend Changes for openhands-auth-python branch: - Change default port from 3000 to 8000 - Update health check paths from /api/health to /health - Replace Node.js/NextAuth env vars with INTEGRATIONS_HUB_* prefix - Add OpenHands cookie auth configuration - Add CORS and DB pool settings - Remove auth.js/NextAuth configuration Co-authored-by: openhands <openhands@all-hands.dev> * Update chart version. * Add migration initContainer to integrations-hub deployment Run alembic migrations before starting the main container to ensure database tables exist. This follows the same pattern as the automation service. * feat: Set default admin emails for integrations-hub Default INTEGRATIONS_HUB_APP_ADMIN_EMAILS to: chuck@openhands.dev,chuck@all-hands.dev,graham@openhands.dev,graham@all-hands.dev Co-authored-by: openhands <openhands@all-hands.dev> * Align integrations-hub chart env vars with the development branch backend The chart previously exported a Next.js-era `INTEGRATIONS_HUB_*` env var prefix plus separate INTEGRATIONS_HUB_DB_HOST/PORT/USER/PASS/NAME values, none of which the FastAPI backend on the development branch actually reads. Rewrite `_env.yaml` to match what backend/app/config.py and oauth_flow.py look for so the pod can actually start and reach Postgres: - Use the canonical `INTHUB_*` prefix (with legacy unprefixed fallbacks the backend already accepts) for: POSTGRES_URL, OPENHANDS_BASE_URL, OPENHANDS_AUTH_COOKIE_NAME, INTERNAL_AUTH_SECRET, CRON_SECRET, CREDENTIAL_ENCRYPTION_KEY, DISABLE_AUTH. - Build a single `INTHUB_POSTGRES_URL` from the configured DB host/port/user/name plus the password secret using Kubernetes $(VAR_NAME) expansion. `database.url` is a new opt-in escape hatch for callers that want to supply a pre-formed URL. - Use the unprefixed `APP_ADMIN_EMAILS` (per backend/README.md, the prefixed form is JSON-decoded by the SDK env parser and would require list syntax). - Add optional `AUTH_REDIRECT_PROXY_URL` / `AUTH_URL` for the OAuth preview-deployment proxy that backend/app/oauth_flow.py honours. - Drop unused env vars (HOST, SERVER_PORT, LOG_LEVEL, BASE_URL, GCP_*, CORS_ORIGINS) the FastAPI app never reads. Also fix the health probes -- the FastAPI app exposes `/api/health` and has no `/ready` endpoint, so the previous `/health` and `/ready` probes would fail. Reuse `/api/health` for startup/liveness/readiness. Finally, correct the umbrella chart's stale `service.port: 3000` (Next.js) to `8000` (FastAPI `EXPOSE 8000`), mirror the new optional fields, and rename the in-cluster wait-for-postgres init container's PGPASSWORD reference to $INTHUB_DB_PASS so it lines up with the renamed env var. Verified with helm template + helm lint against several value matrices (defaults, database.url override, OAuth proxy, internal auth secret, disableAuth=true, datadog enabled, in-cluster postgresql). Co-authored-by: openhands <openhands@all-hands.dev> * Update chart version * Pin umbrella's integrations-hub dep to local sibling version 0.1.0 The Replicated lint job (.github/workflows/lint-replicated-release.yml, Makefile target `build/openhands-0.7.15.tgz`) rewrites every umbrella dependency that has a sibling directory under `charts/` to use a `file://../<dep>` repository before running `helm package -u`. For that rewrite to resolve, the umbrella's `version:` constraint must match the sibling's local `Chart.yaml` version. With charts/integrations-hub/Chart.yaml version: 0.1.0 charts/openhands/Chart.yaml integrations-hub: 0.1.0-alpha.614 helm fails with "can't get a valid version for repositories integrations-hub" because the local sibling has no pre-release version matching the alpha constraint -- which is exactly the failure on run 25837636609 (#614). Point the umbrella's integrations-hub dep at 0.1.0 to match the local sibling, mirroring how runtime-api/plugin-directory/crd-check are wired today. The preview-helm-charts workflow already calls yq -i '(.dependencies[] | select(.name == "integrations-hub")).version = "${INTEGRATIONS_HUB_PREVIEW}"' charts/openhands/Chart.yaml before publishing, so the OCI-published umbrella will still carry the alpha pin (0.1.0-alpha.<PR_NUMBER>) when consumers pull it. Verified by running the Makefile recipe locally (yq dep rewrite + helm package -u): "Successfully packaged chart and saved it to build/openhands-0.7.15.tgz". Co-authored-by: openhands <openhands@all-hands.dev> * Mount integrations-hub under a configurable sub-path (default /integrations-hub) Background ---------- The integrations-hub image is a single FastAPI container that serves both its Next.js SPA bundle and the /api/* routes. On the `development` branch we now bake the SPA with `NEXT_PUBLIC_BASE_PATH=/integrations-hub` and ship `INTHUB_ROOT_PATH=/integrations-hub` as the runtime default, so the image is ready to live behind `<oh_host>/integrations-hub/...` without any ingress-level rewrite. This change exposes that path as a single chart value -- `rootPath` -- and threads it through every place that needed to agree on it. Changes ------- charts/integrations-hub/values.yaml * New top-level `rootPath: "/integrations-hub"` field, with a long comment explaining what it controls (subchart probes, INTHUB_ROOT_PATH env var, parent ingress, image NEXT_PUBLIC_BASE_PATH). * Setting it to "" deliberately clears the image default so the hub serves from the cluster root. charts/integrations-hub/templates/_env.yaml * Always render `INTHUB_ROOT_PATH` from `.Values.rootPath` (including the empty-string case, so an override actually wins over the image default). * Documented the variable in the file header next to the other INTHUB_* env vars. charts/integrations-hub/templates/deployment.yaml * Probe paths are now prefixed by `.Values.rootPath` so they hit `/integrations-hub/api/health` by default and `/api/health` when rootPath is empty -- matching whatever path the backend just mounted. Defining `$healthPath` once keeps the three probe declarations from drifting apart. charts/openhands/templates/ingress-integrations-hub.yaml * Drop the stale `/api/integrations-hub` rule -- the backend never serves anything there; its API routes live under `/integrations-hub/api/...` thanks to INTHUB_ROOT_PATH. Keeping the second rule routed to the same service was harmless in practice but confusing. * The remaining rule's path is sourced from `(index .Values "integrations-hub" "rootPath")` (defaulting to `/integrations-hub`) so the ingress, the container env, and the SPA bundle stay in lockstep. * Added a block comment up top explaining why we deliberately do NOT set an ingress-level `rewrite-target` annotation. charts/openhands/values.yaml * Mirrored the new `rootPath` field into the parent values so cloud deployments can override it just like other integrations-hub knobs, with a comment pointing at all three places it has to agree. Validation ---------- * `helm lint charts/integrations-hub` -> 0 failures (only the pre-existing "icon is recommended" note). * `helm template intg charts/integrations-hub` with the default rootPath: `INTHUB_ROOT_PATH=/integrations-hub` and all three probe paths render as `/integrations-hub/api/health`. * Same template with `--set rootPath=""`: `INTHUB_ROOT_PATH=""` and probes fall back to plain `/api/health`, confirming the empty-override path works end-to-end. * `helm template oh charts/openhands -s templates/ingress-integrations-hub.yaml` (run against a locally-built copy of the subchart, since the OCI 0.1.0 release isn't published yet): renders a single Ingress with `path: "/integrations-hub"` by default and `path: "/foo"` when overridden via `--set integrations-hub.rootPath=/foo`. Co-authored-by: openhands <openhands@all-hands.dev> * Update chart version * Avoid exposing integrations DB password in args Co-authored-by: openhands <openhands@all-hands.dev> --------- Co-authored-by: openhands <openhands@all-hands.dev>
1 parent cf8ab16 commit acfe18a

12 files changed

Lines changed: 833 additions & 4 deletions

File tree

.github/workflows/preview-helm-charts.yml

Lines changed: 30 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ jobs:
1919
image-loader: ${{ steps.changes.outputs.image-loader }}
2020
automation: ${{ steps.changes.outputs.automation }}
2121
plugin-directory: ${{ steps.changes.outputs.plugin-directory }}
22+
integrations-hub: ${{ steps.changes.outputs.integrations-hub }}
2223
openhands: ${{ steps.changes.outputs.openhands }}
2324
openhands-secrets: ${{ steps.changes.outputs.openhands-secrets }}
2425
steps:
@@ -39,7 +40,7 @@ jobs:
3940
echo "$CHANGED_FILES"
4041
4142
# Check each chart for changes
42-
for chart in crd-check runtime-api image-loader automation plugin-directory openhands openhands-secrets; do
43+
for chart in crd-check runtime-api image-loader automation plugin-directory integrations-hub openhands openhands-secrets; do
4344
if echo "$CHANGED_FILES" | grep -q "^charts/${chart}/"; then
4445
echo "${chart}=true" >> $GITHUB_OUTPUT
4546
echo "Changes detected in charts/${chart}"
@@ -62,13 +63,15 @@ jobs:
6263
matrix:
6364
chart:
6465
# Order matters! Charts that are dependencies must be published first.
65-
# crd-check, automation, plugin-directory, and runtime-api are dependencies of openhands.
66+
# crd-check, automation, plugin-directory, integrations-hub, and runtime-api are dependencies of openhands.
6667
- name: crd-check
6768
path: charts/crd-check
6869
- name: automation
6970
path: charts/automation
7071
- name: plugin-directory
7172
path: charts/plugin-directory
73+
- name: integrations-hub
74+
path: charts/integrations-hub
7275
- name: runtime-api
7376
path: charts/runtime-api
7477
- name: image-loader
@@ -88,13 +91,15 @@ jobs:
8891
HAS_CHANGES_IMAGE_LOADER: ${{ needs.detect-changes.outputs.image-loader }}
8992
HAS_CHANGES_AUTOMATION: ${{ needs.detect-changes.outputs.automation }}
9093
HAS_CHANGES_PLUGIN_DIRECTORY: ${{ needs.detect-changes.outputs.plugin-directory }}
94+
HAS_CHANGES_INTEGRATIONS_HUB: ${{ needs.detect-changes.outputs.integrations-hub }}
9195
HAS_CHANGES_OPENHANDS: ${{ needs.detect-changes.outputs.openhands }}
9296
HAS_CHANGES_OPENHANDS_SECRETS: ${{ needs.detect-changes.outputs.openhands-secrets }}
9397
IS_PUBLISHABLE_CRD_CHECK: ${{ needs.validate-chart-versions.outputs.crd-check-publishable }}
9498
IS_PUBLISHABLE_RUNTIME_API: ${{ needs.validate-chart-versions.outputs.runtime-api-publishable }}
9599
IS_PUBLISHABLE_IMAGE_LOADER: ${{ needs.validate-chart-versions.outputs.image-loader-publishable }}
96100
IS_PUBLISHABLE_AUTOMATION: ${{ needs.validate-chart-versions.outputs.automation-publishable }}
97101
IS_PUBLISHABLE_PLUGIN_DIRECTORY: ${{ needs.validate-chart-versions.outputs.plugin-directory-publishable }}
102+
IS_PUBLISHABLE_INTEGRATIONS_HUB: ${{ needs.validate-chart-versions.outputs.integrations-hub-publishable }}
98103
IS_PUBLISHABLE_OPENHANDS: ${{ needs.validate-chart-versions.outputs.openhands-publishable }}
99104
IS_PUBLISHABLE_OPENHANDS_SECRETS: ${{ needs.validate-chart-versions.outputs.openhands-secrets-publishable }}
100105
run: |
@@ -120,6 +125,10 @@ jobs:
120125
HAS_CHANGES="$HAS_CHANGES_PLUGIN_DIRECTORY"
121126
IS_PUBLISHABLE="$IS_PUBLISHABLE_PLUGIN_DIRECTORY"
122127
;;
128+
integrations-hub)
129+
HAS_CHANGES="$HAS_CHANGES_INTEGRATIONS_HUB"
130+
IS_PUBLISHABLE="$IS_PUBLISHABLE_INTEGRATIONS_HUB"
131+
;;
123132
openhands)
124133
HAS_CHANGES="$HAS_CHANGES_OPENHANDS"
125134
IS_PUBLISHABLE="$IS_PUBLISHABLE_OPENHANDS"
@@ -203,6 +212,14 @@ jobs:
203212
204213
yq -i "(.dependencies[] | select(.name == \"plugin-directory\")).version = \"${PLUGIN_DIRECTORY_PREVIEW}\"" charts/openhands/Chart.yaml
205214
215+
- name: Update integrations-hub dependency version
216+
if: steps.check.outputs.should_publish == 'true' && matrix.chart.name == 'openhands' && needs.detect-changes.outputs.integrations-hub == 'true'
217+
run: |
218+
INTEGRATIONS_HUB_VERSION=$(yq '.version' charts/integrations-hub/Chart.yaml)
219+
INTEGRATIONS_HUB_PREVIEW="${INTEGRATIONS_HUB_VERSION}-alpha.${{ github.event.pull_request.number }}"
220+
221+
yq -i "(.dependencies[] | select(.name == \"integrations-hub\")).version = \"${INTEGRATIONS_HUB_PREVIEW}\"" charts/openhands/Chart.yaml
222+
206223
- name: Test ${{ matrix.chart.name }} chart with default values
207224
if: steps.check.outputs.should_publish == 'true'
208225
run: |
@@ -279,6 +296,8 @@ jobs:
279296
path: charts/automation
280297
- name: plugin-directory
281298
path: charts/plugin-directory
299+
- name: integrations-hub
300+
path: charts/integrations-hub
282301
- name: openhands
283302
path: charts/openhands
284303
- name: openhands-secrets
@@ -331,6 +350,15 @@ jobs:
331350
echo "Updating openhands plugin-directory dependency to ${PLUGIN_DIRECTORY_PREVIEW}"
332351
yq -i "(.dependencies[] | select(.name == \"plugin-directory\")).version = \"${PLUGIN_DIRECTORY_PREVIEW}\"" charts/openhands/Chart.yaml
333352
353+
- name: Update integrations-hub dependency version for openhands
354+
if: matrix.chart.name == 'openhands' && needs.detect-changes.outputs.integrations-hub == 'true'
355+
run: |
356+
INTEGRATIONS_HUB_VERSION=$(yq '.version' charts/integrations-hub/Chart.yaml)
357+
INTEGRATIONS_HUB_PREVIEW="${INTEGRATIONS_HUB_VERSION}-alpha.${{ github.event.pull_request.number }}"
358+
359+
echo "Updating openhands integrations-hub dependency to ${INTEGRATIONS_HUB_PREVIEW}"
360+
yq -i "(.dependencies[] | select(.name == \"integrations-hub\")).version = \"${INTEGRATIONS_HUB_PREVIEW}\"" charts/openhands/Chart.yaml
361+
334362
- name: Lint ${{ matrix.chart.name }} chart
335363
run: |
336364
echo "Testing ${{ matrix.chart.name }} chart"

.github/workflows/publish-helm-charts.yml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ jobs:
2828
matrix:
2929
chart:
3030
# Order matters: openhands depends on crd-check, runtime-api, automation,
31-
# and plugin-directory, so those must publish first.
31+
# plugin-directory, and integrations-hub, so those must publish first.
3232
- name: crd-check
3333
path: charts/crd-check
3434
- name: runtime-api
@@ -39,6 +39,8 @@ jobs:
3939
path: charts/automation
4040
- name: plugin-directory
4141
path: charts/plugin-directory
42+
- name: integrations-hub
43+
path: charts/integrations-hub
4244
- name: openhands
4345
path: charts/openhands
4446
- name: openhands-secrets

.github/workflows/validate-chart-versions.yml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,9 @@ on:
2929
plugin-directory-publishable:
3030
description: 'Whether plugin-directory chart is publishable (no changes or version bumped)'
3131
value: ${{ jobs.validate-chart-versions.outputs.plugin-directory-publishable }}
32+
integrations-hub-publishable:
33+
description: 'Whether integrations-hub chart is publishable (no changes or version bumped)'
34+
value: ${{ jobs.validate-chart-versions.outputs.integrations-hub-publishable }}
3235
openhands-publishable:
3336
description: 'Whether openhands chart is publishable (no changes or version bumped)'
3437
value: ${{ jobs.validate-chart-versions.outputs.openhands-publishable }}
@@ -47,6 +50,7 @@ jobs:
4750
image-loader-publishable: ${{ steps.validate.outputs.image-loader-publishable }}
4851
automation-publishable: ${{ steps.validate.outputs.automation-publishable }}
4952
plugin-directory-publishable: ${{ steps.validate.outputs.plugin-directory-publishable }}
53+
integrations-hub-publishable: ${{ steps.validate.outputs.integrations-hub-publishable }}
5054
openhands-publishable: ${{ steps.validate.outputs.openhands-publishable }}
5155
openhands-secrets-publishable: ${{ steps.validate.outputs.openhands-secrets-publishable }}
5256

charts/integrations-hub/Chart.yaml

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
apiVersion: v2
2+
name: integrations-hub
3+
description: OpenHands Integrations Hub - Agent context layer with managed connectors and MCP integrations
4+
type: application
5+
version: 0.1.0
6+
appVersion: "0.1.0"
7+
dependencies:
8+
- name: postgresql
9+
version: 15.x.x
10+
repository: https://charts.bitnami.com/bitnami
11+
condition: postgresql.enabled
Lines changed: 192 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,192 @@
1+
{{- /*
2+
Environment variables consumed by the Integrations Hub backend
3+
(Python/FastAPI on the `development` branch of OpenHands/integrations-hub).
4+
5+
The backend reads its config in backend/app/config.py using the prefix
6+
`INTHUB_*`, with legacy unprefixed fallbacks for backwards compatibility:
7+
8+
INTHUB_POSTGRES_URL fallback: POSTGRES_URL, DATABASE_URL
9+
INTHUB_DISABLE_AUTH (bool, defaults to false)
10+
INTHUB_OPENHANDS_BASE_URL fallback: OPENHANDS_BASE_URL
11+
INTHUB_OPENHANDS_AUTH_COOKIE_NAME fallback: OPENHANDS_AUTH_COOKIE_NAME
12+
INTHUB_INTERNAL_AUTH_SECRET fallback: INTERNAL_AUTH_SECRET, AUTH_SECRET, NEXTAUTH_SECRET
13+
INTHUB_CRON_SECRET fallback: CRON_SECRET
14+
APP_ADMIN_EMAILS (use the unprefixed name; the prefixed
15+
form is JSON-decoded by the SDK env
16+
parser and would require list syntax)
17+
INTHUB_CREDENTIAL_ENCRYPTION_KEY fallback: CREDENTIAL_ENCRYPTION_KEY
18+
INTHUB_STATIC_DIR set in the Dockerfile to /app/out
19+
INTHUB_ROOT_PATH sub-path mount; defaulted by the
20+
Dockerfile to /integrations-hub and
21+
overridable via .Values.rootPath
22+
23+
In addition, backend/app/oauth_flow.py reads:
24+
AUTH_REDIRECT_PROXY_URL stable origin for OAuth callback proxy
25+
AUTH_URL / NEXTAUTH_URL preview-deployment callback target
26+
*/}}
27+
{{- define "integrations-hub.env.defaults" }}
28+
# Database connection -- backend reads INTHUB_POSTGRES_URL first; we build
29+
# it from the configured host/port/user/db plus the password secret using
30+
# Kubernetes $(VAR_NAME) expansion. If .Values.database.url is set it wins.
31+
{{- if .Values.database.url }}
32+
- name: INTHUB_POSTGRES_URL
33+
value: {{ .Values.database.url | quote }}
34+
{{- else }}
35+
- name: INTHUB_DB_HOST
36+
value: {{ .Values.database.host | quote }}
37+
- name: INTHUB_DB_PORT
38+
value: {{ .Values.database.port | quote }}
39+
- name: INTHUB_DB_USER
40+
value: {{ .Values.database.user | quote }}
41+
- name: INTHUB_DB_NAME
42+
value: {{ .Values.database.name | quote }}
43+
- name: INTHUB_DB_PASS
44+
valueFrom:
45+
secretKeyRef:
46+
name: {{ .Values.database.secretName }}
47+
key: {{ .Values.database.secretKey }}
48+
- name: INTHUB_POSTGRES_URL
49+
value: "postgresql://$(INTHUB_DB_USER):$(INTHUB_DB_PASS)@$(INTHUB_DB_HOST):$(INTHUB_DB_PORT)/$(INTHUB_DB_NAME)"
50+
{{- end }}
51+
52+
# OpenHands identity provider used to validate session cookies / API keys.
53+
{{- $openhandsBaseUrl := .Values.openhands.baseUrl }}
54+
{{- if and (not $openhandsBaseUrl) .Values.global }}
55+
{{- if .Values.global.ingress }}
56+
{{- if .Values.global.ingress.host }}
57+
{{- $host := .Values.global.ingress.host }}
58+
{{- if and .Values.global.ingress.prefixWithBranch .Values.global.branchSanitized }}
59+
{{- $host = printf "%s.%s" .Values.global.branchSanitized .Values.global.ingress.host }}
60+
{{- end }}
61+
{{- $openhandsBaseUrl = printf "https://%s" $host }}
62+
{{- end }}
63+
{{- end }}
64+
{{- end }}
65+
{{- if $openhandsBaseUrl }}
66+
- name: INTHUB_OPENHANDS_BASE_URL
67+
value: {{ $openhandsBaseUrl | quote }}
68+
{{- end }}
69+
{{- if .Values.openhands.authCookieName }}
70+
- name: INTHUB_OPENHANDS_AUTH_COOKIE_NAME
71+
value: {{ .Values.openhands.authCookieName | quote }}
72+
{{- end }}
73+
74+
# Local-dev auth bypass. Defaults to false; the FastAPI app will reject
75+
# non-loopback requests when this is true, so it should never be enabled
76+
# in staging/production.
77+
- name: INTHUB_DISABLE_AUTH
78+
value: {{ .Values.disableAuth | default false | quote }}
79+
80+
# Sub-path mount for the FastAPI app. The Dockerfile already defaults
81+
# INTHUB_ROOT_PATH to /integrations-hub so the image works behind the
82+
# default ingress out of the box; this block re-emits the value so chart
83+
# overrides flow through. Rendered unconditionally (including when
84+
# .Values.rootPath is "") so that an empty override actually clears the
85+
# image default instead of silently inheriting it.
86+
- name: INTHUB_ROOT_PATH
87+
value: {{ .Values.rootPath | default "" | quote }}
88+
89+
# Credential encryption key (required: signs/encrypts stored OAuth tokens
90+
# and other provider secrets at rest).
91+
- name: INTHUB_CREDENTIAL_ENCRYPTION_KEY
92+
valueFrom:
93+
secretKeyRef:
94+
name: {{ .Values.credentialEncryption.secretName }}
95+
key: {{ .Values.credentialEncryption.secretKey }}
96+
97+
# Internal service-to-service secret. Optional in SPA-only K8s deployments
98+
# (the backend validates the OpenHands session cookie directly when no
99+
# Next.js proxy is in front of it) but required if any internal caller
100+
# attaches the X-Integrations-Hub-Internal-Secret header.
101+
{{- if and .Values.auth .Values.auth.internalSecretName }}
102+
- name: INTHUB_INTERNAL_AUTH_SECRET
103+
valueFrom:
104+
secretKeyRef:
105+
name: {{ .Values.auth.internalSecretName }}
106+
key: {{ .Values.auth.internalSecretKey | default "internal-auth-secret" }}
107+
{{- end }}
108+
109+
# Admin allowlist for /api/admin/* routes. Use the unprefixed name --
110+
# INTHUB_APP_ADMIN_EMAILS goes through the SDK env parser which expects
111+
# JSON list syntax.
112+
{{- if .Values.admin.emails }}
113+
- name: APP_ADMIN_EMAILS
114+
value: {{ .Values.admin.emails | quote }}
115+
{{- end }}
116+
117+
# Cron secret -- required by /api/cron/expire-grants.
118+
- name: INTHUB_CRON_SECRET
119+
valueFrom:
120+
secretKeyRef:
121+
name: {{ .Values.cron.secretName }}
122+
key: {{ .Values.cron.secretKey }}
123+
124+
# OAuth preview-deployment proxy (optional). When set, service OAuth
125+
# callbacks register/exchange against the stable origin and then forward
126+
# back to the preview deployment captured in OAuth state.
127+
{{- if .Values.openhands.redirectProxyUrl }}
128+
- name: AUTH_REDIRECT_PROXY_URL
129+
value: {{ .Values.openhands.redirectProxyUrl | quote }}
130+
{{- end }}
131+
{{- if .Values.openhands.authUrl }}
132+
- name: AUTH_URL
133+
value: {{ .Values.openhands.authUrl | quote }}
134+
{{- end }}
135+
136+
{{- if .Values.datadog.enabled }}
137+
# Datadog APM
138+
- name: DD_AGENT_HOST
139+
value: "datadog-agent.all-hands-system.svc.cluster.local"
140+
- name: DD_TRACE_AGENT_PORT
141+
value: "8126"
142+
- name: DD_SERVICE
143+
value: {{ .Values.datadog.serviceName | quote }}
144+
- name: DD_ENV
145+
value: {{ .Values.datadog.env | quote }}
146+
- name: DD_TRACE_ENABLED
147+
value: "true"
148+
{{- end }}
149+
{{- end }}
150+
151+
{{/*
152+
integrations-hub.env — Deduplicated environment variable list.
153+
154+
This wrapper renders the default env vars from "integrations-hub.env.defaults",
155+
then removes any entries whose name conflicts with a key in .Values.env,
156+
and finally appends the .Values.env overrides. The result is a clean list
157+
with no duplicate names, which prevents:
158+
- Helm warnings about duplicate env vars
159+
- Strategic Merge Patch conflicts during helm upgrade
160+
("The order in patch list doesn't match $setElementOrder list")
161+
162+
How it works:
163+
1. Render "integrations-hub.env.defaults" via include (evaluates all conditionals)
164+
2. Parse the rendered YAML list into Go objects with fromYamlArray
165+
3. Filter out any default entries whose name appears in .Values.env
166+
4. Append .Values.env entries (user overrides always win)
167+
5. Re-render the deduplicated list with toYaml
168+
*/}}
169+
{{- define "integrations-hub.env" }}
170+
{{- $defaults := include "integrations-hub.env.defaults" . | fromYamlArray }}
171+
{{- /* Build a lookup dict of override keys for O(1) membership checks */}}
172+
{{- $overrideKeys := dict }}
173+
{{- if .Values.env }}
174+
{{- range $key, $_ := .Values.env }}
175+
{{- $_ := set $overrideKeys $key true }}
176+
{{- end }}
177+
{{- end }}
178+
{{- /* Keep only default entries that are NOT overridden by .Values.env */}}
179+
{{- $filtered := list }}
180+
{{- range $entry := $defaults }}
181+
{{- if not (hasKey $overrideKeys (get $entry "name")) }}
182+
{{- $filtered = append $filtered $entry }}
183+
{{- end }}
184+
{{- end }}
185+
{{- /* Append user overrides from .Values.env (these take precedence) */}}
186+
{{- if .Values.env }}
187+
{{- range $key, $value := .Values.env }}
188+
{{- $filtered = append $filtered (dict "name" $key "value" ($value | toString)) }}
189+
{{- end }}
190+
{{- end }}
191+
{{- $filtered | toYaml }}
192+
{{- end }}

0 commit comments

Comments
 (0)