mirror of
https://github.com/NVIDIA/OpenShell.git
synced 2026-10-20 06:00:06 +08:00
Previously, Helm installations could not enable the gateway OCSF JSONL destination through chart values because generated `gateway.toml` omitted the `openshell.gateway.ocsf_log` table. Now, setting `server.ocsfLog.enabled` renders the path, optional schema version, rotation, retention, and queue limits into gateway configuration. Output is disabled by default. The default path, `/tmp/gateway-ocsf.jsonl`, is writable in the gateway container with either the StatefulSet or Deployment workload, so enabling output does not require persistent storage. Invalid schema versions, rotation values, non-positive limits, or an empty path while enabled fail chart rendering. Additionally, `server.extraVolumes` and `server.extraVolumeMounts` add operator-supplied volumes to the gateway pod, so operators who want records to survive restarts can place the OCSF path on persistent storage without replacing chart-generated configuration. The gateway pod's default termination grace period rises from 5 to 30 seconds. Gateway shutdown can spend up to 10 seconds on supervisor session cleanup before allowing 5 seconds to drain queued OCSF records, so the 5-second default risked a SIGKILL before the final records were written. The grace period is only an upper bound: the gateway exits as soon as its shutdown completes. Refs #2762 Signed-off-by: Kris Hicks <khicks@nvidia.com>
1421 lines
52 KiB
YAML
1421 lines
52 KiB
YAML
# SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
|
|
# SPDX-License-Identifier: Apache-2.0
|
|
|
|
suite: gateway TOML config shape
|
|
templates:
|
|
- templates/gateway-config.yaml
|
|
- templates/deployment.yaml
|
|
- templates/peer-role.yaml
|
|
- templates/peer-service.yaml
|
|
- templates/statefulset.yaml
|
|
release:
|
|
name: openshell
|
|
namespace: my-namespace
|
|
|
|
tests:
|
|
- it: disables the WebSocket tunnel unless explicitly enabled for an edge proxy
|
|
template: templates/gateway-config.yaml
|
|
asserts:
|
|
- matchRegex:
|
|
path: data["gateway.toml"]
|
|
pattern: '(?m)^enable_websocket_tunnel\s*=\s*false$'
|
|
|
|
- it: enables the WebSocket tunnel when requested
|
|
template: templates/gateway-config.yaml
|
|
set:
|
|
server.enableWebsocketTunnel: true
|
|
asserts:
|
|
- matchRegex:
|
|
path: data["gateway.toml"]
|
|
pattern: '(?m)^enable_websocket_tunnel\s*=\s*true$'
|
|
|
|
- it: defaults to label admission with caller driver config disabled
|
|
template: templates/gateway-config.yaml
|
|
asserts:
|
|
- matchRegex:
|
|
path: data["gateway.toml"]
|
|
pattern: '(?m)^allow_driver_config\s*=\s*false$'
|
|
- matchRegex:
|
|
path: data["gateway.toml"]
|
|
pattern: '(?ms)\[openshell\.drivers\.kubernetes\.resource_admission\]\s*enabled\s*=\s*true'
|
|
- notMatchRegex:
|
|
path: data["gateway.toml"]
|
|
pattern: '\[openshell\.drivers\.kubernetes\.resource_admission\.required_labels\]'
|
|
|
|
- it: preserves explicit admission opt-out without enabling driver config
|
|
template: templates/gateway-config.yaml
|
|
set:
|
|
server.drivers.kubernetes.resourceAdmission.enabled: false
|
|
asserts:
|
|
- matchRegex:
|
|
path: data["gateway.toml"]
|
|
pattern: '(?m)^allow_driver_config\s*=\s*false$'
|
|
- matchRegex:
|
|
path: data["gateway.toml"]
|
|
pattern: '(?ms)\[openshell\.drivers\.kubernetes\.resource_admission\]\s*enabled\s*=\s*false'
|
|
|
|
- it: renders replacement labels and literal workspace substitution
|
|
template: templates/gateway-config.yaml
|
|
set:
|
|
server.drivers.kubernetes.allowDriverConfig: true
|
|
server.drivers.kubernetes.resourceAdmission.requiredLabels:
|
|
platform.example.com/sandbox-attachable: "approved"
|
|
platform.example.com/team: '${workspace}'
|
|
asserts:
|
|
- matchRegex:
|
|
path: data["gateway.toml"]
|
|
pattern: '"platform.example.com/sandbox-attachable" = "approved"'
|
|
- matchRegex:
|
|
path: data["gateway.toml"]
|
|
pattern: '"platform.example.com/team" = "\$\{workspace\}"'
|
|
- notMatchRegex:
|
|
path: data["gateway.toml"]
|
|
pattern: 'openshell.ai/sandbox-attachable'
|
|
|
|
- it: identifies the gateway by chart fullname by default
|
|
template: templates/gateway-config.yaml
|
|
asserts:
|
|
- matchRegex:
|
|
path: data["gateway.toml"]
|
|
pattern: '(?m)^name\s*=\s*"openshell"$'
|
|
|
|
- it: renders an explicit gateway name
|
|
template: templates/gateway-config.yaml
|
|
set:
|
|
server.name: production-us-west
|
|
asserts:
|
|
- matchRegex:
|
|
path: data["gateway.toml"]
|
|
pattern: '(?m)^name\s*=\s*"production-us-west"$'
|
|
|
|
- it: renders schema version 2 and the Kubernetes compute driver selector
|
|
template: templates/gateway-config.yaml
|
|
asserts:
|
|
- matchRegex:
|
|
path: data["gateway.toml"]
|
|
pattern: '(?ms)\[openshell\]\s*version\s*=\s*2'
|
|
- matchRegex:
|
|
path: data["gateway.toml"]
|
|
pattern: '(?ms)\[openshell\.gateway\][^\[]*?compute_driver\s*=\s*"kubernetes"'
|
|
|
|
- it: preserves a complete tagged gateway image reference
|
|
template: templates/statefulset.yaml
|
|
set:
|
|
gateway.image.registry: registry.example
|
|
gateway.image.repository: gateway
|
|
gateway.image.tag: branch
|
|
asserts:
|
|
- equal:
|
|
path: spec.template.spec.containers[0].image
|
|
value: registry.example/gateway:branch
|
|
|
|
- it: defaults a repository-only gateway image to the chart appVersion
|
|
template: templates/statefulset.yaml
|
|
set:
|
|
gateway.image.registry: registry.example
|
|
gateway.image.repository: gateway
|
|
asserts:
|
|
- equal:
|
|
path: spec.template.spec.containers[0].image
|
|
value: registry.example/gateway:0.0.0
|
|
|
|
- it: applies global image values to gateway, supervisor, and sandbox runtime
|
|
template: templates/gateway-config.yaml
|
|
set:
|
|
global.image.registry: registry.example.com
|
|
global.image.tag: v0.1.0
|
|
global.image.pullPolicy: IfNotPresent
|
|
asserts:
|
|
- matchRegex:
|
|
path: data["gateway.toml"]
|
|
pattern: 'supervisor_image\s*=\s*"registry\.example\.com/openshell/supervisor:v0\.1\.0"'
|
|
- matchRegex:
|
|
path: data["gateway.toml"]
|
|
pattern: 'sandbox_runtime_image\s*=\s*"registry\.example\.com/openshell/sandbox:v0\.1\.0"'
|
|
|
|
- it: lets an individual registry override the global registry
|
|
template: templates/statefulset.yaml
|
|
set:
|
|
global.image.registry: registry.example.com
|
|
gateway.image.registry: mirror.example.com
|
|
gateway.image.repository: custom/gateway
|
|
global.image.tag: v0.1.0
|
|
asserts:
|
|
- equal:
|
|
path: spec.template.spec.containers[0].image
|
|
value: mirror.example.com/custom/gateway:v0.1.0
|
|
|
|
- it: applies the global registry to the gateway image
|
|
template: templates/statefulset.yaml
|
|
set:
|
|
global.image.registry: registry.example.com
|
|
global.image.tag: v0.1.0
|
|
asserts:
|
|
- equal:
|
|
path: spec.template.spec.containers[0].image
|
|
value: registry.example.com/openshell/gateway:v0.1.0
|
|
|
|
- it: defaults a repository-only sandbox image to latest
|
|
template: templates/gateway-config.yaml
|
|
set:
|
|
sandbox.image.repository: registry.example/sandbox
|
|
sandbox.image.tag: ""
|
|
asserts:
|
|
- matchRegex:
|
|
path: data["gateway.toml"]
|
|
pattern: 'default_image\s*=\s*"registry\.example/sandbox:latest"'
|
|
|
|
- it: gives a gateway digest precedence over its tag
|
|
template: templates/statefulset.yaml
|
|
set:
|
|
gateway.image.registry: registry.example
|
|
gateway.image.repository: gateway
|
|
gateway.image.tag: ignored
|
|
gateway.image.digest: sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
|
|
asserts:
|
|
- equal:
|
|
path: spec.template.spec.containers[0].image
|
|
value: registry.example/gateway@sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
|
|
|
|
- it: gives supervisor and sandbox digests precedence over their tags
|
|
template: templates/gateway-config.yaml
|
|
set:
|
|
supervisor.image.registry: registry.example
|
|
supervisor.image.repository: supervisor
|
|
supervisor.image.tag: ignored
|
|
supervisor.image.digest: sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
|
|
sandbox.image.repository: registry.example/sandbox
|
|
sandbox.image.tag: ignored
|
|
sandbox.image.digest: sha256:bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
|
|
asserts:
|
|
- matchRegex:
|
|
path: data["gateway.toml"]
|
|
pattern: 'registry\.example/supervisor@sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa'
|
|
- matchRegex:
|
|
path: data["gateway.toml"]
|
|
pattern: 'default_image\s*=\s*"registry\.example/sandbox@sha256:bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"'
|
|
|
|
# Regression for Drew's P2: a ConfigMap-only mutation in `helm upgrade`
|
|
# must roll the StatefulSet, otherwise pods keep running with stale config.
|
|
- it: annotates the StatefulSet pod template with a ConfigMap checksum
|
|
template: templates/statefulset.yaml
|
|
asserts:
|
|
- exists:
|
|
path: spec.template.metadata.annotations["checksum/gateway-config"]
|
|
|
|
- it: mounts gateway.toml as a regular read-only subPath file
|
|
template: templates/statefulset.yaml
|
|
asserts:
|
|
- contains:
|
|
path: spec.template.spec.containers[0].volumeMounts
|
|
content:
|
|
name: gateway-config
|
|
mountPath: /etc/openshell/gateway.toml
|
|
subPath: gateway.toml
|
|
readOnly: true
|
|
- contains:
|
|
path: spec.template.spec.volumes
|
|
content:
|
|
name: gateway-config
|
|
configMap:
|
|
name: openshell-config
|
|
|
|
- it: renders a StatefulSet by default
|
|
template: templates/statefulset.yaml
|
|
asserts:
|
|
- equal:
|
|
path: kind
|
|
value: StatefulSet
|
|
|
|
- it: treats a null workload map as the default StatefulSet
|
|
template: templates/statefulset.yaml
|
|
set:
|
|
workload: null
|
|
asserts:
|
|
- equal:
|
|
path: kind
|
|
value: StatefulSet
|
|
|
|
- it: uses a stable gateway container name
|
|
template: templates/statefulset.yaml
|
|
asserts:
|
|
- equal:
|
|
path: spec.template.spec.containers[0].name
|
|
value: openshell-gateway
|
|
|
|
- it: enables anonymous telemetry by default
|
|
template: templates/statefulset.yaml
|
|
asserts:
|
|
- contains:
|
|
path: spec.template.spec.containers[0].env
|
|
content:
|
|
name: OPENSHELL_TELEMETRY_ENABLED
|
|
value: "true"
|
|
|
|
- it: disables anonymous telemetry when configured
|
|
template: templates/statefulset.yaml
|
|
set:
|
|
server.telemetryEnabled: false
|
|
asserts:
|
|
- contains:
|
|
path: spec.template.spec.containers[0].env
|
|
content:
|
|
name: OPENSHELL_TELEMETRY_ENABLED
|
|
value: "false"
|
|
|
|
- it: renders OTLP tracing configuration when configured
|
|
template: templates/gateway-config.yaml
|
|
set:
|
|
server.otlp.endpoint: http://otel-collector.observability.svc:4317
|
|
server.otlp.serviceName: production-gateway
|
|
asserts:
|
|
- matchRegex:
|
|
path: data["gateway.toml"]
|
|
pattern: '(?ms)\[openshell\.gateway\.otlp\].*?endpoint\s*=\s*"http://otel-collector\.observability\.svc:4317".*?service_name\s*=\s*"production-gateway"'
|
|
|
|
- it: omits OTLP tracing configuration by default
|
|
template: templates/gateway-config.yaml
|
|
asserts:
|
|
- notMatchRegex:
|
|
path: data["gateway.toml"]
|
|
pattern: '\[openshell\.gateway\.otlp\]'
|
|
|
|
- it: renders OIDC transport security settings
|
|
template: templates/gateway-config.yaml
|
|
set:
|
|
server.oidc.issuer: https://issuer.example.com
|
|
server.oidc.dangerouslyAllowInsecureHttp: false
|
|
server.oidc.jwksAllowedOrigins[0]: https://keys.example.com
|
|
asserts:
|
|
- matchRegex:
|
|
path: data["gateway.toml"]
|
|
pattern: '(?m)^dangerously_allow_insecure_http\s*=\s*false$'
|
|
- matchRegex:
|
|
path: data["gateway.toml"]
|
|
pattern: '(?m)^jwks_allowed_origins\s*=\s*\["https://keys.example.com"\]$'
|
|
|
|
- it: treats a null OTLP map as disabled
|
|
template: templates/gateway-config.yaml
|
|
set:
|
|
server.otlp: null
|
|
asserts:
|
|
- notMatchRegex:
|
|
path: data["gateway.toml"]
|
|
pattern: '\[openshell\.gateway\.otlp\]'
|
|
|
|
- it: omits OCSF JSONL output by default
|
|
template: templates/gateway-config.yaml
|
|
asserts:
|
|
- notMatchRegex:
|
|
path: data["gateway.toml"]
|
|
pattern: '\[openshell\.gateway\.ocsf_log\]'
|
|
|
|
- it: treats a null OCSF log map as disabled
|
|
template: templates/gateway-config.yaml
|
|
set:
|
|
server.ocsfLog: null
|
|
asserts:
|
|
- notMatchRegex:
|
|
path: data["gateway.toml"]
|
|
pattern: '\[openshell\.gateway\.ocsf_log\]'
|
|
|
|
- it: omits OCSF JSONL output when a path is set without enabling it
|
|
template: templates/gateway-config.yaml
|
|
set:
|
|
server.ocsfLog.path: /var/openshell/gateway-ocsf.jsonl
|
|
asserts:
|
|
- notMatchRegex:
|
|
path: data["gateway.toml"]
|
|
pattern: '\[openshell\.gateway\.ocsf_log\]'
|
|
|
|
- it: writes OCSF JSONL to the default container path when enabled
|
|
template: templates/gateway-config.yaml
|
|
set:
|
|
server.ocsfLog.enabled: true
|
|
asserts:
|
|
- matchRegex:
|
|
path: data["gateway.toml"]
|
|
pattern: '(?ms)\[openshell\.gateway\.ocsf_log\].*?path\s*=\s*"/tmp/gateway-ocsf\.jsonl"'
|
|
|
|
- it: rejects enabled OCSF JSONL output without a path
|
|
template: templates/statefulset.yaml
|
|
set:
|
|
server.ocsfLog.enabled: true
|
|
server.ocsfLog.path: ""
|
|
asserts:
|
|
- failedTemplate:
|
|
errorMessage: "server.ocsfLog.path must be set when server.ocsfLog.enabled is true"
|
|
|
|
- it: renders OCSF JSONL output when configured
|
|
template: templates/gateway-config.yaml
|
|
set:
|
|
server.ocsfLog.enabled: true
|
|
server.ocsfLog.path: /var/openshell/gateway-ocsf.jsonl
|
|
server.ocsfLog.schemaVersion: "1.3"
|
|
server.ocsfLog.rotation: daily
|
|
server.ocsfLog.maxFiles: 14
|
|
server.ocsfLog.queueCapacity: 20000
|
|
server.ocsfLog.queueMaxBytes: 33554432
|
|
asserts:
|
|
- matchRegex:
|
|
path: data["gateway.toml"]
|
|
pattern: '(?ms)\[openshell\.gateway\.ocsf_log\].*?path\s*=\s*"/var/openshell/gateway-ocsf\.jsonl".*?schema_version\s*=\s*"1\.3".*?rotation\s*=\s*"daily".*?max_files\s*=\s*14.*?queue_capacity\s*=\s*20000.*?queue_max_bytes\s*=\s*33554432'
|
|
|
|
- it: omits OCSF retention when rotation is disabled
|
|
template: templates/gateway-config.yaml
|
|
set:
|
|
server.ocsfLog.enabled: true
|
|
server.ocsfLog.path: /var/openshell/gateway-ocsf.jsonl
|
|
server.ocsfLog.rotation: never
|
|
asserts:
|
|
- matchRegex:
|
|
path: data["gateway.toml"]
|
|
pattern: '(?ms)\[openshell\.gateway\.ocsf_log\].*?rotation\s*=\s*"never"'
|
|
- notMatchRegex:
|
|
path: data["gateway.toml"]
|
|
pattern: 'max_files\s*='
|
|
|
|
- it: rejects an unsupported OCSF rotation mode
|
|
template: templates/statefulset.yaml
|
|
set:
|
|
server.ocsfLog.enabled: true
|
|
server.ocsfLog.path: /var/openshell/gateway-ocsf.jsonl
|
|
server.ocsfLog.rotation: hourly
|
|
asserts:
|
|
- failedTemplate:
|
|
errorMessage: "server.ocsfLog.rotation must be daily or never"
|
|
|
|
- it: rejects a zero OCSF queueCapacity
|
|
template: templates/statefulset.yaml
|
|
set:
|
|
server.ocsfLog.enabled: true
|
|
server.ocsfLog.queueCapacity: 0
|
|
asserts:
|
|
- failedTemplate:
|
|
errorMessage: "server.ocsfLog.queueCapacity and queueMaxBytes must be positive"
|
|
|
|
- it: rejects a zero OCSF queueMaxBytes
|
|
template: templates/statefulset.yaml
|
|
set:
|
|
server.ocsfLog.enabled: true
|
|
server.ocsfLog.queueMaxBytes: 0
|
|
asserts:
|
|
- failedTemplate:
|
|
errorMessage: "server.ocsfLog.queueCapacity and queueMaxBytes must be positive"
|
|
|
|
- it: rejects a zero OCSF maxFiles
|
|
template: templates/statefulset.yaml
|
|
set:
|
|
server.ocsfLog.enabled: true
|
|
server.ocsfLog.maxFiles: 0
|
|
asserts:
|
|
- failedTemplate:
|
|
errorMessage: "server.ocsfLog.maxFiles must be positive when rotation is daily"
|
|
|
|
- it: uses default OCSF limits when they are null
|
|
template: templates/gateway-config.yaml
|
|
set:
|
|
server.ocsfLog.enabled: true
|
|
server.ocsfLog.queueCapacity: null
|
|
server.ocsfLog.queueMaxBytes: null
|
|
server.ocsfLog.maxFiles: null
|
|
asserts:
|
|
- matchRegex:
|
|
path: data["gateway.toml"]
|
|
pattern: '(?ms)\[openshell\.gateway\.ocsf_log\].*?max_files\s*=\s*7.*?queue_capacity\s*=\s*10000.*?queue_max_bytes\s*=\s*16777216'
|
|
|
|
- it: rejects an unsupported OCSF schema version
|
|
template: templates/statefulset.yaml
|
|
set:
|
|
server.ocsfLog.enabled: true
|
|
server.ocsfLog.path: /var/openshell/gateway-ocsf.jsonl
|
|
server.ocsfLog.schemaVersion: "1.8"
|
|
asserts:
|
|
- failedTemplate:
|
|
errorMessage: "server.ocsfLog.schemaVersion must be empty, 1.1, or 1.3"
|
|
|
|
- it: mounts extra gateway volumes for OCSF output in a Deployment
|
|
template: templates/deployment.yaml
|
|
set:
|
|
workload.kind: deployment
|
|
server.externalDbSecret: my-pg-secret
|
|
server.ocsfLog.enabled: true
|
|
server.ocsfLog.path: /var/log/openshell/gateway-ocsf.jsonl
|
|
server.extraVolumes:
|
|
- name: ocsf-log
|
|
persistentVolumeClaim:
|
|
claimName: openshell-ocsf-log
|
|
server.extraVolumeMounts:
|
|
- name: ocsf-log
|
|
mountPath: /var/log/openshell
|
|
subPathExpr: $(OPENSHELL_POD_NAME)
|
|
asserts:
|
|
- contains:
|
|
path: spec.template.spec.volumes
|
|
content:
|
|
name: ocsf-log
|
|
persistentVolumeClaim:
|
|
claimName: openshell-ocsf-log
|
|
- contains:
|
|
path: spec.template.spec.containers[0].volumeMounts
|
|
content:
|
|
name: ocsf-log
|
|
mountPath: /var/log/openshell
|
|
subPathExpr: $(OPENSHELL_POD_NAME)
|
|
|
|
- it: mounts the OIDC CA bundle when TLS is disabled
|
|
template: templates/statefulset.yaml
|
|
set:
|
|
server.disableTls: true
|
|
server.oidc.issuer: https://issuer.example.com
|
|
server.oidc.caConfigMapName: openshell-oidc-ca
|
|
asserts:
|
|
- equal:
|
|
path: spec.template.spec.containers[0].volumeMounts[4].name
|
|
value: oidc-ca
|
|
- equal:
|
|
path: spec.template.spec.containers[0].volumeMounts[4].mountPath
|
|
value: /etc/openshell-tls/oidc-ca
|
|
- equal:
|
|
path: spec.template.spec.volumes[3].name
|
|
value: oidc-ca
|
|
- equal:
|
|
path: spec.template.spec.volumes[3].configMap.name
|
|
value: openshell-oidc-ca
|
|
|
|
# Regression for the P1 bug Drew flagged: grpc_endpoint MUST live in the
|
|
# Kubernetes driver table, not in [openshell.gateway]. The gateway-side
|
|
# schema has `deny_unknown_fields` and no `grpc_endpoint` field, so writing
|
|
# it at gateway scope makes `config_file::load` reject the default install.
|
|
- it: renders grpc_endpoint under [openshell.drivers.kubernetes], not [openshell.gateway]
|
|
template: templates/gateway-config.yaml
|
|
asserts:
|
|
- matchRegex:
|
|
path: data["gateway.toml"]
|
|
pattern: '(?ms)\[openshell\.drivers\.kubernetes\].*?grpc_endpoint'
|
|
- notMatchRegex:
|
|
path: data["gateway.toml"]
|
|
pattern: '(?ms)\[openshell\.gateway\][^\[]*?grpc_endpoint'
|
|
|
|
- it: applies the global pull policy by default
|
|
template: templates/gateway-config.yaml
|
|
asserts:
|
|
- matchRegex:
|
|
path: data["gateway.toml"]
|
|
pattern: '(?ms)supervisor_image_pull_policy\s*=\s*"if_not_present".*?sandbox_runtime_image_pull_policy\s*=\s*"if_not_present"'
|
|
|
|
- it: renders canonical image pull policies in the Kubernetes driver table
|
|
template: templates/gateway-config.yaml
|
|
set:
|
|
sandbox.image.pullPolicy: if_not_present
|
|
supervisor.image.pullPolicy: never
|
|
asserts:
|
|
- matchRegex:
|
|
path: data["gateway.toml"]
|
|
pattern: '(?ms)\[openshell\.drivers\.kubernetes\].*?image_pull_policy\s*=\s*"if_not_present".*?supervisor_image_pull_policy\s*=\s*"never"'
|
|
|
|
- it: translates legacy Kubernetes pull policy values to canonical gateway values
|
|
template: templates/gateway-config.yaml
|
|
set:
|
|
sandbox.image.pullPolicy: Always
|
|
supervisor.image.pullPolicy: IfNotPresent
|
|
asserts:
|
|
- matchRegex:
|
|
path: data["gateway.toml"]
|
|
pattern: '(?ms)\[openshell\.drivers\.kubernetes\].*?image_pull_policy\s*=\s*"always".*?supervisor_image_pull_policy\s*=\s*"if_not_present"'
|
|
|
|
- it: rejects unsupported sandbox image pull policies
|
|
template: templates/statefulset.yaml
|
|
set:
|
|
sandbox.image.pullPolicy: Sometimes
|
|
asserts:
|
|
- failedTemplate:
|
|
errorMessage: 'image pull policy "Sometimes" must be one of: always, if_not_present, never, Always, IfNotPresent, Never'
|
|
|
|
- it: rejects unsupported supervisor image pull policies
|
|
template: templates/statefulset.yaml
|
|
set:
|
|
supervisor.image.pullPolicy: newer
|
|
asserts:
|
|
- failedTemplate:
|
|
errorMessage: 'image pull policy "newer" must be one of: always, if_not_present, never, Always, IfNotPresent, Never'
|
|
|
|
- it: renders driver-owned Kubernetes settings only in its driver table
|
|
template: templates/gateway-config.yaml
|
|
set:
|
|
server.hostGatewayIP: 10.0.0.1
|
|
server.enableUserNamespaces: true
|
|
supervisor.image.tag: test
|
|
asserts:
|
|
- matchRegex:
|
|
path: data["gateway.toml"]
|
|
pattern: '(?ms)\[openshell\.drivers\.kubernetes\].*?namespace\s*=\s*"my-namespace".*?default_image\s*=.*?supervisor_image\s*=.*?host_gateway_ip\s*=\s*"10\.0\.0\.1".*?client_tls_secret_name\s*=.*?service_account_name\s*=\s*"openshell-sandbox".*?enable_user_namespaces\s*=\s*true.*?sa_token_ttl_secs\s*='
|
|
- notMatchRegex:
|
|
path: data["gateway.toml"]
|
|
pattern: '(?ms)\[openshell\.gateway\][^\[]*?(sandbox_namespace|default_image|supervisor_image|client_tls_secret_name|service_account_name|host_gateway_ip|enable_user_namespaces|sa_token_ttl_secs)\s*='
|
|
- notMatchRegex:
|
|
path: data["gateway.toml"]
|
|
pattern: 'guest_tls_(ca|cert|key)\s*='
|
|
|
|
- it: renders user namespace enablement under [openshell.drivers.kubernetes]
|
|
template: templates/gateway-config.yaml
|
|
set:
|
|
server.enableUserNamespaces: true
|
|
asserts:
|
|
- matchRegex:
|
|
path: data["gateway.toml"]
|
|
pattern: '(?ms)\[openshell\.drivers\.kubernetes\].*?enable_user_namespaces\s*=\s*true'
|
|
- notMatchRegex:
|
|
path: data["gateway.toml"]
|
|
pattern: '(?ms)\[openshell\.gateway\][^\[]*?enable_user_namespaces'
|
|
|
|
- it: renders operator-owned upstream proxy settings under the Kubernetes driver
|
|
template: templates/gateway-config.yaml
|
|
set:
|
|
upstreamProxy.url: http://proxy.corp.example:8080
|
|
upstreamProxy.noProxy: .svc.cluster.local,10.96.0.0/12
|
|
upstreamProxy.authSecret.name: corporate-proxy-auth
|
|
upstreamProxy.authSecret.key: credentials
|
|
upstreamProxy.authAllowInsecure: true
|
|
upstreamProxy.connectByHostname: true
|
|
asserts:
|
|
- matchRegex:
|
|
path: data["gateway.toml"]
|
|
pattern: '(?ms)\[openshell\.drivers\.kubernetes\].*?https_proxy\s*=\s*"http://proxy\.corp\.example:8080"'
|
|
- matchRegex:
|
|
path: data["gateway.toml"]
|
|
pattern: 'proxy_auth_secret_name\s*=\s*"corporate-proxy-auth"'
|
|
- matchRegex:
|
|
path: data["gateway.toml"]
|
|
pattern: 'proxy_auth_secret_key\s*=\s*"credentials"'
|
|
- matchRegex:
|
|
path: data["gateway.toml"]
|
|
pattern: 'proxy_auth_allow_insecure\s*=\s*true'
|
|
- matchRegex:
|
|
path: data["gateway.toml"]
|
|
pattern: 'no_proxy\s*=\s*"\.svc\.cluster\.local,10\.96\.0\.0/12"'
|
|
- matchRegex:
|
|
path: data["gateway.toml"]
|
|
pattern: 'proxy_connect_by_hostname\s*=\s*true'
|
|
|
|
- it: renders proxy_ca_bundle when a corporate CA ConfigMap is configured
|
|
template: templates/gateway-config.yaml
|
|
set:
|
|
upstreamProxy.url: https://proxy.corp.example:3130
|
|
upstreamProxy.caBundle.configMapName: corporate-proxy-ca
|
|
asserts:
|
|
- matchRegex:
|
|
path: data["gateway.toml"]
|
|
pattern: '(?ms)\[openshell\.drivers\.kubernetes\].*?proxy_ca_bundle\s*=\s*"/etc/openshell-tls/proxy-ca/ca\.crt"'
|
|
|
|
- it: omits proxy_ca_bundle when no corporate CA ConfigMap is configured
|
|
template: templates/gateway-config.yaml
|
|
set:
|
|
upstreamProxy.url: http://proxy.corp.example:8080
|
|
asserts:
|
|
- notMatchRegex:
|
|
path: data["gateway.toml"]
|
|
pattern: 'proxy_ca_bundle\s*='
|
|
|
|
- it: renders the default supervisor image
|
|
template: templates/gateway-config.yaml
|
|
asserts:
|
|
- matchRegex:
|
|
path: data["gateway.toml"]
|
|
pattern: 'supervisor_image\s*=\s*"ghcr\.io/nvidia/openshell/supervisor:0\.0\.0"'
|
|
|
|
- it: uses the gateway built-in sandbox runtime image by default
|
|
template: templates/gateway-config.yaml
|
|
asserts:
|
|
- notMatchRegex:
|
|
path: data["gateway.toml"]
|
|
pattern: 'sandbox_runtime_image\s*='
|
|
|
|
- it: renders independent sandbox runtime and supervisor image overrides
|
|
template: templates/gateway-config.yaml
|
|
set:
|
|
sandboxRuntime.image.registry: registry.example.com
|
|
sandboxRuntime.image.repository: openshell/sandbox
|
|
sandboxRuntime.image.tag: sandbox-build
|
|
sandboxRuntime.image.pullPolicy: Always
|
|
supervisor.image.registry: registry.example.com
|
|
supervisor.image.repository: openshell/supervisor
|
|
supervisor.image.tag: supervisor-build
|
|
asserts:
|
|
- matchRegex:
|
|
path: data["gateway.toml"]
|
|
pattern: 'sandbox_runtime_image\s*=\s*"registry\.example\.com/openshell/sandbox:sandbox-build"'
|
|
- matchRegex:
|
|
path: data["gateway.toml"]
|
|
pattern: 'sandbox_runtime_image_pull_policy\s*=\s*"always"'
|
|
- matchRegex:
|
|
path: data["gateway.toml"]
|
|
pattern: 'supervisor_image\s*=\s*"registry\.example\.com/openshell/supervisor:supervisor-build"'
|
|
|
|
- it: gives a sandbox runtime digest precedence over its tag
|
|
template: templates/gateway-config.yaml
|
|
set:
|
|
sandboxRuntime.image.registry: registry.example.com
|
|
sandboxRuntime.image.repository: openshell/sandbox
|
|
sandboxRuntime.image.tag: ignored
|
|
sandboxRuntime.image.digest: sha256:cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
|
|
asserts:
|
|
- matchRegex:
|
|
path: data["gateway.toml"]
|
|
pattern: 'sandbox_runtime_image\s*=\s*"registry\.example\.com/openshell/sandbox@sha256:cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc"'
|
|
|
|
- it: renders a supervisor tag override with the official repository
|
|
template: templates/gateway-config.yaml
|
|
set:
|
|
supervisor.image.tag: 1.2.3
|
|
asserts:
|
|
- matchRegex:
|
|
path: data["gateway.toml"]
|
|
pattern: 'supervisor_image\s*=\s*"ghcr\.io/nvidia/openshell/supervisor:1\.2\.3"'
|
|
|
|
- it: renders a supervisor repository override with the default chart tag
|
|
template: templates/gateway-config.yaml
|
|
set:
|
|
supervisor.image.registry: registry.example.com
|
|
supervisor.image.repository: openshell/supervisor
|
|
asserts:
|
|
- matchRegex:
|
|
path: data["gateway.toml"]
|
|
pattern: 'supervisor_image\s*=\s*"registry\.example\.com/openshell/supervisor:0\.0\.0"'
|
|
|
|
- it: renders complete supervisor repository and tag overrides
|
|
template: templates/gateway-config.yaml
|
|
set:
|
|
supervisor.image.registry: registry.example.com
|
|
supervisor.image.repository: openshell/supervisor
|
|
supervisor.image.tag: supervisor-build
|
|
asserts:
|
|
- matchRegex:
|
|
path: data["gateway.toml"]
|
|
pattern: 'supervisor_image\s*=\s*"registry\.example\.com/openshell/supervisor:supervisor-build"'
|
|
|
|
- it: configures managed SSH isolation with the gateway peer
|
|
template: templates/gateway-config.yaml
|
|
asserts:
|
|
- matchRegex:
|
|
path: data["gateway.toml"]
|
|
pattern: '(?ms)\[openshell\.drivers\.kubernetes\.managed_ssh_ingress\].*?enabled\s*=\s*true.*?gateway_namespace\s*=\s*"my-namespace".*?gateway_pod_selector\s*=.*?app\.kubernetes\.io/name.*?openshell'
|
|
|
|
- it: renders sandbox image pull secrets under [openshell.drivers.kubernetes]
|
|
template: templates/gateway-config.yaml
|
|
set:
|
|
server.sandboxImagePullSecrets:
|
|
- name: regcred
|
|
- name: backup-regcred
|
|
asserts:
|
|
- matchRegex:
|
|
path: data["gateway.toml"]
|
|
pattern: '(?ms)\[openshell\.drivers\.kubernetes\].*?image_pull_secrets\s*=\s*\["regcred", "backup-regcred"\]'
|
|
|
|
- it: does not reuse gateway image pull secrets for sandbox pods
|
|
template: templates/gateway-config.yaml
|
|
set:
|
|
imagePullSecrets:
|
|
- name: gateway-regcred
|
|
asserts:
|
|
- notMatchRegex:
|
|
path: data["gateway.toml"]
|
|
pattern: 'image_pull_secrets\s*='
|
|
|
|
- it: does not render local mTLS user auth for Kubernetes deployments
|
|
template: templates/gateway-config.yaml
|
|
asserts:
|
|
- notMatchRegex:
|
|
path: data["gateway.toml"]
|
|
pattern: '\[openshell\.gateway\.mtls_auth\]'
|
|
|
|
- it: does not allow unauthenticated users by default
|
|
template: templates/gateway-config.yaml
|
|
asserts:
|
|
- notMatchRegex:
|
|
path: data["gateway.toml"]
|
|
pattern: '\[openshell\.gateway\.auth\]'
|
|
|
|
- it: renders explicit unauthenticated user dev mode when enabled
|
|
template: templates/gateway-config.yaml
|
|
set:
|
|
server.auth.allowUnauthenticatedUsers: true
|
|
asserts:
|
|
- matchRegex:
|
|
path: data["gateway.toml"]
|
|
pattern: '(?ms)\[openshell\.gateway\.auth\].*?allow_unauthenticated_users\s*=\s*true'
|
|
|
|
- it: omits the gRPC rate limit by default
|
|
template: templates/gateway-config.yaml
|
|
asserts:
|
|
- notMatchRegex:
|
|
path: data["gateway.toml"]
|
|
pattern: 'grpc_rate_limit_requests\s*='
|
|
- notMatchRegex:
|
|
path: data["gateway.toml"]
|
|
pattern: 'grpc_rate_limit_window_seconds\s*='
|
|
|
|
- it: renders fail-closed policy validation posture by default
|
|
template: templates/gateway-config.yaml
|
|
asserts:
|
|
- matchRegex:
|
|
path: data["gateway.toml"]
|
|
pattern: '(?ms)\[openshell\.gateway\].*?policy_validation_failure_mode\s*=\s*"fail_closed"'
|
|
|
|
- it: renders retain-last-valid policy validation posture
|
|
template: templates/gateway-config.yaml
|
|
set:
|
|
server.policyValidationFailureMode: retain_last_valid
|
|
asserts:
|
|
- matchRegex:
|
|
path: data["gateway.toml"]
|
|
pattern: '(?ms)\[openshell\.gateway\].*?policy_validation_failure_mode\s*=\s*"retain_last_valid"'
|
|
|
|
- it: renders the gRPC rate limit under [openshell.gateway] when both values are positive
|
|
template: templates/gateway-config.yaml
|
|
set:
|
|
server.grpcRateLimit.requests: 120
|
|
server.grpcRateLimit.windowSeconds: 60
|
|
asserts:
|
|
- matchRegex:
|
|
path: data["gateway.toml"]
|
|
pattern: '(?ms)\[openshell\.gateway\].*?grpc_rate_limit_requests\s*=\s*120'
|
|
- matchRegex:
|
|
path: data["gateway.toml"]
|
|
pattern: '(?ms)\[openshell\.gateway\].*?grpc_rate_limit_window_seconds\s*=\s*60'
|
|
|
|
# The validation lives in gateway-config.yaml but surfaces through the
|
|
# statefulset checksum include, so the failure is asserted against that
|
|
# template (mirrors the postgres serviceBindings failure test below).
|
|
- it: fails to render when only requests is positive
|
|
template: templates/statefulset.yaml
|
|
set:
|
|
server.grpcRateLimit.requests: 120
|
|
asserts:
|
|
- failedTemplate:
|
|
errorMessage: "server.grpcRateLimit requires both requests and windowSeconds to be positive to enable rate limiting, or both 0/unset to disable it"
|
|
|
|
- it: fails to render when only windowSeconds is positive
|
|
template: templates/statefulset.yaml
|
|
set:
|
|
server.grpcRateLimit.windowSeconds: 60
|
|
asserts:
|
|
- failedTemplate:
|
|
errorMessage: "server.grpcRateLimit requires both requests and windowSeconds to be positive to enable rate limiting, or both 0/unset to disable it"
|
|
|
|
- it: fails to render when requests is negative
|
|
template: templates/statefulset.yaml
|
|
set:
|
|
server.grpcRateLimit.requests: -1
|
|
asserts:
|
|
- failedTemplate:
|
|
errorMessage: "server.grpcRateLimit.requests and server.grpcRateLimit.windowSeconds must not be negative; they map to unsigned gateway settings"
|
|
|
|
- it: fails to render when windowSeconds is negative
|
|
template: templates/statefulset.yaml
|
|
set:
|
|
server.grpcRateLimit.windowSeconds: -5
|
|
asserts:
|
|
- failedTemplate:
|
|
errorMessage: "server.grpcRateLimit.requests and server.grpcRateLimit.windowSeconds must not be negative; they map to unsigned gateway settings"
|
|
|
|
- it: uses the configured existing sandbox service account name
|
|
template: templates/gateway-config.yaml
|
|
set:
|
|
sandboxServiceAccount.create: false
|
|
sandboxServiceAccount.name: precreated-sandbox
|
|
asserts:
|
|
- matchRegex:
|
|
path: data["gateway.toml"]
|
|
pattern: '(?ms)\[openshell\.drivers\.kubernetes\].*?service_account_name\s*=\s*"precreated-sandbox"'
|
|
|
|
- it: omits server_sans when no DNS SANs are configured
|
|
template: templates/gateway-config.yaml
|
|
asserts:
|
|
- notMatchRegex:
|
|
path: data["gateway.toml"]
|
|
pattern: 'server_sans\s*='
|
|
|
|
- it: emits disable_tls=true and omits the [openshell.gateway.tls] section when disableTls is set
|
|
set:
|
|
server.disableTls: true
|
|
certManager.enabled: false
|
|
pkiInitJob.enabled: false
|
|
template: templates/gateway-config.yaml
|
|
asserts:
|
|
- matchRegex:
|
|
path: data["gateway.toml"]
|
|
pattern: 'disable_tls\s*=\s*true'
|
|
- notMatchRegex:
|
|
path: data["gateway.toml"]
|
|
pattern: '\[openshell\.gateway\.tls\]'
|
|
|
|
- it: omits client_ca_path from the TLS section when mTLS is disabled
|
|
template: templates/gateway-config.yaml
|
|
set:
|
|
server.tls.enableMtls: false
|
|
asserts:
|
|
- matchRegex:
|
|
path: data["gateway.toml"]
|
|
pattern: '\[openshell\.gateway\.tls\]'
|
|
- matchRegex:
|
|
path: data["gateway.toml"]
|
|
pattern: 'cert_path\s*='
|
|
- matchRegex:
|
|
path: data["gateway.toml"]
|
|
pattern: 'key_path\s*='
|
|
- notMatchRegex:
|
|
path: data["gateway.toml"]
|
|
pattern: 'client_ca_path\s*='
|
|
|
|
- it: renders client_ca_path for built-in PKI by default
|
|
template: templates/gateway-config.yaml
|
|
asserts:
|
|
- matchRegex:
|
|
path: data["gateway.toml"]
|
|
pattern: 'client_ca_path\s*=\s*"/etc/openshell-tls/client-ca/ca\.crt"'
|
|
|
|
- it: omits client_ca_path when clientCaSecretName is empty for HTTPS-only mode
|
|
template: templates/gateway-config.yaml
|
|
set:
|
|
server.tls.clientCaSecretName: ""
|
|
asserts:
|
|
- matchRegex:
|
|
path: data["gateway.toml"]
|
|
pattern: '\[openshell\.gateway\.tls\]'
|
|
- matchRegex:
|
|
path: data["gateway.toml"]
|
|
pattern: 'cert_path\s*=\s*"/etc/openshell-tls/server/tls\.crt"'
|
|
- matchRegex:
|
|
path: data["gateway.toml"]
|
|
pattern: 'key_path\s*=\s*"/etc/openshell-tls/server/tls\.key"'
|
|
- notMatchRegex:
|
|
path: data["gateway.toml"]
|
|
pattern: 'client_ca_path\s*='
|
|
|
|
- it: keeps external server certificate fields when clientCaSecretName is empty
|
|
template: templates/gateway-config.yaml
|
|
set:
|
|
certManager.enabled: true
|
|
certManager.serverIssuerRef.name: letsencrypt-prod
|
|
certManager.serverDnsNames:
|
|
- gateway.example.com
|
|
server.tls.clientCaSecretName: ""
|
|
asserts:
|
|
- notMatchRegex:
|
|
path: data["gateway.toml"]
|
|
pattern: 'client_ca_path\s*='
|
|
- matchRegex:
|
|
path: data["gateway.toml"]
|
|
pattern: 'external_cert_path\s*=\s*"/etc/openshell-tls/server-external/tls\.crt"'
|
|
- matchRegex:
|
|
path: data["gateway.toml"]
|
|
pattern: 'external_key_path\s*=\s*"/etc/openshell-tls/server-external/tls\.key"'
|
|
- matchRegex:
|
|
path: data["gateway.toml"]
|
|
pattern: 'external_server_names\s*=\s*\["gateway\.example\.com"\]'
|
|
|
|
- it: omits client_ca_path for cert-manager shared CA when clientCaSecretName is empty
|
|
template: templates/gateway-config.yaml
|
|
set:
|
|
certManager.enabled: true
|
|
certManager.clientCaFromServerTlsSecret: true
|
|
server.tls.clientCaSecretName: ""
|
|
asserts:
|
|
- matchRegex:
|
|
path: data["gateway.toml"]
|
|
pattern: '\[openshell\.gateway\.tls\]'
|
|
- matchRegex:
|
|
path: data["gateway.toml"]
|
|
pattern: 'cert_path\s*=\s*"/etc/openshell-tls/server/tls\.crt"'
|
|
- matchRegex:
|
|
path: data["gateway.toml"]
|
|
pattern: 'key_path\s*=\s*"/etc/openshell-tls/server/tls\.key"'
|
|
- notMatchRegex:
|
|
path: data["gateway.toml"]
|
|
pattern: 'client_ca_path\s*='
|
|
|
|
- it: omits client_ca_path when cert-manager owns TLS and no client CA secret is set
|
|
template: templates/gateway-config.yaml
|
|
set:
|
|
certManager.enabled: true
|
|
certManager.clientCaFromServerTlsSecret: false
|
|
pkiInitJob.enabled: true
|
|
server.tls.clientCaSecretName: ""
|
|
asserts:
|
|
- matchRegex:
|
|
path: data["gateway.toml"]
|
|
pattern: '\[openshell\.gateway\.tls\]'
|
|
- matchRegex:
|
|
path: data["gateway.toml"]
|
|
pattern: 'cert_path\s*=\s*"/etc/openshell-tls/server/tls\.crt"'
|
|
- matchRegex:
|
|
path: data["gateway.toml"]
|
|
pattern: 'key_path\s*=\s*"/etc/openshell-tls/server/tls\.key"'
|
|
- notMatchRegex:
|
|
path: data["gateway.toml"]
|
|
pattern: 'client_ca_path\s*='
|
|
|
|
- it: renders server_sans from certManager.serverDnsNames
|
|
set:
|
|
certManager.enabled: true
|
|
certManager.serverDnsNames:
|
|
- openshell
|
|
- "*.dev.openshell.localhost"
|
|
pkiInitJob.enabled: false
|
|
template: templates/gateway-config.yaml
|
|
asserts:
|
|
- matchRegex:
|
|
path: data["gateway.toml"]
|
|
pattern: 'server_sans\s*=\s*\["openshell", "\*\.dev\.openshell\.localhost"\]'
|
|
|
|
- it: passes sqlite db-url via --db-url arg by default
|
|
template: templates/statefulset.yaml
|
|
asserts:
|
|
- contains:
|
|
path: spec.template.spec.containers[0].args
|
|
content: "sqlite:/var/openshell/openshell.db"
|
|
|
|
- it: fails when clientCaSecretName is null
|
|
template: templates/statefulset.yaml
|
|
set:
|
|
server.tls.clientCaSecretName: null
|
|
asserts:
|
|
- failedTemplate:
|
|
errorMessage: "server.tls.clientCaSecretName cannot be null; omit the key to use the chart default (openshell-server-client-ca), or set to \"\" to disable client certificate verification for HTTPS-only mode"
|
|
|
|
- it: configures gateway peer identity and projected peer token
|
|
template: templates/statefulset.yaml
|
|
asserts:
|
|
- equal:
|
|
path: spec.serviceName
|
|
value: openshell-peer
|
|
- contains:
|
|
path: spec.template.spec.containers[0].env
|
|
content:
|
|
name: OPENSHELL_PEER_SERVICE_NAME
|
|
value: openshell-peer
|
|
- contains:
|
|
path: spec.template.spec.containers[0].env
|
|
content:
|
|
name: OPENSHELL_PEER_TOKEN_AUDIENCE
|
|
value: openshell-gateway-peer
|
|
- contains:
|
|
path: spec.template.spec.containers[0].env
|
|
content:
|
|
name: OPENSHELL_PEER_TLS_SERVER_NAME
|
|
value: openshell.my-namespace.svc.cluster.local
|
|
- contains:
|
|
path: spec.template.spec.containers[0].env
|
|
content:
|
|
name: OPENSHELL_PEER_TLS_CA_FILE
|
|
value: /etc/openshell-tls/server/ca.crt
|
|
- contains:
|
|
path: spec.template.spec.containers[0].env
|
|
content:
|
|
name: OPENSHELL_PEER_TLS_CERT_FILE
|
|
value: /etc/openshell-tls/peer-client/tls.crt
|
|
- contains:
|
|
path: spec.template.spec.containers[0].env
|
|
content:
|
|
name: OPENSHELL_PEER_TLS_KEY_FILE
|
|
value: /etc/openshell-tls/peer-client/tls.key
|
|
- contains:
|
|
path: spec.template.spec.containers[0].volumeMounts
|
|
content:
|
|
name: gateway-peer-token
|
|
mountPath: /var/run/secrets/openshell-peer
|
|
readOnly: true
|
|
- contains:
|
|
path: spec.template.spec.containers[0].volumeMounts
|
|
content:
|
|
name: peer-client-tls
|
|
mountPath: /etc/openshell-tls/peer-client
|
|
readOnly: true
|
|
- equal:
|
|
path: spec.template.spec.volumes[2].projected.sources[0].serviceAccountToken.audience
|
|
value: openshell-gateway-peer
|
|
|
|
- it: configures gateway peer identity and projected peer token for Deployment
|
|
template: templates/deployment.yaml
|
|
set:
|
|
workload.kind: deployment
|
|
server.externalDbSecret: my-pg-secret
|
|
server.disableTls: true
|
|
asserts:
|
|
- contains:
|
|
path: spec.template.spec.containers[0].env
|
|
content:
|
|
name: OPENSHELL_POD_IP
|
|
valueFrom:
|
|
fieldRef:
|
|
fieldPath: status.podIP
|
|
- contains:
|
|
path: spec.template.spec.containers[0].env
|
|
content:
|
|
name: OPENSHELL_PEER_ENDPOINT
|
|
value: http://$(OPENSHELL_POD_IP):8080
|
|
- contains:
|
|
path: spec.template.spec.containers[0].env
|
|
content:
|
|
name: OPENSHELL_PEER_SERVICE_NAME
|
|
value: openshell-peer
|
|
- contains:
|
|
path: spec.template.spec.containers[0].env
|
|
content:
|
|
name: OPENSHELL_PEER_TOKEN_AUDIENCE
|
|
value: openshell-gateway-peer
|
|
- contains:
|
|
path: spec.template.spec.containers[0].volumeMounts
|
|
content:
|
|
name: gateway-peer-token
|
|
mountPath: /var/run/secrets/openshell-peer
|
|
readOnly: true
|
|
- equal:
|
|
path: spec.template.spec.volumes[2].projected.sources[0].serviceAccountToken.audience
|
|
value: openshell-gateway-peer
|
|
|
|
- it: renders headless gateway peer service
|
|
template: templates/peer-service.yaml
|
|
asserts:
|
|
- equal:
|
|
path: metadata.name
|
|
value: openshell-peer
|
|
- equal:
|
|
path: spec.clusterIP
|
|
value: None
|
|
- equal:
|
|
path: spec.publishNotReadyAddresses
|
|
value: true
|
|
|
|
- it: grants release-namespace pod lookup for gateway peer identity validation
|
|
template: templates/peer-role.yaml
|
|
asserts:
|
|
- hasDocuments:
|
|
count: 2
|
|
- equal:
|
|
path: metadata.namespace
|
|
value: my-namespace
|
|
documentIndex: 0
|
|
- equal:
|
|
path: rules[0].resources[0]
|
|
value: pods
|
|
documentIndex: 0
|
|
- equal:
|
|
path: subjects[0].name
|
|
value: openshell
|
|
documentIndex: 1
|
|
|
|
- it: fails when legacy postgres.enabled is set
|
|
template: templates/statefulset.yaml
|
|
set:
|
|
postgres.enabled: true
|
|
asserts:
|
|
- failedTemplate:
|
|
errorPattern: "postgres.enabled was removed"
|
|
|
|
- it: fails when multiple replicas use the default SQLite database
|
|
template: templates/statefulset.yaml
|
|
set:
|
|
replicaCount: 2
|
|
asserts:
|
|
- failedTemplate:
|
|
errorPattern: "replicaCount > 1 requires server.externalDbSecret"
|
|
|
|
- it: renders a Deployment for external database-backed gateway workloads
|
|
template: templates/deployment.yaml
|
|
set:
|
|
workload.kind: deployment
|
|
replicaCount: 2
|
|
server.externalDbSecret: my-pg-secret
|
|
server.credentialDrivers.kubernetesSecrets.enabled: true
|
|
asserts:
|
|
- equal:
|
|
path: kind
|
|
value: Deployment
|
|
- equal:
|
|
path: spec.replicas
|
|
value: 2
|
|
- equal:
|
|
path: spec.template.spec.containers[0].name
|
|
value: openshell-gateway
|
|
- contains:
|
|
path: spec.template.spec.containers[0].volumeMounts
|
|
content:
|
|
name: gateway-config
|
|
mountPath: /etc/openshell/gateway.toml
|
|
subPath: gateway.toml
|
|
readOnly: true
|
|
- notContains:
|
|
path: spec.template.spec.containers[0].volumeMounts
|
|
content:
|
|
name: openshell-data
|
|
mountPath: /var/openshell
|
|
- contains:
|
|
path: spec.template.spec.containers[0].env
|
|
content:
|
|
name: OPENSHELL_DB_URL
|
|
valueFrom:
|
|
secretKeyRef:
|
|
name: my-pg-secret
|
|
key: uri
|
|
|
|
- it: fails when a Deployment uses the default SQLite database
|
|
template: templates/statefulset.yaml
|
|
set:
|
|
workload.kind: deployment
|
|
asserts:
|
|
- failedTemplate:
|
|
errorPattern: "workload.kind=deployment requires server.externalDbSecret"
|
|
|
|
- it: fails when multiple replicas use a StatefulSet without an override
|
|
template: templates/statefulset.yaml
|
|
set:
|
|
replicaCount: 2
|
|
server.externalDbSecret: my-pg-secret
|
|
asserts:
|
|
- failedTemplate:
|
|
errorPattern: "replicaCount > 1 with workload.kind=statefulset requires workload.allowMultiReplicaStatefulSet=true"
|
|
|
|
- it: allows a multi-replica StatefulSet with an external database and explicit override
|
|
template: templates/statefulset.yaml
|
|
set:
|
|
replicaCount: 2
|
|
server.externalDbSecret: my-pg-secret
|
|
workload.allowMultiReplicaStatefulSet: true
|
|
server.credentialDrivers.kubernetesSecrets.enabled: true
|
|
asserts:
|
|
- equal:
|
|
path: kind
|
|
value: StatefulSet
|
|
- equal:
|
|
path: spec.replicas
|
|
value: 2
|
|
- contains:
|
|
path: spec.template.spec.containers[0].volumeMounts
|
|
content:
|
|
name: openshell-data
|
|
mountPath: /var/openshell
|
|
|
|
- it: fails when workload.kind is invalid
|
|
template: templates/statefulset.yaml
|
|
set:
|
|
workload.kind: daemonset
|
|
asserts:
|
|
- failedTemplate:
|
|
errorPattern: "workload.kind must be one of: statefulset, deployment"
|
|
|
|
- it: does not pass --db-url in args when externalDbSecret is set
|
|
template: templates/statefulset.yaml
|
|
set:
|
|
server.externalDbSecret: my-pg-secret
|
|
asserts:
|
|
- notContains:
|
|
path: spec.template.spec.containers[0].args
|
|
content: "--db-url"
|
|
|
|
- it: references externalDbSecret when set
|
|
template: templates/statefulset.yaml
|
|
set:
|
|
server.externalDbSecret: my-pg-secret
|
|
asserts:
|
|
- contains:
|
|
path: spec.template.spec.containers[0].env
|
|
content:
|
|
name: OPENSHELL_DB_URL
|
|
valueFrom:
|
|
secretKeyRef:
|
|
name: my-pg-secret
|
|
key: uri
|
|
|
|
- it: renders HA external database configuration from the CI overlay as a Deployment
|
|
template: templates/deployment.yaml
|
|
values:
|
|
- ../ci/values-high-availability.yaml
|
|
asserts:
|
|
- equal:
|
|
path: kind
|
|
value: Deployment
|
|
- equal:
|
|
path: spec.replicas
|
|
value: 2
|
|
- contains:
|
|
path: spec.template.spec.containers[0].env
|
|
content:
|
|
name: OPENSHELL_DB_URL
|
|
valueFrom:
|
|
secretKeyRef:
|
|
name: openshell-ha-pg
|
|
key: uri
|
|
- it: renders provider SPIFFE token grants while keeping gateway JWT auth
|
|
set:
|
|
server.providerTokenGrants.spiffe.enabled: true
|
|
template: templates/gateway-config.yaml
|
|
asserts:
|
|
- matchRegex:
|
|
path: data["gateway.toml"]
|
|
pattern: '\[openshell\.gateway\.gateway_jwt\]'
|
|
- matchRegex:
|
|
path: data["gateway.toml"]
|
|
pattern: 'provider_spiffe_workload_api_socket_path\s*=\s*"/spiffe-workload-api/spire-agent\.sock"'
|
|
- notMatchRegex:
|
|
path: data["gateway.toml"]
|
|
pattern: '\[openshell\.gateway\.spiffe\]'
|
|
|
|
- it: mounts the gateway SPIFFE socket while keeping sandbox JWT auth
|
|
set:
|
|
server.providerTokenGrants.spiffe.enabled: true
|
|
template: templates/statefulset.yaml
|
|
asserts:
|
|
- contains:
|
|
path: spec.template.spec.containers[0].env
|
|
content:
|
|
name: OPENSHELL_GATEWAY_SPIFFE_WORKLOAD_API_SOCKET
|
|
value: /spiffe-workload-api/spire-agent.sock
|
|
- contains:
|
|
path: spec.template.spec.containers[0].volumeMounts
|
|
content:
|
|
name: sandbox-jwt
|
|
mountPath: /etc/openshell-jwt
|
|
readOnly: true
|
|
- contains:
|
|
path: spec.template.spec.containers[0].volumeMounts
|
|
content:
|
|
name: spiffe-workload-api
|
|
mountPath: /spiffe-workload-api
|
|
readOnly: true
|
|
- contains:
|
|
path: spec.template.spec.volumes
|
|
content:
|
|
name: sandbox-jwt
|
|
secret:
|
|
defaultMode: 256
|
|
secretName: openshell-jwt-keys
|
|
- contains:
|
|
path: spec.template.spec.volumes
|
|
content:
|
|
name: spiffe-workload-api
|
|
csi:
|
|
driver: csi.spiffe.io
|
|
readOnly: true
|
|
|
|
- it: fails when serverIssuerRef is set but certManager is disabled
|
|
template: templates/statefulset.yaml
|
|
set:
|
|
certManager.serverIssuerRef.name: letsencrypt-prod
|
|
certManager.enabled: false
|
|
asserts:
|
|
- failedTemplate:
|
|
errorMessage: "certManager.serverIssuerRef.name is set but certManager.enabled is false \u2014 the external server certificate, its Secret mount, and the gateway TLS configuration all require cert-manager to be enabled. Set certManager.enabled=true or remove certManager.serverIssuerRef.name."
|
|
|
|
- it: does not render the schema-v1 compute_drivers selector
|
|
template: templates/gateway-config.yaml
|
|
asserts:
|
|
- notMatchRegex:
|
|
path: data["gateway.toml"]
|
|
pattern: "(?m)^\\s*compute_drivers\\s*="
|
|
|
|
- it: uses the release fullname and namespace for default Kubernetes settings
|
|
template: templates/gateway-config.yaml
|
|
asserts:
|
|
- matchRegex:
|
|
path: data["gateway.toml"]
|
|
pattern: "(?ms)name\\s*=\\s*\\\"openshell\\\".*?\\[openshell\\.drivers\\.kubernetes\\].*?namespace\\s*=\\s*\\\"my-namespace\\\".*?grpc_endpoint\\s*=\\s*\\\"https://openshell\\.my-namespace\\.svc\\.cluster\\.local:8080\\\""
|
|
|
|
- it: uses an explicit server grpc endpoint verbatim
|
|
template: templates/gateway-config.yaml
|
|
set:
|
|
server.grpcEndpoint: https://gateway.example.test:9443
|
|
asserts:
|
|
- matchRegex:
|
|
path: data["gateway.toml"]
|
|
pattern: "(?ms)\\[openshell\\.drivers\\.kubernetes\\].*?grpc_endpoint\\s*=\\s*\\\"https://gateway\\.example\\.test:9443\\\""
|
|
|
|
- it: uses HTTP callback and omits client TLS secret when TLS is disabled
|
|
template: templates/gateway-config.yaml
|
|
set:
|
|
server.disableTls: true
|
|
asserts:
|
|
- matchRegex:
|
|
path: data["gateway.toml"]
|
|
pattern: "(?ms)\\[openshell\\.drivers\\.kubernetes\\].*?grpc_endpoint\\s*=\\s*\\\"http://openshell\\.my-namespace\\.svc\\.cluster\\.local:8080\\\""
|
|
- notMatchRegex:
|
|
path: data["gateway.toml"]
|
|
pattern: "client_tls_secret_name\\s*="
|
|
|
|
- it: accepts Always pull policy spelling for sandbox and supervisor
|
|
template: templates/gateway-config.yaml
|
|
set:
|
|
sandbox.image.pullPolicy: Always
|
|
supervisor.image.pullPolicy: Always
|
|
asserts:
|
|
- matchRegex:
|
|
path: data["gateway.toml"]
|
|
pattern: "(?ms)\\[openshell\\.drivers\\.kubernetes\\].*?image_pull_policy\\s*=\\s*\\\"always\\\".*?supervisor_image_pull_policy\\s*=\\s*\\\"always\\\""
|
|
|
|
- it: accepts IfNotPresent pull policy spelling for sandbox and supervisor
|
|
template: templates/gateway-config.yaml
|
|
set:
|
|
sandbox.image.pullPolicy: IfNotPresent
|
|
supervisor.image.pullPolicy: IfNotPresent
|
|
asserts:
|
|
- matchRegex:
|
|
path: data["gateway.toml"]
|
|
pattern: "(?ms)\\[openshell\\.drivers\\.kubernetes\\].*?image_pull_policy\\s*=\\s*\\\"if_not_present\\\".*?supervisor_image_pull_policy\\s*=\\s*\\\"if_not_present\\\""
|
|
|
|
- it: accepts Never pull policy spelling for sandbox and supervisor
|
|
template: templates/gateway-config.yaml
|
|
set:
|
|
sandbox.image.pullPolicy: Never
|
|
supervisor.image.pullPolicy: Never
|
|
asserts:
|
|
- matchRegex:
|
|
path: data["gateway.toml"]
|
|
pattern: "(?ms)\\[openshell\\.drivers\\.kubernetes\\].*?image_pull_policy\\s*=\\s*\\\"never\\\".*?supervisor_image_pull_policy\\s*=\\s*\\\"never\\\""
|
|
|
|
- it: accepts canonical lowercase pull policies for sandbox and supervisor
|
|
template: templates/gateway-config.yaml
|
|
set:
|
|
sandbox.image.pullPolicy: always
|
|
supervisor.image.pullPolicy: always
|
|
asserts:
|
|
- matchRegex:
|
|
path: data["gateway.toml"]
|
|
pattern: "(?ms)\\[openshell\\.drivers\\.kubernetes\\].*?image_pull_policy\\s*=\\s*\\\"always\\\".*?supervisor_image_pull_policy\\s*=\\s*\\\"always\\\""
|
|
|
|
- it: accepts canonical if_not_present pull policies for sandbox and supervisor
|
|
template: templates/gateway-config.yaml
|
|
set:
|
|
sandbox.image.pullPolicy: if_not_present
|
|
supervisor.image.pullPolicy: if_not_present
|
|
asserts:
|
|
- matchRegex:
|
|
path: data["gateway.toml"]
|
|
pattern: "(?ms)\\[openshell\\.drivers\\.kubernetes\\].*?image_pull_policy\\s*=\\s*\\\"if_not_present\\\".*?supervisor_image_pull_policy\\s*=\\s*\\\"if_not_present\\\""
|
|
|
|
- it: accepts canonical never pull policies for sandbox and supervisor
|
|
template: templates/gateway-config.yaml
|
|
set:
|
|
sandbox.image.pullPolicy: never
|
|
supervisor.image.pullPolicy: never
|
|
asserts:
|
|
- matchRegex:
|
|
path: data["gateway.toml"]
|
|
pattern: "(?ms)\\[openshell\\.drivers\\.kubernetes\\].*?image_pull_policy\\s*=\\s*\\\"never\\\".*?supervisor_image_pull_policy\\s*=\\s*\\\"never\\\""
|
|
|
|
- it: gives the gateway time to finish shutdown by default
|
|
template: templates/statefulset.yaml
|
|
asserts:
|
|
- equal:
|
|
path: spec.template.spec.terminationGracePeriodSeconds
|
|
value: 30
|
|
|
|
- it: keeps an explicit termination grace period
|
|
template: templates/statefulset.yaml
|
|
set:
|
|
podLifecycle.terminationGracePeriodSeconds: 3
|
|
asserts:
|
|
- equal:
|
|
path: spec.template.spec.terminationGracePeriodSeconds
|
|
value: 3
|