diff --git a/helm/litellm/templates/NOTES.txt b/helm/litellm/templates/NOTES.txt index 468cf621b32..135dcb876e0 100644 --- a/helm/litellm/templates/NOTES.txt +++ b/helm/litellm/templates/NOTES.txt @@ -22,12 +22,14 @@ Reminders: - database.writer.{host,port,dbname} (writer connection pieces) - database.writer.passwordSecret.{name,usernameKey,passwordKey} (Secret holding the writer DB username + password) + - database.writer.passwordSecret.{hostKey,urlKey} (optional, read writer host or full URL from that Secret) - database.writer.useIAMAuth: true (optional — chart sets IAM_TOKEN_DB_AUTH=true and omits DATABASE_PASSWORD / DATABASE_URL so the proxy mints the URL from an IAM token at startup) - - database.reader.host (optional — enables read-replica routing; reader - .passwordSecret.name is required when set, unless - .useIAMAuth is true) + - database.reader.host / database.reader.passwordSecret.{hostKey,urlKey} + (optional, enables read-replica routing. The Secret + name is required for hostKey, urlKey, and for host + with password auth) - database.reader.useIAMAuth: true (optional, requires database.writer.useIAMAuth: true — chart emits DATABASE_*_READ_REPLICA env vars and omits DATABASE_PASSWORD_READ_REPLICA / @@ -35,6 +37,8 @@ Reminders: reader URL from an IAM token at startup) - redis.passwordSecret.name (optional — set when redis.host is provided and the cache requires auth) + - redis.passwordSecret.{hostKey,urlKey} (optional, read Redis host or full URL from that Secret) + (the Secret name is required when either key is set) - redis.cluster: true (optional — chart sets REDIS_CLUSTER_NODES from redis.host / redis.port so the proxy's Cache() constructs a RedisClusterCache; the cluster client diff --git a/helm/litellm/templates/_helpers.tpl b/helm/litellm/templates/_helpers.tpl index 294ce90c907..3a073b1444f 100644 --- a/helm/litellm/templates/_helpers.tpl +++ b/helm/litellm/templates/_helpers.tpl @@ -202,7 +202,9 @@ the chart context (needed for .Values), `component` selects which component's Sensitive values (master key, DB username + password, Redis password) come only from referenced Secrets; the chart never accepts inline values for them. -The chart never assembles DATABASE_URL itself. It emits only the discrete +The chart never assembles DATABASE_URL itself. When +database.writer.passwordSecret.urlKey is set, the chart passes the operator's +URL through from the Secret. Otherwise it emits only the discrete DATABASE_HOST/PORT/USER/NAME/SCHEMA (+ DATABASE_PASSWORD for password auth) vars; the proxy's entrypoint (DatabaseURLSettings in litellm/proxy/db/db_url_settings.py) builds the URL from them and @@ -218,11 +220,11 @@ token instead of a static password. `database.writer.useAzureEntraAuth: true` does the same with AZURE_POSTGRESQL_AUTH=true and a Microsoft Entra ID token, for Azure Database for PostgreSQL. The two are mutually exclusive. -The read replica is opt-in via `database.reader.host`. The chart emits -DATABASE_HOST_READ_REPLICA / DATABASE_PORT_READ_REPLICA / -DATABASE_NAME_READ_REPLICA (+ DATABASE_SCHEMA_READ_REPLICA) for both auth -modes, plus DATABASE_USER_READ_REPLICA / DATABASE_PASSWORD_READ_REPLICA for -password auth. When `database.reader.useIAMAuth: true` (or +The read replica is opt-in via `database.reader.host`, `hostKey`, or `urlKey`. +The chart emits DATABASE_URL_READ_REPLICA alone for urlKey, or discrete +DATABASE_*_READ_REPLICA vars for host and hostKey configurations, plus +DATABASE_USER_READ_REPLICA / DATABASE_PASSWORD_READ_REPLICA for password auth. +When `database.reader.useIAMAuth: true` (or `database.reader.useAzureEntraAuth: true`) it omits DATABASE_PASSWORD_READ_REPLICA and the entrypoint mints the reader URL the same way. Reader token auth only takes effect when the writer uses the same @@ -242,8 +244,36 @@ IAM_TOKEN_DB_AUTH / AZURE_POSTGRESQL_AUTH toggle that only the writer sets. value: {{ $component.logLevel | quote }} {{- end }} {{- with $root.Values.database.writer }} +{{- $hostKey := .passwordSecret.hostKey }} +{{- $urlKey := .passwordSecret.urlKey }} +{{- if and .host $hostKey }} +{{- fail "database.writer.host and database.writer.passwordSecret.hostKey cannot both be set" }} +{{- end }} +{{- if and $urlKey (or .host $hostKey .dbname .schema .sslMode .sslRootCert .useIAMAuth .useAzureEntraAuth) }} +{{- fail "database.writer.passwordSecret.urlKey cannot be combined with host, hostKey, dbname, schema, sslMode, sslRootCert, useIAMAuth, or useAzureEntraAuth" }} +{{- end }} +{{- if and (not .host) (not $hostKey) (not $urlKey) }} +{{- fail "one of database.writer.host, database.writer.passwordSecret.hostKey, or database.writer.passwordSecret.urlKey is required" }} +{{- end }} +{{- if and (or $hostKey $urlKey) (not .passwordSecret.name) }} +{{- fail "database.writer.passwordSecret.name is required when hostKey or urlKey is set" }} +{{- end }} +{{- if $urlKey }} +- name: DATABASE_URL + valueFrom: + secretKeyRef: + name: {{ .passwordSecret.name | quote }} + key: {{ $urlKey | quote }} +{{- else }} - name: DATABASE_HOST - value: {{ required "database.writer.host is required" .host | quote }} + {{- if $hostKey }} + valueFrom: + secretKeyRef: + name: {{ .passwordSecret.name | quote }} + key: {{ $hostKey | quote }} + {{- else }} + value: {{ .host | quote }} + {{- end }} - name: DATABASE_PORT value: {{ .port | default 5432 | quote }} - name: DATABASE_USER @@ -282,8 +312,29 @@ IAM_TOKEN_DB_AUTH / AZURE_POSTGRESQL_AUTH toggle that only the writer sets. key: {{ .passwordSecret.passwordKey | default "password" }} {{- end }} {{- end }} +{{- end }} {{- with $root.Values.database.reader }} -{{- if .host }} +{{- $readerSecret := .passwordSecret | default dict }} +{{- $hostKey := $readerSecret.hostKey }} +{{- $urlKey := $readerSecret.urlKey }} +{{- $readerEnabled := or .host $hostKey $urlKey }} +{{- if and .host $hostKey }} +{{- fail "database.reader.host and database.reader.passwordSecret.hostKey cannot both be set" }} +{{- end }} +{{- if and $urlKey (or .host $hostKey .dbname .schema .useIAMAuth .useAzureEntraAuth) }} +{{- fail "database.reader.passwordSecret.urlKey cannot be combined with host, hostKey, dbname, schema, useIAMAuth, or useAzureEntraAuth" }} +{{- end }} +{{- if and (or $hostKey $urlKey) (not $readerSecret.name) }} +{{- fail "database.reader.passwordSecret.name is required when hostKey or urlKey is set" }} +{{- end }} +{{- if $readerEnabled }} +{{- if $urlKey }} +- name: DATABASE_URL_READ_REPLICA + valueFrom: + secretKeyRef: + name: {{ $readerSecret.name | quote }} + key: {{ $urlKey | quote }} +{{- else }} {{- if and .useIAMAuth (not $root.Values.database.writer.useIAMAuth) }} {{- fail "database.reader.useIAMAuth requires database.writer.useIAMAuth: true (the proxy gates IAM URL minting on IAM_TOKEN_DB_AUTH, which is only set by the writer)" }} {{- end }} @@ -291,11 +342,18 @@ IAM_TOKEN_DB_AUTH / AZURE_POSTGRESQL_AUTH toggle that only the writer sets. {{- fail "database.reader.useAzureEntraAuth requires database.writer.useAzureEntraAuth: true (the proxy gates Entra URL minting on AZURE_POSTGRESQL_AUTH, which is only set by the writer)" }} {{- end }} - name: DATABASE_HOST_READ_REPLICA + {{- if $hostKey }} + valueFrom: + secretKeyRef: + name: {{ $readerSecret.name | quote }} + key: {{ $hostKey | quote }} + {{- else }} value: {{ .host | quote }} + {{- end }} - name: DATABASE_PORT_READ_REPLICA value: {{ .port | default 5432 | quote }} - name: DATABASE_NAME_READ_REPLICA - value: {{ required "database.reader.dbname is required when database.reader.host is set" .dbname | quote }} + value: {{ required "database.reader.dbname is required when database.reader.host, hostKey, or urlKey is set" .dbname | quote }} {{- if .schema }} - name: DATABASE_SCHEMA_READ_REPLICA value: {{ .schema | quote }} @@ -325,6 +383,7 @@ IAM_TOKEN_DB_AUTH / AZURE_POSTGRESQL_AUTH toggle that only the writer sets. {{- end }} {{- end }} {{- end }} +{{- end }} {{/* The migrations Job (helm.sh/hook: pre-upgrade) is the single owner of `prisma migrate deploy`. Without this, every gateway/backend pod also runs @@ -341,9 +400,34 @@ harmless no-op for the Job and authoritative for the app pods. tracking, pod lock manager) via its REDIS_* env fallback. An explicit `general_settings.coordination_redis` block in proxy_config takes precedence over anything emitted here. */}} -{{- if $root.Values.redis.host }} +{{- $redisSecret := $root.Values.redis.passwordSecret | default dict }} +{{- $hostKey := $redisSecret.hostKey }} +{{- $urlKey := $redisSecret.urlKey }} +{{- if and $root.Values.redis.host $hostKey }} +{{- fail "redis.host and redis.passwordSecret.hostKey cannot both be set" }} +{{- end }} +{{- if and $urlKey (or $root.Values.redis.host $hostKey $root.Values.redis.cluster) }} +{{- fail "redis.passwordSecret.urlKey cannot be combined with host, hostKey, or cluster" }} +{{- end }} +{{- if and (or $hostKey $urlKey) (not $redisSecret.name) }} +{{- fail "redis.passwordSecret.name is required when hostKey or urlKey is set" }} +{{- end }} +{{- if $urlKey }} +- name: REDIS_URL + valueFrom: + secretKeyRef: + name: {{ $redisSecret.name | quote }} + key: {{ $urlKey | quote }} +{{- else if or $root.Values.redis.host $hostKey }} - name: REDIS_HOST + {{- if $hostKey }} + valueFrom: + secretKeyRef: + name: {{ $redisSecret.name | quote }} + key: {{ $hostKey | quote }} + {{- else }} value: {{ $root.Values.redis.host | quote }} + {{- end }} - name: REDIS_PORT value: {{ $root.Values.redis.port | quote }} {{- if $root.Values.redis.passwordSecret.name }} @@ -360,7 +444,11 @@ harmless no-op for the Job and authoritative for the app pods. configured endpoint; the cluster client discovers the remaining nodes from CLUSTER SLOTS at startup. */}} - name: REDIS_CLUSTER_NODES + {{- if $hostKey }} + value: {{ printf "[{\"host\":\"$(REDIS_HOST)\",\"port\":%v}]" (int $root.Values.redis.port) | quote }} + {{- else }} value: {{ printf "[{\"host\":%q,\"port\":%v}]" $root.Values.redis.host (int $root.Values.redis.port) | quote }} + {{- end }} {{- end }} {{- end }} {{- with $component.extraEnv }} diff --git a/helm/litellm/tests/database_auth_tests.yaml b/helm/litellm/tests/database_auth_tests.yaml index add531f68bb..bc6653de700 100644 --- a/helm/litellm/tests/database_auth_tests.yaml +++ b/helm/litellm/tests/database_auth_tests.yaml @@ -191,3 +191,400 @@ tests: content: name: DATABASE_PASSWORD_READ_REPLICA any: true + + - it: writer hostKey reads the host from the Secret and supports IAM auth + template: gateway/deployment.yaml + set: + database.writer.host: "" + database.writer.passwordSecret.hostKey: hostname + database.writer.useIAMAuth: true + asserts: + - contains: + path: spec.template.spec.containers[0].env + content: + name: DATABASE_HOST + valueFrom: + secretKeyRef: + name: litellm-writer-secret + key: hostname + - contains: + path: spec.template.spec.containers[0].env + content: + name: IAM_TOKEN_DB_AUTH + value: "true" + - notContains: + path: spec.template.spec.containers[0].env + content: + name: DATABASE_PASSWORD + any: true + + - it: writer urlKey supplies only DATABASE_URL to each database client container and keeps pool env + set: + database.writer.host: "" + database.writer.dbname: "" + database.writer.schema: "" + database.writer.sslMode: "" + database.writer.sslRootCert: "" + database.writer.passwordSecret.urlKey: connection-url + database.connectionPool.enabled: true + gateway.collector.enabled: true + gateway.metricsServer.enabled: true + asserts: + - contains: + path: spec.template.spec.containers[0].env + content: + name: DATABASE_URL + valueFrom: + secretKeyRef: + name: litellm-writer-secret + key: connection-url + template: gateway/deployment.yaml + - notContains: + path: spec.template.spec.containers[0].env + content: + name: DATABASE_HOST + any: true + template: gateway/deployment.yaml + - notContains: + path: spec.template.spec.containers[0].env + content: + name: DATABASE_PORT + any: true + template: gateway/deployment.yaml + - notContains: + path: spec.template.spec.containers[0].env + content: + name: DATABASE_USER + any: true + template: gateway/deployment.yaml + - notContains: + path: spec.template.spec.containers[0].env + content: + name: DATABASE_NAME + any: true + template: gateway/deployment.yaml + - notContains: + path: spec.template.spec.containers[0].env + content: + name: DATABASE_SCHEMA + any: true + template: gateway/deployment.yaml + - notContains: + path: spec.template.spec.containers[0].env + content: + name: DATABASE_SSLMODE + any: true + template: gateway/deployment.yaml + - notContains: + path: spec.template.spec.containers[0].env + content: + name: DATABASE_SSLROOTCERT + any: true + template: gateway/deployment.yaml + - notContains: + path: spec.template.spec.containers[0].env + content: + name: DATABASE_PASSWORD + any: true + template: gateway/deployment.yaml + - notExists: + path: >- + spec.template.spec.containers[0].env[?(@.name == "DATABASE_HOST" || + @.name == "DATABASE_PORT" || @.name == "DATABASE_USER" || + @.name == "DATABASE_NAME" || @.name == "DATABASE_SCHEMA" || + @.name == "DATABASE_SSLMODE" || @.name == "DATABASE_SSLROOTCERT" || + @.name == "DATABASE_PASSWORD")] + template: gateway/deployment.yaml + - notExists: + path: >- + spec.template.spec.containers[2].env[?(@.name == "DATABASE_HOST" || + @.name == "DATABASE_PORT" || @.name == "DATABASE_USER" || + @.name == "DATABASE_NAME" || @.name == "DATABASE_SCHEMA" || + @.name == "DATABASE_SSLMODE" || @.name == "DATABASE_SSLROOTCERT" || + @.name == "DATABASE_PASSWORD")] + template: gateway/deployment.yaml + - contains: + path: spec.template.spec.containers[0].env + content: + name: LITELLM_PGBOUNCER_ENABLED + value: "true" + template: gateway/deployment.yaml + - contains: + path: spec.template.spec.containers[2].env + content: + name: DATABASE_URL + valueFrom: + secretKeyRef: + name: litellm-writer-secret + key: connection-url + template: gateway/deployment.yaml + - notContains: + path: spec.template.spec.containers[2].env + content: + name: DATABASE_USER + any: true + template: gateway/deployment.yaml + - notContains: + path: spec.template.spec.containers[2].env + content: + name: DATABASE_PASSWORD + any: true + template: gateway/deployment.yaml + - contains: + path: spec.template.spec.containers[2].env + content: + name: DATABASE_URL + valueFrom: + secretKeyRef: + name: litellm-writer-secret + key: connection-url + template: gateway/deployment.yaml + - contains: + path: spec.template.spec.containers[2].env + content: + name: LITELLM_PGBOUNCER_ENABLED + value: "true" + template: gateway/deployment.yaml + - contains: + path: spec.template.spec.containers[0].env + content: + name: DATABASE_URL + valueFrom: + secretKeyRef: + name: litellm-writer-secret + key: connection-url + template: backend/deployment.yaml + - notContains: + path: spec.template.spec.containers[0].env + content: + name: DATABASE_USER + any: true + template: backend/deployment.yaml + - notContains: + path: spec.template.spec.containers[0].env + content: + name: DATABASE_PASSWORD + any: true + template: backend/deployment.yaml + - notExists: + path: >- + spec.template.spec.containers[0].env[?(@.name == "DATABASE_HOST" || + @.name == "DATABASE_PORT" || @.name == "DATABASE_USER" || + @.name == "DATABASE_NAME" || @.name == "DATABASE_SCHEMA" || + @.name == "DATABASE_SSLMODE" || @.name == "DATABASE_SSLROOTCERT" || + @.name == "DATABASE_PASSWORD")] + template: backend/deployment.yaml + - contains: + path: spec.template.spec.containers[0].env + content: + name: DATABASE_URL + valueFrom: + secretKeyRef: + name: litellm-writer-secret + key: connection-url + template: migrations-job.yaml + - notContains: + path: spec.template.spec.containers[0].env + content: + name: DATABASE_USER + any: true + template: migrations-job.yaml + - notContains: + path: spec.template.spec.containers[0].env + content: + name: DATABASE_PASSWORD + any: true + template: migrations-job.yaml + - notExists: + path: >- + spec.template.spec.containers[0].env[?(@.name == "DATABASE_HOST" || + @.name == "DATABASE_PORT" || @.name == "DATABASE_USER" || + @.name == "DATABASE_NAME" || @.name == "DATABASE_SCHEMA" || + @.name == "DATABASE_SSLMODE" || @.name == "DATABASE_SSLROOTCERT" || + @.name == "DATABASE_PASSWORD")] + template: migrations-job.yaml + + - it: writer rejects host together with hostKey + template: gateway/deployment.yaml + set: + database.writer.passwordSecret.hostKey: hostname + asserts: + - failedTemplate: + errorMessage: "database.writer.host and database.writer.passwordSecret.hostKey cannot both be set" + + - it: writer rejects urlKey with discrete connection settings + template: gateway/deployment.yaml + set: + database.writer.host: "" + database.writer.passwordSecret.urlKey: connection-url + database.writer.schema: public + database.writer.sslMode: require + database.writer.sslRootCert: /etc/ssl/ca.crt + asserts: + - failedTemplate: + errorMessage: "database.writer.passwordSecret.urlKey cannot be combined with host, hostKey, dbname, schema, sslMode, sslRootCert, useIAMAuth, or useAzureEntraAuth" + + - it: writer rejects urlKey with IAM auth + template: gateway/deployment.yaml + set: + database.writer.host: "" + database.writer.dbname: "" + database.writer.passwordSecret.urlKey: connection-url + database.writer.useIAMAuth: true + asserts: + - failedTemplate: + errorMessage: "database.writer.passwordSecret.urlKey cannot be combined with host, hostKey, dbname, schema, sslMode, sslRootCert, useIAMAuth, or useAzureEntraAuth" + + - it: writer rejects urlKey with Entra auth + template: gateway/deployment.yaml + set: + database.writer.host: "" + database.writer.dbname: "" + database.writer.passwordSecret.urlKey: connection-url + database.writer.useAzureEntraAuth: true + asserts: + - failedTemplate: + errorMessage: "database.writer.passwordSecret.urlKey cannot be combined with host, hostKey, dbname, schema, sslMode, sslRootCert, useIAMAuth, or useAzureEntraAuth" + + - it: writer requires an endpoint + template: gateway/deployment.yaml + set: + database.writer.host: "" + asserts: + - failedTemplate: + errorMessage: "one of database.writer.host, database.writer.passwordSecret.hostKey, or database.writer.passwordSecret.urlKey is required" + + - it: writer hostKey requires a Secret name + template: gateway/deployment.yaml + set: + database.writer.host: "" + database.writer.passwordSecret.hostKey: hostname + database.writer.passwordSecret.name: "" + asserts: + - failedTemplate: + errorMessage: "database.writer.passwordSecret.name is required when hostKey or urlKey is set" + + - it: reader renders without replica env vars when passwordSecret is null + template: gateway/deployment.yaml + set: + database.reader.passwordSecret: null + asserts: + - notContains: + path: spec.template.spec.containers[0].env + content: + name: DATABASE_HOST_READ_REPLICA + any: true + - notContains: + path: spec.template.spec.containers[0].env + content: + name: DATABASE_URL_READ_REPLICA + any: true + + - it: reader hostKey enables replica routing and reads its host from the Secret + template: gateway/deployment.yaml + set: + database.writer.useIAMAuth: true + database.reader.passwordSecret.hostKey: hostname + database.reader.dbname: litellm + database.reader.useIAMAuth: true + asserts: + - contains: + path: spec.template.spec.containers[0].env + content: + name: DATABASE_HOST_READ_REPLICA + valueFrom: + secretKeyRef: + name: litellm-reader-secret + key: hostname + - contains: + path: spec.template.spec.containers[0].env + content: + name: DATABASE_NAME_READ_REPLICA + value: litellm + - contains: + path: spec.template.spec.containers[0].env + content: + name: IAM_TOKEN_DB_AUTH + value: "true" + + - it: reader urlKey emits only the replica URL + template: gateway/deployment.yaml + set: + database.reader.passwordSecret.urlKey: connection-url + asserts: + - contains: + path: spec.template.spec.containers[0].env + content: + name: DATABASE_URL_READ_REPLICA + valueFrom: + secretKeyRef: + name: litellm-reader-secret + key: connection-url + - notContains: + path: spec.template.spec.containers[0].env + content: + name: DATABASE_HOST_READ_REPLICA + any: true + - notContains: + path: spec.template.spec.containers[0].env + content: + name: DATABASE_PORT_READ_REPLICA + any: true + - notContains: + path: spec.template.spec.containers[0].env + content: + name: DATABASE_USER_READ_REPLICA + any: true + - notContains: + path: spec.template.spec.containers[0].env + content: + name: DATABASE_PASSWORD_READ_REPLICA + any: true + - notContains: + path: spec.template.spec.containers[0].env + content: + name: DATABASE_NAME_READ_REPLICA + any: true + - notContains: + path: spec.template.spec.containers[0].env + content: + name: DATABASE_SCHEMA_READ_REPLICA + any: true + + - it: reader rejects host together with hostKey + template: gateway/deployment.yaml + set: + database.reader.host: reader.example.com + database.reader.passwordSecret.hostKey: hostname + asserts: + - failedTemplate: + errorMessage: "database.reader.host and database.reader.passwordSecret.hostKey cannot both be set" + + - it: reader rejects urlKey with discrete connection settings + template: gateway/deployment.yaml + set: + database.reader.passwordSecret.urlKey: connection-url + database.reader.dbname: litellm + database.reader.schema: public + asserts: + - failedTemplate: + errorMessage: "database.reader.passwordSecret.urlKey cannot be combined with host, hostKey, dbname, schema, useIAMAuth, or useAzureEntraAuth" + + - it: reader rejects urlKey with token auth + template: gateway/deployment.yaml + set: + database.writer.useIAMAuth: true + database.reader.passwordSecret.urlKey: connection-url + database.reader.useIAMAuth: true + asserts: + - failedTemplate: + errorMessage: "database.reader.passwordSecret.urlKey cannot be combined with host, hostKey, dbname, schema, useIAMAuth, or useAzureEntraAuth" + + - it: reader urlKey requires a Secret name + template: gateway/deployment.yaml + set: + database.reader.passwordSecret.urlKey: connection-url + database.reader.passwordSecret.name: "" + asserts: + - failedTemplate: + errorMessage: "database.reader.passwordSecret.name is required when hostKey or urlKey is set" diff --git a/helm/litellm/tests/redis_env_tests.yaml b/helm/litellm/tests/redis_env_tests.yaml index 684d7071b35..c9304cedd9c 100644 --- a/helm/litellm/tests/redis_env_tests.yaml +++ b/helm/litellm/tests/redis_env_tests.yaml @@ -21,6 +21,27 @@ tests: name: REDIS_CLUSTER_NODES any: true + - it: gateway renders without redis env vars when passwordSecret is null + template: gateway/deployment.yaml + set: + redis.passwordSecret: null + asserts: + - notContains: + path: spec.template.spec.containers[0].env + content: + name: REDIS_HOST + any: true + - notContains: + path: spec.template.spec.containers[0].env + content: + name: REDIS_URL + any: true + - notContains: + path: spec.template.spec.containers[0].env + content: + name: REDIS_PASSWORD + any: true + - it: gateway emits host, port and password when redis is configured template: gateway/deployment.yaml set: @@ -68,6 +89,27 @@ tests: name: redis-secret key: redis-password + - it: backend renders without redis env vars when passwordSecret is null + template: backend/deployment.yaml + set: + redis.passwordSecret: null + asserts: + - notContains: + path: spec.template.spec.containers[0].env + content: + name: REDIS_HOST + any: true + - notContains: + path: spec.template.spec.containers[0].env + content: + name: REDIS_URL + any: true + - notContains: + path: spec.template.spec.containers[0].env + content: + name: REDIS_PASSWORD + any: true + - it: gateway omits REDIS_PASSWORD for an auth-less redis template: gateway/deployment.yaml set: @@ -107,3 +149,174 @@ tests: content: name: REDIS_CLUSTER_NODES any: true + + - it: gateway gets redis host from hostKey and keeps port and password + template: gateway/deployment.yaml + set: + redis.host: "" + redis.port: 6380 + redis.passwordSecret.name: redis-secret + redis.passwordSecret.hostKey: hostname + redis.passwordSecret.passwordKey: redis-password + asserts: + - contains: + path: spec.template.spec.containers[0].env + content: + name: REDIS_HOST + valueFrom: + secretKeyRef: + name: redis-secret + key: hostname + - contains: + path: spec.template.spec.containers[0].env + content: + name: REDIS_PORT + value: "6380" + - contains: + path: spec.template.spec.containers[0].env + content: + name: REDIS_PASSWORD + valueFrom: + secretKeyRef: + name: redis-secret + key: redis-password + + - it: backend gets redis host from hostKey with the same coordination env + template: backend/deployment.yaml + set: + redis.host: "" + redis.passwordSecret.name: redis-secret + redis.passwordSecret.hostKey: hostname + asserts: + - contains: + path: spec.template.spec.containers[0].env + content: + name: REDIS_HOST + valueFrom: + secretKeyRef: + name: redis-secret + key: hostname + - contains: + path: spec.template.spec.containers[0].env + content: + name: REDIS_PORT + value: "6379" + - contains: + path: spec.template.spec.containers[0].env + content: + name: REDIS_PASSWORD + valueFrom: + secretKeyRef: + name: redis-secret + key: password + + - it: gateway expands the redis cluster seed from hostKey at runtime + template: gateway/deployment.yaml + set: + redis.host: "" + redis.passwordSecret.name: redis-secret + redis.passwordSecret.hostKey: hostname + redis.cluster: true + asserts: + - contains: + path: spec.template.spec.containers[0].env + content: + name: REDIS_CLUSTER_NODES + value: '[{"host":"$(REDIS_HOST)","port":6379}]' + - equal: + path: spec.template.spec.containers[0].env[8].name + value: REDIS_HOST + - equal: + path: spec.template.spec.containers[0].env[11].name + value: REDIS_CLUSTER_NODES + + - it: gateway gets REDIS_URL from urlKey without discrete Redis env + template: gateway/deployment.yaml + set: + redis.passwordSecret.name: redis-secret + redis.passwordSecret.urlKey: connection-url + asserts: + - contains: + path: spec.template.spec.containers[0].env + content: + name: REDIS_URL + valueFrom: + secretKeyRef: + name: redis-secret + key: connection-url + - notContains: + path: spec.template.spec.containers[0].env + content: + name: REDIS_HOST + any: true + - notContains: + path: spec.template.spec.containers[0].env + content: + name: REDIS_PORT + any: true + - notContains: + path: spec.template.spec.containers[0].env + content: + name: REDIS_PASSWORD + any: true + - notContains: + path: spec.template.spec.containers[0].env + content: + name: REDIS_CLUSTER_NODES + any: true + + - it: redis rejects host together with hostKey + template: gateway/deployment.yaml + set: + redis.host: redis.example.com + redis.passwordSecret.hostKey: hostname + redis.passwordSecret.name: redis-secret + asserts: + - failedTemplate: + errorMessage: "redis.host and redis.passwordSecret.hostKey cannot both be set" + + - it: redis urlKey rejects host + template: gateway/deployment.yaml + set: + redis.host: redis.example.com + redis.passwordSecret.urlKey: connection-url + redis.passwordSecret.name: redis-secret + asserts: + - failedTemplate: + errorMessage: "redis.passwordSecret.urlKey cannot be combined with host, hostKey, or cluster" + + - it: redis urlKey rejects hostKey + template: gateway/deployment.yaml + set: + redis.passwordSecret.hostKey: hostname + redis.passwordSecret.urlKey: connection-url + redis.passwordSecret.name: redis-secret + asserts: + - failedTemplate: + errorMessage: "redis.passwordSecret.urlKey cannot be combined with host, hostKey, or cluster" + + - it: redis urlKey rejects cluster mode + template: gateway/deployment.yaml + set: + redis.cluster: true + redis.passwordSecret.urlKey: connection-url + redis.passwordSecret.name: redis-secret + asserts: + - failedTemplate: + errorMessage: "redis.passwordSecret.urlKey cannot be combined with host, hostKey, or cluster" + + - it: redis hostKey requires a Secret name + template: gateway/deployment.yaml + set: + redis.passwordSecret.hostKey: hostname + asserts: + - failedTemplate: + errorMessage: "redis.passwordSecret.name is required when hostKey or urlKey is set" + + - it: redis urlKey requires a Secret name + template: gateway/deployment.yaml + set: + redis.passwordSecret.urlKey: connection-url + asserts: + - failedTemplate: + errorMessage: "redis.passwordSecret.name is required when hostKey or urlKey is set" diff --git a/helm/litellm/values.yaml b/helm/litellm/values.yaml index a3c620b3434..f17a379189d 100644 --- a/helm/litellm/values.yaml +++ b/helm/litellm/values.yaml @@ -208,6 +208,8 @@ database: name: litellm-writer-secret usernameKey: username passwordKey: password + hostKey: "" # Secret key containing the writer database host + urlKey: "" # Secret key containing the complete writer database URL # libpq sslmode / sslrootcert applied to the writer and reader URLs (Prisma and the # in-container PgBouncer); e.g. verify-full with /etc/ssl/certs/ca-certificates.crt for AWS RDS. # sslRootCert on its own implies sslMode verify-full @@ -229,6 +231,8 @@ database: name: litellm-reader-secret usernameKey: username passwordKey: password + hostKey: "" # Secret key containing the reader database host + urlKey: "" # Secret key containing the complete reader database URL # In-container connection pool (PgBouncer, transaction mode) shared by every # gateway worker in the pod. Without it each of the `gateway.numWorkers` @@ -274,6 +278,8 @@ redis: passwordSecret: name: "" # Leave empty for auth-less Redis passwordKey: password + hostKey: "" # Secret key containing the Redis host + urlKey: "" # Secret key containing the complete Redis URL # ---------- gateway (LLM data plane) ---------- gateway: