codeant-ai-for-open-source[bot] commented on code in PR #41777:
URL: https://github.com/apache/superset/pull/41777#discussion_r3536902080
##########
helm/superset/templates/_helpers.tpl:
##########
@@ -105,80 +106,564 @@ app.kubernetes.io/component: {{ .component }}
{{- end -}}
-{{- define "superset-config" }}
+{{/*
+Coalescing resolvers for DB and Redis connection parameters.
+Each resolver checks (in order):
+ 1. New top-level database.* / cache.* values
+ 2. Legacy supersetNode.connections.* keys (deprecation path, using safe
index to avoid errors on absent maps)
+ 3. cluster.* service name overrides
+ 4. Hard-coded defaults derived from the release name
+Call with root context: {{ include "superset.db.host" . }}
+*/}}
+
+{{/*
+Helper to safely read .Values.supersetNode.connections.<key> without erroring
when maps are absent.
+*/}}
+{{- define "_superset.legacyConn" -}}
+{{- $sn := index .Values "supersetNode" | default dict -}}
+{{- $conn := index $sn "connections" | default dict -}}
+{{- $conn | toJson -}}
+{{- end -}}
+
+{{- define "superset.db.host" -}}
+{{- $conn := include "_superset.legacyConn" . | fromJson -}}
+{{- tpl (coalesce .Values.database.host (index $conn "db_host")
.Values.cluster.databaseServiceName (printf "%s-postgresql" .Release.Name)) $
-}}
+{{- end -}}
+
+{{- define "superset.db.port" -}}
+{{- $conn := include "_superset.legacyConn" . | fromJson -}}
+{{- if .Values.database.port -}}
+{{- .Values.database.port | toString -}}
+{{- else -}}
+{{- coalesce (index $conn "db_port") "5432" -}}
+{{- end -}}
+{{- end -}}
+
+{{- define "superset.db.user" -}}
+{{- $conn := include "_superset.legacyConn" . | fromJson -}}
+{{- coalesce .Values.database.user (index $conn "db_user") "superset" -}}
+{{- end -}}
+
+{{- define "superset.db.password" -}}
+{{- $conn := include "_superset.legacyConn" . | fromJson -}}
+{{- coalesce .Values.database.password (index $conn "db_pass") "superset" -}}
+{{- end -}}
+
+{{- define "superset.db.name" -}}
+{{- $conn := include "_superset.legacyConn" . | fromJson -}}
+{{- coalesce .Values.database.name (index $conn "db_name") "superset" -}}
+{{- end -}}
+
+{{- define "superset.db.driver" -}}
+{{- $conn := include "_superset.legacyConn" . | fromJson -}}
+{{- coalesce .Values.database.driver (index $conn "db_type")
"postgresql+psycopg2" -}}
+{{- end -}}
+
+{{- define "superset.redis.host" -}}
+{{- $conn := include "_superset.legacyConn" . | fromJson -}}
+{{- tpl (coalesce .Values.cache.host (index $conn "redis_host")
.Values.cluster.redisServiceName (printf "%s-redis-headless" .Release.Name)) $
-}}
+{{- end -}}
+
+{{- define "superset.redis.port" -}}
+{{- $conn := include "_superset.legacyConn" . | fromJson -}}
+{{- if .Values.cache.port -}}
+{{- .Values.cache.port | toString -}}
+{{- else -}}
+{{- coalesce (index $conn "redis_port") "6379" -}}
+{{- end -}}
+{{- end -}}
+
+{{- define "superset.redis.user" -}}
+{{- $conn := include "_superset.legacyConn" . | fromJson -}}
+{{- coalesce .Values.cache.user (index $conn "redis_user") "" -}}
+{{- end -}}
+
+{{- define "superset.redis.password" -}}
+{{- $conn := include "_superset.legacyConn" . | fromJson -}}
+{{- coalesce .Values.cache.password (index $conn "redis_password") "" -}}
+{{- end -}}
+
+{{- define "superset.redis.cacheDb" -}}
+{{- $conn := include "_superset.legacyConn" . | fromJson -}}
+{{- coalesce (.Values.cache.cacheDb | toString) (index $conn "redis_cache_db")
"1" -}}
+{{- end -}}
+
+{{- define "superset.redis.celeryDb" -}}
+{{- $conn := include "_superset.legacyConn" . | fromJson -}}
+{{- coalesce (.Values.cache.celeryDb | toString) (index $conn
"redis_celery_db") "0" -}}
+{{- end -}}
+
+{{- define "superset.redis.proto" -}}
+{{- if .Values.cache.driver }}{{ .Values.cache.driver }}{{- else if
.Values.cache.ssl.enabled }}rediss{{- else }}redis{{- end -}}
+{{- end -}}
+
+{{- define "superset.config" }}
+{{- /* SECURITY: Validate admin password is set if admin creation is enabled
*/}}
+{{- if and .Values.init.createAdmin (or (not .Values.init.adminUser.password)
(eq .Values.init.adminUser.password "")) }}
+{{- fail "SECURITY ERROR: init.createAdmin is true but init.adminUser.password
is empty. You must set a secure password using --set
init.adminUser.password='your-password' or via external secret." }}
+{{- end }}
+
import os
+{{- if or .Values.config.cacheConfig .Values.config.dataCacheConfig
.Values.config.resultsBackend .Values.config.celeryConfig .Values.cache.enabled
}}
from flask_caching.backends.rediscache import RedisCache
+{{- end }}
def env(key, default=None):
return os.getenv(key, default)
-# Redis Base URL
-{{- if .Values.supersetNode.connections.redis_password }}
-REDIS_BASE_URL=f"{env('REDIS_DRIVER') or
env('REDIS_PROTO')}://{env('REDIS_USER',
'')}:{env('REDIS_PASSWORD')}@{env('REDIS_HOST')}:{env('REDIS_PORT')}"
+{{- /* Database Configuration - Superset always requires a database */}}
+{{- if .Values.database.uri }}
+SQLALCHEMY_DATABASE_URI = {{ .Values.database.uri | quote }}
+{{- else }}
+{{- $driver := include "superset.db.driver" . }}
+{{- $sslParams := "" }}
+{{- if and (hasKey .Values.database "ssl") .Values.database.ssl.enabled }}
+{{- $sslMode := .Values.database.ssl.mode | default "require" }}
+{{- $sslParams = printf "?sslmode=%s" $sslMode }}
+{{- end }}
+SQLALCHEMY_DATABASE_URI = f"{{ $driver }}://{{ include "superset.db.user" .
}}:{{ include "superset.db.password" . }}@{{ include "superset.db.host" . }}:{{
include "superset.db.port" . }}/{{ include "superset.db.name" . }}{{ $sslParams
}}"
Review Comment:
**Suggestion:** The database URI is assembled by interpolating raw username
and password into the URL without percent-encoding, so common special
characters (for example `@`, `:`, `/`, `#`) in credentials will produce an
invalid connection string and fail database connectivity. Encode credentials
before embedding them into the URI. [logic error]
<details>
<summary><b>Severity Level:</b> Critical 🚨</summary>
```mdx
❌ Superset web fails connecting metadata DB via SQLAlchemy.
❌ Init job superset db upgrade crashes on invalid URI.
⚠️ Deployments with complex passwords require manual workarounds or escaping.
```
</details>
<details>
<summary><b>Steps of Reproduction ✅ </b></summary>
```mdx
1. Configure the chart with database credentials containing reserved URL
characters, for
example `--set database.user='dbuser' --set database.password='pa@ss:word'`
as defined in
`helm/superset/values.yaml:270-287`.
2. Leave `.Values.database.uri` unset (the default at
`values.yaml:272-274`); in this case
`superset.config` at `helm/superset/templates/_helpers.tpl:214-225`
constructs
`SQLALCHEMY_DATABASE_URI` using the f-string at line 224, interpolating the
raw username
and password into `dbuser:pa@ss:word@host:port/dbname`.
3. Secret `helm/superset/templates/secret-superset-config.yaml:20-30` writes
this URI into
`superset_config.py`, which is mounted by the web deployment
(`deployment.yaml:192-195`)
and init job (`init-job.yaml:136-139`) via the `superset-config` secret at
`configMountPath`.
4. When the init job runs `superset db upgrade` (command defined at
`helm/superset/values.yaml:909-913`) and the web server starts
(`supersetNode.command` at
`values.yaml:372-377`), Superset and SQLAlchemy attempt to parse
`SQLALCHEMY_DATABASE_URI`; the unescaped `@` and `:` in the password corrupt
the authority
portion of the URI, causing connection failures and preventing the metadata
database from
being reached.
```
</details>
[](https://app.codeant.ai/fix-in-ide?tool=cursor&prompt_id=571ffb70f3a44804b0f0f602e43b9528&service=github&base_url=https%3A%2F%2Fgithub.com&org=apache&repo=apache%2Fsuperset)
[](https://app.codeant.ai/fix-in-ide?tool=vscode-claude&prompt_id=571ffb70f3a44804b0f0f602e43b9528&service=github&base_url=https%3A%2F%2Fgithub.com&org=apache&repo=apache%2Fsuperset)
*(Use Cmd/Ctrl + Click for best experience)*
<details>
<summary><b>Prompt for AI Agent 🤖 </b></summary>
```mdx
This is a comment left during a code review.
**Path:** helm/superset/templates/_helpers.tpl
**Line:** 224:224
**Comment:**
*Logic Error: The database URI is assembled by interpolating raw
username and password into the URL without percent-encoding, so common special
characters (for example `@`, `:`, `/`, `#`) in credentials will produce an
invalid connection string and fail database connectivity. Encode credentials
before embedding them into the URI.
Validate the correctness of the flagged issue. If correct, How can I resolve
this? If you propose a fix, implement it and please make it concise.
Once fix is implemented, also check other comments on the same PR, and ask
user if the user wants to fix the rest of the comments as well. if said yes,
then fetch all the comments validate the correctness and implement a minimal fix
```
</details>
<a
href='https://app.codeant.ai/feedback?pr_url=https%3A%2F%2Fgithub.com%2Fapache%2Fsuperset%2Fpull%2F41777&comment_hash=083a2cf0305091179ab604754b454bd8b28816c1f9855598e90ae96a936620ec&reaction=like'>👍</a>
| <a
href='https://app.codeant.ai/feedback?pr_url=https%3A%2F%2Fgithub.com%2Fapache%2Fsuperset%2Fpull%2F41777&comment_hash=083a2cf0305091179ab604754b454bd8b28816c1f9855598e90ae96a936620ec&reaction=dislike'>👎</a>
##########
helm/superset/templates/_helpers.tpl:
##########
@@ -105,80 +106,564 @@ app.kubernetes.io/component: {{ .component }}
{{- end -}}
-{{- define "superset-config" }}
+{{/*
+Coalescing resolvers for DB and Redis connection parameters.
+Each resolver checks (in order):
+ 1. New top-level database.* / cache.* values
+ 2. Legacy supersetNode.connections.* keys (deprecation path, using safe
index to avoid errors on absent maps)
+ 3. cluster.* service name overrides
+ 4. Hard-coded defaults derived from the release name
+Call with root context: {{ include "superset.db.host" . }}
+*/}}
+
+{{/*
+Helper to safely read .Values.supersetNode.connections.<key> without erroring
when maps are absent.
+*/}}
+{{- define "_superset.legacyConn" -}}
+{{- $sn := index .Values "supersetNode" | default dict -}}
+{{- $conn := index $sn "connections" | default dict -}}
+{{- $conn | toJson -}}
+{{- end -}}
+
+{{- define "superset.db.host" -}}
+{{- $conn := include "_superset.legacyConn" . | fromJson -}}
+{{- tpl (coalesce .Values.database.host (index $conn "db_host")
.Values.cluster.databaseServiceName (printf "%s-postgresql" .Release.Name)) $
-}}
+{{- end -}}
+
+{{- define "superset.db.port" -}}
+{{- $conn := include "_superset.legacyConn" . | fromJson -}}
+{{- if .Values.database.port -}}
+{{- .Values.database.port | toString -}}
+{{- else -}}
+{{- coalesce (index $conn "db_port") "5432" -}}
+{{- end -}}
+{{- end -}}
+
+{{- define "superset.db.user" -}}
+{{- $conn := include "_superset.legacyConn" . | fromJson -}}
+{{- coalesce .Values.database.user (index $conn "db_user") "superset" -}}
+{{- end -}}
+
+{{- define "superset.db.password" -}}
+{{- $conn := include "_superset.legacyConn" . | fromJson -}}
+{{- coalesce .Values.database.password (index $conn "db_pass") "superset" -}}
+{{- end -}}
+
+{{- define "superset.db.name" -}}
+{{- $conn := include "_superset.legacyConn" . | fromJson -}}
+{{- coalesce .Values.database.name (index $conn "db_name") "superset" -}}
+{{- end -}}
+
+{{- define "superset.db.driver" -}}
+{{- $conn := include "_superset.legacyConn" . | fromJson -}}
+{{- coalesce .Values.database.driver (index $conn "db_type")
"postgresql+psycopg2" -}}
+{{- end -}}
+
+{{- define "superset.redis.host" -}}
+{{- $conn := include "_superset.legacyConn" . | fromJson -}}
+{{- tpl (coalesce .Values.cache.host (index $conn "redis_host")
.Values.cluster.redisServiceName (printf "%s-redis-headless" .Release.Name)) $
-}}
+{{- end -}}
+
+{{- define "superset.redis.port" -}}
+{{- $conn := include "_superset.legacyConn" . | fromJson -}}
+{{- if .Values.cache.port -}}
+{{- .Values.cache.port | toString -}}
+{{- else -}}
+{{- coalesce (index $conn "redis_port") "6379" -}}
+{{- end -}}
+{{- end -}}
+
+{{- define "superset.redis.user" -}}
+{{- $conn := include "_superset.legacyConn" . | fromJson -}}
+{{- coalesce .Values.cache.user (index $conn "redis_user") "" -}}
+{{- end -}}
+
+{{- define "superset.redis.password" -}}
+{{- $conn := include "_superset.legacyConn" . | fromJson -}}
+{{- coalesce .Values.cache.password (index $conn "redis_password") "" -}}
+{{- end -}}
+
+{{- define "superset.redis.cacheDb" -}}
+{{- $conn := include "_superset.legacyConn" . | fromJson -}}
+{{- coalesce (.Values.cache.cacheDb | toString) (index $conn "redis_cache_db")
"1" -}}
+{{- end -}}
+
+{{- define "superset.redis.celeryDb" -}}
+{{- $conn := include "_superset.legacyConn" . | fromJson -}}
+{{- coalesce (.Values.cache.celeryDb | toString) (index $conn
"redis_celery_db") "0" -}}
+{{- end -}}
+
+{{- define "superset.redis.proto" -}}
+{{- if .Values.cache.driver }}{{ .Values.cache.driver }}{{- else if
.Values.cache.ssl.enabled }}rediss{{- else }}redis{{- end -}}
+{{- end -}}
+
+{{- define "superset.config" }}
+{{- /* SECURITY: Validate admin password is set if admin creation is enabled
*/}}
+{{- if and .Values.init.createAdmin (or (not .Values.init.adminUser.password)
(eq .Values.init.adminUser.password "")) }}
+{{- fail "SECURITY ERROR: init.createAdmin is true but init.adminUser.password
is empty. You must set a secure password using --set
init.adminUser.password='your-password' or via external secret." }}
+{{- end }}
+
import os
+{{- if or .Values.config.cacheConfig .Values.config.dataCacheConfig
.Values.config.resultsBackend .Values.config.celeryConfig .Values.cache.enabled
}}
from flask_caching.backends.rediscache import RedisCache
+{{- end }}
def env(key, default=None):
return os.getenv(key, default)
-# Redis Base URL
-{{- if .Values.supersetNode.connections.redis_password }}
-REDIS_BASE_URL=f"{env('REDIS_DRIVER') or
env('REDIS_PROTO')}://{env('REDIS_USER',
'')}:{env('REDIS_PASSWORD')}@{env('REDIS_HOST')}:{env('REDIS_PORT')}"
+{{- /* Database Configuration - Superset always requires a database */}}
+{{- if .Values.database.uri }}
+SQLALCHEMY_DATABASE_URI = {{ .Values.database.uri | quote }}
+{{- else }}
+{{- $driver := include "superset.db.driver" . }}
+{{- $sslParams := "" }}
+{{- if and (hasKey .Values.database "ssl") .Values.database.ssl.enabled }}
+{{- $sslMode := .Values.database.ssl.mode | default "require" }}
+{{- $sslParams = printf "?sslmode=%s" $sslMode }}
+{{- end }}
+SQLALCHEMY_DATABASE_URI = f"{{ $driver }}://{{ include "superset.db.user" .
}}:{{ include "superset.db.password" . }}@{{ include "superset.db.host" . }}:{{
include "superset.db.port" . }}/{{ include "superset.db.name" . }}{{ $sslParams
}}"
+{{- end }}
+{{- if hasKey .Values.config "SQLALCHEMY_TRACK_MODIFICATIONS" }}
+SQLALCHEMY_TRACK_MODIFICATIONS = {{
.Values.config.SQLALCHEMY_TRACK_MODIFICATIONS | toString | title }}
{{- else }}
-REDIS_BASE_URL=f"{env('REDIS_DRIVER') or
env('REDIS_PROTO')}://{env('REDIS_HOST')}:{env('REDIS_PORT')}"
+SQLALCHEMY_TRACK_MODIFICATIONS = False
{{- end }}
-# Redis URL Params
-{{- if .Values.supersetNode.connections.redis_ssl.enabled }}
-REDIS_URL_PARAMS = f"?ssl_cert_reqs={env('REDIS_SSL_CERT_REQS')}"
+{{- /* Redis Configuration - only if Redis cache is configured */}}
+{{- if .Values.cache.enabled }}
+{{- if .Values.cache.cacheUrl }}
+CACHE_REDIS_URL = {{ .Values.cache.cacheUrl | quote }}
+{{- else }}
+{{- $redisHost := include "superset.redis.host" . }}
+{{- $redisUser := include "superset.redis.user" . }}
+{{- $redisPort := include "superset.redis.port" . }}
+{{- $redisPassword := include "superset.redis.password" . }}
+{{- $useSSL := and (hasKey .Values.cache "ssl") .Values.cache.ssl.enabled }}
+{{- if $redisPassword }}
+{{- if $redisUser }}
+REDIS_BASE_URL = f"{{ include "superset.redis.proto" . }}://{{ $redisUser
}}:{{ $redisPassword }}@{{ $redisHost }}:{{ $redisPort }}"
Review Comment:
**Suggestion:** The Redis URL builder has the same escaping problem as the
SQL URI: raw credentials are inserted directly into the URL, so special
characters in usernames/passwords can generate malformed Redis URLs and break
cache/Celery startup. Percent-encode credential components before composing the
URL. [logic error]
<details>
<summary><b>Severity Level:</b> Critical 🚨</summary>
```mdx
❌ Redis-backed cache fails; web nodes start without caching.
❌ Celery workers cannot connect Redis broker, tasks stuck.
⚠️ Async queries features unreliable when Redis URL malformed.
```
</details>
<details>
<summary><b>Steps of Reproduction ✅ </b></summary>
```mdx
1. Configure Redis credentials with special characters, for example `--set
cache.user='redis-user' --set cache.password='p@ss:word'` using the cache
configuration
documented at `helm/superset/values.yaml:293-337`.
2. With `.Values.cache.cacheUrl` unset (default at `values.yaml:299-300`),
`superset.config` in `helm/superset/templates/_helpers.tpl:232-259` computes
`REDIS_BASE_URL`; when `$redisPassword` is set, line 244 builds
`redis://redis-user:p@ss:word@host:port` without percent-encoding the
credential
components.
3. Subsequent lines in `_helpers.tpl` derive `CACHE_REDIS_URL` and
`CELERY_REDIS_URL` from
`REDIS_BASE_URL` at 257-264, and these are injected into `CACHE_CONFIG`
(lines 274-283)
and the Celery configuration (317-351); web, worker, and beat deployments
consume these
settings via the mounted `superset_config.py` secret
(`secret-superset-config.yaml:29-32`
and volume mounts in `deployment.yaml:121-124`,
`deployment-worker.yaml:115-118`,
`deployment-beat.yaml:110-116`).
4. At runtime the Redis client parses the malformed URL; the embedded `@`
and `:` in the
password confuse the parser, leading to authentication or host parsing
errors, so cache
initialization, Celery broker connections, and async query features relying
on Redis all
fail, surfacing connection errors in Superset logs.
```
</details>
[](https://app.codeant.ai/fix-in-ide?tool=cursor&prompt_id=ac589ce2c5394298a81f3fb186bccccc&service=github&base_url=https%3A%2F%2Fgithub.com&org=apache&repo=apache%2Fsuperset)
[](https://app.codeant.ai/fix-in-ide?tool=vscode-claude&prompt_id=ac589ce2c5394298a81f3fb186bccccc&service=github&base_url=https%3A%2F%2Fgithub.com&org=apache&repo=apache%2Fsuperset)
*(Use Cmd/Ctrl + Click for best experience)*
<details>
<summary><b>Prompt for AI Agent 🤖 </b></summary>
```mdx
This is a comment left during a code review.
**Path:** helm/superset/templates/_helpers.tpl
**Line:** 244:244
**Comment:**
*Logic Error: The Redis URL builder has the same escaping problem as
the SQL URI: raw credentials are inserted directly into the URL, so special
characters in usernames/passwords can generate malformed Redis URLs and break
cache/Celery startup. Percent-encode credential components before composing the
URL.
Validate the correctness of the flagged issue. If correct, How can I resolve
this? If you propose a fix, implement it and please make it concise.
Once fix is implemented, also check other comments on the same PR, and ask
user if the user wants to fix the rest of the comments as well. if said yes,
then fetch all the comments validate the correctness and implement a minimal fix
```
</details>
<a
href='https://app.codeant.ai/feedback?pr_url=https%3A%2F%2Fgithub.com%2Fapache%2Fsuperset%2Fpull%2F41777&comment_hash=730625871a4716ff2af1a85d0dd79165cd4f4d617a487971ea8019efdb8d65dd&reaction=like'>👍</a>
| <a
href='https://app.codeant.ai/feedback?pr_url=https%3A%2F%2Fgithub.com%2Fapache%2Fsuperset%2Fpull%2F41777&comment_hash=730625871a4716ff2af1a85d0dd79165cd4f4d617a487971ea8019efdb8d65dd&reaction=dislike'>👎</a>
##########
helm/superset/templates/_helpers.tpl:
##########
@@ -105,80 +106,564 @@ app.kubernetes.io/component: {{ .component }}
{{- end -}}
-{{- define "superset-config" }}
+{{/*
+Coalescing resolvers for DB and Redis connection parameters.
+Each resolver checks (in order):
+ 1. New top-level database.* / cache.* values
+ 2. Legacy supersetNode.connections.* keys (deprecation path, using safe
index to avoid errors on absent maps)
+ 3. cluster.* service name overrides
+ 4. Hard-coded defaults derived from the release name
+Call with root context: {{ include "superset.db.host" . }}
+*/}}
+
+{{/*
+Helper to safely read .Values.supersetNode.connections.<key> without erroring
when maps are absent.
+*/}}
+{{- define "_superset.legacyConn" -}}
+{{- $sn := index .Values "supersetNode" | default dict -}}
+{{- $conn := index $sn "connections" | default dict -}}
+{{- $conn | toJson -}}
+{{- end -}}
+
+{{- define "superset.db.host" -}}
+{{- $conn := include "_superset.legacyConn" . | fromJson -}}
+{{- tpl (coalesce .Values.database.host (index $conn "db_host")
.Values.cluster.databaseServiceName (printf "%s-postgresql" .Release.Name)) $
-}}
+{{- end -}}
+
+{{- define "superset.db.port" -}}
+{{- $conn := include "_superset.legacyConn" . | fromJson -}}
+{{- if .Values.database.port -}}
+{{- .Values.database.port | toString -}}
+{{- else -}}
+{{- coalesce (index $conn "db_port") "5432" -}}
+{{- end -}}
+{{- end -}}
+
+{{- define "superset.db.user" -}}
+{{- $conn := include "_superset.legacyConn" . | fromJson -}}
+{{- coalesce .Values.database.user (index $conn "db_user") "superset" -}}
+{{- end -}}
+
+{{- define "superset.db.password" -}}
+{{- $conn := include "_superset.legacyConn" . | fromJson -}}
+{{- coalesce .Values.database.password (index $conn "db_pass") "superset" -}}
+{{- end -}}
+
+{{- define "superset.db.name" -}}
+{{- $conn := include "_superset.legacyConn" . | fromJson -}}
+{{- coalesce .Values.database.name (index $conn "db_name") "superset" -}}
+{{- end -}}
+
+{{- define "superset.db.driver" -}}
+{{- $conn := include "_superset.legacyConn" . | fromJson -}}
+{{- coalesce .Values.database.driver (index $conn "db_type")
"postgresql+psycopg2" -}}
+{{- end -}}
+
+{{- define "superset.redis.host" -}}
+{{- $conn := include "_superset.legacyConn" . | fromJson -}}
+{{- tpl (coalesce .Values.cache.host (index $conn "redis_host")
.Values.cluster.redisServiceName (printf "%s-redis-headless" .Release.Name)) $
-}}
+{{- end -}}
+
+{{- define "superset.redis.port" -}}
+{{- $conn := include "_superset.legacyConn" . | fromJson -}}
+{{- if .Values.cache.port -}}
+{{- .Values.cache.port | toString -}}
+{{- else -}}
+{{- coalesce (index $conn "redis_port") "6379" -}}
+{{- end -}}
+{{- end -}}
+
+{{- define "superset.redis.user" -}}
+{{- $conn := include "_superset.legacyConn" . | fromJson -}}
+{{- coalesce .Values.cache.user (index $conn "redis_user") "" -}}
+{{- end -}}
+
+{{- define "superset.redis.password" -}}
+{{- $conn := include "_superset.legacyConn" . | fromJson -}}
+{{- coalesce .Values.cache.password (index $conn "redis_password") "" -}}
+{{- end -}}
+
+{{- define "superset.redis.cacheDb" -}}
+{{- $conn := include "_superset.legacyConn" . | fromJson -}}
+{{- coalesce (.Values.cache.cacheDb | toString) (index $conn "redis_cache_db")
"1" -}}
+{{- end -}}
+
+{{- define "superset.redis.celeryDb" -}}
+{{- $conn := include "_superset.legacyConn" . | fromJson -}}
+{{- coalesce (.Values.cache.celeryDb | toString) (index $conn
"redis_celery_db") "0" -}}
+{{- end -}}
+
+{{- define "superset.redis.proto" -}}
+{{- if .Values.cache.driver }}{{ .Values.cache.driver }}{{- else if
.Values.cache.ssl.enabled }}rediss{{- else }}redis{{- end -}}
+{{- end -}}
+
+{{- define "superset.config" }}
+{{- /* SECURITY: Validate admin password is set if admin creation is enabled
*/}}
+{{- if and .Values.init.createAdmin (or (not .Values.init.adminUser.password)
(eq .Values.init.adminUser.password "")) }}
+{{- fail "SECURITY ERROR: init.createAdmin is true but init.adminUser.password
is empty. You must set a secure password using --set
init.adminUser.password='your-password' or via external secret." }}
+{{- end }}
+
import os
+{{- if or .Values.config.cacheConfig .Values.config.dataCacheConfig
.Values.config.resultsBackend .Values.config.celeryConfig .Values.cache.enabled
}}
from flask_caching.backends.rediscache import RedisCache
+{{- end }}
def env(key, default=None):
return os.getenv(key, default)
-# Redis Base URL
-{{- if .Values.supersetNode.connections.redis_password }}
-REDIS_BASE_URL=f"{env('REDIS_DRIVER') or
env('REDIS_PROTO')}://{env('REDIS_USER',
'')}:{env('REDIS_PASSWORD')}@{env('REDIS_HOST')}:{env('REDIS_PORT')}"
+{{- /* Database Configuration - Superset always requires a database */}}
+{{- if .Values.database.uri }}
+SQLALCHEMY_DATABASE_URI = {{ .Values.database.uri | quote }}
+{{- else }}
+{{- $driver := include "superset.db.driver" . }}
+{{- $sslParams := "" }}
+{{- if and (hasKey .Values.database "ssl") .Values.database.ssl.enabled }}
+{{- $sslMode := .Values.database.ssl.mode | default "require" }}
+{{- $sslParams = printf "?sslmode=%s" $sslMode }}
+{{- end }}
+SQLALCHEMY_DATABASE_URI = f"{{ $driver }}://{{ include "superset.db.user" .
}}:{{ include "superset.db.password" . }}@{{ include "superset.db.host" . }}:{{
include "superset.db.port" . }}/{{ include "superset.db.name" . }}{{ $sslParams
}}"
+{{- end }}
+{{- if hasKey .Values.config "SQLALCHEMY_TRACK_MODIFICATIONS" }}
+SQLALCHEMY_TRACK_MODIFICATIONS = {{
.Values.config.SQLALCHEMY_TRACK_MODIFICATIONS | toString | title }}
{{- else }}
-REDIS_BASE_URL=f"{env('REDIS_DRIVER') or
env('REDIS_PROTO')}://{env('REDIS_HOST')}:{env('REDIS_PORT')}"
+SQLALCHEMY_TRACK_MODIFICATIONS = False
{{- end }}
-# Redis URL Params
-{{- if .Values.supersetNode.connections.redis_ssl.enabled }}
-REDIS_URL_PARAMS = f"?ssl_cert_reqs={env('REDIS_SSL_CERT_REQS')}"
+{{- /* Redis Configuration - only if Redis cache is configured */}}
+{{- if .Values.cache.enabled }}
+{{- if .Values.cache.cacheUrl }}
+CACHE_REDIS_URL = {{ .Values.cache.cacheUrl | quote }}
+{{- else }}
+{{- $redisHost := include "superset.redis.host" . }}
+{{- $redisUser := include "superset.redis.user" . }}
+{{- $redisPort := include "superset.redis.port" . }}
+{{- $redisPassword := include "superset.redis.password" . }}
+{{- $useSSL := and (hasKey .Values.cache "ssl") .Values.cache.ssl.enabled }}
+{{- if $redisPassword }}
+{{- if $redisUser }}
+REDIS_BASE_URL = f"{{ include "superset.redis.proto" . }}://{{ $redisUser
}}:{{ $redisPassword }}@{{ $redisHost }}:{{ $redisPort }}"
+{{- else }}
+REDIS_BASE_URL = f"{{ include "superset.redis.proto" . }}://:{{ $redisPassword
}}@{{ $redisHost }}:{{ $redisPort }}"
+{{- end }}
+{{- else }}
+REDIS_BASE_URL = f"{{ include "superset.redis.proto" . }}://{{ $redisHost
}}:{{ $redisPort }}"
+{{- end }}
+{{- if $useSSL }}
+{{- $sslCertReqs := .Values.cache.ssl.ssl_cert_reqs | default "required" }}
+REDIS_URL_PARAMS = f"?ssl_cert_reqs={{ $sslCertReqs }}"
{{- else }}
REDIS_URL_PARAMS = ""
-{{- end}}
-
-# Build Redis URLs
-CACHE_REDIS_URL = f"{REDIS_BASE_URL}/{env('REDIS_DB', 1)}{REDIS_URL_PARAMS}"
-CELERY_REDIS_URL = f"{REDIS_BASE_URL}/{env('REDIS_CELERY_DB',
0)}{REDIS_URL_PARAMS}"
+{{- end }}
+{{- $cacheDb := include "superset.redis.cacheDb" . }}
+CACHE_REDIS_URL = f"{REDIS_BASE_URL}/{{ $cacheDb }}{REDIS_URL_PARAMS}"
+{{- end }}
+{{- if .Values.cache.celeryUrl }}
+CELERY_REDIS_URL = {{ .Values.cache.celeryUrl | quote }}
+{{- else if not .Values.cache.cacheUrl }}
+{{- $celeryDb := include "superset.redis.celeryDb" . }}
+CELERY_REDIS_URL = f"{REDIS_BASE_URL}/{{ $celeryDb }}{REDIS_URL_PARAMS}"
+{{- else }}
+{{- if or .Values.config.celeryConfig (not .Values.cache.enabled) }}
+{{- /* Custom celeryConfig provided or cache disabled - OK */}}
+{{- else }}
+{{- fail "CONFIGURATION ERROR: cache.cacheUrl is set but cache.celeryUrl is
not set. When using cacheUrl, you must also set celeryUrl for Celery to work.
Alternatively, set config.celeryConfig to provide a custom Celery
configuration." }}
+{{- end }}
+{{- end }}
+{{- end }}
-MAPBOX_API_KEY = env('MAPBOX_API_KEY', '')
+{{- /* Cache Configuration */}}
+{{- if .Values.config.cacheConfig }}
+CACHE_CONFIG = {{ .Values.config.cacheConfig | toJson | indent 2 }}
+{{- else if .Values.cache.enabled }}
CACHE_CONFIG = {
- 'CACHE_TYPE': 'RedisCache',
- 'CACHE_DEFAULT_TIMEOUT': 300,
- 'CACHE_KEY_PREFIX': 'superset_',
- 'CACHE_REDIS_URL': CACHE_REDIS_URL,
+ 'CACHE_TYPE': 'RedisCache',
+ 'CACHE_DEFAULT_TIMEOUT': {{ .Values.cache.defaultTimeout | default
(.Values.config.cacheDefaultTimeout | default 86400) | int }},
+ 'CACHE_KEY_PREFIX': {{ .Values.cache.keyPrefix | default "superset_" |
quote }},
+ 'CACHE_REDIS_URL': CACHE_REDIS_URL,
}
+{{- end }}
+
+{{- if .Values.config.dataCacheConfig }}
+DATA_CACHE_CONFIG = {{ .Values.config.dataCacheConfig | toJson | indent 2 }}
+{{- else if .Values.config.cacheConfig }}
DATA_CACHE_CONFIG = CACHE_CONFIG
+{{- else if .Values.cache.enabled }}
+DATA_CACHE_CONFIG = CACHE_CONFIG
+{{- end }}
+{{- /* SQLLAB_ASYNC_TIME_LIMIT_SEC - Required for async_queries module import
(default: 6 hours) */}}
+{{- if .Values.config.SQLLAB_ASYNC_TIME_LIMIT_SEC }}
+SQLLAB_ASYNC_TIME_LIMIT_SEC = {{ .Values.config.SQLLAB_ASYNC_TIME_LIMIT_SEC |
int }}
+{{- else }}
+from datetime import timedelta
+SQLLAB_ASYNC_TIME_LIMIT_SEC = int(timedelta(hours=6).total_seconds())
+{{- end }}
-if os.getenv("SQLALCHEMY_DATABASE_URI"):
- SQLALCHEMY_DATABASE_URI = os.getenv("SQLALCHEMY_DATABASE_URI")
-else:
- {{- if eq .Values.supersetNode.connections.db_type "postgresql" }}
- SQLALCHEMY_DATABASE_URI =
f"postgresql+psycopg2://{os.getenv('DB_USER')}:{os.getenv('DB_PASS')}@{os.getenv('DB_HOST')}:{os.getenv('DB_PORT')}/{os.getenv('DB_NAME')}"
- {{- else if eq .Values.supersetNode.connections.db_type "mysql" }}
- SQLALCHEMY_DATABASE_URI =
f"mysql+mysqldb://{os.getenv('DB_USER')}:{os.getenv('DB_PASS')}@{os.getenv('DB_HOST')}:{os.getenv('DB_PORT')}/{os.getenv('DB_NAME')}"
- {{- else }}
- {{ fail (printf "Unsupported database type: %s. Please use 'postgresql' or
'mysql'." .Values.supersetNode.connections.db_type) }}
- {{- end }}
+{{- /* Celery Configuration */}}
+{{- if .Values.config.celeryConfig }}
+{{- if kindIs "string" .Values.config.celeryConfig }}
+{{ .Values.config.celeryConfig }}
+{{- else }}
+class CeleryConfig:
+{{- range $key, $value := .Values.config.celeryConfig }}
+ {{ $key }} = {{ $value | toJson }}
+{{- end }}
+
+{{- if hasKey .Values.config.celeryConfig "imports" }}
+CELERY_IMPORTS = CeleryConfig.imports
+{{- end }}
+CELERY_CONFIG = CeleryConfig
+{{- end }}
+{{- else if .Values.cache.enabled }}
+from celery.schedules import crontab
+from datetime import timedelta
class CeleryConfig:
- imports = ("superset.sql_lab", )
- broker_url = CELERY_REDIS_URL
- result_backend = CELERY_REDIS_URL
+ imports = (
+ "superset.sql_lab",
+ "superset.tasks.scheduler",
+ "superset.tasks.thumbnails",
+ "superset.tasks.cache",
+ )
+ broker_connection_retry_on_startup = True
+ worker_prefetch_multiplier = 10
+ task_acks_late = True
+ broker_url = CELERY_REDIS_URL
+ result_backend = CELERY_REDIS_URL
+ task_annotations = {
+ "sql_lab.get_sql_results": {
+ "rate_limit": "100/s",
+ },
+ }
+ beat_schedule = {
+ "reports.scheduler": {
+ "task": "reports.scheduler",
+ "schedule": crontab(minute="*", hour="*"),
+ "options": {"expires": int(timedelta(weeks=1).total_seconds())},
+ },
+ "reports.prune_log": {
+ "task": "reports.prune_log",
+ "schedule": crontab(minute=0, hour=0),
+ },
+ }
+CELERY_IMPORTS = CeleryConfig.imports
CELERY_CONFIG = CeleryConfig
+{{- end }}
+
+{{- /* Celery Worker Health Check - File-based health probes for Kubernetes
*/}}
+{{- if and .Values.supersetWorker.healthCheck
.Values.supersetWorker.healthCheck.enabled }}
+# Celery Worker Health Check Configuration
+import threading
+from celery import bootsteps
+from celery.signals import worker_ready, worker_shutdown, worker_init
+
+_readiness_file = {{ .Values.supersetWorker.healthCheck.readinessFile |
default "/tmp/celery_worker_ready" | quote }}
+_liveness_file = {{ .Values.supersetWorker.healthCheck.livenessFile | default
"/tmp/celery_worker_alive" | quote }}
+_heartbeat_interval = {{
.Values.supersetWorker.healthCheck.livenessHeartbeatInterval | default 10 | int
}}
+_liveness_thread = None
+_liveness_stop_event = None
+
+@worker_ready.connect
+def create_ready_file(sender, **kwargs):
+ try:
+ open(_readiness_file, 'w').close()
+ except Exception as e:
+ print(f"Warning: Could not create readiness file: {e}")
+
+@worker_shutdown.connect
+def remove_ready_file(sender, **kwargs):
+ global _liveness_thread, _liveness_stop_event
+ if _liveness_stop_event:
+ _liveness_stop_event.set()
+ if _liveness_thread:
+ _liveness_thread.join(timeout=5)
+ try:
+ if os.path.exists(_readiness_file):
+ os.remove(_readiness_file)
+ if os.path.exists(_liveness_file):
+ os.remove(_liveness_file)
+ except Exception as e:
+ print(f"Warning: Could not remove health check files: {e}")
+
+@worker_init.connect
+def start_liveness_heartbeat(sender, **kwargs):
+ global _liveness_thread, _liveness_stop_event
+ _liveness_stop_event = threading.Event()
+
+ def update_liveness():
+ while not _liveness_stop_event.is_set():
+ try:
+ with open(_liveness_file, 'w') as f:
+ f.write(str(os.getpid()))
+ except Exception as e:
+ print(f"Warning: Could not update liveness file: {e}")
+ _liveness_stop_event.wait(_heartbeat_interval)
+
+ _liveness_thread = threading.Thread(target=update_liveness, daemon=True)
+ _liveness_thread.start()
+{{- else }}
+CELERY_WORKER_HEALTH_CHECK_ENABLED = False
+{{- end }}
+
+{{- /* Results Backend */}}
+{{- $redisHostForBackend := include "superset.redis.host" . }}
+{{- $redisPortForBackend := include "superset.redis.port" . }}
+{{- $redisPasswordForBackend := include "superset.redis.password" . }}
+{{- if .Values.config.resultsBackend }}
+{{- if kindIs "string" .Values.config.resultsBackend }}
+RESULTS_BACKEND = {{ .Values.config.resultsBackend }}
+{{- else }}
+RESULTS_BACKEND = RedisCache(
+ host={{ $redisHostForBackend | quote }},
+ {{- if $redisPasswordForBackend }}
+ password={{ $redisPasswordForBackend | quote }},
+ {{- end }}
+ port={{ $redisPortForBackend | int }},
+ key_prefix={{ .Values.cache.resultsBackendKeyPrefix | default
"superset_results" | quote }},
+ {{- if and (hasKey .Values.cache "ssl") .Values.cache.ssl.enabled }}
+ ssl=True,
+ ssl_cert_reqs={{ .Values.cache.ssl.ssl_cert_reqs | default "required" |
quote }},
+ {{- end }}
+)
+{{- end }}
+{{- else if .Values.cache.enabled }}
RESULTS_BACKEND = RedisCache(
- host=env('REDIS_HOST'),
- {{- if .Values.supersetNode.connections.redis_password }}
- password=env('REDIS_PASSWORD'),
- {{- end }}
- port=env('REDIS_PORT'),
- key_prefix='superset_results',
- {{- if .Values.supersetNode.connections.redis_ssl.enabled }}
- ssl=True,
- ssl_cert_reqs=env('REDIS_SSL_CERT_REQS'),
- {{- end }}
+ host={{ $redisHostForBackend | quote }},
+ {{- if $redisPasswordForBackend }}
+ password={{ $redisPasswordForBackend | quote }},
+ {{- end }}
+ port={{ $redisPortForBackend | int }},
+ key_prefix={{ .Values.cache.resultsBackendKeyPrefix | default
"superset_results" | quote }},
+ {{- if and (hasKey .Values.cache "ssl") .Values.cache.ssl.enabled }}
+ ssl=True,
+ ssl_cert_reqs={{ .Values.cache.ssl.ssl_cert_reqs | default "required" |
quote }},
+ {{- end }}
)
+{{- end }}
+
+{{- /* Global Async Queries Cache Backend */}}
+{{- $redisUserForGaq := include "superset.redis.user" . }}
+{{- if .Values.config.GLOBAL_ASYNC_QUERIES_CACHE_BACKEND }}
+GLOBAL_ASYNC_QUERIES_CACHE_BACKEND = {{
.Values.config.GLOBAL_ASYNC_QUERIES_CACHE_BACKEND | toJson | indent 2 }}
+{{- else if .Values.cache.enabled }}
+GLOBAL_ASYNC_QUERIES_CACHE_BACKEND = {
+ "CACHE_TYPE": "RedisCache",
+ "CACHE_REDIS_HOST": {{ $redisHostForBackend | quote }},
+ "CACHE_REDIS_PORT": {{ $redisPortForBackend | int }},
+ {{- if $redisUserForGaq }}
+ "CACHE_REDIS_USER": {{ $redisUserForGaq | quote }},
+ {{- end }}
+ {{- if $redisPasswordForBackend }}
+ "CACHE_REDIS_PASSWORD": {{ $redisPasswordForBackend | quote }},
+ {{- else }}
+ "CACHE_REDIS_PASSWORD": "",
+ {{- end }}
+ "CACHE_REDIS_DB": {{ .Values.cache.asyncQueries.db | default
.Values.cache.cacheDb | default 0 | int }},
+ "CACHE_KEY_PREFIX": {{ .Values.cache.asyncQueries.keyPrefix | default
"qc-" | quote }},
+ "CACHE_DEFAULT_TIMEOUT": {{ .Values.cache.asyncQueries.timeout | default
86400 | int }},
+ {{- if and .Values.cache.sentinel .Values.cache.sentinel.enabled }}
+ {{- if .Values.cache.sentinel.sentinels }}
+ "CACHE_REDIS_SENTINELS": {{ .Values.cache.sentinel.sentinels | toJson }},
+ {{- else }}
+ {{- fail "CONFIGURATION ERROR: cache.sentinel.enabled is true but
cache.sentinel.sentinels is not set. You must provide Sentinel host(s) in
cache.sentinel.sentinels (e.g., [['sentinel-host', 26379]])." }}
+ {{- end }}
+ "CACHE_REDIS_SENTINEL_MASTER": {{ .Values.cache.sentinel.master | default
"mymaster" | quote }},
+ {{- if .Values.cache.sentinel.password }}
+ "CACHE_REDIS_SENTINEL_PASSWORD": {{ .Values.cache.sentinel.password |
quote }},
+ {{- else }}
+ "CACHE_REDIS_SENTINEL_PASSWORD": None,
+ {{- end }}
+ {{- end }}
+ {{- if and (hasKey .Values.cache "ssl") .Values.cache.ssl.enabled }}
+ "CACHE_REDIS_SSL": True,
+ "CACHE_REDIS_SSL_CERTFILE": {{ if .Values.cache.ssl.certfile }}{{
.Values.cache.ssl.certfile | quote }}{{ else }}None{{ end }},
+ "CACHE_REDIS_SSL_KEYFILE": {{ if .Values.cache.ssl.keyfile }}{{
.Values.cache.ssl.keyfile | quote }}{{ else }}None{{ end }},
+ "CACHE_REDIS_SSL_CERT_REQS": {{ .Values.cache.ssl.ssl_cert_reqs | default
"required" | quote }},
+ "CACHE_REDIS_SSL_CA_CERTS": {{ if .Values.cache.ssl.ca_certs }}{{
.Values.cache.ssl.ca_certs | quote }}{{ else }}None{{ end }},
+ {{- else }}
+ "CACHE_REDIS_SSL": False,
+ "CACHE_REDIS_SSL_CERTFILE": None,
+ "CACHE_REDIS_SSL_KEYFILE": None,
+ "CACHE_REDIS_SSL_CERT_REQS": {{ .Values.cache.ssl.ssl_cert_reqs | default
"required" | quote }},
+ "CACHE_REDIS_SSL_CA_CERTS": None,
+ {{- end }}
+}
+{{- end }}
+
+{{- /* Global Async Queries Results Backend */}}
+{{- if .Values.config.GLOBAL_ASYNC_QUERIES_RESULTS_BACKEND }}
+GLOBAL_ASYNC_QUERIES_RESULTS_BACKEND = {{
.Values.config.GLOBAL_ASYNC_QUERIES_RESULTS_BACKEND | toJson | indent 2 }}
+{{- else if .Values.cache.enabled }}
+GLOBAL_ASYNC_QUERIES_RESULTS_BACKEND = {
+ "backend": "redis",
+ "host": {{ $redisHostForBackend | quote }},
+ "port": {{ $redisPortForBackend | int }},
+ "prefix": {{ .Values.cache.asyncQueries.keyPrefix | default "qc-" | quote
}},
+ "db": {{ .Values.cache.asyncQueries.db | default .Values.cache.cacheDb |
default 0 | int }},
+ {{- if $redisPasswordForBackend }}
+ "password": {{ $redisPasswordForBackend | quote }},
+ {{- end }}
+}
+{{- end }}
+
+{{- /* Feature Flags */}}
+{{- if .Values.featureFlags }}
+FEATURE_FLAGS = {
+{{- range $key, $value := .Values.featureFlags }}
+{{- if kindIs "bool" $value }}
+ "{{ $key }}": {{ if $value }}True{{ else }}False{{ end }},
+{{- else if kindIs "string" $value }}
+ "{{ $key }}": {{ $value | quote }},
+{{- else if kindIs "float64" $value }}
+ "{{ $key }}": {{ $value }},
+{{- else if kindIs "int" $value }}
+ "{{ $key }}": {{ $value }},
+{{- else }}
+ "{{ $key }}": {{ $value | toJson }},
+{{- end }}
+{{- end }}
+}
+{{- end }}
+
+{{- /* FAB Security API - Required for List Roles view in 6.0.0+ */}}
+{{- if not (hasKey .Values.config "FAB_ADD_SECURITY_API") }}
+FAB_ADD_SECURITY_API = True
+{{- end }}
+{{- if not (hasKey .Values.config "FAB_ADD_SECURITY_VIEWS") }}
+FAB_ADD_SECURITY_VIEWS = True
+{{- end }}
+
+{{- /* Global Async Queries Transport - Auto-configure for websockets if
enabled */}}
+{{- if .Values.config.GLOBAL_ASYNC_QUERIES_TRANSPORT }}
+GLOBAL_ASYNC_QUERIES_TRANSPORT = {{
.Values.config.GLOBAL_ASYNC_QUERIES_TRANSPORT | quote }}
+{{- else if .Values.supersetWebsockets.enabled }}
+GLOBAL_ASYNC_QUERIES_TRANSPORT = "ws"
+{{- else }}
+GLOBAL_ASYNC_QUERIES_TRANSPORT = "polling"
+{{- end }}
+
+{{- /* Global Async Queries WebSocket URL */}}
+{{- $wsUrl := "" }}
+{{- if .Values.config.GLOBAL_ASYNC_QUERIES_WEBSOCKET_URL }}
+{{- $wsUrl = .Values.config.GLOBAL_ASYNC_QUERIES_WEBSOCKET_URL }}
+GLOBAL_ASYNC_QUERIES_WEBSOCKET_URL = {{ $wsUrl | quote }}
+{{- else if and .Values.supersetWebsockets.enabled
.Values.supersetWebsockets.websocketUrl }}
+{{- $wsUrl = .Values.supersetWebsockets.websocketUrl }}
+GLOBAL_ASYNC_QUERIES_WEBSOCKET_URL = {{ $wsUrl | quote }}
+{{- else if .Values.supersetWebsockets.enabled }}
+{{- $wsServiceName := .Values.cluster.websocketServiceName }}
+{{- if not $wsServiceName }}
+{{- $wsServiceName = printf "%s-ws" (include "superset.fullname" .) }}
+{{- end }}
+{{- $wsPort := .Values.supersetWebsockets.service.port | default 8080 }}
+{{- $wsPath := "/ws" }}
+{{- $clusterDomain := .Values.cluster.domain | default ".svc.cluster.local" }}
+{{- $wsUrl = printf "ws://%s.%s%s:%d%s" $wsServiceName .Release.Namespace
$clusterDomain $wsPort $wsPath }}
+GLOBAL_ASYNC_QUERIES_WEBSOCKET_URL = {{ $wsUrl | quote }}
+{{- end }}
+
+{{- /* Global Async Queries JWT Secret */}}
+{{- if .Values.config.GLOBAL_ASYNC_QUERIES_JWT_SECRET }}
+GLOBAL_ASYNC_QUERIES_JWT_SECRET = {{
.Values.config.GLOBAL_ASYNC_QUERIES_JWT_SECRET | quote }}
+{{- else if and .Values.supersetWebsockets.enabled
.Values.supersetWebsockets.config.jwtSecret }}
+GLOBAL_ASYNC_QUERIES_JWT_SECRET = {{
.Values.supersetWebsockets.config.jwtSecret | quote }}
+{{- end }}
+
+{{- /* Global Async Queries JWT Cookie Settings */}}
+{{- if hasKey .Values.config "GLOBAL_ASYNC_QUERIES_JWT_COOKIE_SECURE" }}
+GLOBAL_ASYNC_QUERIES_JWT_COOKIE_SECURE = {{
.Values.config.GLOBAL_ASYNC_QUERIES_JWT_COOKIE_SECURE | toString | title }}
+{{- else if and .Values.supersetWebsockets.enabled (or (hasPrefix "wss://"
$wsUrl) .Values.ingress.tls) }}
+GLOBAL_ASYNC_QUERIES_JWT_COOKIE_SECURE = True
+{{- else if .Values.supersetWebsockets.enabled }}
+GLOBAL_ASYNC_QUERIES_JWT_COOKIE_SECURE = False
+{{- end }}
+
+{{- if .Values.config.GLOBAL_ASYNC_QUERIES_JWT_COOKIE_SAMESITE }}
+GLOBAL_ASYNC_QUERIES_JWT_COOKIE_SAMESITE = {{
.Values.config.GLOBAL_ASYNC_QUERIES_JWT_COOKIE_SAMESITE | quote }}
+{{- else if .Values.supersetWebsockets.enabled }}
+GLOBAL_ASYNC_QUERIES_JWT_COOKIE_SAMESITE = "Lax"
+{{- end }}
+
+{{- if .Values.config.GLOBAL_ASYNC_QUERIES_JWT_COOKIE_NAME }}
+GLOBAL_ASYNC_QUERIES_JWT_COOKIE_NAME = {{
.Values.config.GLOBAL_ASYNC_QUERIES_JWT_COOKIE_NAME | quote }}
+{{- else if and .Values.supersetWebsockets.enabled
.Values.supersetWebsockets.config.jwtCookieName }}
+GLOBAL_ASYNC_QUERIES_JWT_COOKIE_NAME = {{
.Values.supersetWebsockets.config.jwtCookieName | quote }}
+{{- end }}
+
+{{- /* Content Security Policy (CSP) */}}
+{{- if and .Values.supersetWebsockets.enabled $wsUrl (not (hasKey
.Values.config "TALISMAN_CONFIG")) }}
+TALISMAN_CONFIG = {
+ "content_security_policy": {
+ "base-uri": ["'self'"],
+ "default-src": ["'self'"],
+ "img-src": [
+ "'self'",
+ "blob:",
+ "data:",
+ "https://apachesuperset.gateway.scarf.sh",
+ "https://static.scarf.sh/",
+ "ows.terrestris.de",
+ "https://cdn.document360.io",
+ ],
+ "worker-src": ["'self'", "blob:"],
+ "connect-src": [
+ "'self'",
+ {{ $wsUrl | quote }},
+ "https://api.mapbox.com",
+ "https://events.mapbox.com",
+ "https://tile.openstreetmap.org",
+ "https://tile.osm.ch",
+ ],
+ "object-src": "'none'",
+ "style-src": [
+ "'self'",
+ "'unsafe-inline'",
+ ],
+ "script-src": ["'self'", "'strict-dynamic'"],
+ },
+ "content_security_policy_nonce_in": ["script-src"],
+ {{- if or (hasPrefix "wss://" $wsUrl) .Values.ingress.tls }}
+ "force_https": True,
+ "session_cookie_secure": True,
+ {{- else }}
+ "force_https": False,
+ "session_cookie_secure": False,
+ {{- end }}
+}
+{{- end }}
-{{ if .Values.configOverrides }}
-# Overrides
+{{- /* General Configuration - iterate through all config values */}}
+{{- range $key, $value := .Values.config }}
+{{- if and (ne $key "cacheConfig") (ne $key "dataCacheConfig") (ne $key
"celeryConfig") (ne $key "resultsBackend") (ne $key
"GLOBAL_ASYNC_QUERIES_CACHE_BACKEND") (ne $key
"GLOBAL_ASYNC_QUERIES_RESULTS_BACKEND") (ne $key
"GLOBAL_ASYNC_QUERIES_TRANSPORT") (ne $key
"GLOBAL_ASYNC_QUERIES_WEBSOCKET_URL") (ne $key
"GLOBAL_ASYNC_QUERIES_JWT_SECRET") (ne $key
"GLOBAL_ASYNC_QUERIES_JWT_COOKIE_SECURE") (ne $key
"GLOBAL_ASYNC_QUERIES_JWT_COOKIE_SAMESITE") (ne $key
"GLOBAL_ASYNC_QUERIES_JWT_COOKIE_NAME") (ne $key "TALISMAN_CONFIG") (ne $key
"SQLLAB_ASYNC_TIME_LIMIT_SEC") (ne $key "SQLALCHEMY_TRACK_MODIFICATIONS") }}
+{{- if kindIs "map" $value }}
+{{ $key }} = {{ $value | toJson | indent 2 }}
Review Comment:
**Suggestion:** Rendering map values with `toJson` directly into Python
assignments can produce JSON literals like `true`, `false`, and `null`, which
are invalid Python names at runtime; this will break `superset_config.py`
import for configs containing booleans or nulls. Render Python-safe literals
instead of raw JSON for dict values. [type error]
<details>
<summary><b>Severity Level:</b> Critical 🚨</summary>
```mdx
❌ Web deployment fails importing rendered superset_config.py secret.
❌ Worker and beat deployments crash due to config syntax.
⚠️ Helm users face confusing Python errors on startup.
```
</details>
<details>
<summary><b>Steps of Reproduction ✅ </b></summary>
```mdx
1. Configure the Helm release with a nested config map containing booleans,
for example
via `--set config.MY_FEATURE.enabled=true` (config keys documented at
`helm/superset/values.yaml:338-345`).
2. During template rendering, `superset.config` in
`helm/superset/templates/_helpers.tpl:636-655` iterates `.Values.config`;
for map values
it hits the branch at line 639 and emits `MY_FEATURE = { "enabled": true }`
using `toJson`
at line 640, producing JSON literals `true`/`null`.
3. Secret `helm/superset/templates/secret-superset-config.yaml:20-32` embeds
this rendered
text into `stringData.superset_config.py`, which is then mounted by the web
and worker
deployments at `helm/superset/templates/deployment.yaml:121-124` and
`deployment-worker.yaml:115-118` via the `superset-config` volume.
4. At runtime Superset imports `superset_config.py` as configuration (see
docker docs at
`docker/pythonpath_dev/superset_config_local.example:22-23` describing how
`superset_config.py` is loaded); Python evaluates the dict literal containing
`true`/`null` and raises a `NameError`, causing the module import and
Superset processes
to crash.
```
</details>
[](https://app.codeant.ai/fix-in-ide?tool=cursor&prompt_id=39cf8417fdae4d9788c886d0627fc0c8&service=github&base_url=https%3A%2F%2Fgithub.com&org=apache&repo=apache%2Fsuperset)
[](https://app.codeant.ai/fix-in-ide?tool=vscode-claude&prompt_id=39cf8417fdae4d9788c886d0627fc0c8&service=github&base_url=https%3A%2F%2Fgithub.com&org=apache&repo=apache%2Fsuperset)
*(Use Cmd/Ctrl + Click for best experience)*
<details>
<summary><b>Prompt for AI Agent 🤖 </b></summary>
```mdx
This is a comment left during a code review.
**Path:** helm/superset/templates/_helpers.tpl
**Line:** 639:640
**Comment:**
*Type Error: Rendering map values with `toJson` directly into Python
assignments can produce JSON literals like `true`, `false`, and `null`, which
are invalid Python names at runtime; this will break `superset_config.py`
import for configs containing booleans or nulls. Render Python-safe literals
instead of raw JSON for dict values.
Validate the correctness of the flagged issue. If correct, How can I resolve
this? If you propose a fix, implement it and please make it concise.
Once fix is implemented, also check other comments on the same PR, and ask
user if the user wants to fix the rest of the comments as well. if said yes,
then fetch all the comments validate the correctness and implement a minimal fix
```
</details>
<a
href='https://app.codeant.ai/feedback?pr_url=https%3A%2F%2Fgithub.com%2Fapache%2Fsuperset%2Fpull%2F41777&comment_hash=654861b226c165da47a546896d64a6c5f364fd66f52085063760bb8112e93a7f&reaction=like'>👍</a>
| <a
href='https://app.codeant.ai/feedback?pr_url=https%3A%2F%2Fgithub.com%2Fapache%2Fsuperset%2Fpull%2F41777&comment_hash=654861b226c165da47a546896d64a6c5f364fd66f52085063760bb8112e93a7f&reaction=dislike'>👎</a>
##########
helm/superset/templates/_helpers.tpl:
##########
@@ -187,7 +672,54 @@ RESULTS_BACKEND = RedisCache(
{{- end }}
{{- end }}
+{{- end -}}
+
+{{- define "superset.initScript" -}}
+#!/bin/sh
+set -eu
+echo "Upgrading DB schema..."
+superset db upgrade
+echo "Initializing roles and permissions..."
+superset init
+echo "Init job: Creating admin user and loading initial data..."
+{{- if .Values.init.createAdmin }}
+echo "Creating admin user..."
+superset fab create-admin \
+ --username {{ .Values.init.adminUser.username | quote }} \
+ --firstname {{ .Values.init.adminUser.firstname | quote }} \
+ --lastname {{ .Values.init.adminUser.lastname | quote }} \
+ --email {{ .Values.init.adminUser.email | quote }} \
+ --password {{ .Values.init.adminUser.password | quote }} \
+ || true
Review Comment:
**Suggestion:** Swallowing all errors from admin creation makes the init job
report success even when user creation fails for real reasons (not just
“already exists”), leaving the deployment in a partially initialized state.
Handle only the idempotent “already exists” case and fail on other errors.
[logic error]
<details>
<summary><b>Severity Level:</b> Major ⚠️</summary>
```mdx
⚠️ Init job reports success despite failing admin user creation.
⚠️ Cluster starts without expected bootstrap admin account configured.
⚠️ Real initialization errors masked, complicating troubleshooting
post-upgrade.
```
</details>
<details>
<summary><b>Steps of Reproduction ✅ </b></summary>
```mdx
1. Use the default init settings in `helm/superset/values.yaml:895-927` where
`.Values.init.enabled=true` and `.Values.init.createAdmin=true`; Helm renders
`superset.initScript` from `helm/superset/templates/_helpers.tpl:677-708`
into
`superset_init.sh` via `secret-superset-config.yaml:29-32`.
2. The init Job manifest
`helm/superset/templates/init-job.yaml:20-32,65-102` mounts the
`superset-config` secret at `configMountPath` and executes the command from
`values.yaml:909-913`, which sources `superset_bootstrap.sh` and then
`superset_init.sh`.
3. If the `superset fab create-admin` command in `_helpers.tpl:687-693`
fails for a real
error (for example a database permission issue, invalid admin email, or
future CLI
change), the leading `set -eu` at line 679 would normally terminate the
script, but the
`|| true` at line 693 forces the shell to ignore the non-zero exit code and
continue.
4. The script proceeds to load examples and prints "Init job complete."
(lines 697-707),
and Kubernetes reports the post-install/post-upgrade Job as succeeded (hook
annotations at
`helm/superset/values.yaml:915-917`), even though the admin user was not
created, leaving
the deployment partially initialized and masking the underlying failure from
operators.
```
</details>
[](https://app.codeant.ai/fix-in-ide?tool=cursor&prompt_id=8fffcd292c23431cad035370c0cfc950&service=github&base_url=https%3A%2F%2Fgithub.com&org=apache&repo=apache%2Fsuperset)
[](https://app.codeant.ai/fix-in-ide?tool=vscode-claude&prompt_id=8fffcd292c23431cad035370c0cfc950&service=github&base_url=https%3A%2F%2Fgithub.com&org=apache&repo=apache%2Fsuperset)
*(Use Cmd/Ctrl + Click for best experience)*
<details>
<summary><b>Prompt for AI Agent 🤖 </b></summary>
```mdx
This is a comment left during a code review.
**Path:** helm/superset/templates/_helpers.tpl
**Line:** 687:693
**Comment:**
*Logic Error: Swallowing all errors from admin creation makes the init
job report success even when user creation fails for real reasons (not just
“already exists”), leaving the deployment in a partially initialized state.
Handle only the idempotent “already exists” case and fail on other errors.
Validate the correctness of the flagged issue. If correct, How can I resolve
this? If you propose a fix, implement it and please make it concise.
Once fix is implemented, also check other comments on the same PR, and ask
user if the user wants to fix the rest of the comments as well. if said yes,
then fetch all the comments validate the correctness and implement a minimal fix
```
</details>
<a
href='https://app.codeant.ai/feedback?pr_url=https%3A%2F%2Fgithub.com%2Fapache%2Fsuperset%2Fpull%2F41777&comment_hash=e189dec55d396505626057ded2ee7ea26b878033ba49cf81d49feead9218904c&reaction=like'>👍</a>
| <a
href='https://app.codeant.ai/feedback?pr_url=https%3A%2F%2Fgithub.com%2Fapache%2Fsuperset%2Fpull%2F41777&comment_hash=e189dec55d396505626057ded2ee7ea26b878033ba49cf81d49feead9218904c&reaction=dislike'>👎</a>
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: [email protected]
For queries about this service, please contact Infrastructure at:
[email protected]
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]