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

henry3260 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 6abeae2e774 [airflow-ctl/v0-1-test] Fix airflowctl pools export/import 
dropping pool team ownership (#73132) (#74846)
6abeae2e774 is described below

commit 6abeae2e77449366726cc9600f432ed5bb829ebf
Author: github-actions[bot] 
<41898282+github-actions[bot]@users.noreply.github.com>
AuthorDate: Fri Oct 16 16:54:35 2026 +0800

    [airflow-ctl/v0-1-test] Fix airflowctl pools export/import dropping pool 
team ownership (#73132) (#74846)
    
    Multi-team deployments assign pools to a team via team_name. Backing pools
    up and restoring them silently lost that ownership: the exported file never
    carried the field, and an import with --action-on-existing-key overwrite
    sent it as null, which the server then wrote back over the existing pool.
    
    The deprecated airflow pools export/import that this command supersedes does
    round-trip team_name, so the gap only bites users who follow the migration
    notice onto airflowctl.
    (cherry picked from commit 91a8ab6950a1abc53828716b8826d671659cebf1)
    
    Co-authored-by: Y-C <[email protected]>
    Co-authored-by: Eason09053360 
<[email protected]>
---
 .../src/airflowctl/ctl/commands/pool_command.py    |  2 +
 .../airflow_ctl/ctl/commands/test_pool_command.py  | 45 ++++++++++++++++++++++
 2 files changed, 47 insertions(+)

diff --git a/airflow-ctl/src/airflowctl/ctl/commands/pool_command.py 
b/airflow-ctl/src/airflowctl/ctl/commands/pool_command.py
index 08e56eed87b..8815827ab6f 100644
--- a/airflow-ctl/src/airflowctl/ctl/commands/pool_command.py
+++ b/airflow-ctl/src/airflowctl/ctl/commands/pool_command.py
@@ -68,6 +68,7 @@ def export(args, api_client: Client = NEW_API_CLIENT) -> None:
                 "scheduled_slots": pool.scheduled_slots,
                 "open_slots": pool.open_slots,
                 "deferred_slots": pool.deferred_slots,
+                "team_name": pool.team_name,
             }
             for pool in pools_response.pools
         ]
@@ -105,6 +106,7 @@ def _import_helper(api_client: Client, filepath: Path, 
action_on_existence: Bulk
                 slots=pool_config["slots"],
                 description=pool_config.get("description", ""),
                 include_deferred=pool_config.get("include_deferred", False),
+                team_name=pool_config.get("team_name"),
             )
         )
 
diff --git a/airflow-ctl/tests/airflow_ctl/ctl/commands/test_pool_command.py 
b/airflow-ctl/tests/airflow_ctl/ctl/commands/test_pool_command.py
index 0bc24389294..15ce83e7af1 100644
--- a/airflow-ctl/tests/airflow_ctl/ctl/commands/test_pool_command.py
+++ b/airflow-ctl/tests/airflow_ctl/ctl/commands/test_pool_command.py
@@ -136,6 +136,23 @@ class TestPoolImportCommand:
         action = call_args["pools"].actions[0]
         assert action.action_on_existence == expected_enum
 
+    def test_import_forwards_team_name(self, mock_client, tmp_path):
+        """Test that team_name in the file reaches the server instead of being 
sent as null."""
+        pools_file = tmp_path / "pools.json"
+        pools_file.write_text(json.dumps([{"name": "test_pool", "slots": 1, 
"team_name": "data-team"}]))
+
+        mock_response = mock.MagicMock()
+        mock_response.success = ["test_pool"]
+        mock_response.errors = []
+        mock_bulk_builder = mock.MagicMock()
+        mock_bulk_builder.create = mock_response
+        mock_client.pools.bulk.return_value = mock_bulk_builder
+
+        pool_command.import_(mock.MagicMock(file=pools_file, 
action_on_existing_key="overwrite"))
+
+        sent = 
mock_client.pools.bulk.call_args[1]["pools"].model_dump(mode="json")
+        assert sent["actions"][0]["entities"][0]["team_name"] == "data-team"
+
 
 class TestPoolExportCommand:
     """Test cases for pool export command."""
@@ -155,6 +172,7 @@ class TestPoolExportCommand:
             "scheduled_slots": 0,
             "open_slots": 1,
             "deferred_slots": 0,
+            "team_name": None,
         }
         # Create a mock response with proper dictionary attributes
         mock_pools = mock.MagicMock()
@@ -191,6 +209,7 @@ class TestPoolExportCommand:
             "scheduled_slots": 0,
             "open_slots": 5,
             "deferred_slots": 0,
+            "team_name": None,
         }
         mock_pools = mock.MagicMock()
         mock_pools.pools = [type("Pool", (), pool_attrs)()]
@@ -204,6 +223,32 @@ class TestPoolExportCommand:
                 output=output_format,
             )
 
+    def test_export_includes_team_name(self, mock_client, tmp_path):
+        """Test that export writes team_name so the file can be imported back 
without losing it."""
+        export_file = tmp_path / "export.json"
+        pool_attrs = {
+            "name": "test_pool",
+            "slots": 1,
+            "description": "Test pool",
+            "include_deferred": True,
+            "occupied_slots": 0,
+            "running_slots": 0,
+            "queued_slots": 0,
+            "scheduled_slots": 0,
+            "open_slots": 1,
+            "deferred_slots": 0,
+            "team_name": "data-team",
+        }
+        mock_pools = mock.MagicMock()
+        mock_pools.pools = [type("Pool", (), pool_attrs)()]
+        mock_pools.total_entries = 1
+        mock_client.pools.list.return_value = mock_pools
+
+        pool_command.export(mock.MagicMock(file=export_file, output="json"))
+
+        exported_data = json.loads(export_file.read_text())
+        assert exported_data[0]["team_name"] == "data-team"
+
     def test_export_failure(self, mock_client, tmp_path):
         """Test pool export with API failure."""
         export_file = tmp_path / "export.json"

Reply via email to