This is an automated email from the ASF dual-hosted git repository.

rusackas pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/superset.git


The following commit(s) were added to refs/heads/master by this push:
     new 95e5e25e1a2 feat(helm): mitigate init-db Job hang under Istio sidecar 
injection (closes #25798) (#41396)
95e5e25e1a2 is described below

commit 95e5e25e1a274e77a911a27029124a7c1a1d9aa3
Author: Evan Rusackas <[email protected]>
AuthorDate: Mon Jul 27 10:09:30 2026 -0700

    feat(helm): mitigate init-db Job hang under Istio sidecar injection (closes 
#25798) (#41396)
    
    Co-authored-by: Devin AI 
<devin-ai-integration[bot]@users.noreply.github.com>
    Co-authored-by: Claude Opus 4.8 <[email protected]>
---
 helm/superset/Chart.yaml              |   2 +-
 helm/superset/README.md               |   6 +-
 helm/superset/templates/_helpers.tpl  |   8 ++
 helm/superset/templates/init-job.yaml |   4 +
 helm/superset/tests/test-istio.sh     | 183 ++++++++++++++++++++++++++++++++++
 helm/superset/values.yaml             |  25 +++++
 6 files changed, 226 insertions(+), 2 deletions(-)

diff --git a/helm/superset/Chart.yaml b/helm/superset/Chart.yaml
index ccfb0c2cff0..2ea4b51b51e 100644
--- a/helm/superset/Chart.yaml
+++ b/helm/superset/Chart.yaml
@@ -29,7 +29,7 @@ maintainers:
   - name: craig-rueda
     email: [email protected]
     url: https://github.com/craig-rueda
-version: 0.22.3 # See 
[README](https://github.com/apache/superset/blob/master/helm/superset/README.md#versioning)
 for version details.
+version: 0.22.4 # See 
[README](https://github.com/apache/superset/blob/master/helm/superset/README.md#versioning)
 for version details.
 dependencies:
   - name: postgresql
     version: 16.7.27
diff --git a/helm/superset/README.md b/helm/superset/README.md
index 28ed5688b98..7c941177b41 100644
--- a/helm/superset/README.md
+++ b/helm/superset/README.md
@@ -23,7 +23,7 @@ NOTE: This file is generated by helm-docs: 
https://github.com/norwoodj/helm-docs
 
 # superset
 
-![Version: 
0.22.3](https://img.shields.io/badge/Version-0.22.3-informational?style=flat-square)
+![Version: 
0.22.4](https://img.shields.io/badge/Version-0.22.4-informational?style=flat-square)
 
 Apache Superset is a modern, enterprise-ready business intelligence web 
application
 
@@ -159,6 +159,10 @@ Alternatively, perform a fresh install. This is a one-time 
migration; subsequent
 | init.extraInitContainers | list | `[]` | Extra init containers appended 
after init job initContainers |
 | init.initContainers | list | a container waiting for postgres | List of 
initContainers |
 | init.initscript | string | unused; kept for backwards-compatibility only | 
DEPRECATED: this field is no longer used by the chart. The init script is 
rendered entirely from the internal `superset.initScript` template (which runs 
`superset db upgrade`, `superset init`, admin creation, and examples). Any 
customization placed here is silently ignored. See UPGRADING.md. |
+| init.istio | object | 
`{"disableSidecarInjection":false,"quitEndpoint":"http://localhost:15020/quitquitquit","terminateSidecarOnExit":false}`
 | Configuration for compatibility with the Istio service mesh. Without these 
mitigations, when the init-db Job runs in a namespace with automatic Istio 
sidecar injection enabled, the injected envoy-proxy container keeps running 
after the init container has exited, preventing the Job from ever reaching the 
Completed state. See https://github.com/a [...]
+| init.istio.disableSidecarInjection | bool | `false` | When true, adds the 
`sidecar.istio.io/inject: "false"` label to the init job pod template, opting 
it out of Istio automatic sidecar injection. This is the recommended fix when 
the cluster's mesh policy allows opting out per pod. |
+| init.istio.quitEndpoint | string | `"http://localhost:15020/quitquitquit"` | 
Endpoint that the trap POSTs to in order to terminate the sidecar. The default 
matches the standard Istio pilot-agent admin port. |
+| init.istio.terminateSidecarOnExit | bool | `false` | When true, the rendered 
`superset_init.sh` script (from the internal `superset.initScript` template) 
registers an `EXIT` trap that POSTs to the Istio pilot-agent's `/quitquitquit` 
endpoint after the init logic finishes, gracefully terminating an injected 
envoy-proxy sidecar so the Job can complete. Enable this when sidecar injection 
cannot be disabled per pod (e.g. when enforced by a cluster-wide Istio policy). 
Note: requires `curl`  [...]
 | init.jobAnnotations."helm.sh/hook" | string | `"post-install,post-upgrade"` 
|  |
 | init.jobAnnotations."helm.sh/hook-delete-policy" | string | 
`"before-hook-creation"` |  |
 | init.loadExamples | bool | `false` |  |
diff --git a/helm/superset/templates/_helpers.tpl 
b/helm/superset/templates/_helpers.tpl
index bd993b1df0e..de82305034e 100644
--- a/helm/superset/templates/_helpers.tpl
+++ b/helm/superset/templates/_helpers.tpl
@@ -685,6 +685,14 @@ TALISMAN_CONFIG = {
 {{- define "superset.initScript" -}}
 #!/bin/sh
 set -eu
+{{- if dig "istio" "terminateSidecarOnExit" false .Values.init }}
+# Notify the Istio pilot-agent sidecar to exit when this script completes
+# (whether successfully or via `set -e`), so that the Job can reach the
+# Completed state instead of hanging on a still-running envoy-proxy.
+# See https://github.com/apache/superset/issues/25798
+ISTIO_QUIT_ENDPOINT={{ dig "istio" "quitEndpoint" 
"http://localhost:15020/quitquitquit"; .Values.init | replace "'" "'\\''" | 
squote }}
+trap 'rc=$?; curl -fsS -m 5 -X POST "$ISTIO_QUIT_ENDPOINT" >/dev/null 2>&1 || 
echo "WARNING: failed to notify Istio sidecar at $ISTIO_QUIT_ENDPOINT to quit; 
the Job may hang if sidecar injection is active" >&2; exit $rc' EXIT
+{{- end }}
 echo "Upgrading DB schema..."
 superset db upgrade
 echo "Initializing roles and permissions..."
diff --git a/helm/superset/templates/init-job.yaml 
b/helm/superset/templates/init-job.yaml
index 72aab88c7a0..1f56150267f 100644
--- a/helm/superset/templates/init-job.yaml
+++ b/helm/superset/templates/init-job.yaml
@@ -35,6 +35,7 @@ spec:
       {{- if .Values.init.podAnnotations }}
       annotations: {{- toYaml .Values.init.podAnnotations | nindent 8 }}
       {{- end }}
+      {{- $istioDisableInject := dig "istio" "disableSidecarInjection" false 
.Values.init }}
       labels:
         {{- include "superset.componentSelectorLabels" (dict "component" 
"init" "root" .) | nindent 8 }}
         job: {{ template "superset.fullname" . }}-init-db
@@ -44,6 +45,9 @@ spec:
         {{- if .Values.init.podLabels }}
           {{- toYaml .Values.init.podLabels | nindent 8 }}
         {{- end }}
+        {{- if $istioDisableInject }}
+        sidecar.istio.io/inject: "false"
+        {{- end }}
     spec:
       {{- if .Values.init.additionalPodSpec }}
       {{- tpl (toYaml .Values.init.additionalPodSpec) . | nindent 6 }}
diff --git a/helm/superset/tests/test-istio.sh 
b/helm/superset/tests/test-istio.sh
new file mode 100755
index 00000000000..7882533b23e
--- /dev/null
+++ b/helm/superset/tests/test-istio.sh
@@ -0,0 +1,183 @@
+#!/usr/bin/env bash
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License.  You may obtain a copy of the License at
+#
+#    http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+# Renders the chart with `helm template` for several `init.istio.*` value
+# combinations and asserts that the relevant manifests contain (or omit)
+# the expected fields. Intended to be run from the chart directory or via
+# `bash helm/superset/tests/test-istio.sh` from the repo root.
+#
+# Covers the fix for:
+#   https://github.com/apache/superset/issues/25798
+set -euo pipefail
+
+SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
+CHART_DIR="$(cd "${SCRIPT_DIR}/.." && pwd)"
+
+pass=0
+fail=0
+
+assert_contains() {
+    local label="$1"
+    local needle="$2"
+    local haystack="$3"
+    if grep -qF -- "${needle}" <<<"${haystack}"; then
+        echo "  PASS: ${label}"
+        pass=$((pass + 1))
+    else
+        echo "  FAIL: ${label}"
+        echo "    expected to contain: ${needle}"
+        fail=$((fail + 1))
+    fi
+}
+
+assert_not_contains() {
+    local label="$1"
+    local needle="$2"
+    local haystack="$3"
+    if ! grep -qF -- "${needle}" <<<"${haystack}"; then
+        echo "  PASS: ${label}"
+        pass=$((pass + 1))
+    else
+        echo "  FAIL: ${label}"
+        echo "    expected NOT to contain: ${needle}"
+        fail=$((fail + 1))
+    fi
+}
+
+render() {
+    helm template release "${CHART_DIR}" "$@"
+}
+
+extract_init_job() {
+    awk '
+        /^# Source: superset\/templates\/init-job\.yaml/ { capture = 1 }
+        capture && /^---$/ { capture = 0 }
+        capture { print }
+    ' <<<"$1"
+}
+
+extract_config_secret() {
+    awk '
+        /^# Source: superset\/templates\/secret-superset-config\.yaml/ { 
capture = 1 }
+        capture && /^---$/ { capture = 0 }
+        capture { print }
+    ' <<<"$1"
+}
+
+echo "==> defaults: no istio mitigations applied"
+out_default="$(render)"
+init_job_default="$(extract_init_job "${out_default}")"
+config_default="$(extract_config_secret "${out_default}")"
+assert_not_contains "default does not set sidecar.istio.io/inject label" \
+    'sidecar.istio.io/inject' "${init_job_default}"
+assert_not_contains "default initscript does not register quitquitquit trap" \
+    'quitquitquit' "${config_default}"
+
+echo "==> init.istio.disableSidecarInjection=true"
+out_disable="$(render --set init.istio.disableSidecarInjection=true)"
+init_job_disable="$(extract_init_job "${out_disable}")"
+config_disable="$(extract_config_secret "${out_disable}")"
+assert_contains "init job sets sidecar.istio.io/inject: \"false\"" \
+    'sidecar.istio.io/inject: "false"' "${init_job_disable}"
+assert_not_contains "disableSidecarInjection alone does not add quitquitquit 
trap" \
+    'quitquitquit' "${config_disable}"
+
+echo "==> init.istio.terminateSidecarOnExit=true"
+out_terminate="$(render --set init.istio.terminateSidecarOnExit=true)"
+init_job_terminate="$(extract_init_job "${out_terminate}")"
+config_terminate="$(extract_config_secret "${out_terminate}")"
+assert_not_contains "terminateSidecarOnExit alone does not add inject label" \
+    'sidecar.istio.io/inject' "${init_job_terminate}"
+assert_contains "initscript registers EXIT trap that calls quitquitquit" \
+    "trap 'rc=\$?; curl -fsS -m 5 -X POST" "${config_terminate}"
+assert_contains "initscript trap targets the configured quit endpoint" \
+    'http://localhost:15020/quitquitquit' "${config_terminate}"
+assert_contains "initscript trap binds to the EXIT signal" \
+    "' EXIT" "${config_terminate}"
+
+echo "==> init.istio.quitEndpoint override"
+out_endpoint="$(render --set init.istio.terminateSidecarOnExit=true \
+    --set init.istio.quitEndpoint=http://127.0.0.1:15020/quitquitquit)"
+config_endpoint="$(extract_config_secret "${out_endpoint}")"
+assert_contains "trap honours custom quitEndpoint" \
+    'http://127.0.0.1:15020/quitquitquit' "${config_endpoint}"
+
+echo "==> both options combined"
+out_both="$(render --set init.istio.disableSidecarInjection=true \
+    --set init.istio.terminateSidecarOnExit=true)"
+init_job_both="$(extract_init_job "${out_both}")"
+config_both="$(extract_config_secret "${out_both}")"
+assert_contains "combined: inject label present" \
+    'sidecar.istio.io/inject: "false"' "${init_job_both}"
+assert_contains "combined: trap present" \
+    'quitquitquit' "${config_both}"
+
+echo "==> existing init.podLabels are preserved alongside istio label"
+out_labels="$(render --set init.istio.disableSidecarInjection=true \
+    --set init.podLabels.team=data-platform)"
+init_job_labels="$(extract_init_job "${out_labels}")"
+assert_contains "user-supplied podLabel still rendered" \
+    'team: data-platform' "${init_job_labels}"
+assert_contains "istio inject label rendered alongside" \
+    'sidecar.istio.io/inject: "false"' "${init_job_labels}"
+
+echo "==> init.istio explicitly overridden to null"
+out_null_istio="$(render --set init.istio=null)"
+init_job_null_istio="$(extract_init_job "${out_null_istio}")"
+config_null_istio="$(extract_config_secret "${out_null_istio}")"
+assert_not_contains "null init.istio does not set inject label" \
+    'sidecar.istio.io/inject' "${init_job_null_istio}"
+assert_not_contains "null init.istio does not register quitquitquit trap" \
+    'quitquitquit' "${config_null_istio}"
+
+echo "==> EXIT trap propagates the script's exit code, not the notification's"
+# The trap's own curl call is best-effort (failures are logged with
+# `|| echo ... >&2`, not swallowed with `|| true`) and must not mask a
+# failed migration. Extract the two rendered lines and actually run them,
+# with curl pointed at a closed local port so the notification itself fails,
+# to make sure the wrapped script's real exit code still comes through.
+quit_endpoint_line="$(grep -F 'ISTIO_QUIT_ENDPOINT=' <<<"${config_terminate}" 
|| true)"
+trap_line="$(grep -F "trap 'rc=\$?; curl" <<<"${config_terminate}" || true)"
+if [[ -z "${quit_endpoint_line}" || -z "${trap_line}" ]]; then
+    echo "  FAIL: script exit code (42) survives a failing quitquitquit 
notification"
+    echo "    could not locate the rendered ISTIO_QUIT_ENDPOINT/trap lines to 
exercise"
+    fail=$((fail + 1))
+else
+    set +e
+    (
+        eval "${quit_endpoint_line}"
+        ISTIO_QUIT_ENDPOINT="http://127.0.0.1:1/quitquitquit";
+        eval "${trap_line}"
+        exit 42
+    )
+    trap_test_rc=$?
+    set -e
+    if [[ "${trap_test_rc}" -eq 42 ]]; then
+        echo "  PASS: script exit code (42) survives a failing quitquitquit 
notification"
+        pass=$((pass + 1))
+    else
+        echo "  FAIL: script exit code (42) survives a failing quitquitquit 
notification"
+        echo "    got exit code: ${trap_test_rc}"
+        fail=$((fail + 1))
+    fi
+fi
+
+echo
+echo "passed: ${pass}, failed: ${fail}"
+if [[ ${fail} -gt 0 ]]; then
+    exit 1
+fi
diff --git a/helm/superset/values.yaml b/helm/superset/values.yaml
index ff9a6d67f51..c1cb3ec435f 100644
--- a/helm/superset/values.yaml
+++ b/helm/superset/values.yaml
@@ -1241,6 +1241,31 @@ init:
   topologySpreadConstraints: []
   # -- Set priorityClassName for init job pods
   priorityClassName: ~
+  # -- Configuration for compatibility with the Istio service mesh.
+  # Without these mitigations, when the init-db Job runs in a namespace with
+  # automatic Istio sidecar injection enabled, the injected envoy-proxy
+  # container keeps running after the init container has exited, preventing
+  # the Job from ever reaching the Completed state.
+  # See https://github.com/apache/superset/issues/25798
+  istio:
+    # -- When true, adds the `sidecar.istio.io/inject: "false"` label to the
+    # init job pod template, opting it out of Istio automatic sidecar
+    # injection. This is the recommended fix when the cluster's mesh policy
+    # allows opting out per pod.
+    disableSidecarInjection: false
+    # -- When true, the rendered `superset_init.sh` script (from the internal
+    # `superset.initScript` template) registers an `EXIT` trap that POSTs to
+    # the Istio pilot-agent's `/quitquitquit` endpoint after the init logic
+    # finishes, gracefully terminating an injected envoy-proxy sidecar so
+    # the Job can complete. Enable this when sidecar injection cannot be
+    # disabled per pod (e.g. when enforced by a cluster-wide Istio policy).
+    # Note: requires `curl` to be available in the init container image
+    # (it is included in the default `apache/superset` image) and only
+    # takes effect when `init.command` sources the rendered `superset_init.sh`.
+    terminateSidecarOnExit: false
+    # -- Endpoint that the trap POSTs to in order to terminate the sidecar.
+    # The default matches the standard Istio pilot-agent admin port.
+    quitEndpoint: "http://localhost:15020/quitquitquit";
 
 # -- Configuration values for the postgresql dependency.
 # ref: https://github.com/bitnami/charts/tree/main/bitnami/postgresql

Reply via email to