Skip to content
Merged
Show file tree
Hide file tree
Changes from 13 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
32 changes: 30 additions & 2 deletions .github/workflows/preview-helm-charts.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ jobs:
image-loader: ${{ steps.changes.outputs.image-loader }}
automation: ${{ steps.changes.outputs.automation }}
plugin-directory: ${{ steps.changes.outputs.plugin-directory }}
integrations-hub: ${{ steps.changes.outputs.integrations-hub }}
openhands: ${{ steps.changes.outputs.openhands }}
openhands-secrets: ${{ steps.changes.outputs.openhands-secrets }}
steps:
Expand All @@ -39,7 +40,7 @@ jobs:
echo "$CHANGED_FILES"

# Check each chart for changes
for chart in crd-check runtime-api image-loader automation plugin-directory openhands openhands-secrets; do
for chart in crd-check runtime-api image-loader automation plugin-directory integrations-hub openhands openhands-secrets; do
if echo "$CHANGED_FILES" | grep -q "^charts/${chart}/"; then
echo "${chart}=true" >> $GITHUB_OUTPUT
echo "Changes detected in charts/${chart}"
Expand All @@ -62,13 +63,15 @@ jobs:
matrix:
chart:
# Order matters! Charts that are dependencies must be published first.
# crd-check, automation, plugin-directory, and runtime-api are dependencies of openhands.
# crd-check, automation, plugin-directory, integrations-hub, and runtime-api are dependencies of openhands.
- name: crd-check
path: charts/crd-check
- name: automation
path: charts/automation
- name: plugin-directory
path: charts/plugin-directory
- name: integrations-hub
path: charts/integrations-hub
- name: runtime-api
path: charts/runtime-api
- name: image-loader
Expand All @@ -88,13 +91,15 @@ jobs:
HAS_CHANGES_IMAGE_LOADER: ${{ needs.detect-changes.outputs.image-loader }}
HAS_CHANGES_AUTOMATION: ${{ needs.detect-changes.outputs.automation }}
HAS_CHANGES_PLUGIN_DIRECTORY: ${{ needs.detect-changes.outputs.plugin-directory }}
HAS_CHANGES_INTEGRATIONS_HUB: ${{ needs.detect-changes.outputs.integrations-hub }}
HAS_CHANGES_OPENHANDS: ${{ needs.detect-changes.outputs.openhands }}
HAS_CHANGES_OPENHANDS_SECRETS: ${{ needs.detect-changes.outputs.openhands-secrets }}
IS_PUBLISHABLE_CRD_CHECK: ${{ needs.validate-chart-versions.outputs.crd-check-publishable }}
IS_PUBLISHABLE_RUNTIME_API: ${{ needs.validate-chart-versions.outputs.runtime-api-publishable }}
IS_PUBLISHABLE_IMAGE_LOADER: ${{ needs.validate-chart-versions.outputs.image-loader-publishable }}
IS_PUBLISHABLE_AUTOMATION: ${{ needs.validate-chart-versions.outputs.automation-publishable }}
IS_PUBLISHABLE_PLUGIN_DIRECTORY: ${{ needs.validate-chart-versions.outputs.plugin-directory-publishable }}
IS_PUBLISHABLE_INTEGRATIONS_HUB: ${{ needs.validate-chart-versions.outputs.integrations-hub-publishable }}
IS_PUBLISHABLE_OPENHANDS: ${{ needs.validate-chart-versions.outputs.openhands-publishable }}
IS_PUBLISHABLE_OPENHANDS_SECRETS: ${{ needs.validate-chart-versions.outputs.openhands-secrets-publishable }}
run: |
Expand All @@ -120,6 +125,10 @@ jobs:
HAS_CHANGES="$HAS_CHANGES_PLUGIN_DIRECTORY"
IS_PUBLISHABLE="$IS_PUBLISHABLE_PLUGIN_DIRECTORY"
;;
integrations-hub)
HAS_CHANGES="$HAS_CHANGES_INTEGRATIONS_HUB"
IS_PUBLISHABLE="$IS_PUBLISHABLE_INTEGRATIONS_HUB"
;;
openhands)
HAS_CHANGES="$HAS_CHANGES_OPENHANDS"
IS_PUBLISHABLE="$IS_PUBLISHABLE_OPENHANDS"
Expand Down Expand Up @@ -203,6 +212,14 @@ jobs:

yq -i "(.dependencies[] | select(.name == \"plugin-directory\")).version = \"${PLUGIN_DIRECTORY_PREVIEW}\"" charts/openhands/Chart.yaml

