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

potiuk pushed a commit to branch backport-a5af91d-chart/v1-2x-test
in repository https://gitbox.apache.org/repos/asf/airflow.git

commit f10c2e730ceab00b65339c4b13a68c84c33f0253
Author: Jarek Potiuk <[email protected]>
AuthorDate: Sat Oct 24 14:55:00 2026 +0200

    [chart/v1-2x-test] Enable auto-merge, gated on a dedicated CI passed check 
(#75142)
    
    Adapted backport: this branch still runs CI from the combined 
ci-amd-arm.yml, so the CI passed gate job and the check-ci-gate-needs hook 
target that workflow. The .asf.yaml rulesets, the newsfragment workflow trigger 
and the committer docs only take effect on main and are left out.
    
    (cherry picked from commit a5af91d2c52fb410a58eb6939ab83e44612afdfe)
    Generated-by: Claude Opus 5
---
 .github/workflows/ci-amd-arm.yml                  | 53 +++++++++++++++++
 .pre-commit-config.yaml                           |  7 +++
 scripts/ci/prek/check_ci_gate_needs.py            | 70 +++++++++++++++++++++++
 scripts/tests/ci/prek/test_check_ci_gate_needs.py | 60 +++++++++++++++++++
 4 files changed, 190 insertions(+)

diff --git a/.github/workflows/ci-amd-arm.yml b/.github/workflows/ci-amd-arm.yml
index 5dd58c09617..5339c71b873 100644
--- a/.github/workflows/ci-amd-arm.yml
+++ b/.github/workflows/ci-amd-arm.yml
@@ -934,6 +934,59 @@ jobs:
       use-uv: ${{ needs.build-info.outputs.use-uv }}
       debug-resources: ${{ needs.build-info.outputs.debug-resources }}
 
+  ci-passed:
+    # A skipped build-info means project CI did not run at all: the fork CI 
gate (AIP-120) left the
+    # pull request to CI in the contributor's fork, and every job here is 
skipped. Reporting
+    # "CI passed" then would let it merge on no CI, so the job reports under 
another name and
+    # the fork CI reconciler posts "CI passed" from the fork run instead. 
Until the gate lands,
+    # build-info has no condition and this always reports "CI passed".
+    name: >-
+      ${{ needs.build-info.result == 'skipped' && 'CI passed (project CI not 
run)' || 'CI passed' }}
+    # The one check the release branches' ruleset requires, so auto-merge 
waits for CI to finish.
+    # GitHub treats a skipped required check as passed, so this job always 
runs and fails itself
+    # when any job it needs failed or was cancelled. Jobs skipped by selective 
checks count as passed.
+    if: always()
+    needs:
+      - additional-ci-image-checks
+      - additional-prod-image-tests
+      - basic-tests
+      - build-ci-images
+      - build-info
+      - build-prod-images
+      - ci-image-checks
+      - finalize-tests
+      - generate-constraints
+      - print-platform-amd
+      - print-platform-arm
+      - providers
+      - tests-airflow-ctl
+      - tests-go-sdk
+      - tests-helm
+      - tests-integration-system
+      - tests-kubernetes
+      - tests-mysql-core
+      - tests-mysql-providers
+      - tests-non-db-core
+      - tests-non-db-providers
+      - tests-postgres-core
+      - tests-postgres-providers
+      - tests-special
+      - tests-sqlite-core
+      - tests-sqlite-providers
+      - tests-task-sdk
+      - tests-with-lowest-direct-resolution-core
+      - tests-with-lowest-direct-resolution-providers
+      - verify-release-calendar
+    runs-on: ["ubuntu-slim"]
+    steps:
+      - name: "Fail if any CI job failed or was cancelled"
+        if: contains(needs.*.result, 'failure') || contains(needs.*.result, 
'cancelled')
+        run: |
+          echo "At least one CI job failed or was cancelled."
+          exit 1
+      - name: "All CI jobs passed or were skipped"
+        run: echo "All CI jobs passed or were skipped."
+
   notify-slack:
     name: "Notify Slack"
     needs:
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index 80d0464bb73..b64e03679b5 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -399,6 +399,13 @@ repos:
         language: python
         pass_filenames: false
         files: ^shared/.*$
+      - id: check-ci-gate-needs
+        name: Check the CI passed gate job needs every CI job
+        description: Auto-merge waits for "CI passed"; a job missing from its 
needs could fail without stopping a merge
+        entry: ./scripts/ci/prek/check_ci_gate_needs.py
+        language: python
+        pass_filenames: false
+        files: 
^\.github/workflows/ci-amd-arm\.yml$|^scripts/ci/prek/check_ci_gate_needs\.py$
       - id: check-shared-distributions-usage
         name: Check shared distributions usage
         entry: ./scripts/ci/prek/check_shared_distributions_usage.py
diff --git a/scripts/ci/prek/check_ci_gate_needs.py 
b/scripts/ci/prek/check_ci_gate_needs.py
new file mode 100755
index 00000000000..f696a59a125
--- /dev/null
+++ b/scripts/ci/prek/check_ci_gate_needs.py
@@ -0,0 +1,70 @@
+#!/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.11,<3.12"
+# dependencies = [
+#   "pyyaml>=6.0.3",
+# ]
+# ///
+"""Check that the ``CI passed`` gate job in ``ci-amd-arm.yml`` needs every 
other job.
+
+``CI passed`` is the check the release branches' ruleset requires, and 
auto-merge waits for it. It
+only fails when a job it needs failed, so a job missing from its ``needs`` 
could fail and the PR
+would still auto-merge. Reporting-only jobs that cannot make a PR wrong are 
exempt.
+"""
+
+from __future__ import annotations
+
+import sys
+from pathlib import Path
+
+import yaml
+
+AIRFLOW_ROOT = Path(__file__).parents[3]
+CI_WORKFLOW = AIRFLOW_ROOT / ".github/workflows/ci-amd-arm.yml"
+GATE_JOB = "ci-passed"
+EXEMPT_JOBS = {"notify-slack", "summarize-warnings"}
+
+
+def find_problems(jobs: dict) -> list[str]:
+    if GATE_JOB not in jobs:
+        return [f"job {GATE_JOB!r} is missing"]
+    needs = jobs[GATE_JOB].get("needs") or []
+    needs = {needs} if isinstance(needs, str) else set(needs)
+    expected = set(jobs) - EXEMPT_JOBS - {GATE_JOB}
+    problems = [
+        f"job {job!r} is not in {GATE_JOB}.needs, so its failure would not 
stop auto-merge"
+        for job in sorted(expected - needs)
+    ]
+    problems += [f"{GATE_JOB}.needs lists {job!r}, which is not a job" for job 
in sorted(needs - set(jobs))]
+    problems += [
+        f"{GATE_JOB}.needs lists exempt reporting job {job!r}" for job in 
sorted(needs & EXEMPT_JOBS)
+    ]
+    return problems
+
+
+def main() -> int:
+    jobs = yaml.safe_load(CI_WORKFLOW.read_text())["jobs"]
+    problems = find_problems(jobs)
+    for problem in problems:
+        print(f"{CI_WORKFLOW.relative_to(AIRFLOW_ROOT)}: {problem}")
+    return 1 if problems else 0
+
+
+if __name__ == "__main__":
+    sys.exit(main())
diff --git a/scripts/tests/ci/prek/test_check_ci_gate_needs.py 
b/scripts/tests/ci/prek/test_check_ci_gate_needs.py
new file mode 100644
index 00000000000..77248def0ab
--- /dev/null
+++ b/scripts/tests/ci/prek/test_check_ci_gate_needs.py
@@ -0,0 +1,60 @@
+# 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.
+from __future__ import annotations
+
+import pytest
+import yaml
+from check_ci_gate_needs import CI_WORKFLOW, find_problems
+
+
+def test_real_workflow_gate_needs_every_job():
+    assert find_problems(yaml.safe_load(CI_WORKFLOW.read_text())["jobs"]) == []
+
+
[email protected](
+    ("jobs", "expected"),
+    [
+        pytest.param(
+            {"build": {}, "test": {}, "notify-slack": {}, "ci-passed": 
{"needs": ["build", "test"]}},
+            [],
+            id="complete",
+        ),
+        pytest.param({"build": {}}, ["job 'ci-passed' is missing"], 
id="no-gate"),
+        pytest.param(
+            {"build": {}, "test": {}, "ci-passed": {"needs": ["build"]}},
+            ["job 'test' is not in ci-passed.needs, so its failure would not 
stop auto-merge"],
+            id="missing-job",
+        ),
+        pytest.param(
+            {"build": {}, "ci-passed": {"needs": "build"}},
+            [],
+            id="needs-as-string",
+        ),
+        pytest.param(
+            {"build": {}, "ci-passed": {"needs": ["build", "gone"]}},
+            ["ci-passed.needs lists 'gone', which is not a job"],
+            id="unknown-job",
+        ),
+        pytest.param(
+            {"build": {}, "summarize-warnings": {}, "ci-passed": {"needs": 
["build", "summarize-warnings"]}},
+            ["ci-passed.needs lists exempt reporting job 
'summarize-warnings'"],
+            id="exempt-job-listed",
+        ),
+    ],
+)
+def test_find_problems(jobs, expected):
+    assert find_problems(jobs) == expected

Reply via email to