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

bugraoz93 pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/airflow.git


The following commit(s) were added to refs/heads/main by this push:
     new 7bbd9a21ba3 Initial overlay example for Keda and Structure pre-commit 
hook (#65897)
7bbd9a21ba3 is described below

commit 7bbd9a21ba343f1b75b5751ec02b7b0ea71c1cee
Author: Bugra Ozturk <[email protected]>
AuthorDate: Wed May 6 19:27:32 2026 +0200

    Initial overlay example for Keda and Structure pre-commit hook (#65897)
    
    * Initial overlay example for Keda and Structure pre-commit hook
    
    * Make file executable
    
    * Apply suggestions from code review
    
    Co-authored-by: Jens Scheffler <[email protected]>
    
    * Rename STATUS to STATUS.yaml
    
    * Add disclaimer to kustomize-overlays that it is not distributed with 
chart releases
    
    * Add pydantic schema validation for status.yaml
    
    * Apply suggestions from code review
    
    Co-authored-by: Przemysław Mirowski 
<[email protected]>
    
    * Make document link
    
    * Update chart/kustomize-overlays/CONTRIBUTING.rst
    
    Co-authored-by: Jens Scheffler <[email protected]>
    
    ---------
    
    Co-authored-by: Jens Scheffler <[email protected]>
    Co-authored-by: Przemysław Mirowski 
<[email protected]>
---
 chart/.pre-commit-config.yaml                      |   7 +
 chart/kustomize-overlays/CONTRIBUTING.rst          | 158 ++++++++++++++
 chart/kustomize-overlays/README.rst                |  71 +++++++
 chart/kustomize-overlays/keda/README.rst           | 180 ++++++++++++++++
 chart/kustomize-overlays/keda/STATUS.yaml          |  24 +++
 chart/kustomize-overlays/keda/kustomization.yaml   |  38 ++++
 chart/kustomize-overlays/keda/scaledobject.yaml    |  53 +++++
 .../keda/triggerauthentication.yaml                |  34 ++++
 scripts/ci/prek/build_kustomize_overlays.py        | 226 +++++++++++++++++++++
 9 files changed, 791 insertions(+)

diff --git a/chart/.pre-commit-config.yaml b/chart/.pre-commit-config.yaml
index 16634adb4e1..699beac90d6 100644
--- a/chart/.pre-commit-config.yaml
+++ b/chart/.pre-commit-config.yaml
@@ -81,6 +81,13 @@ repos:
         pass_filenames: false
         files: ^.*
         require_serial: true
+      - id: build-kustomize-overlays
+        name: Build and smoke-test Kustomize overlays
+        entry: ../scripts/ci/prek/build_kustomize_overlays.py
+        language: python
+        pass_filenames: false
+        files: 
^kustomize-overlays/.*\.(yaml|yml)$|^kustomize-overlays/.*/STATUS$
+        require_serial: true
       - id: lint-json-schema
         name: Lint chart/values.schema.json
         entry: ../scripts/ci/prek/lint_json_schema.py
diff --git a/chart/kustomize-overlays/CONTRIBUTING.rst 
b/chart/kustomize-overlays/CONTRIBUTING.rst
new file mode 100644
index 00000000000..138788c23a2
--- /dev/null
+++ b/chart/kustomize-overlays/CONTRIBUTING.rst
@@ -0,0 +1,158 @@
+.. 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.
+
+Contributing Kustomize Overlays
+===============================
+
+This document is the authoritative reference for adding, evolving, and
+retiring overlays under ``chart/kustomize-overlays/``.
+
+Why this directory exists
+-------------------------
+
+The Airflow Helm chart has historically carried components that are not
+Airflow-native. They make the chart heavier than it needs to be and pull
+maintenance toward things that already have external owners. Expressing
+these components as Kustomize overlays keeps the chart focused on Airflow
+itself while still giving users a working starting point for the rest.
+
+The chart never removes a component without a working overlay already in
+place. Users always have a migration path before anything disappears.
+
+Criteria for chart vs Kustomize
+-------------------------------
+
+A component **belongs in the chart** when all of the following are true:
+
+* It is required to run Airflow (scheduler, API server, dag-processor,
+  triggerer, workers).
+* Removing/adding it requires changes to Airflow's own configuration.
+* It has no external owner.
+* It is used by the larger majority of users (>80%)
+
+A component **belongs in Kustomize** when any of the following are true:
+
+* It can be expressed as a standalone Kubernetes resource without modifying
+  chart-rendered resources.
+* It is environment-specific (authentication schemes, logging backends,
+  autoscaling controllers, etc.).
+* It has an external owner (KEDA, Elasticsearch, any PostgreSQL distribution, 
etc.).
+* It requires CRDs that the chart does not install.
+* It is used by a minority of users, such that the additional complexity and 
maintenance burden do not pay off
+
+If a component qualifies for Kustomize but no overlay exists yet, it stays in
+the chart until the overlay is in place and verified.
+
+Overlay structure
+-----------------
+
+Each overlay directory must contain:
+
+* ``kustomization.yaml`` - the Kustomize entry point.
+* The Kubernetes resources the overlay produces.
+* ``STATUS.yaml`` - a small YAML document declaring the verification state.
+* ``README.rst`` - usage instructions and a migration guide from the
+  equivalent chart-side configuration.
+
+STATUS file format
+------------------
+
+The ``STATUS.yaml`` file is a small YAML document with the following fields.
+
+For a verified overlay:
+
+.. code-block:: yaml
+
+    status: tested
+    chart-version: "1.21.0"
+    last-verified: "2026-04-25"
+
+For a starting-point overlay without functional CI coverage:
+
+.. code-block:: yaml
+
+    status: not-tested
+    reason: "Pending community validation. Use as a starting point only."
+
+For an overlay scheduled for removal:
+
+.. code-block:: yaml
+
+    status: deprecated
+    message: "Replaced by <overlay-name>. Will be removed in chart 3.0.0."
+
+Lifecycle
+---------
+
+The lifecycle mirrors how providers work, just on a smaller scale. Two
+checks gate the ``STATUS`` field, and they are deliberately separate.
+
+The ``build_kustomize_overlays`` prek hook
+(``scripts/ci/prek/build_kustomize_overlays.py``) runs on every commit and
+applies a generic structural check to every overlay: the build succeeds, the
+output parses as valid YAML, every resource has ``apiVersion``, ``kind`` and
+``metadata.name``, and there are no duplicate resource keys. This is enough
+to catch most authoring mistakes but it does not validate against the CRD
+schemas of the controllers the overlay targets, and nothing is ever applied
+to a live cluster.
+
+A functional integration test is the separate, stronger check. It applies
+the overlay against a real cluster (typically a kind cluster with the chart
+already installed and the relevant controller running) and asserts the
+runtime behaviour the overlay promises. Until such a test exists for an
+overlay, its ``STATUS`` must stay at ``not-tested``.
+
+Lifecycle steps:
+
+* A new overlay is proposed via a PR and lands with ``status: not-tested``.
+  The prek hook automatically applies the generic structural check; if the
+  overlay needs invariants beyond that (for example a cross-reference
+  between resources), they belong in the integration test, not in the prek
+  hook.
+* A follow-up PR adds a functional integration test for the overlay. Once
+  that test passes, ``STATUS`` is flipped to ``tested``.
+* An overlay is deprecated by setting ``status: deprecated`` together with a
+  ``message`` field pointing to the replacement.
+* Deprecated overlays remain for one chart major version before they are
+  removed, so users always have a window to migrate.
+
+Adding a new overlay
+--------------------
+
+1. Confirm the component meets the Kustomize criteria above.
+2. Create ``chart/kustomize-overlays/<name>/`` with the required files.
+3. Use placeholders such as ``RELEASE-NAME`` for values the user must fill in,
+   and document the substitutions in the overlay's ``README.rst``.
+4. Land the PR with ``status: not-tested``.
+5. Add a row to the table in ``chart/kustomize-overlays/README.rst``.
+6. Follow up with a CI test and flip ``STATUS`` to ``tested``.
+
+Migration guide pattern
+-----------------------
+
+Each overlay ``README.rst`` should include a migration guide section with
+exactly three parts:
+
+1. **What the chart currently does** - the relevant ``values.yaml`` keys and
+   the Kubernetes resources they produce today.
+2. **What the overlay provides** - the equivalent resources rendered from the
+   overlay.
+3. **How to switch** - step-by-step instructions, with the explicit order of
+   operations.
+
+The guide must be written against the current chart template. It is not
+speculative documentation.
diff --git a/chart/kustomize-overlays/README.rst 
b/chart/kustomize-overlays/README.rst
new file mode 100644
index 00000000000..dfa321af733
--- /dev/null
+++ b/chart/kustomize-overlays/README.rst
@@ -0,0 +1,71 @@
+.. 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.
+
+Airflow Helm Chart - Kustomize Overlays
+=======================================
+
+.. note::
+
+   **Not distributed with chart releases.**
+   This directory lives in the source repository as a reference for users but
+   is **not** packaged or published as part of the official Airflow Helm chart
+   release artifacts. Consume it directly from the repository at the tag that
+   matches your chart version.
+
+This directory contains Kustomize overlays that complement the Airflow Helm
+chart for components that are not Airflow-native.
+
+The motivation, criteria, and lifecycle for these overlays are defined in
+``CONTRIBUTING.rst`` in this directory.
+
+Available overlays
+------------------
+
++----------+----------------------+----------------------------------------------+
+| Overlay  | STATUS               | Purpose                                    
  |
++==========+======================+==============================================+
+| ``keda`` | not-tested (PoC)     | Autoscaling for Celery workers via KEDA.   
  |
++----------+----------------------+----------------------------------------------+
+
+Each overlay directory contains its own ``README.rst`` with usage details and
+a migration guide from the equivalent chart-side configuration.
+
+Using an overlay
+----------------
+
+The overlays are designed for the "standalone addition" pattern. They do not
+modify resources rendered by the chart. A typical workflow is:
+
+1. Install the Airflow chart as usual.
+2. Reference the overlay from your own ``kustomization.yaml`` and apply the
+   substitutions described in the overlay's ``README.rst`` (release name,
+   namespace, secret references).
+3. Apply the rendered manifests with ``kubectl apply -k`` against the same
+   namespace as the chart release.
+
+Status conventions
+------------------
+
+Each overlay carries a ``STATUS.yaml`` file that declares its verification 
level:
+
+* ``tested`` - the overlay is verified in Apache Airflow CI against the 
current chart version.
+* ``not-tested`` - the overlay builds successfully but has no functional CI
+  coverage. Treat it as a starting point that you adapt to your environment.
+* ``deprecated`` - the overlay is scheduled for removal. The ``STATUS.yaml`` 
file
+  carries a ``message`` field pointing to the replacement.
+
+See `CONTRIBUTING.rst <CONTRIBUTING.rst>`_ for the full status grammar and 
lifecycle.
diff --git a/chart/kustomize-overlays/keda/README.rst 
b/chart/kustomize-overlays/keda/README.rst
new file mode 100644
index 00000000000..888a22d74cc
--- /dev/null
+++ b/chart/kustomize-overlays/keda/README.rst
@@ -0,0 +1,180 @@
+.. 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.
+
+KEDA Autoscaler Overlay
+=======================
+
+This overlay produces a `KEDA <https://keda.sh/>`__ ``ScaledObject`` plus a
+``TriggerAuthentication`` for the chart-rendered Celery workers. It is a
+standalone addition: no resource produced by the Helm chart is modified.
+
+It is the Kustomize equivalent of enabling ``workers.celery.keda.enabled``
+in ``values.yaml``, and is the recommended migration path for users who
+want to keep Celery autoscaling without relying on chart-side templating.
+
+Prerequisites
+-------------
+
+* `KEDA <https://keda.sh/docs/latest/deploy/>`__ installed in the cluster.
+* The Airflow chart installed with the **CeleryExecutor**.
+* The chart's metadata Secret (``<release>-airflow-metadata``) reachable
+  from KEDA's namespace - usually the same namespace as the chart release.
+
+Resources produced
+------------------
+
+* ``TriggerAuthentication/airflow-keda-postgres-auth`` - reads the metadata
+  DB connection string directly from the chart's metadata Secret.
+* ``ScaledObject/airflow-worker`` - targets the chart-rendered worker
+  Deployment and scales it based on the count of running and queued task
+  instances.
+
+Usage
+-----
+
+Reference this overlay from your own kustomization and substitute the
+release name. A minimal example:
+
+.. code-block:: yaml
+
+    # my-overlay/kustomization.yaml
+    apiVersion: kustomize.config.k8s.io/v1beta1
+    kind: Kustomization
+    namespace: airflow
+
+    resources:
+      - 
github.com/apache/airflow/chart/kustomize-overlays/keda?ref=helm-chart/1.21.0
+
+    replacements:
+      - source:
+          kind: ConfigMap
+          name: airflow-overlay-config
+          fieldPath: data.releaseName
+        targets:
+          - select:
+              kind: ScaledObject
+              name: airflow-worker
+            fieldPaths:
+              - spec.scaleTargetRef.name
+            options:
+              delimiter: "-"
+              index: 0
+          - select:
+              kind: TriggerAuthentication
+              name: airflow-keda-postgres-auth
+            fieldPaths:
+              - spec.secretTargetRef.0.name
+            options:
+              delimiter: "-"
+              index: 0
+
+    configMapGenerator:
+      - name: airflow-overlay-config
+        literals:
+          - releaseName=airflow
+
+Apply with:
+
+.. code-block:: bash
+
+    kubectl apply -k my-overlay/
+
+For a quick test, you can also just ``sed`` the placeholder:
+
+.. code-block:: bash
+
+    kustomize build chart/kustomize-overlays/keda | \
+      sed 's/RELEASE-NAME/airflow/g' | \
+      kubectl apply -f -
+
+Tuning the trigger query
+------------------------
+
+The default query mirrors the chart for a single Celery queue named
+``default`` with ``worker_concurrency=16``. If you set different values in
+your chart install, edit ``scaledobject.yaml`` accordingly:
+
+* Replace ``16`` with the value of ``config.celery.worker_concurrency``.
+* Extend ``queue IN ('default')`` to list every entry from
+  ``workers.celery.queue`` (comma-separated in ``values.yaml``, single-quoted
+  here).
+
+Pgbouncer
+---------
+
+If pgbouncer is enabled and you do not want KEDA polling through it, change
+the ``key`` field in ``triggerauthentication.yaml`` from ``connection`` to
+``kedaConnection``. The chart writes a direct-to-Postgres connection string
+under that key for exactly this purpose.
+
+Persistence
+-----------
+
+If your worker is deployed as a ``StatefulSet`` (i.e. you set
+``workers.celery.persistence.enabled=true``), change ``kind: Deployment`` to
+``kind: StatefulSet`` under ``scaleTargetRef`` in ``scaledobject.yaml``.
+
+Migration guide from the chart
+------------------------------
+
+What the chart currently does
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+When ``workers.celery.keda.enabled=true``, the chart renders:
+
+* A ``ScaledObject`` named ``<release>-worker`` targeting the worker
+  Deployment or StatefulSet.
+* The KEDA trigger reads the connection string from the worker pod's
+  ``KEDA_DB_CONN`` (or ``AIRFLOW_CONN_AIRFLOW_DB``) env var, which is
+  itself sourced from the metadata Secret.
+* Tuning is exposed under ``workers.celery.keda.*`` in ``values.yaml``.
+
+What this overlay provides
+^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+* The same ``ScaledObject`` as a standalone resource.
+* A ``TriggerAuthentication`` that reads the connection string directly
+  from the chart's metadata Secret. This avoids the indirection through
+  the worker pod env var, and means the overlay does not need to patch any
+  chart-rendered resource.
+
+How to switch
+^^^^^^^^^^^^^
+
+1. Install or upgrade the chart with ``workers.celery.keda.enabled=false``.
+2. Render this overlay with the substitutions described above.
+3. Apply the rendered manifests.
+4. Confirm KEDA reports ``ScaledObject`` ``Ready`` and the worker scales
+   on demand. Useful command:
+
+   .. code-block:: bash
+
+       kubectl describe scaledobject airflow-worker -n <namespace>
+
+If you previously set custom ``pollingInterval``, ``cooldownPeriod``,
+``minReplicaCount``, ``maxReplicaCount``, ``advanced``, or ``query`` under
+``workers.celery.keda``, copy them into ``scaledobject.yaml`` before
+applying.
+
+Status
+------
+
+This overlay carries ``status: not-tested``. It builds successfully but has
+no functional CI coverage yet. Treat it as a starting point and adapt it
+to your environment. Feedback and improvements via pull request are very
+welcome under the `helm-chart refurbish umbrella issue
+<https://github.com/apache/airflow/issues/64037>`__.
diff --git a/chart/kustomize-overlays/keda/STATUS.yaml 
b/chart/kustomize-overlays/keda/STATUS.yaml
new file mode 100644
index 00000000000..e072e6be44b
--- /dev/null
+++ b/chart/kustomize-overlays/keda/STATUS.yaml
@@ -0,0 +1,24 @@
+# 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.
+
+---
+status: not-tested
+reason: >-
+  First proof-of-concept overlay for KEDA. Builds successfully and passes the
+  generic structural check in scripts/ci/prek/build_kustomize_overlays.py, but
+  has no CRD-schema or functional integration test yet. Use as a starting
+  point only. Tracked under https://github.com/apache/airflow/issues/64037.
diff --git a/chart/kustomize-overlays/keda/kustomization.yaml 
b/chart/kustomize-overlays/keda/kustomization.yaml
new file mode 100644
index 00000000000..2909d4e22d2
--- /dev/null
+++ b/chart/kustomize-overlays/keda/kustomization.yaml
@@ -0,0 +1,38 @@
+# 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.
+
+# Kustomize base for the KEDA autoscaler overlay.
+#
+# This base ships with placeholder values (RELEASE-NAME, NAMESPACE) that
+# downstream kustomizations must override before applying. See README.rst
+# in this directory for substitution patterns and a migration guide from
+# the equivalent chart-side configuration.
+
+---
+apiVersion: kustomize.config.k8s.io/v1beta1
+kind: Kustomization
+
+resources:
+  - triggerauthentication.yaml
+  - scaledobject.yaml
+
+labels:
+  - includeSelectors: false
+    pairs:
+      app.kubernetes.io/managed-by: kustomize
+      app.kubernetes.io/part-of: airflow
+      app.kubernetes.io/component: worker-autoscaler
diff --git a/chart/kustomize-overlays/keda/scaledobject.yaml 
b/chart/kustomize-overlays/keda/scaledobject.yaml
new file mode 100644
index 00000000000..982839b34f5
--- /dev/null
+++ b/chart/kustomize-overlays/keda/scaledobject.yaml
@@ -0,0 +1,53 @@
+# 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.
+
+# Standalone ScaledObject for the chart-rendered Celery worker.
+#
+# Substitute the placeholders before applying:
+#   * RELEASE-NAME    - the Helm release name
+#   * If your worker uses persistence, change `kind: Deployment`
+#                       to `kind: StatefulSet` under scaleTargetRef.
+#
+# The default `query` mirrors the chart for a single Celery queue named
+# "default" with worker_concurrency=16. Adjust both numbers if you
+# customised those values, and extend the `queue IN (...)` clause if
+# your chart values set additional queues.
+---
+apiVersion: keda.sh/v1alpha1
+kind: ScaledObject
+metadata:
+  name: airflow-worker
+spec:
+  scaleTargetRef:
+    kind: Deployment
+    name: RELEASE-NAME-worker
+    envSourceContainerName: worker
+  pollingInterval: 5
+  cooldownPeriod: 30
+  minReplicaCount: 0
+  maxReplicaCount: 10
+  triggers:
+    - type: postgresql
+      metadata:
+        targetQueryValue: "1"
+        query: >-
+          SELECT ceil(COUNT(*)::decimal / 16)
+          FROM task_instance
+          WHERE (state='running' OR state='queued')
+          AND queue IN ('default')
+      authenticationRef:
+        name: airflow-keda-postgres-auth
diff --git a/chart/kustomize-overlays/keda/triggerauthentication.yaml 
b/chart/kustomize-overlays/keda/triggerauthentication.yaml
new file mode 100644
index 00000000000..60971fbdc39
--- /dev/null
+++ b/chart/kustomize-overlays/keda/triggerauthentication.yaml
@@ -0,0 +1,34 @@
+# 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.
+
+# Reads the metadata DB connection string directly from the secret that
+# the chart already creates. This keeps the overlay a pure standalone
+# addition - no chart-rendered resource is patched.
+#
+# Substitute RELEASE-NAME with your Helm release name. If pgbouncer is
+# enabled and you do not want KEDA polling through it, change the `key`
+# from `connection` to `kedaConnection`.
+---
+apiVersion: keda.sh/v1alpha1
+kind: TriggerAuthentication
+metadata:
+  name: airflow-keda-postgres-auth
+spec:
+  secretTargetRef:
+    - parameter: connection
+      name: RELEASE-NAME-airflow-metadata
+      key: connection
diff --git a/scripts/ci/prek/build_kustomize_overlays.py 
b/scripts/ci/prek/build_kustomize_overlays.py
new file mode 100755
index 00000000000..71787def6e2
--- /dev/null
+++ b/scripts/ci/prek/build_kustomize_overlays.py
@@ -0,0 +1,226 @@
+#!/usr/bin/env python
+# 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.
+# /// script
+# requires-python = ">=3.10,<3.11"
+# dependencies = [
+#   "pydantic>=2.0",
+#   "PyYAML>=6.0",
+#   "rich>=13.6.0",
+# ]
+# ///
+
+# =============================================================================
+# Build and structural smoke test for chart/kustomize-overlays/*.
+#
+# Runs the same checks against every overlay; nothing here is overlay- or
+# CRD-specific.
+#
+# What this hook validates:
+#   * `kubectl kustomize` builds the overlay successfully.
+#   * The output parses as valid YAML.
+#   * At least one resource is produced.
+#   * Every resource carries apiVersion, kind, and metadata.name.
+#   * No two resources share the same (apiVersion, kind, namespace, name).
+#
+# What this hook does not validate:
+#   * Field schema correctness against the targeted CRD. A typo in a field
+#     name will still pass.
+#   * Cross-references between resources, or references to resources
+#     produced elsewhere (for example by the chart).
+#   * Runtime behaviour: the overlay is never applied to a live API server
+#     and no controller ever reconciles it.
+#
+# Treat a passing run as "the overlay is structurally well-formed", not as
+# "the overlay works against Kubernetes". An overlay's STATUS file may only
+# advance to `tested` once a functional integration test is in place; this
+# hook alone is not enough to support that claim. See CONTRIBUTING.rst in
+# `chart/kustomize-overlays/` for the lifecycle.
+# =============================================================================
+
+from __future__ import annotations
+
+import os
+import subprocess
+import sys
+from pathlib import Path
+from typing import Annotated, Literal
+
+import yaml
+from common_prek_utils import AIRFLOW_ROOT_PATH, console, 
initialize_breeze_prek
+from pydantic import BaseModel, ConfigDict, Field, TypeAdapter, ValidationError
+
+initialize_breeze_prek(__name__, __file__)
+
+
+# ---------------------------------------------------------------------------
+# STATUS.yaml contract — Pydantic discriminated union, one variant per status.
+# ---------------------------------------------------------------------------
+
+
+class _TestedStatus(BaseModel):
+    model_config = ConfigDict(extra="forbid", populate_by_name=True)
+
+    status: Literal["tested"]
+    chart_version: str = Field(alias="chart-version")
+    last_verified: str = Field(alias="last-verified", 
pattern=r"^\d{4}-\d{2}-\d{2}$")
+
+
+class _NotTestedStatus(BaseModel):
+    model_config = ConfigDict(extra="forbid")
+
+    status: Literal["not-tested"]
+    reason: str | None = None
+
+
+class _DeprecatedStatus(BaseModel):
+    model_config = ConfigDict(extra="forbid")
+
+    status: Literal["deprecated"]
+    message: str
+
+
+_StatusDoc = Annotated[
+    _TestedStatus | _NotTestedStatus | _DeprecatedStatus,
+    Field(discriminator="status"),
+]
+_STATUS_ADAPTER: TypeAdapter[_StatusDoc] = TypeAdapter(_StatusDoc)
+
+
+def _validate_status(overlay_dir: Path) -> list[str]:
+    status_path = overlay_dir / "STATUS.yaml"
+    if not status_path.exists():
+        return [f"missing STATUS.yaml in {overlay_dir.name}"]
+    try:
+        data = yaml.safe_load(status_path.read_text())
+    except yaml.YAMLError as exc:
+        return [f"STATUS.yaml is not valid YAML: {exc}"]
+    try:
+        _STATUS_ADAPTER.validate_python(data)
+    except ValidationError as exc:
+        return [f"STATUS.yaml schema error: {exc}"]
+    return []
+
+
+def _structural_check(docs: list[object]) -> list[str]:
+    """Run generic structural checks that hold for any Kustomize overlay."""
+    errors: list[str] = []
+    if not docs:
+        return ["no resources produced"]
+
+    seen: set[tuple[str, str, str, str]] = set()
+    for index, doc in enumerate(docs):
+        if not isinstance(doc, dict):
+            errors.append(f"document {index} is not a mapping 
({type(doc).__name__})")
+            continue
+        api_version = doc.get("apiVersion") or ""
+        kind = doc.get("kind") or ""
+        if not api_version:
+            errors.append(f"document {index} missing apiVersion")
+        if not kind:
+            errors.append(f"document {index} missing kind")
+        metadata = doc.get("metadata") or {}
+        name = metadata.get("name") or ""
+        if not name:
+            errors.append(f"document {index} missing metadata.name")
+        namespace = metadata.get("namespace") or ""
+        key = (api_version, kind, namespace, name)
+        if key in seen:
+            errors.append(f"duplicate resource ({api_version} {kind} 
{namespace}/{name})")
+        seen.add(key)
+    return errors
+
+
+res_setup = subprocess.run(["breeze", "k8s", "setup-env"], check=True)
+if res_setup.returncode != 0:
+    console.print("[red]\nError while setting up k8s environment.")
+    sys.exit(res_setup.returncode)
+
+KUBECTL_BIN_PATH = AIRFLOW_ROOT_PATH / ".venv" / "bin" / "kubectl"
+OVERLAYS_DIR = AIRFLOW_ROOT_PATH / "chart" / "kustomize-overlays"
+
+if not OVERLAYS_DIR.is_dir():
+    console.print(f"[yellow]No overlay directory at {OVERLAYS_DIR}, nothing to 
check.")
+    sys.exit(0)
+
+kustomizations = sorted(OVERLAYS_DIR.rglob("kustomization.yaml"))
+if not kustomizations:
+    console.print(f"[yellow]No kustomization.yaml files under {OVERLAYS_DIR}, 
nothing to check.")
+    sys.exit(0)
+
+
+def _build(overlay_dir: Path) -> tuple[list[object] | None, str]:
+    result = subprocess.run(
+        [os.fspath(KUBECTL_BIN_PATH), "kustomize", os.fspath(overlay_dir)],
+        check=False,
+        capture_output=True,
+        text=True,
+    )
+    if result.returncode != 0:
+        return None, f"build failed:\n{result.stderr}"
+    try:
+        docs = [doc for doc in yaml.safe_load_all(result.stdout) if doc]
+    except yaml.YAMLError as exc:
+        return None, f"build produced invalid YAML: {exc}"
+    if result.stderr.strip():
+        console.print(f"[yellow]warnings:\n{result.stderr.strip()}")
+    return docs, ""
+
+
+failures: list[str] = []
+for kustomization in kustomizations:
+    overlay_dir = kustomization.parent
+    rel = overlay_dir.relative_to(AIRFLOW_ROOT_PATH)
+    console.print(f"[blue]\nKustomize overlay [bold]{rel}[/bold]")
+
+    docs, build_err = _build(overlay_dir)
+    if docs is None:
+        console.print(f"[red]  {build_err}")
+        failures.append(str(rel))
+        continue
+    console.print(f"[green]  build ok ({len(docs)} resource(s))")
+
+    errors = _structural_check(docs)
+    if errors:
+        console.print("[red]  structural check failed:")
+        for err in errors:
+            console.print(f"    - {err}")
+        failures.append(str(rel))
+    else:
+        console.print("[green]  structural check ok")
+
+    status_errors = _validate_status(overlay_dir)
+    if status_errors:
+        console.print("[red]  STATUS.yaml check failed:")
+        for err in status_errors:
+            console.print(f"    - {err}")
+        if str(rel) not in failures:
+            failures.append(str(rel))
+    else:
+        console.print("[green]  STATUS.yaml ok")
+
+if failures:
+    console.print(f"[red]\n{len(failures)} overlay(s) failed:")
+    for failure in failures:
+        console.print(f"  - {failure}")
+    sys.exit(1)
+
+console.print("[green]\nAll Kustomize overlays built and passed the generic 
structural check.")
+console.print(
+    "[yellow]Note: this hook does not validate CRD schemas or runtime 
behaviour. "
+    "An overlay's STATUS may only advance to `tested` once a functional 
integration test exists."
+)


Reply via email to