diff --git a/.gitignore b/.gitignore index f09011e..ed9a863 100644 --- a/.gitignore +++ b/.gitignore @@ -89,4 +89,7 @@ dmypy.json cython_debug/ # macOS temporary files -.DS_Store \ No newline at end of file +.DS_Store + +# Auto-generated working files +ISSUE_CONTEXT.md \ No newline at end of file diff --git a/CLAUDE.md b/CLAUDE.md index 6ac1f2c..893432e 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -75,7 +75,7 @@ The chart supports multiple ChromaDB versions from 0.4.3 to 1.0.x with version-s - **Authentication**: Supported in versions < 1.0.0 (token auth from 0.4.8+, basic auth from 0.4.7+) - **Logging Configuration**: Custom log levels and config maps (versions < 1.0.0) - **Cache Management**: LRU cache policy configuration (versions < 1.0.0) -- **CORS Configuration**: List-based CORS origins (wildcard not supported in 1.0.0+) +- **CORS Configuration**: List-based CORS origins (wildcard supported for all versions) - **Telemetry**: OTEL telemetry support with configurable endpoints ### GitHub Workflows diff --git a/README.md b/README.md index 64bc503..8209ea1 100644 --- a/README.md +++ b/README.md @@ -63,12 +63,12 @@ helm install chroma chroma/chromadb --set chromadb.allowReset="true" | Key | Type | Default | Description | |-----------------------------------------------------|---------|---------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -| `chromadb.apiVersion` | string | `1.0.10` (Chart app version) | The ChromaDB version. Supported version `0.4.3` - `1.0.x` | +| `chromadb.apiVersion` | string | `1.5.0` (Chart app version) | The ChromaDB version. Supported version `0.4.3` - `1.x` | | `chromadb.allowReset` | boolean | `false` | Allows resetting the index (delete all data) | | `chromadb.isPersistent` | boolean | `true` | A flag to control whether data is persisted | | `chromadb.persistDirectory` | string | `/data` | The location to store the index data. This configure both chromadb and underlying persistent volume | | `chromadb.anonymizedTelemetry` | boolean | `false` | The flag to send anonymized stats using posthog. By default this is enabled in the chromadb however for user's privacy we have disabled it so it is opt-in | -| `chromadb.corsAllowOrigins` | list | N/A | The CORS config. Wildcard ["*"] is not supported in version 1.0.0 or later. | +| `chromadb.corsAllowOrigins` | list | `[]` | List of allowed CORS origins. Wildcard `["*"]` is supported. | | `chromadb.apiImpl` | string | `- "chromadb.api.segment.SegmentAPI"` | The default API impl. It uses SegmentAPI however FastAPI is also available. Note: FastAPI seems to be bugging so we discourage users to use it in releases prior or equal to 0.4.3 Deprecated in since 0.1.23 (will be removed in 0.2.0) | | `chromadb.serverHost` | string | `0.0.0.0` | The API server host. | | `chromadb.serverHttpPort` | int | `8000` | The API server port. | @@ -103,6 +103,7 @@ helm install chroma chroma/chromadb --set chromadb.allowReset="true" | `chromadb.telemetry.serviceName` | string | `chroma` | The service name that will show up in traces. | | `imagePullSecrets` | list | `[]` | List of image pull secrets for the ChromaDB pod (e.g. `[{name: "my-secret"}]`). | | `global.imagePullSecrets` | list | `[]` | Global image pull secrets shared across all subcharts. Merged with `imagePullSecrets`. | +| `chromadb.extraConfig` | object | `{}` | Extra config keys merged into the v1 server config (>= 1.0.0). Overrides chart-managed keys. See [Extra Config](#extra-config). | | `commonLabels` | object | `{}` | Additional labels applied to all chart resources (StatefulSet, Service, Ingress, ConfigMaps, Secrets, PVCs, test Jobs). | | `podLabels` | object | `{}` | Additional labels applied to pods only. Does not affect `matchLabels`. | @@ -243,6 +244,29 @@ Then, run `helm dependency update` to install the chart. When using as a subchart, `global.imagePullSecrets` lets you define pull secrets once in the parent chart and have them propagated to all subcharts (including ChromaDB). Chart-level `imagePullSecrets` only applies to this chart. Both lists are merged, so there is no conflict if the same secret appears in both — though it may appear as a duplicate, Kubernetes handles this gracefully. +## Extra Config + +For Chroma >= 1.0.0 (Rust server), `chromadb.extraConfig` lets you inject arbitrary config keys into the server's YAML +config file. This is useful for setting options not yet exposed as dedicated chart values. + +```yaml +chromadb: + extraConfig: + circuit_breaker: + requests: 500 + sqlite_filename: "custom.db" + open_telemetry: + filters: + - crate_name: "chroma_frontend" + filter_level: "info" +``` + +> [!WARNING] +> Keys in `extraConfig` override chart-managed keys of the same name. Overriding `port` or +> `listen_address` via `extraConfig` is **not allowed** and will cause template rendering to fail. +> Use `chromadb.serverHttpPort` and `chromadb.serverHost` instead so that the Service, container +> port, and health probes remain in sync. + ## References - Chroma: https://docs.trychroma.com/docs/overview/getting-started diff --git a/charts/chromadb-chart/templates/_helpers.tpl b/charts/chromadb-chart/templates/_helpers.tpl index 5b1c969..930b884 100644 --- a/charts/chromadb-chart/templates/_helpers.tpl +++ b/charts/chromadb-chart/templates/_helpers.tpl @@ -120,6 +120,46 @@ Get the chroma api version {{- end }} {{- end }} +{{/* +Build the server config dict for the v1-config ConfigMap. +*/}} +{{- define "chromadb.serverConfig" -}} +{{- $port := .Values.chromadb.serverHttpPort | int -}} +{{- if le $port 0 -}} + {{- fail (printf "chromadb.serverHttpPort must be a positive integer, got: %v" .Values.chromadb.serverHttpPort) -}} +{{- end -}} +{{- $maxPayload := .Values.chromadb.maxPayloadSizeBytes | int64 -}} +{{- if le $maxPayload 0 -}} + {{- fail (printf "chromadb.maxPayloadSizeBytes must be a positive integer, got: %v" .Values.chromadb.maxPayloadSizeBytes) -}} +{{- end -}} +{{- $config := dict -}} +{{- $_ := set $config "port" $port -}} +{{- $_ := set $config "listen_address" .Values.chromadb.serverHost -}} +{{- $_ := set $config "max_payload_size_bytes" $maxPayload -}} +{{- $_ := set $config "persist_path" .Values.chromadb.persistDirectory -}} +{{- $_ := set $config "allow_reset" .Values.chromadb.allowReset -}} +{{- if .Values.chromadb.corsAllowOrigins -}} + {{- $_ := set $config "cors_allow_origins" .Values.chromadb.corsAllowOrigins -}} +{{- end -}} +{{- if .Values.chromadb.telemetry.enabled -}} + {{- if not .Values.chromadb.telemetry.endpoint -}} + {{- fail "chromadb.telemetry.endpoint must be set when chromadb.telemetry.enabled is true" -}} + {{- end -}} + {{- $otel := dict "service_name" .Values.chromadb.telemetry.serviceName "endpoint" .Values.chromadb.telemetry.endpoint -}} + {{- $_ := set $config "open_telemetry" $otel -}} +{{- end -}} +{{- with .Values.chromadb.extraConfig -}} + {{- $config = mergeOverwrite $config . -}} +{{- end -}} +{{- if ne (get $config "port" | int) ($port) -}} + {{- fail (printf "extraConfig.port (%v) conflicts with chromadb.serverHttpPort (%v) — update serverHttpPort instead" (get $config "port") $.Values.chromadb.serverHttpPort) -}} +{{- end -}} +{{- if ne (get $config "listen_address") .Values.chromadb.serverHost -}} + {{- fail (printf "extraConfig.listen_address (%s) conflicts with chromadb.serverHost (%s) — update serverHost instead" (get $config "listen_address") .Values.chromadb.serverHost) -}} +{{- end -}} +{{- $config | toYaml -}} +{{- end -}} + {{/* Get the Chroma auth token header type */}} diff --git a/charts/chromadb-chart/templates/config.yaml b/charts/chromadb-chart/templates/config.yaml index 0302539..c8b245d 100644 --- a/charts/chromadb-chart/templates/config.yaml +++ b/charts/chromadb-chart/templates/config.yaml @@ -170,19 +170,4 @@ metadata: "helm.sh/hook-weight": "-5" data: config.yaml: |- - {{- if .Values.chromadb.telemetry.enabled }} - open_telemetry: - service_name: {{ .Values.chromadb.telemetry.serviceName }} - endpoint: {{ .Values.chromadb.telemetry.endpoint }} - {{- end }} - port: {{ .Values.chromadb.serverHttpPort }} - listen_address: {{ .Values.chromadb.serverHost }} - max_payload_size_bytes: {{ .Values.chromadb.maxPayloadSizeBytes | int64 }} - {{- if .Values.chromadb.corsAllowOrigins }} - {{- if and (eq (len .Values.chromadb.corsAllowOrigins) 1) (eq (index .Values.chromadb.corsAllowOrigins 0) "*") }} - {{ fail "cors_allow_origins must not be set to '*' when only one origin is allowed" }} - {{- end }} - cors_allow_origins: {{ .Values.chromadb.corsAllowOrigins | toJson }} - {{- end }} - persist_path: {{ .Values.chromadb.persistDirectory }} - allow_reset: {{ .Values.chromadb.allowReset }} \ No newline at end of file + {{- include "chromadb.serverConfig" . | nindent 4 }} \ No newline at end of file diff --git a/charts/chromadb-chart/values.yaml b/charts/chromadb-chart/values.yaml index 375a870..5bd5448 100644 --- a/charts/chromadb-chart/values.yaml +++ b/charts/chromadb-chart/values.yaml @@ -124,7 +124,7 @@ chromadb: chromadb: "DEBUG" uvicorn: "INFO" anonymizedTelemetry: false - corsAllowOrigins: [] # as of version 1.0.x * is not allowed + corsAllowOrigins: [] serverHost: "0.0.0.0" serverHttpPort: 8000 maxPayloadSizeBytes: "41943040" @@ -150,4 +150,9 @@ chromadb: token: headerType: "Authorization" #possible values Authorization, X-Chroma-Token value: null # The string used as the token (value). Only used if value not null, otherwise a random string will be generated and used. + # Extra config keys merged into the v1 server config (>= 1.0.0). Overrides chart-managed keys. + extraConfig: {} + # circuit_breaker: + # requests: 500 + # sqlite_filename: "chroma.sqlite3" diff --git a/tests/test_v1_config.sh b/tests/test_v1_config.sh new file mode 100755 index 0000000..c4dfe29 --- /dev/null +++ b/tests/test_v1_config.sh @@ -0,0 +1,135 @@ +#!/usr/bin/env bash +set -euo pipefail + +CHART_DIR="$(cd "$(dirname "$0")/../charts/chromadb-chart" && pwd)" +PASS=0 +FAIL=0 + +assert_config_key() { + local desc="$1" yaml="$2" key="$3" expected="$4" + actual=$(echo "$yaml" | yq eval ".$key" -) + if [ "$actual" = "$expected" ]; then + echo " PASS: $desc" + PASS=$((PASS+1)) + else + echo " FAIL: $desc (expected '$expected', got '$actual')" + FAIL=$((FAIL+1)) + fi +} + +assert_config_key_missing() { + local desc="$1" yaml="$2" key="$3" + actual=$(echo "$yaml" | yq eval ".$key" -) + if [ "$actual" = "null" ]; then + echo " PASS: $desc" + PASS=$((PASS+1)) + else + echo " FAIL: $desc (expected key '$key' to be absent, got '$actual')" + FAIL=$((FAIL+1)) + fi +} + +get_v1_config() { + local output + output=$(helm template test "$CHART_DIR" "$@" 2>&1) || { + echo "TEMPLATE_ERROR: $output" >&2 + return 1 + } + echo "$output" | yq eval 'select(.metadata.name == "v1-config") | .data["config.yaml"]' - +} + +assert_template_fails() { + local desc="$1"; shift + local output + if output=$(helm template test "$CHART_DIR" "$@" 2>&1); then + echo " FAIL: $desc (expected template to fail, but it succeeded)" + FAIL=$((FAIL+1)) + else + echo " PASS: $desc" + PASS=$((PASS+1)) + fi +} + +# --- Test suite --- + +echo "=== v1-config template tests ===" + +echo "" +echo "1. Default values" +config=$(get_v1_config) +assert_config_key "port defaults to 8000" "$config" "port" "8000" +assert_config_key "listen_address defaults to 0.0.0.0" "$config" "listen_address" "0.0.0.0" +assert_config_key "max_payload_size_bytes defaults to 41943040" "$config" "max_payload_size_bytes" "41943040" +assert_config_key "persist_path defaults to /data" "$config" "persist_path" "/data" +assert_config_key "allow_reset defaults to false" "$config" "allow_reset" "false" +assert_config_key_missing "cors_allow_origins absent when empty" "$config" "cors_allow_origins" +assert_config_key_missing "open_telemetry absent when disabled" "$config" "open_telemetry" + +echo "" +echo "2. CORS wildcard (should work)" +config=$(get_v1_config --set 'chromadb.corsAllowOrigins={*}') +assert_config_key "cors_allow_origins contains wildcard" "$config" "cors_allow_origins[0]" "*" + +echo "" +echo "3. CORS multiple origins" +config=$(get_v1_config --set 'chromadb.corsAllowOrigins={https://a.com,https://b.com}') +assert_config_key "first origin" "$config" "cors_allow_origins[0]" "https://a.com" +assert_config_key "second origin" "$config" "cors_allow_origins[1]" "https://b.com" + +echo "" +echo "4. OpenTelemetry enabled" +config=$(get_v1_config \ + --set 'chromadb.telemetry.enabled=true' \ + --set 'chromadb.telemetry.endpoint=http://otel:4317' \ + --set 'chromadb.telemetry.serviceName=my-chroma') +assert_config_key "otel endpoint" "$config" "open_telemetry.endpoint" "http://otel:4317" +assert_config_key "otel service_name" "$config" "open_telemetry.service_name" "my-chroma" + +echo "" +echo "5. Custom server settings" +config=$(get_v1_config \ + --set 'chromadb.serverHttpPort=9000' \ + --set 'chromadb.serverHost=127.0.0.1' \ + --set 'chromadb.allowReset=true' \ + --set 'chromadb.persistDirectory=/mnt/data' \ + --set 'chromadb.maxPayloadSizeBytes=52428800') +assert_config_key "custom port" "$config" "port" "9000" +assert_config_key "custom listen_address" "$config" "listen_address" "127.0.0.1" +assert_config_key "allow_reset true" "$config" "allow_reset" "true" +assert_config_key "custom persist_path" "$config" "persist_path" "/mnt/data" +assert_config_key "custom max_payload_size_bytes" "$config" "max_payload_size_bytes" "52428800" + +echo "" +echo "6. extraConfig merge" +config=$(get_v1_config \ + --set 'chromadb.extraConfig.circuit_breaker.requests=500' \ + --set 'chromadb.extraConfig.sqlite_filename=custom.db') +assert_config_key "circuit_breaker.requests from extraConfig" "$config" "circuit_breaker.requests" "500" +assert_config_key "sqlite_filename from extraConfig" "$config" "sqlite_filename" "custom.db" +assert_config_key "port still present after merge" "$config" "port" "8000" + +echo "" +echo "7. extraConfig override of port fails" +assert_template_fails "extraConfig.port override rejected" \ + --set 'chromadb.extraConfig.port=9999' + +echo "" +echo "8. extraConfig override of listen_address fails" +assert_template_fails "extraConfig.listen_address override rejected" \ + --set 'chromadb.extraConfig.listen_address=127.0.0.1' + +echo "" +echo "9. telemetry enabled without endpoint fails" +assert_template_fails "telemetry.enabled without endpoint rejected" \ + --set 'chromadb.telemetry.enabled=true' + +echo "" +# v1-config is only mounted for >= 1.0.0; this test validates template +# rendering only, not runtime CORS behavior for pre-1.0 versions. +echo "10. CORS wildcard on < 1.0.0 (ConfigMap renders)" +config=$(get_v1_config --set 'chromadb.corsAllowOrigins={*}' --set 'chromadb.apiVersion=0.6.3') +assert_config_key "cors_allow_origins wildcard for pre-1.0" "$config" "cors_allow_origins[0]" "*" + +echo "" +echo "--- Results: $PASS passed, $FAIL failed ---" +[ "$FAIL" -eq 0 ] || exit 1