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

potiuk 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 98eaca0ebaa Add teams verify CLI command (#70947)
98eaca0ebaa is described below

commit 98eaca0ebaad577e2abf7902c7c8d387e903263b
Author: SameerMesiah97 <[email protected]>
AuthorDate: Mon Aug 17 13:45:05 2026 +0100

    Add teams verify CLI command (#70947)
    
    Adds a new  command to validate multi-team
    configuration consistency. The command checks that every team has its
    system-managed default pool, that DAG bundles reference existing teams,
    and that team-owned pools follow the expected naming convention. Any
    issues are reported together before the command exits with a non-zero
    status.
    
    Added unit tests covering successful verification, disabled multi-team
    mode, missing default pools, and invalid DAG bundle team references.
    
    Co-authored-by: Sameer Mesiah <[email protected]>
---
 airflow-core/newsfragments/70947.feature.rst       |  1 +
 airflow-core/src/airflow/cli/cli_config.py         |  7 +++
 .../src/airflow/cli/commands/team_command.py       | 43 ++++++++++++++++
 .../tests/unit/cli/commands/test_team_command.py   | 60 ++++++++++++++++++++++
 4 files changed, 111 insertions(+)

diff --git a/airflow-core/newsfragments/70947.feature.rst 
b/airflow-core/newsfragments/70947.feature.rst
new file mode 100644
index 00000000000..1d0f8ab6aff
--- /dev/null
+++ b/airflow-core/newsfragments/70947.feature.rst
@@ -0,0 +1 @@
+Add an ``airflow teams verify`` CLI command for validating multi-team 
configuration. The command checks that each team has its system-managed default 
pool, that DAG bundles reference existing teams, and reports any configuration 
inconsistencies.
diff --git a/airflow-core/src/airflow/cli/cli_config.py 
b/airflow-core/src/airflow/cli/cli_config.py
index 46a0ebfa391..1a83af7a272 100644
--- a/airflow-core/src/airflow/cli/cli_config.py
+++ b/airflow-core/src/airflow/cli/cli_config.py
@@ -1681,6 +1681,13 @@ TEAMS_COMMANDS = (
         func=lazy_load_command("airflow.cli.commands.team_command.team_sync"),
         args=(ARG_VERBOSE,),
     ),
+    ActionCommand(
+        name="verify",
+        help="Verify multi-team configuration",
+        description=("Verify that the multi-team configuration is internally 
consistent.\n"),
+        
func=lazy_load_command("airflow.cli.commands.team_command.team_verify"),
+        args=(ARG_VERBOSE,),
+    ),
 )
 STATE_STORE_COMMANDS = (
     ActionCommand(
diff --git a/airflow-core/src/airflow/cli/commands/team_command.py 
b/airflow-core/src/airflow/cli/commands/team_command.py
index c108ac203f8..855a42071de 100644
--- a/airflow-core/src/airflow/cli/commands/team_command.py
+++ b/airflow-core/src/airflow/cli/commands/team_command.py
@@ -238,3 +238,46 @@ def team_sync(args, *, session=NEW_SESSION):
 
     if teams_added > 0:
         print(f"{teams_added} teams added.")
+
+
+@cli_utils.action_cli
+@providers_configuration_loaded
+@provide_session
+def team_verify(args, *, session=NEW_SESSION):
+    """Verify that the multi-team configuration is consistent."""
+    if not conf.getboolean("core", "multi_team"):
+        print("Multi-team is not enabled.")
+        return
+
+    issues: list[str] = []
+
+    teams = session.scalars(select(Team)).all()
+
+    for team in teams:
+        default_pool_name = Pool.get_default_team_pool_name(team.name)
+
+        default_pool = session.scalar(
+            select(Pool).where(
+                Pool.pool == default_pool_name,
+                Pool.team_name == team.name,
+            )
+        )
+
+        if default_pool is None:
+            issues.append(f"Team '{team.name}' is missing default pool 
'{default_pool_name}'.")
+
+    existing_teams = {team.name for team in teams}
+
+    for bundle_name, bundle in DagBundlesManager()._bundle_config.items():
+        if bundle.team_name and bundle.team_name not in existing_teams:
+            issues.append(f"DAG bundle '{bundle_name}' references unknown team 
'{bundle.team_name}'.")
+
+    if issues:
+        print("Verification failed.\n")
+
+        for issue in issues:
+            print(f"✗ {issue}")
+
+        raise SystemExit(1)
+
+    print("Verification succeeded.")
diff --git a/airflow-core/tests/unit/cli/commands/test_team_command.py 
b/airflow-core/tests/unit/cli/commands/test_team_command.py
index 02d8a44516f..3af2e4ee5b8 100644
--- a/airflow-core/tests/unit/cli/commands/test_team_command.py
+++ b/airflow-core/tests/unit/cli/commands/test_team_command.py
@@ -475,3 +475,63 @@ class TestCliTeams:
 
         assert pool is not None
         assert pool.team_name == "team1"
+
+    def test_team_verify_multi_team_disabled(self, stdout_capture):
+        with conf_vars({("core", "multi_team"): "False"}):
+            with stdout_capture as stdout:
+                team_command.team_verify(self.parser.parse_args(["teams", 
"verify"]))
+
+        assert "Multi-team is not enabled." in stdout.getvalue()
+
+    def test_team_verify_success(self, stdout_capture):
+        bundle_config = [
+            {
+                "name": "bundleone",
+                "classpath": 
"airflow.dag_processing.bundles.local.LocalDagBundle",
+                "kwargs": {"path": "/dev/null", "refresh_interval": 0},
+                "team_name": "team1",
+            },
+        ]
+
+        with conf_vars(
+            {
+                ("core", "multi_team"): "True",
+                ("dag_processor", "dag_bundle_config_list"): 
json.dumps(bundle_config),
+            }
+        ):
+            team_command.team_sync(self.parser.parse_args(["teams", "sync"]))
+
+            with stdout_capture as stdout:
+                team_command.team_verify(self.parser.parse_args(["teams", 
"verify"]))
+
+        assert "Verification succeeded." in stdout.getvalue()
+
+    def test_team_verify_missing_default_pool(self):
+        self.session.add(Team(name="team1"))
+        self.session.commit()
+
+        with conf_vars({("core", "multi_team"): "True"}):
+            with pytest.raises(SystemExit):
+                team_command.team_verify(self.parser.parse_args(["teams", 
"verify"]))
+
+    def test_team_verify_unknown_bundle_team(self, stdout_capture):
+        bundle_config = [
+            {
+                "name": "bundleone",
+                "classpath": 
"airflow.dag_processing.bundles.local.LocalDagBundle",
+                "kwargs": {"path": "/dev/null", "refresh_interval": 0},
+                "team_name": "missing-team",
+            },
+        ]
+
+        with conf_vars(
+            {
+                ("core", "multi_team"): "True",
+                ("dag_processor", "dag_bundle_config_list"): 
json.dumps(bundle_config),
+            }
+        ):
+            with stdout_capture as stdout:
+                with pytest.raises(SystemExit):
+                    team_command.team_verify(self.parser.parse_args(["teams", 
"verify"]))
+
+        assert "references unknown team 'missing-team'" in stdout.getvalue()

Reply via email to