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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ spec:
{{- if .Values.database.createDatabaseUser }}
# Create automation database and user in an existing PostgreSQL instance
- name: create-db-user
image: postgres:14
image: "{{ .Values.global.postgresClientImage.repository }}:{{ .Values.global.postgresClientImage.tag }}"
env:
- name: PGPASSWORD
valueFrom:
Expand Down Expand Up @@ -124,7 +124,7 @@ spec:
{{- else if .Values.postgresql.enabled }}
# Wait for the automation's own PostgreSQL subchart to be ready
- name: wait-for-postgres
image: bitnamilegacy/postgresql:latest
image: "{{ .Values.global.postgresClientImage.repository }}:{{ .Values.global.postgresClientImage.tag }}"
command: ['sh', '-c']
args:
- |
Expand Down
5 changes: 5 additions & 0 deletions charts/openhands/charts/automation/values.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -230,6 +230,11 @@ postgresql:
enabled: false

global:
# psql/pg_isready image for the database init containers and jobs. The openhands
# umbrella's global wins; this default only applies when rendering the subchart alone.
postgresClientImage:
repository: docker.io/alpine/psql
tag: 18.6@sha256:08f354a83552fcefa508116268463b371717577fe49d3fcb7b82cf8d2be464b8
security:
# This allows using the bitnamilegacy image repo
allowInsecureImages: true
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ spec:
# Create database and user in PostgreSQL. Disable this when the database
# and user are provisioned outside the chart.
- name: create-db-user
image: postgres:14
image: "{{ .Values.global.postgresClientImage.repository }}:{{ .Values.global.postgresClientImage.tag }}"
env:
- name: PGPASSWORD
valueFrom:
Expand Down Expand Up @@ -109,7 +109,7 @@ spec:
{{- else if .Values.postgresql.enabled }}
# Wait for the service's own PostgreSQL subchart to be ready
- name: wait-for-postgres
image: bitnamilegacy/postgresql:latest
image: "{{ .Values.global.postgresClientImage.repository }}:{{ .Values.global.postgresClientImage.tag }}"
command: ['sh', '-c']
args:
- |
Expand Down
5 changes: 5 additions & 0 deletions charts/openhands/charts/integrations-hub/values.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,11 @@ postgresql:
enabled: false

global:
# psql/pg_isready image for the database init containers and jobs. The openhands
# umbrella's global wins; this default only applies when rendering the subchart alone.
postgresClientImage:
repository: docker.io/alpine/psql
tag: 18.6@sha256:08f354a83552fcefa508116268463b371717577fe49d3fcb7b82cf8d2be464b8
security:
# This allows using the bitnamilegacy image repo
allowInsecureImages: true
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{{- define "plugin-directory.dbInitContainers" }}
{{- if .Values.databaseMigrations.waitForDatabase }}
- name: wait-for-db
image: "bitnamilegacy/postgresql:latest"
image: "{{ .Values.global.postgresClientImage.repository }}:{{ .Values.global.postgresClientImage.tag }}"
command: ['sh', '-c']
args:
- |
Expand All @@ -16,7 +16,7 @@
{{- end }}
{{- if .Values.databaseMigrations.createDatabases }}
- name: create-db
image: "bitnamilegacy/postgresql:latest"
image: "{{ .Values.global.postgresClientImage.repository }}:{{ .Values.global.postgresClientImage.tag }}"
command: ['sh', '-c']
args:
- |
Expand Down
5 changes: 5 additions & 0 deletions charts/openhands/charts/plugin-directory/values.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,11 @@ datadog:
env: {}

global:
# psql/pg_isready image for the database init containers and jobs. The openhands
# umbrella's global wins; this default only applies when rendering the subchart alone.
postgresClientImage:
repository: docker.io/alpine/psql
tag: 18.6@sha256:08f354a83552fcefa508116268463b371717577fe49d3fcb7b82cf8d2be464b8
scheduling:
# Affinity applied to this chart's pods when `affinity` above is empty. The
# openhands umbrella sets this once for every chart it owns; this default
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{{- define "runtime-api.dbInitContainers" }}
{{- if .Values.databaseMigrations.waitForDatabase }}
- name: wait-for-db
image: "bitnamilegacy/postgresql:latest"
image: "{{ .Values.global.postgresClientImage.repository }}:{{ .Values.global.postgresClientImage.tag }}"
command: ['sh', '-c']
args:
- |
Expand All @@ -18,7 +18,7 @@
{{- end }}
{{- if .Values.databaseMigrations.createDatabases }}
- name: create-db
image: "bitnamilegacy/postgresql:latest"
image: "{{ .Values.global.postgresClientImage.repository }}:{{ .Values.global.postgresClientImage.tag }}"
command: ['sh', '-c']
args:
- |
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ spec:
{{- end }}
containers:
- name: create-user
image: postgres:14
image: "{{ .Values.global.postgresClientImage.repository }}:{{ .Values.global.postgresClientImage.tag }}"
env:
- name: PGPASSWORD
valueFrom:
Expand Down
5 changes: 5 additions & 0 deletions charts/openhands/charts/runtime-api/values.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -370,6 +370,11 @@ replicated:
enabled: false

