Skip to content

Commit b71c3a7

Browse files
committed
feat(helm): add optional PostgreSQL backing store with Secret-based credentials
- Add postgres.enabled and postgres.deploy values to control database backend (SQLite vs PostgreSQL) and subchart deployment independently. - Introduce db-secret.yaml template for Opaque Secret with assembled postgresql:// connection string injected via OPENSHELL_DB_URL env var. - Add Bitnami PostgreSQL as optional subchart dependency keyed on postgres.deploy to prevent subchart deployment in external mode. - Externalize JWT signing key file mode via sandboxJwt.secretDefaultMode with 0400 default matching upstream. - Add validation guard for postgres.deploy=true without postgres.enabled. - Add helm unit tests covering internal, external, URL-override, special character encoding, and misconfiguration error paths. - Update README with Kubernetes and OpenShift install examples for bundled and external PostgreSQL configurations. - Add helm dependency build to lint and unittest tasks.
1 parent 9e5aee4 commit b71c3a7

11 files changed

Lines changed: 304 additions & 5 deletions

File tree

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -187,6 +187,9 @@ rootfs/
187187
# Docker build artifacts (image tarballs, packaged helm charts)
188188
deploy/docker/.build/
189189

190+
# Helm subchart tarballs (regenerated by `helm dependency build`)
191+
deploy/helm/openshell/charts/
192+
190193
# SBOM generated output (JSON, CSV) — release artifacts, not committed
191194
deploy/sbom/output/
192195

deploy/helm/openshell/Chart.lock

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
dependencies:
2+
- name: postgresql
3+
repository: oci://registry-1.docker.io/bitnamicharts
4+
version: 18.6.7
5+
digest: sha256:ad78500c7c3a7ee365fd151890cf3368444d6b167c972052fc245024f5a25d9c
6+
generated: "2026-05-27T17:48:47.648592-04:00"

deploy/helm/openshell/Chart.yaml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,3 +11,9 @@ type: application
1111
# empty), so a released chart automatically pulls the matching gateway and supervisor images.
1212
version: 0.0.0
1313
appVersion: "0.0.0"
14+
dependencies:
15+
- name: postgresql
16+
version: 18.6.7
17+
repository: oci://registry-1.docker.io/bitnamicharts
18+
condition: postgres.deploy
19+
alias: postgres

deploy/helm/openshell/README.md

Lines changed: 68 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,9 +32,8 @@ oc create ns openshell
3232
# Sandboxes are deployed into the openshell namespace and use the openshell-sandbox service account
3333
oc adm policy add-scc-to-user privileged -z openshell-sandbox -n openshell
3434

35-
# Deploy openshell with overrides to allow SCC assignment of fsGroup and runAsUser for the gateway
35+
# Deploy openshell with overrides for OpenShift SCC compatibility
3636
helm install openshell oci://ghcr.io/nvidia/openshell/helm-chart --version <version> -n openshell \
37-
--set pkiInitJob.enabled=false \
3837
--set server.disableTls=true \
3938
--set podSecurityContext.fsGroup=null \
4039
--set securityContext.runAsUser=null
@@ -58,6 +57,73 @@ See [`values.yaml`](values.yaml) for source defaults. Selected overlays:
5857
- [`ci/values-cert-manager.yaml`](ci/values-cert-manager.yaml) - cert-manager integration
5958
- [`ci/values-keycloak.yaml`](ci/values-keycloak.yaml) - Keycloak OIDC integration
6059