- name: Update integrations-hub dependency version
if: steps.check.outputs.should_publish == 'true' && matrix.chart.name == 'openhands' && needs.detect-changes.outputs.integrations-hub == 'true'
run: |
INTEGRATIONS_HUB_VERSION=$(yq '.version' charts/integrations-hub/Chart.yaml)
INTEGRATIONS_HUB_PREVIEW="${INTEGRATIONS_HUB_VERSION}-alpha.${{ github.event.pull_request.number }}"

yq -i "(.dependencies[] | select(.name == \"integrations-hub\")).version = \"${INTEGRATIONS_HUB_PREVIEW}\"" charts/openhands/Chart.yaml

- name: Test ${{ matrix.chart.name }} chart with default values
if: steps.check.outputs.should_publish == 'true'
run: |
Expand Down Expand Up @@ -279,6 +296,8 @@ jobs:
path: charts/automation
- name: plugin-directory
path: charts/plugin-directory
- name: integrations-hub
path: charts/integrations-hub
- name: openhands
path: charts/openhands
- name: openhands-secrets
Expand Down Expand Up @@ -331,6 +350,15 @@ jobs:
echo "Updating openhands plugin-directory dependency to ${PLUGIN_DIRECTORY_PREVIEW}"
yq -i "(.dependencies[] | select(.name == \"plugin-directory\")).version = \"${PLUGIN_DIRECTORY_PREVIEW}\"" charts/openhands/Chart.yaml

- name: Update integrations-hub dependency version for openhands
if: matrix.chart.name == 'openhands' && needs.detect-changes.outputs.integrations-hub == 'true'
run: |
INTEGRATIONS_HUB_VERSION=$(yq '.version' charts/integrations-hub/Chart.yaml)
INTEGRATIONS_HUB_PREVIEW="${INTEGRATIONS_HUB_VERSION}-alpha.${{ github.event.pull_request.number }}"

echo "Updating openhands integrations-hub dependency to ${INTEGRATIONS_HUB_PREVIEW}"
yq -i "(.dependencies[] | select(.name == \"integrations-hub\")).version = \"${INTEGRATIONS_HUB_PREVIEW}\"" charts/openhands/Chart.yaml

- name: Lint ${{ matrix.chart.name }} chart
run: |
echo "Testing ${{ matrix.chart.name }} chart"
Expand Down
4 changes: 3 additions & 1 deletion .github/workflows/publish-helm-charts.yml
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ jobs:
matrix:
chart:
# Order matters: openhands depends on crd-check, runtime-api, automation,
# and plugin-directory, so those must publish first.
# plugin-directory, and integrations-hub, so those must publish first.
- name: crd-check
path: charts/crd-check
- name: runtime-api
Expand All @@ -39,6 +39,8 @@ jobs:
path: charts/automation
- name: plugin-directory
path: charts/plugin-directory
- name: integrations-hub
path: charts/integrations-hub
- name: openhands
path: charts/openhands
- name: openhands-secrets
Expand Down
4 changes: 4 additions & 0 deletions .github/workflows/validate-chart-versions.yml
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,9 @@ on:
plugin-directory-publishable:
description: 'Whether plugin-directory chart is publishable (no changes or version bumped)'
value: ${{ jobs.validate-chart-versions.outputs.plugin-directory-publishable }}
integrations-hub-publishable:
description: 'Whether integrations-hub chart is publishable (no changes or version bumped)'
value: ${{ jobs.validate-chart-versions.outputs.integrations-hub-publishable }}
openhands-publishable:
description: 'Whether openhands chart is publishable (no changes or version bumped)'
value: ${{ jobs.validate-chart-versions.outputs.openhands-publishable }}
Expand All @@ -47,6 +50,7 @@ jobs:
image-loader-publishable: ${{ steps.validate.outputs.image-loader-publishable }}
automation-publishable: ${{ steps.validate.outputs.automation-publishable }}
plugin-directory-publishable: ${{ steps.validate.outputs.plugin-directory-publishable }}
integrations-hub-publishable: ${{ steps.validate.outputs.integrations-hub-publishable }}
openhands-publishable: ${{ steps.validate.outputs.openhands-publishable }}
openhands-secrets-publishable: ${{ steps.validate.outputs.openhands-secrets-publishable }}

Expand Down
11 changes: 11 additions & 0 deletions charts/integrations-hub/Chart.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
apiVersion: v2
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟠 Important - Missing Documentation: No README.md for the new chart.

Helm charts should include a README.md with:

  • Overview of what the chart deploys
  • Prerequisites (PostgreSQL setup, required secrets)
  • Installation instructions
  • Configuration examples (GCP Cloud SQL vs in-cluster PostgreSQL)
  • Values documentation (or auto-generated from values.yaml comments)
  • Upgrade notes

This is especially important for a new chart where users need guidance on the GCP vs non-GCP configurations, secret creation, and the rootPath / NEXT_PUBLIC_BASE_PATH coordination.

