This is an automated email from the ASF dual-hosted git repository. ephraimanierobi pushed a commit to branch backport-d34942e-v3-1-test in repository https://gitbox.apache.org/repos/asf/airflow.git
commit 39cd15a925f0db6fe7a4573a031111c512ecb756 Author: Ephraim Anierobi <[email protected]> AuthorDate: Tue Dec 16 02:18:46 2025 +0100 [v3-1-test] Remove old task SDK RC when creating RC (#59459) The old task sdk releases should also be removed when creating a new RC. This commit ensures that and also added tests for the removal (cherry picked from commit d34942e) Co-authored-by: Ephraim Anierobi <[email protected]> --- .../commands/release_candidate_command.py | 32 +++- dev/breeze/tests/test_release_candidate_command.py | 187 ++++++++++++++++++++- 2 files changed, 207 insertions(+), 12 deletions(-) diff --git a/dev/breeze/src/airflow_breeze/commands/release_candidate_command.py b/dev/breeze/src/airflow_breeze/commands/release_candidate_command.py index 37c67da5d55..2587ad04688 100644 --- a/dev/breeze/src/airflow_breeze/commands/release_candidate_command.py +++ b/dev/breeze/src/airflow_breeze/commands/release_candidate_command.py @@ -588,12 +588,13 @@ def push_release_candidate_tag_to_github(version, remote_name): console_print("[success]Release candidate tag pushed to GitHub") -def remove_old_releases(version, repo_root): +def remove_old_releases(version, task_sdk_version, repo_root): if not confirm_action("Do you want to look for old RCs to remove?"): return os.chdir(f"{repo_root}/asf-dist/dev/airflow") + # Remove old Airflow releases old_releases = [] for entry in os.scandir(): if entry.name == version: @@ -602,15 +603,38 @@ def remove_old_releases(version, repo_root): if entry.is_dir() and RC_PATTERN.match(entry.name): old_releases.append(entry.name) old_releases.sort() - console_print(f"The following old releases should be removed: {old_releases}") + console_print(f"The following old Airflow releases should be removed: {old_releases}") for old_release in old_releases: - console_print(f"Removing old release {old_release}") + console_print(f"Removing old Airflow release {old_release}") if confirm_action(f"Remove old RC {old_release}?"): run_command(["svn", "rm", old_release], check=True) run_command( ["svn", "commit", "-m", f"Remove old release: {old_release}"], check=True, ) + + # Remove old Task SDK releases + task_sdk_path = f"{repo_root}/asf-dist/dev/airflow/task-sdk" + if os.path.exists(task_sdk_path): + os.chdir(task_sdk_path) + old_task_sdk_releases = [] + for entry in os.scandir(): + if entry.name == task_sdk_version: + # Don't remove the current RC + continue + if entry.is_dir() and RC_PATTERN.match(entry.name): + old_task_sdk_releases.append(entry.name) + old_task_sdk_releases.sort() + console_print(f"The following old Task SDK releases should be removed: {old_task_sdk_releases}") + for old_release in old_task_sdk_releases: + console_print(f"Removing old Task SDK release {old_release}") + if confirm_action(f"Remove old Task SDK RC {old_release}?"): + run_command(["svn", "rm", old_release], check=True) + run_command( + ["svn", "commit", "-m", f"Remove old Task SDK release: {old_release}"], + check=True, + ) + console_print("[success]Old releases removed") os.chdir(repo_root) @@ -758,7 +782,7 @@ def publish_release_candidate( push_artifacts_to_asf_repo(version, task_sdk_version, airflow_repo_root) # Remove old releases - remove_old_releases(version, airflow_repo_root) + remove_old_releases(version, task_sdk_version, airflow_repo_root) # Delete asf-dist directory delete_asf_repo(airflow_repo_root) diff --git a/dev/breeze/tests/test_release_candidate_command.py b/dev/breeze/tests/test_release_candidate_command.py index 3070f9420a4..c92d7b0aad7 100644 --- a/dev/breeze/tests/test_release_candidate_command.py +++ b/dev/breeze/tests/test_release_candidate_command.py @@ -39,6 +39,7 @@ def rc_cmd(): def test_remove_old_releases_only_collects_rc_directories(monkeypatch, rc_cmd): version = "2.10.0rc3" + task_sdk_version = "1.0.6rc3" repo_root = "/repo/root" # Arrange: entries include current RC, old RC directories, a matching "file", and non-RC directory. @@ -59,28 +60,36 @@ def test_remove_old_releases_only_collects_rc_directories(monkeypatch, rc_cmd): if prompt == "Do you want to look for old RCs to remove?": return True # For each candidate, we decline removal to avoid running svn commands. - if prompt.startswith("Remove old RC "): + if prompt.startswith("Remove old RC ") or prompt.startswith("Remove old Task SDK RC "): return False raise AssertionError(f"Unexpected confirm prompt: {prompt}") + def fake_path_exists(path: str) -> bool: + # Task SDK path doesn't exist in this test + return False + monkeypatch.setattr(rc_cmd.os, "chdir", lambda path: chdir_calls.append(path)) monkeypatch.setattr(rc_cmd.os, "scandir", lambda: iter(entries)) + monkeypatch.setattr(rc_cmd.os.path, "exists", fake_path_exists) monkeypatch.setattr(rc_cmd, "confirm_action", fake_confirm_action) monkeypatch.setattr(rc_cmd, "console_print", lambda msg="": console_messages.append(str(msg))) monkeypatch.setattr(rc_cmd, "run_command", lambda cmd, **_kwargs: run_command_calls.append(cmd)) # Act - rc_cmd.remove_old_releases(version=version, repo_root=repo_root) + rc_cmd.remove_old_releases(version=version, task_sdk_version=task_sdk_version, repo_root=repo_root) # Assert: only directory entries matching RC_PATTERN, excluding current version, and sorted. assert f"{repo_root}/asf-dist/dev/airflow" in chdir_calls assert repo_root in chdir_calls - assert "The following old releases should be removed: ['2.10.0rc1', '2.10.0rc2']" in console_messages + assert ( + "The following old Airflow releases should be removed: ['2.10.0rc1', '2.10.0rc2']" in console_messages + ) assert run_command_calls == [] def test_remove_old_releases_returns_early_when_user_declines(monkeypatch, rc_cmd): version = "2.10.0rc3" + task_sdk_version = "1.0.6rc3" repo_root = "/repo/root" confirm_prompts: list[str] = [] @@ -98,13 +107,14 @@ def test_remove_old_releases_returns_early_when_user_declines(monkeypatch, rc_cm monkeypatch.setattr(rc_cmd, "console_print", should_not_be_called) monkeypatch.setattr(rc_cmd, "run_command", should_not_be_called) - rc_cmd.remove_old_releases(version=version, repo_root=repo_root) + rc_cmd.remove_old_releases(version=version, task_sdk_version=task_sdk_version, repo_root=repo_root) assert confirm_prompts == ["Do you want to look for old RCs to remove?"] def test_remove_old_releases_removes_confirmed_old_releases(monkeypatch, rc_cmd): version = "3.1.5rc3" + task_sdk_version = "1.0.6rc3" repo_root = "/repo/root" # Unsorted on purpose to verify sorting before prompting/removing. @@ -129,8 +139,13 @@ def test_remove_old_releases_removes_confirmed_old_releases(monkeypatch, rc_cmd) return False raise AssertionError(f"Unexpected confirm prompt: {prompt}") + def fake_path_exists(path: str) -> bool: + # Task SDK path doesn't exist in this test + return False + monkeypatch.setattr(rc_cmd.os, "chdir", lambda path: chdir_calls.append(path)) monkeypatch.setattr(rc_cmd.os, "scandir", lambda: iter(entries)) + monkeypatch.setattr(rc_cmd.os.path, "exists", fake_path_exists) monkeypatch.setattr(rc_cmd, "confirm_action", fake_confirm_action) monkeypatch.setattr(rc_cmd, "console_print", lambda msg="": console_messages.append(str(msg))) @@ -139,7 +154,7 @@ def test_remove_old_releases_removes_confirmed_old_releases(monkeypatch, rc_cmd) monkeypatch.setattr(rc_cmd, "run_command", fake_run_command) - rc_cmd.remove_old_releases(version=version, repo_root=repo_root) + rc_cmd.remove_old_releases(version=version, task_sdk_version=task_sdk_version, repo_root=repo_root) assert chdir_calls == [f"{repo_root}/asf-dist/dev/airflow", repo_root] assert confirm_prompts == [ @@ -147,9 +162,11 @@ def test_remove_old_releases_removes_confirmed_old_releases(monkeypatch, rc_cmd) "Remove old RC 3.1.0rc1?", "Remove old RC 3.1.5rc2?", ] - assert "The following old releases should be removed: ['3.1.0rc1', '3.1.5rc2']" in console_messages - assert "Removing old release 3.1.0rc1" in console_messages - assert "Removing old release 3.1.5rc2" in console_messages + assert ( + "The following old Airflow releases should be removed: ['3.1.0rc1', '3.1.5rc2']" in console_messages + ) + assert "Removing old Airflow release 3.1.0rc1" in console_messages + assert "Removing old Airflow release 3.1.5rc2" in console_messages assert "[success]Old releases removed" in console_messages # Only rc1 was confirmed, so we should run rm+commit for rc1 only. @@ -157,3 +174,157 @@ def test_remove_old_releases_removes_confirmed_old_releases(monkeypatch, rc_cmd) (["svn", "rm", "3.1.0rc1"], {"check": True}), (["svn", "commit", "-m", "Remove old release: 3.1.0rc1"], {"check": True}), ] + + +def test_remove_old_releases_removes_task_sdk_releases(monkeypatch, rc_cmd): + version = "3.1.5rc3" + task_sdk_version = "1.0.6rc3" + repo_root = "/repo/root" + + # Airflow entries + airflow_entries = [ + FakeDirEntry(version, is_dir=True), + FakeDirEntry("3.1.5rc2", is_dir=True), + ] + + # Task SDK entries + task_sdk_entries = [ + FakeDirEntry(task_sdk_version, is_dir=True), # current RC: should be skipped + FakeDirEntry("1.0.6rc2", is_dir=True), # old RC dir: should be included + FakeDirEntry("1.0.6rc1", is_dir=True), # old RC dir: should be included + ] + + chdir_calls: list[str] = [] + console_messages: list[str] = [] + run_command_calls: list[tuple[list[str], dict]] = [] + confirm_prompts: list[str] = [] + scandir_call_count = 0 + + def fake_confirm_action(prompt: str, **_kwargs) -> bool: + confirm_prompts.append(prompt) + if prompt == "Do you want to look for old RCs to remove?": + return True + # Decline all removals to avoid running svn commands + if prompt.startswith("Remove old RC ") or prompt.startswith("Remove old Task SDK RC "): + return False + raise AssertionError(f"Unexpected confirm prompt: {prompt}") + + def fake_path_exists(path: str) -> bool: + # Task SDK path exists in this test + return path == f"{repo_root}/asf-dist/dev/airflow/task-sdk" + + def fake_scandir(): + nonlocal scandir_call_count + scandir_call_count += 1 + # First call is for Airflow, second is for Task SDK + if scandir_call_count == 1: + return iter(airflow_entries) + if scandir_call_count == 2: + return iter(task_sdk_entries) + raise AssertionError("Unexpected scandir call") + + monkeypatch.setattr(rc_cmd.os, "chdir", lambda path: chdir_calls.append(path)) + monkeypatch.setattr(rc_cmd.os, "scandir", fake_scandir) + monkeypatch.setattr(rc_cmd.os.path, "exists", fake_path_exists) + monkeypatch.setattr(rc_cmd, "confirm_action", fake_confirm_action) + monkeypatch.setattr(rc_cmd, "console_print", lambda msg="": console_messages.append(str(msg))) + monkeypatch.setattr(rc_cmd, "run_command", lambda cmd, **_kwargs: run_command_calls.append((cmd, {}))) + + rc_cmd.remove_old_releases(version=version, task_sdk_version=task_sdk_version, repo_root=repo_root) + + assert f"{repo_root}/asf-dist/dev/airflow" in chdir_calls + assert f"{repo_root}/asf-dist/dev/airflow/task-sdk" in chdir_calls + assert repo_root in chdir_calls + assert "The following old Airflow releases should be removed: ['3.1.5rc2']" in console_messages + assert ( + "The following old Task SDK releases should be removed: ['1.0.6rc1', '1.0.6rc2']" in console_messages + ) + assert "[success]Old releases removed" in console_messages + # No removals were confirmed, so no svn commands should be run + assert run_command_calls == [] + + +def test_remove_old_releases_removes_both_airflow_and_task_sdk_releases(monkeypatch, rc_cmd): + version = "3.1.5rc3" + task_sdk_version = "1.0.6rc3" + repo_root = "/repo/root" + + # Airflow entries + airflow_entries = [ + FakeDirEntry(version, is_dir=True), + FakeDirEntry("3.1.5rc2", is_dir=True), + ] + + # Task SDK entries + task_sdk_entries = [ + FakeDirEntry(task_sdk_version, is_dir=True), + FakeDirEntry("1.0.6rc2", is_dir=True), + FakeDirEntry("1.0.6rc1", is_dir=True), + ] + + chdir_calls: list[str] = [] + console_messages: list[str] = [] + run_command_calls: list[tuple[list[str], dict]] = [] + confirm_prompts: list[str] = [] + scandir_call_count = 0 + + def fake_confirm_action(prompt: str, **_kwargs) -> bool: + confirm_prompts.append(prompt) + if prompt == "Do you want to look for old RCs to remove?": + return True + # Confirm removal of one Airflow and one Task SDK release + if prompt == "Remove old RC 3.1.5rc2?": + return True + if prompt == "Remove old Task SDK RC 1.0.6rc1?": + return True + # Decline others + if prompt.startswith("Remove old RC ") or prompt.startswith("Remove old Task SDK RC "): + return False + raise AssertionError(f"Unexpected confirm prompt: {prompt}") + + def fake_path_exists(path: str) -> bool: + return path == f"{repo_root}/asf-dist/dev/airflow/task-sdk" + + def fake_scandir(): + nonlocal scandir_call_count + scandir_call_count += 1 + if scandir_call_count == 1: + return iter(airflow_entries) + if scandir_call_count == 2: + return iter(task_sdk_entries) + raise AssertionError("Unexpected scandir call") + + monkeypatch.setattr(rc_cmd.os, "chdir", lambda path: chdir_calls.append(path)) + monkeypatch.setattr(rc_cmd.os, "scandir", fake_scandir) + monkeypatch.setattr(rc_cmd.os.path, "exists", fake_path_exists) + monkeypatch.setattr(rc_cmd, "confirm_action", fake_confirm_action) + monkeypatch.setattr(rc_cmd, "console_print", lambda msg="": console_messages.append(str(msg))) + + def fake_run_command(cmd: list[str], **kwargs): + run_command_calls.append((cmd, kwargs)) + + monkeypatch.setattr(rc_cmd, "run_command", fake_run_command) + + rc_cmd.remove_old_releases(version=version, task_sdk_version=task_sdk_version, repo_root=repo_root) + + assert chdir_calls == [ + f"{repo_root}/asf-dist/dev/airflow", + f"{repo_root}/asf-dist/dev/airflow/task-sdk", + repo_root, + ] + assert "The following old Airflow releases should be removed: ['3.1.5rc2']" in console_messages + assert ( + "The following old Task SDK releases should be removed: ['1.0.6rc1', '1.0.6rc2']" in console_messages + ) + assert "Removing old Airflow release 3.1.5rc2" in console_messages + assert "Removing old Task SDK release 1.0.6rc1" in console_messages + assert "Removing old Task SDK release 1.0.6rc2" in console_messages + assert "[success]Old releases removed" in console_messages + + # Both Airflow and Task SDK removals were confirmed + assert run_command_calls == [ + (["svn", "rm", "3.1.5rc2"], {"check": True}), + (["svn", "commit", "-m", "Remove old release: 3.1.5rc2"], {"check": True}), + (["svn", "rm", "1.0.6rc1"], {"check": True}), + (["svn", "commit", "-m", "Remove old Task SDK release: 1.0.6rc1"], {"check": True}), + ]