60+
### Database backend
61+
62+
By default, OpenShell uses SQLite:
63+
64+
```yaml
65+
server:
66+
dbUrl: "sqlite:/var/openshell/openshell.db"
67+
postgres:
68+
enabled: false
69+
```
70+
71+
Enable bundled PostgreSQL:
72+
73+
```bash
74+
helm install openshell oci://ghcr.io/nvidia/openshell/helm-chart --version <version> \
75+
--set postgres.enabled=true \
76+
--set postgres.deploy=true \
77+
--set postgres.auth.password=my-secret-password
78+
```
79+
80+
Enable bundled PostgreSQL(OpenShift):
81+
82+
```bash
83+
helm install openshell oci://ghcr.io/nvidia/openshell/helm-chart --version <version> \
84+
--set postgres.enabled=true \
85+
--set postgres.deploy=true \
86+
--set postgres.auth.password=my-secret-password \
87+
--set server.disableTls=true \
88+
--set podSecurityContext.fsGroup=null \
89+
--set securityContext.runAsUser=null
90+
```
91+
92+
Use external PostgreSQL:
93+
94+
```bash
95+
helm install openshell oci://ghcr.io/nvidia/openshell/helm-chart --version <version> \
96+
--set postgres.enabled=true \
97+
--set postgres.external.host=my-postgres.example.com \
98+
--set postgres.external.port=5432 \
99+
--set postgres.external.database=openshell \
100+
--set postgres.external.username=openshell \
101+
--set postgres.external.password=my-password
102+
```
103+
104+
Use external PostgreSQL (OpenShift):
105+
106+
```bash
107+
helm install openshell oci://ghcr.io/nvidia/openshell/helm-chart --version <version> \
108+
--set postgres.enabled=true \
109+
--set postgres.external.host=my-postgres.example.com \
110+
--set postgres.external.port=5432 \
111+
--set postgres.external.database=openshell \
112+
--set postgres.external.username=openshell \
113+
--set postgres.external.password=my-password \
114+
--set server.disableTls=true \
115+
--set podSecurityContext.fsGroup=null \
116+
--set securityContext.runAsUser=null
117+
```
118+
119+
Or provide a full connection URL directly:
120+
121+
```bash
122+
helm install openshell oci://ghcr.io/nvidia/openshell/helm-chart --version <version> \
123+
--set postgres.enabled=true \
124+
--set postgres.external.url="postgres://user:pass@host:5432/db?sslmode=require"
125+
```
126+
61127
## PKI bootstrap
62128

63129
By default, a pre-install/pre-upgrade hook Job runs `openshell-gateway generate-certs`

deploy/helm/openshell/templates/_helpers.tpl

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,32 @@ Namespace where sandbox pods are created. An explicit
102102
{{- .Values.server.sandboxNamespace | default .Release.Namespace -}}
103103
{{- end }}
104104

105+
{{/*
106+
Gateway database URL.
107+
- postgres.enabled=false: use .Values.server.dbUrl (default sqlite)
108+
- postgres.enabled=true + deploy=true: derive URL from bundled postgres subchart
109+
- postgres.enabled=true + deploy=false: use external.url or compose external fields
110+
*/}}
111+
{{- define "openshell.dbUrl" -}}
112+
{{- if .Values.postgres.enabled -}}
113+
{{- if not .Values.postgres.deploy -}}
114+
{{- if .Values.postgres.external.url -}}
115+
{{- .Values.postgres.external.url -}}
116+
{{- else -}}
117+
{{- $host := required "postgres.external.host is required when postgres.deploy=false and no postgres.external.url is provided" .Values.postgres.external.host -}}
118+
{{- $pw := required "postgres.external.password is required when postgres.deploy=false and no postgres.external.url is provided" .Values.postgres.external.password -}}
119+
{{- printf "postgres://%s:%s@%s:%d/%s" (.Values.postgres.external.username | urlquery) ($pw | urlquery) $host (int (default 5432 .Values.postgres.external.port)) .Values.postgres.external.database -}}
120+
{{- end -}}
121+
{{- else -}}
122+
{{- $pw := required "postgres.auth.password must be set when postgres.enabled=true" .Values.postgres.auth.password -}}
123+
{{- $host := .Values.postgres.host | default (printf "%s-postgres.%s.svc.cluster.local" .Release.Name .Release.Namespace) -}}
124+
{{- printf "postgres://%s:%s@%s:%d/%s" (.Values.postgres.auth.username | urlquery) ($pw | urlquery) $host (int .Values.postgres.port) .Values.postgres.auth.database -}}
125+
{{- end -}}
126+
{{- else -}}
127+
{{- .Values.server.dbUrl -}}
128+
{{- end -}}
129+
{{- end }}
130+
105131
{{/*
106132
gRPC endpoint sandbox pods use to call back into the gateway. An explicit
107133
.Values.server.grpcEndpoint is used verbatim. Otherwise it is derived from
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
# SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
2+
# SPDX-License-Identifier: Apache-2.0
3+
4+
{{- if .Values.postgres.enabled }}
5+
apiVersion: v1
6+
kind: Secret
7+
metadata:
8+
name: {{ include "openshell.fullname" . }}-db
9+
labels:
10+
{{- include "openshell.labels" . | nindent 4 }}
11+
type: Opaque
12+
stringData:
13+
db-url: {{ include "openshell.dbUrl" . | quote }}
14+
{{- end }}

deploy/helm/openshell/templates/gateway-config.yaml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,8 @@ at startup. CLI flags and OPENSHELL_* env vars on the StatefulSet container
88
still override anything in this file.
99

1010
One value is intentionally NOT rendered here:
11-
- server.dbUrl → passed via --db-url in the StatefulSet args
11+
- server.dbUrl → passed via OPENSHELL_DB_URL env var (from Secret)
12+
when postgres.enabled=true, or --db-url arg for SQLite
1213
*/}}
1314
apiVersion: v1
1415
kind: ConfigMap

