This is an automated email from the ASF dual-hosted git repository.
potiuk pushed a commit to branch airflow-ctl/v0-1-test
in repository https://gitbox.apache.org/repos/asf/airflow.git
The following commit(s) were added to refs/heads/airflow-ctl/v0-1-test by this
push:
new 980a78349bd [airflow-ctl/v0-1-test] Enable auto-merge, gated on a
dedicated CI passed check (#75142) (#75555)
980a78349bd is described below
commit 980a78349bdffdeb45f6068c9426ffd5c93f079b
Author: Jarek Potiuk <[email protected]>
AuthorDate: Sat Oct 24 15:25:06 2026 +0200
[airflow-ctl/v0-1-test] Enable auto-merge, gated on a dedicated CI passed
check (#75142) (#75555)
Adapted backport: pull requests to the airflow-ctl release branches did not
trigger the Tests workflow at all, so they had no CI and the CI passed check
the release branches' ruleset requires never reported. They only change
airflow-ctl, so a dedicated workflow runs just the airflow-ctl tests and a CI
passed gate on them. 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/airflow-ctl-tests.yml | 68 ++++++++++++++++++++++
.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, 205 insertions(+)
diff --git a/.github/workflows/airflow-ctl-tests.yml
b/.github/workflows/airflow-ctl-tests.yml
new file mode 100644
index 00000000000..2e2eaa96d4b
--- /dev/null
+++ b/.github/workflows/airflow-ctl-tests.yml
@@ -0,0 +1,68 @@
+# 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.
+#
+---
+# Pull requests to the airflow-ctl release branches only change airflow-ctl,
so they run its tests
+# instead of the full "Tests" workflow, which does not trigger for these
branches.
+name: Airflow CTL tests
+on: # yamllint disable-line rule:truthy
+ pull_request:
+ branches:
+ - airflow-ctl/v[0-9]+-[0-9]+-test
+ - airflow-ctl/v[0-9]+-[0-9]+-stable
+permissions:
+ contents: read
+concurrency:
+ group: airflow-ctl-tests-${{ github.event.pull_request.number }}
+ cancel-in-progress: true
+jobs:
+ tests-airflow-ctl:
+ name: "Airflow CTL tests"
+ uses: ./.github/workflows/airflow-distributions-tests.yml
+ permissions:
+ contents: read
+ packages: read
+ with:
+ runners: '["ubuntu-22.04"]'
+ platform: "linux/amd64"
+ default-python-version: "3.10"
+ python-versions: '["3.10", "3.11", "3.12", "3.13", "3.14"]'
+ use-uv: "true"
+ canary-run: "false"
+ distribution-name: "airflow-ctl"
+ distribution-cmd-format: "prepare-airflow-ctl-distributions"
+ test-type: "airflow-ctl-tests"
+ use-local-venv: 'true'
+ test-timeout: 20
+
+ ci-passed:
+ name: 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.
+ if: always()
+ needs:
+ - tests-airflow-ctl
+ 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."
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index 748cf045d84..d05ccb2a323 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -398,6 +398,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/airflow-ctl-tests\.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..7d50ebe5677
--- /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 ``airflow-ctl-tests.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/airflow-ctl-tests.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