global:
# psql/pg_isready image for the database init containers and jobs. The openhands
# umbrella's global wins; this default only applies when rendering the subchart alone.
postgresClientImage:
repository: docker.io/alpine/psql
tag: 18.6@sha256:08f354a83552fcefa508116268463b371717577fe49d3fcb7b82cf8d2be464b8
# Canonical agent-server image. Defaults any warm-runtime configsByName entry
# that omits its own `image`. In the openhands umbrella the parent chart's
# global wins; this default only applies when rendering the subchart alone.
Expand Down
4 changes: 2 additions & 2 deletions charts/openhands/templates/_init-containers.yaml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{{- define "openhands.dbInitContainers" }}
{{- if .Values.databaseMigrations.waitForDatabase }}
- name: wait-for-db
image: "bitnamilegacy/postgresql:latest"
image: "{{ .Values.global.postgresClientImage.repository }}:{{ .Values.global.postgresClientImage.tag }}"
command: ['sh', '-c']
args:
- |
Expand All @@ -18,7 +18,7 @@
{{- end }}
{{- if .Values.databaseMigrations.createDatabases }}
- name: create-db
image: "bitnamilegacy/postgresql:latest"
image: "{{ .Values.global.postgresClientImage.repository }}:{{ .Values.global.postgresClientImage.tag }}"
command: ['sh', '-c']
args:
- |
Expand Down
17 changes: 10 additions & 7 deletions charts/openhands/values.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -538,15 +538,14 @@ keycloak:
# this is evaluated in the Keycloak subchart context at render time.
- name: KC_DB_URL_PROPERTIES
value: 'sslmode={{ .Values.externalDatabase.sslMode | default "prefer" }}'
waitForDb:
image: "bitnamilegacy/postgresql:latest"
initContainers:
- name: wait-for-db
# The Bitnami Keycloak subchart renders initContainers through common.tplvalues.render,
# so this template expression is evaluated at deploy time rather than being treated as
# a literal string. This lets us override just the image (e.g. for Replicated proxy)
# without duplicating the entire init container.
image: '{{ .Values.waitForDb.image }}'
# Rendered through common.tplvalues.render, so this resolves against the shared global.
image: '{{ .Values.global.postgresClientImage.repository }}:{{ .Values.global.postgresClientImage.tag }}'
# The client image defaults to root; keep the uid the Bitnami image ran this as.
securityContext:
runAsUser: 1001
runAsNonRoot: true
command: ['sh', '-c']
args:
- |
Expand Down Expand Up @@ -1445,6 +1444,10 @@ replicated:
postgresDatabase: ""

global:
# psql/pg_isready image for the database init containers and jobs.
postgresClientImage:
repository: docker.io/alpine/psql
tag: 18.6@sha256:08f354a83552fcefa508116268463b371717577fe49d3fcb7b82cf8d2be464b8
# Canonical agent-server image. Defaults the runtime image (runtime.image) and
# any warm-runtime configsByName entry that omits its own `image`. Override
# either of those for per-use exceptions; set it here to move them together.
Expand Down
9 changes: 5 additions & 4 deletions replicated/openhands.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,9 @@ spec:
# global.agentServerImage.
agentServerImage:
repository: 'images.r9.all-hands.dev/proxy/{{repl LicenseFieldValue "appSlug"}}/ghcr.io/openhands/agent-server'
# Tag pinned in the chart.
postgresClientImage:
repository: 'images.r9.all-hands.dev/proxy/{{repl LicenseFieldValue "appSlug"}}/docker.io/alpine/psql'
# Runtime (agent-server sandbox) env vars. The chart serialises this map
# into OH_AGENT_SERVER_ENV and mirrors it into every warm-runtime entry's
# env (runtime-api claims warm pods by exact env match, so a key here
Expand Down Expand Up @@ -191,8 +194,6 @@ spec:
keycloakConfigCli:
image:
repository: 'proxy/{{repl LicenseFieldValue "appSlug"}}/docker.io/bitnamilegacy/keycloak-config-cli'
waitForDb:
image: 'images.r9.all-hands.dev/proxy/{{repl LicenseFieldValue "appSlug"}}/docker.io/bitnamilegacy/postgresql:latest'
# caBundle wiring: mount the trust-manager Bundle ConfigMap and point
# Keycloak's truststore at the merged PEM. Repeats the chart's default
# KC_* env vars because helm value merging replaces arrays — adding
Expand Down Expand Up @@ -816,6 +817,8 @@ spec:
# warmRuntimes default entry both inherit this. Tag pinned in the chart.
agentServerImage:
repository: '{{repl LocalRegistryHost }}/{{repl LocalRegistryNamespace }}/agent-server'
postgresClientImage:
repository: '{{repl LocalRegistryHost }}/{{repl LocalRegistryNamespace }}/psql'
image:
repository: '{{repl LocalRegistryHost }}/{{repl LocalRegistryNamespace }}/deploy'
keycloak:
Expand All @@ -824,8 +827,6 @@ spec:
keycloakConfigCli:
image:
repository: '{{repl LocalRegistryNamespace }}/keycloak-config-cli'
waitForDb:
image: '{{repl LocalRegistryHost }}/{{repl LocalRegistryNamespace }}/postgresql:latest'
postgresql:
image:
repository: '{{repl LocalRegistryNamespace }}/postgresql'
Expand Down
90 changes: 90 additions & 0 deletions scripts/test_postgres_client_image.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
"""Every psql/pg_isready container must use the pinned global.postgresClientImage, mirrored in each mode."""