deploy/helm/openshell/templates/statefulset.yaml

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
# SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
22
# SPDX-License-Identifier: Apache-2.0
3-
3+
{{- if and .Values.postgres.deploy (not .Values.postgres.enabled) }}
4+
{{- fail "postgres.deploy=true requires postgres.enabled=true" }}
5+
{{- end }}
46
apiVersion: apps/v1
57
kind: StatefulSet
68
metadata:
@@ -21,6 +23,9 @@ spec:
2123
# without this annotation a `helm upgrade` that only mutates the
2224
# ConfigMap would leave pods running with stale config.
2325
checksum/gateway-config: {{ include (print $.Template.BasePath "/gateway-config.yaml") . | sha256sum }}
26+
{{- if .Values.postgres.enabled }}
27+
checksum/db-secret: {{ include (print $.Template.BasePath "/db-secret.yaml") . | sha256sum }}
28+
{{- end }}
2429
{{- with .Values.podAnnotations }}
2530
{{- toYaml . | nindent 8 }}
2631
{{- end }}
@@ -54,9 +59,18 @@ spec:
5459
args:
5560
- --config
5661
- /etc/openshell/gateway.toml
62+
{{- if not .Values.postgres.enabled }}
5763
- --db-url
5864
- {{ .Values.server.dbUrl | quote }}
65+
{{- end }}
5966
env:
67+
{{- if .Values.postgres.enabled }}
68+
- name: OPENSHELL_DB_URL
69+
valueFrom:
70+
secretKeyRef:
71+
name: {{ include "openshell.fullname" . }}-db
72+
key: db-url
73+
{{- end }}
6074
# All gateway settings live in the ConfigMap-backed TOML file
6175
# mounted at /etc/openshell/gateway.toml. The only env var below
6276
# is a process-level setting consumed by libraries outside
@@ -137,7 +151,7 @@ spec:
137151
- name: sandbox-jwt
138152
secret:
139153
secretName: {{ .Values.server.sandboxJwt.signingSecretName | default (printf "%s-jwt-keys" (include "openshell.fullname" .)) }}
140-
defaultMode: 0400
154+
defaultMode: {{ .Values.server.sandboxJwt.secretDefaultMode | default 0400 }}
141155
{{- if not .Values.server.disableTls }}
142156
- name: tls-cert
143157
secret:

