You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@impala.apache.org by ja...@apache.org on 2026/08/12 22:17:35 UTC

(impala) 01/02: IMPALA-15028: Add Helm chart render assertions

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

asf-gitbox-commits pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/impala.git

commit 49c867d40f291bcf88757d9a410d0b7aa29f98e3
Author: Anubhav Jindal <an...@gmail.com>
AuthorDate: Mon Aug 10 15:43:33 2026 -0700

    IMPALA-15028: Add Helm chart render assertions
    
    Add a lightweight CI-friendly chart test harness that runs helm lint,
    renders default and override manifests, and asserts key Impala Helm
    chart behaviors to prevent regressions.
    
    Wire chart checks into bin/run-all-tests.sh as an opt-in stage
    (HELM_CHART_TEST=false by default) and keep Jenkins all-tests behavior
    unchanged unless explicitly enabled.
    
    Cover both core defaults and optional chart functionality, including
    LDAP, Ranger, Kudu, OAuth, and secure-cluster Kerberos/TLS/Istio paths.
    Also validate required-value failures for secure secret inputs.
    
    Align assert script execution with repository conventions by using an
    executable shebang (#!/usr/bin/env impala-python3), invoking the script
    directly, and documenting required local dependencies.
    
    Testing:
    - bash -n bin/jenkins/all-tests.sh
    - bash -n bin/run-all-tests.sh
    - bash -n helm/impala/tests/run-chart-tests.sh
    - python3 bin/jenkins/critique-gerrit-review.py --dryrun
    
    Change-Id: I313be431e81a4b98370e51d298b7b2d35e0aded8
    Assisted-by: GPT-5.3 (Cursor)
    Reviewed-on: http://gerrit.cloudera.org:8080/24368
    Reviewed-by: Jason Fehr <jf...@cloudera.com>
    Tested-by: Jason Fehr <jf...@cloudera.com>
---
 bin/run-all-tests.sh                 |   8 +
 helm/impala/README.md                |  22 ++
 helm/impala/tests/assert_chart.py    | 578 +++++++++++++++++++++++++++++++++++
 helm/impala/tests/run-chart-tests.sh | 176 +++++++++++
 4 files changed, 784 insertions(+)

diff --git a/bin/run-all-tests.sh b/bin/run-all-tests.sh
index 601485ec8..9a740c3d6 100755
--- a/bin/run-all-tests.sh
+++ b/bin/run-all-tests.sh
@@ -68,6 +68,8 @@ fi
 # Run Cluster Tests
 : ${CLUSTER_TEST:=true}
 : ${CLUSTER_TEST_FILES:=}
+# Run Helm chart render assertions on demand.
+: ${HELM_CHART_TEST:=false}
 # Run JS tests
 : ${JS_TEST:=false}
 # Verifiers to run after all tests. Skipped if true.
@@ -412,6 +414,12 @@ do
     export IMPALA_MAX_LOG_FILES="${IMPALA_MAX_LOG_FILES_SAVE}"
   fi
 
+  if [[ "$HELM_CHART_TEST" == true ]]; then
+    if ! "${IMPALA_HOME}/helm/impala/tests/run-chart-tests.sh"; then
+      TEST_RET_CODE=1
+    fi
+  fi
+
   if [ ! -z "${TEST_SUITE_VERIFIERS}" ]; then
     mkdir -p "${TEST_SUITE_VERIFIERS_LOG_DIR}"
     pushd "${IMPALA_HOME}/tests"
diff --git a/helm/impala/README.md b/helm/impala/README.md
index 88f2eb0ce..4c1b885c1 100644
--- a/helm/impala/README.md
+++ b/helm/impala/README.md
@@ -390,6 +390,28 @@ helm upgrade impala ./helm/impala -n impala \
   --set catalogd.hmsEventPollingIntervalS=0
 ```
 
+## Automated chart checks
+
+From the repository root, run:
+
+```bash
+./helm/impala/tests/run-chart-tests.sh
+```
+
+This script runs `helm lint`, renders default and override manifests with
+`helm template`, and validates key chart behavior with assertions.
+Coverage includes default core settings plus optional LDAP, Ranger, Kudu,
+OAuth, and secure-cluster (Kerberos/TLS/Istio) rendering paths.
+
+Required dependencies to run this harness:
+
+- `helm` 3.x on `PATH`
+- `impala-python3` on `PATH` (or Impala dev environment sourced)
+- shell utilities used by the script (`bash`, `mktemp`)
+
+This is a render-time validation suite; it does not require a running
+Kubernetes cluster or Docker runtime.
+
 ## Control-plane guidance
 
 Use one control path per release/namespace:
diff --git a/helm/impala/tests/assert_chart.py b/helm/impala/tests/assert_chart.py
new file mode 100755
index 000000000..983a0a565
--- /dev/null
+++ b/helm/impala/tests/assert_chart.py
@@ -0,0 +1,578 @@
+#!/usr/bin/env impala-python3
+# 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.
+
+import argparse
+import sys
+from pathlib import Path
+
+
+def _read_text(path: str) -> str:
+    return Path(path).read_text(encoding="utf-8")
+
+
+def _require(
+    failures: list[str],
+    condition: bool,
+    success_message: str,
+    failure_message: str,
+) -> None:
+    if condition:
+        print(f"[PASS] {success_message}")
+    else:
+        failures.append(failure_message)
+        print(f"[FAIL] {failure_message}")
+
+
+def _verify_default_images(default_render: str, failures: list[str]) -> None:
+    expected_suffixes = [
+        "catalogd",
+        "impala_quickstart_hms",
+        "impalad_coord_exec",
+        "statestored",
+    ]
+    for suffix in expected_suffixes:
+        image = f"image: apache/impala:4.5.0-{suffix}"
+        _require(
+            failures,
+            image in default_render,
+            f"default image renders {image}",
+            f"default image missing {image}",
+        )
+
+
+def _verify_beeswax_absence(default_render: str, failures: list[str]) -> None:
+    _require(
+        failures,
+        "beeswax" not in default_render.lower(),
+        "beeswax name is absent from rendered manifests",
+        "beeswax references found in rendered manifests",
+    )
+    _require(
+        failures,
+        "containerPort: 21000" not in default_render,
+        "deprecated beeswax container port 21000 is absent",
+        "deprecated beeswax container port 21000 was rendered",
+    )
+    _require(
+        failures,
+        "\n  - port: 21000\n" not in default_render,
+        "deprecated beeswax service port 21000 is absent",
+        "deprecated beeswax service port 21000 was rendered",
+    )
+
+
+def _verify_debug_levels(
+    default_render: str,
+    debug_render: str,
+    failures: list[str],
+) -> None:
+    _require(
+        failures,
+        default_render.count("- -v=1") >= 3,
+        "default debug levels render as -v=1 for daemons",
+        "default debug levels did not render expected -v=1 args",
+    )
+    _require(
+        failures,
+        "- -v=3" in debug_render,
+        "impalad.v override renders -v=3",
+        "impalad.v override did not render -v=3",
+    )
+    _require(
+        failures,
+        "- -v=4" in debug_render,
+        "catalogd.v override renders -v=4",
+        "catalogd.v override did not render -v=4",
+    )
+    _require(
+        failures,
+        "- -v=5" in debug_render,
+        "statestored.v override renders -v=5",
+        "statestored.v override did not render -v=5",
+    )
+
+
+def _verify_hms_polling(
+    default_render: str,
+    polling_render: str,
+    failures: list[str],
+) -> None:
+    _require(
+        failures,
+        "- -hms_event_polling_interval_s=1" in default_render,
+        "default catalogd.hmsEventPollingIntervalS renders as 1",
+        "default catalogd.hmsEventPollingIntervalS did not render as 1",
+    )
+    _require(
+        failures,
+        "- -hms_event_polling_interval_s=0" in polling_render,
+        "catalogd.hmsEventPollingIntervalS override renders as 0",
+        "catalogd.hmsEventPollingIntervalS override did not render as 0",
+    )
+
+
+def _verify_shared_warehouse_access_mode(
+    default_render: str,
+    failures: list[str],
+) -> None:
+    _require(
+        failures,
+        "- ReadWriteMany" in default_render,
+        "default render uses ReadWriteMany for shared warehouse PVC",
+        "default render did not use ReadWriteMany for shared warehouse PVC",
+    )
+
+
+def _verify_hms_security_context(hms_security_render: str, failures: list[str]) -> None:
+    _require(
+        failures,
+        "runAsUser: 0" in hms_security_render,
+        "hms.securityContext.runAsUser renders when set",
+        "hms.securityContext.runAsUser did not render",
+    )
+    _require(
+        failures,
+        "runAsGroup: 0" in hms_security_render,
+        "hms.securityContext.runAsGroup renders when set",
+        "hms.securityContext.runAsGroup did not render",
+    )
+
+
+def _verify_ldap_render(ldap_render: str, failures: list[str]) -> None:
+    _require(
+        failures,
+        "- -enable_ldap_auth" in ldap_render,
+        "LDAP enable flag renders",
+        "LDAP enable flag did not render",
+    )
+    _require(
+        failures,
+        "- -ldap_uri=ldap://ldap.example.org:389" in ldap_render,
+        "LDAP URI flag renders",
+        "LDAP URI flag did not render",
+    )
+    _require(
+        failures,
+        "- -ldap_bind_pattern=cn=#UID,dc=example,dc=org" in ldap_render,
+        "LDAP bind pattern renders",
+        "LDAP bind pattern did not render",
+    )
+    _require(
+        failures,
+        "- -ldap_passwords_in_clear_ok" in ldap_render,
+        "LDAP cleartext-password opt-in flag renders",
+        "LDAP cleartext-password opt-in flag did not render",
+    )
+
+
+def _verify_ranger_render(ranger_render: str, failures: list[str]) -> None:
+    _require(
+        failures,
+        "# Source: impala/templates/ranger-deployment.yaml" in ranger_render,
+        "Ranger deployment template renders when enabled",
+        "Ranger deployment template did not render",
+    )
+    _require(
+        failures,
+        "# Source: impala/templates/ranger-service.yaml" in ranger_render,
+        "Ranger service template renders when enabled",
+        "Ranger service template did not render",
+    )
+    _require(
+        failures,
+        "- -authorization_provider=ranger" in ranger_render,
+        "Impala daemons render Ranger authorization provider flag",
+        "Ranger authorization provider flag did not render",
+    )
+    _require(
+        failures,
+        "- -server_name=server1" in ranger_render,
+        "Ranger server_name flag renders with expected key format",
+        "Ranger server_name flag did not render with expected key format",
+    )
+    _require(
+        failures,
+        "ranger-hive-security.xml" in ranger_render,
+        "Ranger security configuration file is rendered and mounted",
+        "Ranger security configuration file did not render",
+    )
+    _require(
+        failures,
+        "ranger-hive-audit.xml" in ranger_render,
+        "Ranger audit configuration file is rendered and mounted",
+        "Ranger audit configuration file did not render",
+    )
+
+
+def _verify_kudu_render(kudu_render: str, failures: list[str]) -> None:
+    _require(
+        failures,
+        "# Source: impala/templates/kudu-master-deployment.yaml" in kudu_render,
+        "Kudu master StatefulSet template renders when enabled",
+        "Kudu master StatefulSet template did not render",
+    )
+    _require(
+        failures,
+        "# Source: impala/templates/kudu-tserver-deployment.yaml" in kudu_render,
+        "Kudu tserver StatefulSet template renders when enabled",
+        "Kudu tserver StatefulSet template did not render",
+    )
+    _require(
+        failures,
+        "# Source: impala/templates/kudu-master-service.yaml" in kudu_render,
+        "Kudu master service template renders when enabled",
+        "Kudu master service template did not render",
+    )
+    _require(
+        failures,
+        "# Source: impala/templates/kudu-tserver-service.yaml" in kudu_render,
+        "Kudu tserver service template renders when enabled",
+        "Kudu tserver service template did not render",
+    )
+    _require(
+        failures,
+        "- -kudu_master_hosts=impala-kudu-impala-kudu-master:7051" in kudu_render,
+        "Impala daemons render Kudu master hosts when Kudu is enabled",
+        "Kudu master hosts flag did not render in Impala daemons",
+    )
+    _require(
+        failures,
+        "name: kudu-master-data\n          emptyDir: {}" in kudu_render,
+        "Kudu master data volume uses emptyDir when persistence is disabled",
+        "Kudu master data volume did not render emptyDir for disabled persistence",
+    )
+    _require(
+        failures,
+        "name: kudu-tserver-data\n          emptyDir: {}" in kudu_render,
+        "Kudu tserver data volume uses emptyDir when persistence is disabled",
+        "Kudu tserver data volume did not render emptyDir for disabled persistence",
+    )
+    _require(
+        failures,
+        "volumeClaimTemplates:" not in kudu_render,
+        "Kudu StatefulSets skip volumeClaimTemplates when persistence is disabled",
+        "Kudu StatefulSets still render volumeClaimTemplates with disabled persistence",
+    )
+    _require(
+        failures,
+        "claimName:" not in kudu_render,
+        "No PVC claimName mounts are rendered for disabled Kudu persistence",
+        "PVC claimName mounts were rendered despite disabled Kudu persistence",
+    )
+    _require(
+        failures,
+        "# Source: impala/templates/kudu-pvc.yaml" not in kudu_render,
+        "Kudu PVC template is skipped when Kudu persistence is disabled",
+        "Kudu PVC template rendered despite disabled Kudu persistence",
+    )
+
+
+def _verify_oauth_render(oauth_render: str, failures: list[str]) -> None:
+    _require(
+        failures,
+        "- -oauth_token_auth=true" in oauth_render,
+        "OAuth token auth flag renders when enabled",
+        "OAuth token auth flag did not render",
+    )
+    _require(
+        failures,
+        "- -oauth_jwt_custom_claim_username=sub" in oauth_render,
+        "OAuth custom username claim flag renders",
+        "OAuth custom username claim flag did not render",
+    )
+    _require(
+        failures,
+        "- -oauth_jwt_validate_signature=true" in oauth_render,
+        "OAuth signature validation flag renders",
+        "OAuth signature validation flag did not render",
+    )
+    _require(
+        failures,
+        "- -oauth_jwks_url=https://idp.example.org/jwks" in oauth_render,
+        "OAuth JWKS URL flag renders",
+        "OAuth JWKS URL flag did not render",
+    )
+    _require(
+        failures,
+        "- -oauth_allow_without_tls=true" in oauth_render,
+        "OAuth no-TLS development override flag renders when set",
+        "OAuth no-TLS development override flag did not render",
+    )
+
+
+def _verify_secure_render(secure_render: str, failures: list[str]) -> None:
+    _require(
+        failures,
+        secure_render.count('sidecar.istio.io/inject: "true"') >= 4,
+        "Istio sidecar annotation renders across core workloads",
+        "Istio sidecar annotation did not render across core workloads",
+    )
+    _require(
+        failures,
+        "- -principal=impala/_HOST@EXAMPLE.COM" in secure_render,
+        "Kerberos principal flag renders",
+        "Kerberos principal flag did not render",
+    )
+    _require(
+        failures,
+        "- -be_principal=impala/_HOST@EXAMPLE.COM" in secure_render,
+        "Kerberos backend principal flag renders",
+        "Kerberos backend principal flag did not render",
+    )
+    _require(
+        failures,
+        "- -keytab_file=/etc/impala/security/impala.keytab" in secure_render,
+        "Kerberos keytab path flag renders",
+        "Kerberos keytab path flag did not render",
+    )
+    _require(
+        failures,
+        "- -krb5_conf=/etc/krb5/krb5.conf" in secure_render,
+        "Kerberos krb5.conf path flag renders when config map is set",
+        "Kerberos krb5.conf path flag did not render",
+    )
+    _require(
+        failures,
+        "- -ssl_server_certificate=/etc/impala/tls/tls.crt" in secure_render,
+        "TLS certificate flag renders",
+        "TLS certificate flag did not render",
+    )
+    _require(
+        failures,
+        "- -ssl_private_key=/etc/impala/tls/tls.key" in secure_render,
+        "TLS private key flag renders",
+        "TLS private key flag did not render",
+    )
+    _require(
+        failures,
+        "- -ssl_client_ca_certificate=/etc/impala/tls/ca.crt" in secure_render,
+        "TLS client CA flag renders",
+        "TLS client CA flag did not render",
+    )
+    _require(
+        failures,
+        "secretName: impala-kerberos-keytab" in secure_render,
+        "Kerberos keytab secret mount renders",
+        "Kerberos keytab secret mount did not render",
+    )
+    _require(
+        failures,
+        "secretName: impala-tls" in secure_render,
+        "TLS secret mount renders",
+        "TLS secret mount did not render",
+    )
+    _require(
+        failures,
+        "name: impala-krb5-conf" in secure_render,
+        "krb5 config map mount renders when set",
+        "krb5 config map mount did not render",
+    )
+
+
+def _verify_secure_validation_errors(
+    missing_kerberos_principal_render: str,
+    missing_kerberos_render: str,
+    missing_tls_render: str,
+    failures: list[str],
+) -> None:
+    _require(
+        failures,
+        "security.kerberos.principal is required" in missing_kerberos_principal_render,
+        "Kerberos missing-principal validation error is raised",
+        "Kerberos missing-principal validation error was not raised",
+    )
+    _require(
+        failures,
+        "security.kerberos.keytabSecretName is required" in missing_kerberos_render,
+        "Kerberos missing-secret validation error is raised",
+        "Kerberos missing-secret validation error was not raised",
+    )
+    _require(
+        failures,
+        "security.tls.secretName is required" in missing_tls_render,
+        "TLS missing-secret validation error is raised",
+        "TLS missing-secret validation error was not raised",
+    )
+
+
+def _verify_oauth_validation_errors(
+    missing_oauth_jwks_render: str,
+    ambiguous_oauth_jwks_render: str,
+    failures: list[str],
+) -> None:
+    expected_error = (
+        "Set exactly one of auth.oauth.jwksFilePath or auth.oauth.jwksUrl "
+        "when auth.oauth.jwtValidateSignature=true"
+    )
+    _require(
+        failures,
+        expected_error in missing_oauth_jwks_render,
+        "OAuth validation error is raised when JWKS source is missing",
+        "OAuth validation error was not raised for missing JWKS source",
+    )
+    _require(
+        failures,
+        expected_error in ambiguous_oauth_jwks_render,
+        "OAuth validation error is raised when both JWKS sources are set",
+        "OAuth validation error was not raised for ambiguous JWKS source",
+    )
+
+
+def _verify_ranger_validation_error(
+    missing_ranger_admin: str,
+    failures: list[str],
+) -> None:
+    _require(
+        failures,
+        "auth.ranger.adminUrl is required when auth.ranger.enabled=true and "
+        "ranger.enabled=false" in missing_ranger_admin,
+        "Ranger external-mode validation error is raised without adminUrl",
+        "Ranger external-mode validation error was not raised without adminUrl",
+    )
+
+
+def _verify_values_example_render(example_render: str, failures: list[str]) -> None:
+    _require(
+        failures,
+        "- ReadWriteMany" in example_render,
+        "values-example renders shared warehouse PVC with ReadWriteMany",
+        "values-example did not render shared warehouse PVC with ReadWriteMany",
+    )
+
+
+def main() -> int:
+    parser = argparse.ArgumentParser(
+        description="Assert key behavior for Impala Helm chart renders."
+    )
+    parser.add_argument(
+        "--default",
+        required=True,
+        help="Path to default render output",
+    )
+    parser.add_argument(
+        "--debug",
+        required=True,
+        help="Path to debug-level override render",
+    )
+    parser.add_argument(
+        "--polling",
+        required=True,
+        help="Path to hmsEventPollingIntervalS override render",
+    )
+    parser.add_argument(
+        "--hms-security",
+        required=True,
+        help="Path to HMS securityContext override render",
+    )
+    parser.add_argument("--ldap", required=True, help="Path to LDAP render output")
+    parser.add_argument("--ranger", required=True, help="Path to Ranger render output")
+    parser.add_argument("--kudu", required=True, help="Path to Kudu render output")
+    parser.add_argument("--oauth", required=True, help="Path to OAuth render output")
+    parser.add_argument("--secure", required=True, help="Path to secure render output")
+    parser.add_argument(
+        "--missing-kerberos-principal",
+        required=True,
+        help="Path to secure render error output with missing kerberos principal",
+    )
+    parser.add_argument(
+        "--missing-kerberos",
+        required=True,
+        help="Path to secure render error output with missing keytab secret",
+    )
+    parser.add_argument(
+        "--missing-tls",
+        required=True,
+        help="Path to secure render error output with missing TLS secret",
+    )
+    parser.add_argument(
+        "--missing-oauth-jwks",
+        required=True,
+        help="Path to OAuth render error output with no JWKS source configured",
+    )
+    parser.add_argument(
+        "--ambiguous-oauth-jwks",
+        required=True,
+        help="Path to OAuth render error output with both JWKS sources configured",
+    )
+    parser.add_argument(
+        "--missing-ranger-admin",
+        required=True,
+        help="Path to Ranger render error output with missing external admin URL",
+    )
+    parser.add_argument(
+        "--example",
+        required=True,
+        help="Path to render output generated with values-example.yaml",
+    )
+    args = parser.parse_args()
+
+    default_render = _read_text(args.default)
+    debug_render = _read_text(args.debug)
+    polling_render = _read_text(args.polling)
+    hms_security_render = _read_text(args.hms_security)
+    ldap_render = _read_text(args.ldap)
+    ranger_render = _read_text(args.ranger)
+    kudu_render = _read_text(args.kudu)
+    oauth_render = _read_text(args.oauth)
+    secure_render = _read_text(args.secure)
+    missing_kerberos_principal_render = _read_text(args.missing_kerberos_principal)
+    missing_kerberos_render = _read_text(args.missing_kerberos)
+    missing_tls_render = _read_text(args.missing_tls)
+    missing_oauth_jwks_render = _read_text(args.missing_oauth_jwks)
+    ambiguous_oauth_jwks_render = _read_text(args.ambiguous_oauth_jwks)
+    missing_ranger_admin = _read_text(args.missing_ranger_admin)
+    example_render = _read_text(args.example)
+
+    failures: list[str] = []
+
+    _verify_default_images(default_render, failures)
+    _verify_beeswax_absence(default_render, failures)
+    _verify_debug_levels(default_render, debug_render, failures)
+    _verify_hms_polling(default_render, polling_render, failures)
+    _verify_shared_warehouse_access_mode(default_render, failures)
+    _verify_hms_security_context(hms_security_render, failures)
+    _verify_ldap_render(ldap_render, failures)
+    _verify_ranger_render(ranger_render, failures)
+    _verify_kudu_render(kudu_render, failures)
+    _verify_oauth_render(oauth_render, failures)
+    _verify_secure_render(secure_render, failures)
+    _verify_secure_validation_errors(
+        missing_kerberos_principal_render,
+        missing_kerberos_render,
+        missing_tls_render,
+        failures,
+    )
+    _verify_oauth_validation_errors(
+        missing_oauth_jwks_render,
+        ambiguous_oauth_jwks_render,
+        failures,
+    )
+    _verify_ranger_validation_error(missing_ranger_admin, failures)
+    _verify_values_example_render(example_render, failures)
+
+    if failures:
+        print("\nChart assertions failed:")
+        for failure in failures:
+            print(f"- {failure}")
+        return 1
+    print("\nAll chart assertions passed.")
+    return 0
+
+
+if __name__ == "__main__":
+    sys.exit(main())
diff --git a/helm/impala/tests/run-chart-tests.sh b/helm/impala/tests/run-chart-tests.sh
new file mode 100755
index 000000000..4a56aa981
--- /dev/null
+++ b/helm/impala/tests/run-chart-tests.sh
@@ -0,0 +1,176 @@
+#!/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.
+
+set -euo pipefail
+
+SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
+CHART_DIR="$(cd "${SCRIPT_DIR}/.." && pwd)"
+if [[ -z "${IMPALA_HOME:-}" ]]; then
+  IMPALA_HOME="$(cd "${SCRIPT_DIR}/../../.." && pwd)"
+fi
+export IMPALA_HOME
+export PATH="${IMPALA_HOME}/bin:${PATH}"
+TMP_DIR="$(mktemp -d)"
+
+cleanup() {
+  rm -rf "${TMP_DIR}"
+}
+trap cleanup EXIT
+
+echo "Running helm lint..."
+helm lint "${CHART_DIR}"
+
+echo "Rendering default manifests..."
+helm template impala-default "${CHART_DIR}" \
+  > "${TMP_DIR}/default.yaml"
+
+echo "Rendering debug level override manifests..."
+helm template impala-debug "${CHART_DIR}" \
+  --set impalad.v=3 \
+  --set catalogd.v=4 \
+  --set statestored.v=5 \
+  > "${TMP_DIR}/debug.yaml"
+
+echo "Rendering HMS polling compatibility manifests..."
+helm template impala-polling "${CHART_DIR}" \
+  --set catalogd.hmsEventPollingIntervalS=0 \
+  > "${TMP_DIR}/polling.yaml"
+
+echo "Rendering HMS securityContext manifests..."
+helm template impala-hms-security "${CHART_DIR}" \
+  --set hms.securityContext.runAsUser=0 \
+  --set hms.securityContext.runAsGroup=0 \
+  > "${TMP_DIR}/hms-security.yaml"
+
+echo "Rendering LDAP auth manifests..."
+helm template impala-ldap "${CHART_DIR}" \
+  --set auth.ldap.enabled=true \
+  --set auth.ldap.uri="ldap://ldap.example.org:389" \
+  --set-string auth.ldap.bindPattern='cn=#UID\,dc=example\,dc=org' \
+  --set auth.ldap.passwordsInClearOk=true \
+  > "${TMP_DIR}/ldap.yaml"
+
+echo "Rendering Ranger manifests..."
+helm template impala-ranger "${CHART_DIR}" \
+  --set ranger.enabled=true \
+  --set auth.ranger.enabled=true \
+  > "${TMP_DIR}/ranger.yaml"
+
+echo "Rendering Kudu manifests with persistence disabled..."
+helm template impala-kudu "${CHART_DIR}" \
+  --set persistence.enabled=false \
+  --set kudu.enabled=true \
+  --set kudu.master.persistence.enabled=false \
+  --set kudu.tserver.persistence.enabled=false \
+  > "${TMP_DIR}/kudu.yaml"
+
+echo "Rendering OAuth manifests..."
+helm template impala-oauth "${CHART_DIR}" \
+  --set auth.oauth.enabled=true \
+  --set auth.oauth.jwksUrl="https://idp.example.org/jwks" \
+  --set auth.oauth.jwtCustomClaimUsername="sub" \
+  --set auth.oauth.allowWithoutTls=true \
+  > "${TMP_DIR}/oauth.yaml"
+
+echo "Rendering secure-cluster manifests..."
+helm template impala-secure "${CHART_DIR}" \
+  --set security.istio.enabled=true \
+  --set security.kerberos.enabled=true \
+  --set security.kerberos.principal="impala/_HOST@EXAMPLE.COM" \
+  --set security.kerberos.bePrincipal="impala/_HOST@EXAMPLE.COM" \
+  --set security.kerberos.keytabSecretName="impala-kerberos-keytab" \
+  --set security.kerberos.krb5ConfigMapName="impala-krb5-conf" \
+  --set security.tls.enabled=true \
+  --set security.tls.secretName="impala-tls" \
+  > "${TMP_DIR}/secure.yaml"
+
+echo "Rendering values-example manifests..."
+helm template impala-example "${CHART_DIR}" \
+  -f "${CHART_DIR}/values-example.yaml" \
+  > "${TMP_DIR}/example.yaml"
+
+echo "Checking secure-cluster required-value validation..."
+if helm template impala-secure-no-principal "${CHART_DIR}" \
+  --set security.kerberos.enabled=true \
+  --set security.kerberos.keytabSecretName="impala-kerberos-keytab" \
+  > "${TMP_DIR}/missing-kerberos-principal.out" 2>&1; then
+  echo "Expected helm template to fail without kerberos principal."
+  exit 1
+fi
+
+if helm template impala-secure-no-keytab "${CHART_DIR}" \
+  --set security.kerberos.enabled=true \
+  --set security.kerberos.principal="impala/_HOST@EXAMPLE.COM" \
+  > "${TMP_DIR}/missing-kerberos.out" 2>&1; then
+  echo "Expected helm template to fail without kerberos keytab secret."
+  exit 1
+fi
+
+if helm template impala-secure-no-tls-secret "${CHART_DIR}" \
+  --set security.tls.enabled=true \
+  > "${TMP_DIR}/missing-tls.out" 2>&1; then
+  echo "Expected helm template to fail without TLS secret."
+  exit 1
+fi
+
+if helm template impala-oauth-no-jwks "${CHART_DIR}" \
+  --set auth.oauth.enabled=true \
+  --set auth.oauth.jwtValidateSignature=true \
+  > "${TMP_DIR}/missing-oauth-jwks.out" 2>&1; then
+  echo "Expected helm template to fail without OAuth JWKS source."
+  exit 1
+fi
+
+if helm template impala-oauth-ambiguous-jwks "${CHART_DIR}" \
+  --set auth.oauth.enabled=true \
+  --set auth.oauth.jwtValidateSignature=true \
+  --set auth.oauth.jwksUrl="https://idp.example.org/jwks" \
+  --set auth.oauth.jwksFilePath="/etc/impala/jwks.json" \
+  > "${TMP_DIR}/ambiguous-oauth-jwks.out" 2>&1; then
+  echo "Expected helm template to fail when both OAuth JWKS sources are set."
+  exit 1
+fi
+
+if helm template impala-ranger-missing-admin "${CHART_DIR}" \
+  --set auth.ranger.enabled=true \
+  --set ranger.enabled=false \
+  > "${TMP_DIR}/missing-ranger-admin.out" 2>&1; then
+  echo "Expected helm template to fail without external Ranger admin URL."
+  exit 1
+fi
+
+echo "Running chart assertions..."
+"${SCRIPT_DIR}/assert_chart.py" \
+  --default "${TMP_DIR}/default.yaml" \
+  --debug "${TMP_DIR}/debug.yaml" \
+  --polling "${TMP_DIR}/polling.yaml" \
+  --hms-security "${TMP_DIR}/hms-security.yaml" \
+  --ldap "${TMP_DIR}/ldap.yaml" \
+  --ranger "${TMP_DIR}/ranger.yaml" \
+  --kudu "${TMP_DIR}/kudu.yaml" \
+  --oauth "${TMP_DIR}/oauth.yaml" \
+  --secure "${TMP_DIR}/secure.yaml" \
+  --missing-kerberos-principal "${TMP_DIR}/missing-kerberos-principal.out" \
+  --missing-kerberos "${TMP_DIR}/missing-kerberos.out" \
+  --missing-tls "${TMP_DIR}/missing-tls.out" \
+  --missing-oauth-jwks "${TMP_DIR}/missing-oauth-jwks.out" \
+  --ambiguous-oauth-jwks "${TMP_DIR}/ambiguous-oauth-jwks.out" \
+  --missing-ranger-admin "${TMP_DIR}/missing-ranger-admin.out" \
+  --example "${TMP_DIR}/example.yaml"
+
+echo "Impala Helm chart tests completed successfully."