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()