Comment thread
chuckbutkus marked this conversation as resolved.
name: integrations-hub
description: OpenHands Integrations Hub - Agent context layer with managed connectors and MCP integrations
type: application
version: 0.1.0
appVersion: "0.1.0"
dependencies:
- name: postgresql
version: 15.x.x
repository: https://charts.bitnami.com/bitnami
condition: postgresql.enabled
192 changes: 192 additions & 0 deletions charts/integrations-hub/templates/_env.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,192 @@
{{- /*
Environment variables consumed by the Integrations Hub backend
(Python/FastAPI on the `development` branch of OpenHands/integrations-hub).

The backend reads its config in backend/app/config.py using the prefix
`INTHUB_*`, with legacy unprefixed fallbacks for backwards compatibility:

INTHUB_POSTGRES_URL fallback: POSTGRES_URL, DATABASE_URL
INTHUB_DISABLE_AUTH (bool, defaults to false)
INTHUB_OPENHANDS_BASE_URL fallback: OPENHANDS_BASE_URL
INTHUB_OPENHANDS_AUTH_COOKIE_NAME fallback: OPENHANDS_AUTH_COOKIE_NAME
INTHUB_INTERNAL_AUTH_SECRET fallback: INTERNAL_AUTH_SECRET, AUTH_SECRET, NEXTAUTH_SECRET
INTHUB_CRON_SECRET fallback: CRON_SECRET
APP_ADMIN_EMAILS (use the unprefixed name; the prefixed
form is JSON-decoded by the SDK env
parser and would require list syntax)
INTHUB_CREDENTIAL_ENCRYPTION_KEY fallback: CREDENTIAL_ENCRYPTION_KEY
INTHUB_STATIC_DIR set in the Dockerfile to /app/out
INTHUB_ROOT_PATH sub-path mount; defaulted by the
Dockerfile to /integrations-hub and
overridable via .Values.rootPath

In addition, backend/app/oauth_flow.py reads:
AUTH_REDIRECT_PROXY_URL stable origin for OAuth callback proxy
AUTH_URL / NEXTAUTH_URL preview-deployment callback target
*/}}
{{- define "integrations-hub.env.defaults" }}
# Database connection -- backend reads INTHUB_POSTGRES_URL first; we build
# it from the configured host/port/user/db plus the password secret using
# Kubernetes $(VAR_NAME) expansion. If .Values.database.url is set it wins.
{{- if .Values.database.url }}
- name: INTHUB_POSTGRES_URL
value: {{ .Values.database.url | quote }}
{{- else }}
- name: INTHUB_DB_HOST
value: {{ .Values.database.host | quote }}
- name: INTHUB_DB_PORT
value: {{ .Values.database.port | quote }}
- name: INTHUB_DB_USER
value: {{ .Values.database.user | quote }}
- name: INTHUB_DB_NAME
value: {{ .Values.database.name | quote }}
- name: INTHUB_DB_PASS
valueFrom:
secretKeyRef:
name: {{ .Values.database.secretName }}
key: {{ .Values.database.secretKey }}
- name: INTHUB_POSTGRES_URL
value: "postgresql://$(INTHUB_DB_USER):$(INTHUB_DB_PASS)@$(INTHUB_DB_HOST):$(INTHUB_DB_PORT)/$(INTHUB_DB_NAME)"
{{- end }}

# OpenHands identity provider used to validate session cookies / API keys.
{{- $openhandsBaseUrl := .Values.openhands.baseUrl }}
{{- if and (not $openhandsBaseUrl) .Values.global }}
{{- if .Values.global.ingress }}
{{- if .Values.global.ingress.host }}
{{- $host := .Values.global.ingress.host }}
{{- if and .Values.global.ingress.prefixWithBranch .Values.global.branchSanitized }}
{{- $host = printf "%s.%s" .Values.global.branchSanitized .Values.global.ingress.host }}
{{- end }}
{{- $openhandsBaseUrl = printf "https://%s" $host }}
{{- end }}
{{- end }}
{{- end }}
{{- if $openhandsBaseUrl }}
- name: INTHUB_OPENHANDS_BASE_URL
value: {{ $openhandsBaseUrl | quote }}
{{- end }}
{{- if .Values.openhands.authCookieName }}
- name: INTHUB_OPENHANDS_AUTH_COOKIE_NAME
value: {{ .Values.openhands.authCookieName | quote }}
{{- end }}

# Local-dev auth bypass. Defaults to false; the FastAPI app will reject
# non-loopback requests when this is true, so it should never be enabled
# in staging/production.
- name: INTHUB_DISABLE_AUTH
value: {{ .Values.disableAuth | default false | quote }}