deploy/helm/openshell/tests/gateway_config_test.yaml

Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ suite: gateway TOML config shape
55
templates:
66
- templates/gateway-config.yaml
77
- templates/statefulset.yaml
8+
- templates/db-secret.yaml
89
release:
910
name: openshell
1011
namespace: my-namespace
@@ -125,3 +126,124 @@ tests:
125126
- matchRegex:
126127
path: data["gateway.toml"]
127128
pattern: 'server_sans\s*=\s*\["openshell", "\*\.dev\.openshell\.localhost"\]'
129+
130+
- it: passes sqlite db-url via --db-url arg by default
131+
template: templates/statefulset.yaml
132+
asserts:
133+
- contains:
134+
path: spec.template.spec.containers[0].args
135+
content: "sqlite:/var/openshell/openshell.db"
136+
137+
- it: does not create a db Secret when postgres is disabled
138+
template: templates/db-secret.yaml
139+
asserts:
140+
- hasDocuments:
141+
count: 0
142+
143+
- it: does not pass --db-url in args when postgres is enabled
144+
template: templates/statefulset.yaml
145+
set:
146+
postgres.enabled: true
147+
postgres.deploy: true
148+
postgres.auth.password: test-pw
149+
asserts:
150+
- notContains:
151+
path: spec.template.spec.containers[0].args
152+
content: "--db-url"
153+
154+
- it: injects OPENSHELL_DB_URL from Secret when postgres is enabled
155+
template: templates/statefulset.yaml
156+
set:
157+
postgres.enabled: true
158+
postgres.deploy: true
159+
postgres.auth.password: test-pw
160+
asserts:
161+
- contains:
162+
path: spec.template.spec.containers[0].env
163+
content:
164+
name: OPENSHELL_DB_URL
165+
valueFrom:
166+
secretKeyRef:
167+
name: openshell-db
168+
key: db-url
169+
170+
- it: creates db Secret with internal postgres URL
171+
template: templates/db-secret.yaml
172+
set:
173+
postgres.enabled: true
174+
postgres.deploy: true
175+
postgres.auth.password: test-pw
176+
asserts:
177+
- isKind:
178+
of: Secret
179+
- equal:
180+
path: stringData["db-url"]
181+
value: "postgres://openshell:test-pw@openshell-postgres.my-namespace.svc.cluster.local:5432/openshell"
182+
183+
- it: creates db Secret with external postgres URL fields
184+
template: templates/db-secret.yaml
185+
set:
186+
postgres.enabled: true
187+
postgres.deploy: false
188+
postgres.external.host: external-postgres.example.com
189+
postgres.external.port: 5432
190+
postgres.external.database: openshell_ext
191+
postgres.external.username: ext_user
192+
postgres.external.password: ext_pass
193+
asserts:
194+
- isKind:
195+
of: Secret
196+
- equal:
197+
path: stringData["db-url"]
198+
value: "postgres://ext_user:ext_pass@external-postgres.example.com:5432/openshell_ext"
199+
200+
- it: uses external.url verbatim when provided
201+
template: templates/db-secret.yaml
202+
set:
203+
postgres.enabled: true
204+
postgres.deploy: false
205+
postgres.external.url: "postgres://custom:secret@my-host:5433/mydb?sslmode=require"
206+
asserts:
207+
- isKind:
208+
of: Secret
209+
- equal:
210+
path: stringData["db-url"]
211+
value: "postgres://custom:secret@my-host:5433/mydb?sslmode=require"
212+
213+
- it: URL-encodes special characters in credentials
214+
template: templates/db-secret.yaml
215+
set:
216+
postgres.enabled: true
217+
postgres.deploy: true
218+
postgres.auth.password: "p@ss:word"
219+
asserts:
220+
- equal:
221+
path: stringData["db-url"]
222+
value: "postgres://openshell:p%40ss%3Aword@openshell-postgres.my-namespace.svc.cluster.local:5432/openshell"
223+
224+
- it: fails when postgres is enabled but no password is set
225+
template: templates/statefulset.yaml
226+
set:
227+
postgres.enabled: true
228+
postgres.deploy: true
229+
asserts:
230+
- failedTemplate:
231+
errorMessage: "postgres.auth.password must be set when postgres.enabled=true"
232+
233+
- it: fails when postgres.deploy=true but postgres.enabled=false
234+
template: templates/statefulset.yaml
235+
set:
236+
postgres.deploy: true
237+
asserts:
238+
- failedTemplate:
239+
errorMessage: "postgres.deploy=true requires postgres.enabled=true"
240+
241+
- it: fails when external postgres has no password
242+
template: templates/statefulset.yaml
243+
set:
244+
postgres.enabled: true
245+
postgres.deploy: false
246+
postgres.external.host: my-host.example.com
247+
asserts:
248+
- failedTemplate:
249+
errorMessage: "postgres.external.password is required when postgres.deploy=false and no postgres.external.url is provided"

0 commit comments

Comments
 (0)