import subprocess
from pathlib import Path

import pytest
import yaml

ROOT = Path(__file__).resolve().parents[1]
CHART = ROOT / "charts/openhands"
APP, HOST, NS = "test-app", "registry.test", "test-ns"
# Turn on every site that runs the client, including the non-default branches.
SITE_VALUES = [
{
"databaseMigrations": {"createDatabases": True},
"keycloak": {"enabled": True},
"runtime-api": {
"databaseMigrations": {"createDatabases": True},
"database": {"create": True, "host": "db", "user": "postgres", "name": "rt", "new_user": "rt_user"},
},
"plugin-directory": {"enabled": True, "databaseMigrations": {"createDatabases": True}},
"automation": {"enabled": True, "database": {"createDatabaseUser": True}},
"integrations-hub": {"enabled": True, "database": {"host": "db", "createDatabaseUser": True}},
},
{
"automation": {"enabled": True, "database": {"createDatabaseUser": False}, "postgresql": {"enabled": True}},
"integrations-hub": {"enabled": True, "database": {"host": "db", "createDatabaseUser": False}, "postgresql": {"enabled": True}},
},
]


def chart_default():
return yaml.safe_load((CHART / "values.yaml").read_text())["global"]["postgresClientImage"]


def replicated_repository(mode):
spec = yaml.safe_load((ROOT / "replicated/openhands.yaml").read_text())["spec"]
if mode == "online":
values = spec["values"]
else:
values = next(b for b in spec["optionalValues"] if "HasLocalRegistry" in b["when"])["values"]
repo = values["global"]["postgresClientImage"]["repository"]
for source, target in {
'{{repl LicenseFieldValue "appSlug"}}': APP,
"{{repl LocalRegistryHost }}": HOST,
"{{repl LocalRegistryNamespace }}": NS,
}.items():
repo = repo.replace(source, target)
return repo


def client_images(rendered):
images = []
for doc in yaml.safe_load_all(rendered):
if not doc:
continue
spec = doc.get("spec", {})
pod = spec.get("template", {}).get("spec") or spec.get("jobTemplate", {}).get("spec", {}).get("template", {}).get("spec")
for c in (pod or {}).get("initContainers", []) + (pod or {}).get("containers", []):
script = " ".join((c.get("command") or []) + (c.get("args") or []))
if "psql" in script or "pg_isready" in script:
images.append((doc["metadata"]["name"], c["name"], c["image"]))
return images


def test_chart_default_is_digest_pinned():
assert "@sha256:" in chart_default()["tag"]


@pytest.mark.parametrize("mode", ["online", "airgap"])
@pytest.mark.parametrize("sites", range(len(SITE_VALUES)))
def test_every_client_container_uses_the_mirrored_image(mode, sites, tmp_path):
default = chart_default()
expected_repo = {
"online": f"images.r9.all-hands.dev/proxy/{APP}/{default['repository']}",
"airgap": f"{HOST}/{NS}/{default['repository'].rsplit('/', 1)[-1]}",
}[mode]
assert replicated_repository(mode) == expected_repo
values = dict(SITE_VALUES[sites], **{"global": {"postgresClientImage": {"repository": expected_repo}}})
values_path = tmp_path / "values.yaml"
values_path.write_text(yaml.safe_dump(values))
rendered = subprocess.run(
["helm", "template", "openhands", str(CHART), "-f", str(values_path)],
check=True,
capture_output=True,
text=True,
).stdout
images = client_images(rendered)
assert len(images) >= 5
assert all(image == f"{expected_repo}:{default['tag']}" for _, _, image in images), images
Loading