# Sub-path mount for the FastAPI app. The Dockerfile already defaults
# INTHUB_ROOT_PATH to /integrations-hub so the image works behind the
# default ingress out of the box; this block re-emits the value so chart
# overrides flow through. Rendered unconditionally (including when
# .Values.rootPath is "") so that an empty override actually clears the
# image default instead of silently inheriting it.
- name: INTHUB_ROOT_PATH
value: {{ .Values.rootPath | default "" | quote }}

# Credential encryption key (required: signs/encrypts stored OAuth tokens
# and other provider secrets at rest).
- name: INTHUB_CREDENTIAL_ENCRYPTION_KEY
valueFrom:
secretKeyRef:
name: {{ .Values.credentialEncryption.secretName }}
key: {{ .Values.credentialEncryption.secretKey }}

# Internal service-to-service secret. Optional in SPA-only K8s deployments
# (the backend validates the OpenHands session cookie directly when no
# Next.js proxy is in front of it) but required if any internal caller
# attaches the X-Integrations-Hub-Internal-Secret header.
{{- if and .Values.auth .Values.auth.internalSecretName }}
- name: INTHUB_INTERNAL_AUTH_SECRET
valueFrom:
secretKeyRef:
name: {{ .Values.auth.internalSecretName }}
key: {{ .Values.auth.internalSecretKey | default "internal-auth-secret" }}
{{- end }}

# Admin allowlist for /api/admin/* routes. Use the unprefixed name --
# INTHUB_APP_ADMIN_EMAILS goes through the SDK env parser which expects
# JSON list syntax.
{{- if .Values.admin.emails }}
- name: APP_ADMIN_EMAILS
value: {{ .Values.admin.emails | quote }}
{{- end }}

# Cron secret -- required by /api/cron/expire-grants.
- name: INTHUB_CRON_SECRET
valueFrom:
secretKeyRef:
name: {{ .Values.cron.secretName }}
key: {{ .Values.cron.secretKey }}

# OAuth preview-deployment proxy (optional). When set, service OAuth
# callbacks register/exchange against the stable origin and then forward
# back to the preview deployment captured in OAuth state.
{{- if .Values.openhands.redirectProxyUrl }}
- name: AUTH_REDIRECT_PROXY_URL
value: {{ .Values.openhands.redirectProxyUrl | quote }}
{{- end }}
{{- if .Values.openhands.authUrl }}
- name: AUTH_URL
value: {{ .Values.openhands.authUrl | quote }}
{{- end }}

{{- if .Values.datadog.enabled }}
# Datadog APM
- name: DD_AGENT_HOST
value: "datadog-agent.all-hands-system.svc.cluster.local"
- name: DD_TRACE_AGENT_PORT
value: "8126"
- name: DD_SERVICE
value: {{ .Values.datadog.serviceName | quote }}
- name: DD_ENV
value: {{ .Values.datadog.env | quote }}
- name: DD_TRACE_ENABLED
value: "true"
{{- end }}
{{- end }}

{{/*
integrations-hub.env — Deduplicated environment variable list.

This wrapper renders the default env vars from "integrations-hub.env.defaults",
then removes any entries whose name conflicts with a key in .Values.env,
and finally appends the .Values.env overrides. The result is a clean list
with no duplicate names, which prevents:
- Helm warnings about duplicate env vars
- Strategic Merge Patch conflicts during helm upgrade
("The order in patch list doesn't match $setElementOrder list")

How it works:
1. Render "integrations-hub.env.defaults" via include (evaluates all conditionals)
2. Parse the rendered YAML list into Go objects with fromYamlArray
3. Filter out any default entries whose name appears in .Values.env
Comment thread
chuckbutkus marked this conversation as resolved.
4. Append .Values.env entries (user overrides always win)
5. Re-render the deduplicated list with toYaml
*/}}
{{- define "integrations-hub.env" }}
{{- $defaults := include "integrations-hub.env.defaults" . | fromYamlArray }}
{{- /* Build a lookup dict of override keys for O(1) membership checks */}}
{{- $overrideKeys := dict }}
{{- if .Values.env }}
{{- range $key, $_ := .Values.env }}
{{- $_ := set $overrideKeys $key true }}
{{- end }}
{{- end }}
{{- /* Keep only default entries that are NOT overridden by .Values.env */}}
{{- $filtered := list }}
{{- range $entry := $defaults }}
{{- if not (hasKey $overrideKeys (get $entry "name")) }}
{{- $filtered = append $filtered $entry }}
{{- end }}
{{- end }}
{{- /* Append user overrides from .Values.env (these take precedence) */}}
{{- if .Values.env }}
{{- range $key, $value := .Values.env }}
{{- $filtered = append $filtered (dict "name" $key "value" ($value | toString)) }}
{{- end }}
{{- end }}
Comment thread
chuckbutkus marked this conversation as resolved.
{{- $filtered | toYaml }}
{{- end }}
Loading
Loading