This is an automated email from the ASF dual-hosted git repository. kaxilnaik 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 0810b0df301 Dev: Add Task SDK support to `start-rc-process` (#54960) 0810b0df301 is described below commit 0810b0df301f427034ee7b99a90aa4166b82f806 Author: Kaxil Naik <kaxiln...@apache.org> AuthorDate: Wed Aug 27 00:16:43 2025 +0100 Dev: Add Task SDK support to `start-rc-process` (#54960) Added support to automate building Task SDK too and `--remote-name` parameter to allow passing a different remote (for example I use a different remote than origin). Also added some validations to prevent errors! --- dev/README_RELEASE_AIRFLOW.md | 74 +++--- .../output_release-management_start-rc-process.svg | 30 ++- .../output_release-management_start-rc-process.txt | 2 +- .../commands/release_candidate_command.py | 278 +++++++++++++++++++-- .../commands/release_management_commands_config.py | 2 + 5 files changed, 320 insertions(+), 66 deletions(-) diff --git a/dev/README_RELEASE_AIRFLOW.md b/dev/README_RELEASE_AIRFLOW.md index 3800c1b3489..70374c64908 100644 --- a/dev/README_RELEASE_AIRFLOW.md +++ b/dev/README_RELEASE_AIRFLOW.md @@ -272,10 +272,11 @@ The Release Candidate artifacts we vote upon should be the exact ones we vote ag export GPG_TTY=$(tty) # Set Version -export VERSION=2.1.2rc3 -export VERSION_SUFFIX=rc3 +export VERSION=3.0.5rc1 +export VERSION_SUFFIX=rc1 export VERSION_BRANCH=2-1 export VERSION_WITHOUT_RC=${VERSION/rc?/} +export TASK_SDK_VERSION=1.0.5rc1 # Set AIRFLOW_REPO_ROOT to the path of your git repo export AIRFLOW_REPO_ROOT=$(pwd) @@ -359,7 +360,23 @@ uv tool install -e ./dev/breeze ```shell script git checkout main git pull # Ensure that the script is up-to-date - breeze release-management start-rc-process --version ${VERSION} --previous-version <PREVIOUS_VERSION> + breeze release-management start-rc-process \ + --version ${VERSION} \ + --previous-version <PREVIOUS_VERSION> \ + --task-sdk-version ${TASK_SDK_VERSION} + ``` + + **Testing the start-rc-process command:** + Before running the actual release command, you can safely test it using: + + ```shell script + # Test with dry-run (shows what would be executed without doing it) + breeze release-management start-rc-process \ + --version ${VERSION} \ + --previous-version <PREVIOUS_VERSION> \ + --task-sdk-version ${TASK_SDK_VERSION} \ + --remote-name upstream \ + --dry-run ``` - Create issue in github for testing the release using this subject: @@ -748,8 +765,8 @@ this is a valid key already. To suppress the warning you may edit the key's tru by running `gpg --edit-key <key id> trust` and entering `5` to assign trust level `ultimate`. ``` -Checking apache-airflow-2.0.2rc4.tar.gz.asc -gpg: assuming signed data in 'apache-airflow-2.0.2rc4.tar.gz' +Checking apache-airflow-3.0.5rc4.tar.gz.asc +gpg: assuming signed data in 'apache-airflow-3.0.5rc4.tar.gz' gpg: Signature made sob, 22 sie 2020, 20:28:28 CEST gpg: using RSA key 12717556040EEF2EEAF1B9C275FCCD0A25FA0E4B gpg: Good signature from "Kaxil Naik <kaxiln...@gmail.com>" [unknown] @@ -757,8 +774,8 @@ gpg: WARNING: This key is not certified with a trusted signature! gpg: There is no indication that the signature belongs to the owner. Primary key fingerprint: 1271 7556 040E EF2E EAF1 B9C2 75FC CD0A 25FA 0E4B -Checking apache_airflow-2.0.2rc4-py2.py3-none-any.whl.asc -gpg: assuming signed data in 'apache_airflow-2.0.2rc4-py2.py3-none-any.whl' +Checking apache_airflow-3.0.5rc4-py2.py3-none-any.whl.asc +gpg: assuming signed data in 'apache_airflow-3.0.5rc4-py2.py3-none-any.whl' gpg: Signature made sob, 22 sie 2020, 20:28:31 CEST gpg: using RSA key 12717556040EEF2EEAF1B9C275FCCD0A25FA0E4B gpg: Good signature from "Kaxil Naik <kaxiln...@gmail.com>" [unknown] @@ -766,8 +783,8 @@ gpg: WARNING: This key is not certified with a trusted signature! gpg: There is no indication that the signature belongs to the owner. Primary key fingerprint: 1271 7556 040E EF2E EAF1 B9C2 75FC CD0A 25FA 0E4B -Checking apache-airflow-2.0.2rc4-source.tar.gz.asc -gpg: assuming signed data in 'apache-airflow-2.0.2rc4-source.tar.gz' +Checking apache-airflow-3.0.5rc4-source.tar.gz.asc +gpg: assuming signed data in 'apache-airflow-3.0.5rc4-source.tar.gz' gpg: Signature made sob, 22 sie 2020, 20:28:25 CEST gpg: using RSA key 12717556040EEF2EEAF1B9C275FCCD0A25FA0E4B gpg: Good signature from "Kaxil Naik <kaxiln...@gmail.com>" [unknown] @@ -790,9 +807,9 @@ done You should get output similar to: ``` -Checking apache-airflow-2.0.2rc4.tar.gz.sha512 -Checking apache_airflow-2.0.2rc4-py2.py3-none-any.whl.sha512 -Checking apache-airflow-2.0.2rc4-source.tar.gz.sha512 +Checking apache-airflow-3.0.5rc4.tar.gz.sha512 +Checking apache_airflow-3.0.5rc4-py2.py3-none-any.whl.sha512 +Checking apache-airflow-3.0.5rc4-source.tar.gz.sha512 ``` @@ -857,7 +874,7 @@ Once the vote has been passed, you will need to send a result vote to dev@airflo Subject: ``` -[RESULT][VOTE] Release Airflow 2.0.2 from 2.0.2rc3 +[RESULT][VOTE] Release Airflow 3.0.5 from 3.0.5rc1 & Task SDK 1.0.5 from 1.0.5rc1 ``` Message: @@ -865,26 +882,27 @@ Message: ``` Hello, -Apache Airflow 2.0.2 (based on RC3) has been accepted. +The vote to release Apache Airflow version 3.0.5 based on 3.0.5rc3 & Task SDK 1.0.5 from 1.0.5rc3 is now closed. + +The vote PASSED with 6 binding "+1", 4 non-binding "+1" and 0 "-1" votes: -4 "+1" binding votes received: +"+1" Binding votes: - Kaxil Naik -- Bolke de Bruin +- Jens Scheffler +- Jarek Potiuk - Ash Berlin-Taylor -- Tao Feng - - -4 "+1" non-binding votes received: +- Hussein Awala +- Amogh Desai -- Deng Xiaodong -- Stefan Seelmann -- Joshua Patchus -- Felix Uellendall +"+1" non-Binding votes: +- Wei Lee +- Pavankumar Gopidesu +- Ankit Chaurasia +- Rahul Vats -Vote thread: -https://lists.apache.org/thread.html/736404ca3d2b2143b296d0910630b9bd0f8b56a0c54e3a05f4c8b5fe@%3Cdev.airflow.apache.org%3E +Vote thread: https://lists.apache.org/thread/f72gglg5vdxnfmjqtjlhwgvn2tnh4gx4 -I'll continue with the release process, and the release announcement will follow shortly. +I will continue with the release process, and the release announcement will follow shortly. Cheers, <your name> @@ -899,7 +917,7 @@ https://dist.apache.org/repos/dist/release/airflow/ The best way of doing this is to svn cp between the two repos (this avoids having to upload the binaries again, and gives a clearer history in the svn commit logs): ```shell script -export RC=2.0.2rc5 +export RC=3.0.5rc5 export VERSION=${RC/rc?/} # cd to the airflow repo directory and set the environment variable below export AIRFLOW_REPO_ROOT=$(pwd) diff --git a/dev/breeze/doc/images/output_release-management_start-rc-process.svg b/dev/breeze/doc/images/output_release-management_start-rc-process.svg index cd966701475..45c7c863b53 100644 --- a/dev/breeze/doc/images/output_release-management_start-rc-process.svg +++ b/dev/breeze/doc/images/output_release-management_start-rc-process.svg @@ -1,4 +1,4 @@ -<svg class="rich-terminal" viewBox="0 0 1482 440.4" xmlns="http://www.w3.org/2000/svg"> +<svg class="rich-terminal" viewBox="0 0 1482 489.2" xmlns="http://www.w3.org/2000/svg"> <!-- Generated with Rich https://www.textualize.io --> <style> @@ -45,7 +45,7 @@ <defs> <clipPath id="breeze-release-management-start-rc-process-clip-terminal"> - <rect x="0" y="0" width="1463.0" height="389.4" /> + <rect x="0" y="0" width="1463.0" height="438.2" /> </clipPath> <clipPath id="breeze-release-management-start-rc-process-line-0"> <rect x="0" y="1.5" width="1464" height="24.65"/> @@ -92,9 +92,15 @@ <clipPath id="breeze-release-management-start-rc-process-line-14"> <rect x="0" y="343.1" width="1464" height="24.65"/> </clipPath> +<clipPath id="breeze-release-management-start-rc-process-line-15"> + <rect x="0" y="367.5" width="1464" height="24.65"/> + </clipPath> +<clipPath id="breeze-release-management-start-rc-process-line-16"> + <rect x="0" y="391.9" width="1464" height="24.65"/> + </clipPath> </defs> - <rect fill="#292929" stroke="rgba(255,255,255,0.35)" stroke-width="1" x="1" y="1" width="1480" height="438.4" rx="8"/><text class="breeze-release-management-start-rc-process-title" fill="#c5c8c6" text-anchor="middle" x="740" y="27">Command: release-management start-rc-process</text> + <rect fill="#292929" stroke="rgba(255,255,255,0.35)" stroke-width="1" x="1" y="1" width="1480" height="487.2" rx="8"/><text class="breeze-release-management-start-rc-process-title" fill="#c5c8c6" text-anchor="middle" x="740" y="27">Command: release-management start-rc-process</text> <g transform="translate(26,22)"> <circle cx="0" cy="0" r="7" fill="#ff5f57"/> <circle cx="22" cy="0" r="7" fill="#febc2e"/> @@ -112,14 +118,16 @@ </text><text class="breeze-release-management-start-rc-process-r5" x="0" y="142" textLength="24.4" clip-path="url(#breeze-release-management-start-rc-process-line-5)">╭─</text><text class="breeze-release-management-start-rc-process-r5" x="24.4" y="142" textLength="292.8" clip-path="url(#breeze-release-management-start-rc-process-line-5)"> Start RC process flags </text><text class="breeze-release-management-start-rc-process-r5" x="317.2" y="142" textLength="1122.4 [...] </text><text class="breeze-release-management-start-rc-process-r5" x="0" y="166.4" textLength="12.2" clip-path="url(#breeze-release-management-start-rc-process-line-6)">│</text><text class="breeze-release-management-start-rc-process-r6" x="24.4" y="166.4" textLength="12.2" clip-path="url(#breeze-release-management-start-rc-process-line-6)">*</text><text class="breeze-release-management-start-rc-process-r4" x="61" y="166.4" textLength="109.8" clip-path="url(#breeze-release-management-star [...] </text><text class="breeze-release-management-start-rc-process-r5" x="0" y="190.8" textLength="12.2" clip-path="url(#breeze-release-management-start-rc-process-line-7)">│</text><text class="breeze-release-management-start-rc-process-r6" x="24.4" y="190.8" textLength="12.2" clip-path="url(#breeze-release-management-start-rc-process-line-7)">*</text><text class="breeze-release-management-start-rc-process-r4" x="61" y="190.8" textLength="219.6" clip-path="url(#breeze-release-management-star [...] -</text><text class="breeze-release-management-start-rc-process-r5" x="0" y="215.2" textLength="12.2" clip-path="url(#breeze-release-management-start-rc-process-line-8)">│</text><text class="breeze-release-management-start-rc-process-r4" x="61" y="215.2" textLength="170.8" clip-path="url(#breeze-release-management-start-rc-process-line-8)">--github-token</text><text class="breeze-release-management-start-rc-process-r1" x="329.4" y="215.2" textLength="878.4" clip-path="url(#breeze-release- [...] -</text><text class="breeze-release-management-start-rc-process-r5" x="0" y="239.6" textLength="1464" clip-path="url(#breeze-release-management-start-rc-process-line-9)">╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯</text><text class="breeze-release-management-start-rc-process-r1" x="1464" y="239.6" textLength="12.2" clip-path="url(#breeze-release-management-start-rc-process-line-9)"> -</text><text class="breeze-release-management-start-rc-process-r5" x="0" y="264" textLength="24.4" clip-path="url(#breeze-release-management-start-rc-process-line-10)">╭─</text><text class="breeze-release-management-start-rc-process-r5" x="24.4" y="264" textLength="195.2" clip-path="url(#breeze-release-management-start-rc-process-line-10)"> Common options </text><text class="breeze-release-management-start-rc-process-r5" x="219.6" y="264" textLength="1220" clip-path="url(# [...] -</text><text class="breeze-release-management-start-rc-process-r5" x="0" y="288.4" textLength="12.2" clip-path="url(#breeze-release-management-start-rc-process-line-11)">│</text><text class="breeze-release-management-start-rc-process-r4" x="24.4" y="288.4" textLength="97.6" clip-path="url(#breeze-release-management-start-rc-process-line-11)">--answer</text><text class="breeze-release-management-start-rc-process-r9" x="158.6" y="288.4" textLength="24.4" clip-path="url(#breeze-release-mana [...] -</text><text class="breeze-release-management-start-rc-process-r5" x="0" y="312.8" textLength="12.2" clip-path="url(#breeze-release-management-start-rc-process-line-12)">│</text><text class="breeze-release-management-start-rc-process-r4" x="24.4" y="312.8" textLength="109.8" clip-path="url(#breeze-release-management-start-rc-process-line-12)">--dry-run</text><text class="breeze-release-management-start-rc-process-r9" x="158.6" y="312.8" textLength="24.4" clip-path="url(#breeze-release-ma [...] -</text><text class="breeze-release-management-start-rc-process-r5" x="0" y="337.2" textLength="12.2" clip-path="url(#breeze-release-management-start-rc-process-line-13)">│</text><text class="breeze-release-management-start-rc-process-r4" x="24.4" y="337.2" textLength="109.8" clip-path="url(#breeze-release-management-start-rc-process-line-13)">--verbose</text><text class="breeze-release-management-start-rc-process-r9" x="158.6" y="337.2" textLength="24.4" clip-path="url(#breeze-release-ma [...] -</text><text class="breeze-release-management-start-rc-process-r5" x="0" y="361.6" textLength="12.2" clip-path="url(#breeze-release-management-start-rc-process-line-14)">│</text><text class="breeze-release-management-start-rc-process-r4" x="24.4" y="361.6" textLength="73.2" clip-path="url(#breeze-release-management-start-rc-process-line-14)">--help</text><text class="breeze-release-management-start-rc-process-r9" x="158.6" y="361.6" textLength="24.4" clip-path="url(#breeze-release-manage [...] -</text><text class="breeze-release-management-start-rc-process-r5" x="0" y="386" textLength="1464" clip-path="url(#breeze-release-management-start-rc-process-line-15)">╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯</text><text class="breeze-release-management-start-rc-process-r1" x="1464" y="386" textLength="12.2" clip-path="url(#breeze-release-management-start-rc-process-line-15)"> +</text><text class="breeze-release-management-start-rc-process-r5" x="0" y="215.2" textLength="12.2" clip-path="url(#breeze-release-management-start-rc-process-line-8)">│</text><text class="breeze-release-management-start-rc-process-r6" x="24.4" y="215.2" textLength="12.2" clip-path="url(#breeze-release-management-start-rc-process-line-8)">*</text><text class="breeze-release-management-start-rc-process-r4" x="61" y="215.2" textLength="219.6" clip-path="url(#breeze-release-management-star [...] +</text><text class="breeze-release-management-start-rc-process-r5" x="0" y="239.6" textLength="12.2" clip-path="url(#breeze-release-management-start-rc-process-line-9)">│</text><text class="breeze-release-management-start-rc-process-r4" x="61" y="239.6" textLength="170.8" clip-path="url(#breeze-release-management-start-rc-process-line-9)">--github-token</text><text class="breeze-release-management-start-rc-process-r1" x="329.4" y="239.6" textLength="878.4" clip-path="url(#breeze-release- [...] +</text><text class="breeze-release-management-start-rc-process-r5" x="0" y="264" textLength="12.2" clip-path="url(#breeze-release-management-start-rc-process-line-10)">│</text><text class="breeze-release-management-start-rc-process-r4" x="61" y="264" textLength="158.6" clip-path="url(#breeze-release-management-start-rc-process-line-10)">--remote-name</text><text class="breeze-release-management-start-rc-process-r1" x="329.4" y="264" textLength="536.8" clip-path="url(#breeze-release-manag [...] +</text><text class="breeze-release-management-start-rc-process-r5" x="0" y="288.4" textLength="1464" clip-path="url(#breeze-release-management-start-rc-process-line-11)">╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯</text><text class="breeze-release-management-start-rc-process-r1" x="1464" y="288.4" textLength="12.2" clip-path="url(#breeze-release-management-start-rc-process-line-11)"> +</text><text class="breeze-release-management-start-rc-process-r5" x="0" y="312.8" textLength="24.4" clip-path="url(#breeze-release-management-start-rc-process-line-12)">╭─</text><text class="breeze-release-management-start-rc-process-r5" x="24.4" y="312.8" textLength="195.2" clip-path="url(#breeze-release-management-start-rc-process-line-12)"> Common options </text><text class="breeze-release-management-start-rc-process-r5" x="219.6" y="312.8" textLength="1220" clip-path= [...] +</text><text class="breeze-release-management-start-rc-process-r5" x="0" y="337.2" textLength="12.2" clip-path="url(#breeze-release-management-start-rc-process-line-13)">│</text><text class="breeze-release-management-start-rc-process-r4" x="24.4" y="337.2" textLength="97.6" clip-path="url(#breeze-release-management-start-rc-process-line-13)">--answer</text><text class="breeze-release-management-start-rc-process-r9" x="158.6" y="337.2" textLength="24.4" clip-path="url(#breeze-release-mana [...] +</text><text class="breeze-release-management-start-rc-process-r5" x="0" y="361.6" textLength="12.2" clip-path="url(#breeze-release-management-start-rc-process-line-14)">│</text><text class="breeze-release-management-start-rc-process-r4" x="24.4" y="361.6" textLength="109.8" clip-path="url(#breeze-release-management-start-rc-process-line-14)">--dry-run</text><text class="breeze-release-management-start-rc-process-r9" x="158.6" y="361.6" textLength="24.4" clip-path="url(#breeze-release-ma [...] +</text><text class="breeze-release-management-start-rc-process-r5" x="0" y="386" textLength="12.2" clip-path="url(#breeze-release-management-start-rc-process-line-15)">│</text><text class="breeze-release-management-start-rc-process-r4" x="24.4" y="386" textLength="109.8" clip-path="url(#breeze-release-management-start-rc-process-line-15)">--verbose</text><text class="breeze-release-management-start-rc-process-r9" x="158.6" y="386" textLength="24.4" clip-path="url(#breeze-release-manageme [...] +</text><text class="breeze-release-management-start-rc-process-r5" x="0" y="410.4" textLength="12.2" clip-path="url(#breeze-release-management-start-rc-process-line-16)">│</text><text class="breeze-release-management-start-rc-process-r4" x="24.4" y="410.4" textLength="73.2" clip-path="url(#breeze-release-management-start-rc-process-line-16)">--help</text><text class="breeze-release-management-start-rc-process-r9" x="158.6" y="410.4" textLength="24.4" clip-path="url(#breeze-release-manage [...] +</text><text class="breeze-release-management-start-rc-process-r5" x="0" y="434.8" textLength="1464" clip-path="url(#breeze-release-management-start-rc-process-line-17)">╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯</text><text class="breeze-release-management-start-rc-process-r1" x="1464" y="434.8" textLength="12.2" clip-path="url(#breeze-release-management-start-rc-process-line-17)"> </text> </g> </g> diff --git a/dev/breeze/doc/images/output_release-management_start-rc-process.txt b/dev/breeze/doc/images/output_release-management_start-rc-process.txt index b3b458160d9..60bca552ac9 100644 --- a/dev/breeze/doc/images/output_release-management_start-rc-process.txt +++ b/dev/breeze/doc/images/output_release-management_start-rc-process.txt @@ -1 +1 @@ -e213b793839b778288f40a3af9e076f6 +8c938ba1642769000bab086687b60815 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 87aa6dad66a..f71992b6c9d 100644 --- a/dev/breeze/src/airflow_breeze/commands/release_candidate_command.py +++ b/dev/breeze/src/airflow_breeze/commands/release_candidate_command.py @@ -32,7 +32,173 @@ from airflow_breeze.utils.run_utils import run_command from airflow_breeze.utils.shared_options import get_dry_run -def merge_pr(version_branch): +def validate_remote_tracks_apache_airflow(remote_name): + """Validate that the specified remote tracks the apache/airflow repository.""" + console_print(f"[info]Validating remote '{remote_name}' tracks apache/airflow...") + + result = run_command( + ["git", "remote", "get-url", remote_name], + check=False, + capture_output=True, + text=True, + dry_run_override=False, + ) + + if result.returncode != 0: + console_print(f"[error]Remote '{remote_name}' does not exist!") + console_print("Available remotes:") + run_command(["git", "remote", "-v"]) + exit(1) + + remote_url = result.stdout.strip() + + # Check if it's the apache/airflow repository + apache_patterns = [ + "https://github.com/apache/airflow", + "https://github.com/apache/airflow.git", + "g...@github.com:apache/airflow", + "g...@github.com:apache/airflow.git", + "ssh://g...@github.com/apache/airflow", + "ssh://g...@github.com/apache/airflow.git", + ] + + is_apache_repo = any(pattern in remote_url for pattern in apache_patterns) + + if not is_apache_repo: + console_print(f"[error]Remote '{remote_name}' does not track apache/airflow!") + console_print(f"Remote URL: {remote_url}") + console_print("Expected patterns: apache/airflow") + if not confirm_action("Do you want to continue anyway? This is NOT recommended for releases."): + exit(1) + console_print(f"[success]Remote '{remote_name}' correctly tracks apache/airflow") + + +def validate_git_status(): + """Validate that git working directory is clean.""" + console_print("[info]Validating git status...") + + # Check if working directory is clean + result = run_command( + ["git", "status", "--porcelain"], + check=True, + capture_output=True, + text=True, + ) + + if result.stdout.strip(): + console_print("[error]Working directory is not clean!") + run_command(["git", "status"]) + if not confirm_action("Do you want to continue with uncommitted changes? This is NOT recommended."): + exit(1) + console_print("[success]Working directory is clean") + + +def validate_version_branches_exist(version_branch, remote_name): + """Validate that the required version branches exist.""" + console_print(f"[info]Validating version branches exist for {version_branch}...") + + # Check if test branch exists + test_branch = f"v{version_branch}-test" + stable_branch = f"v{version_branch}-stable" + + # Fetch to get latest remote branches + run_command(["git", "fetch", remote_name], check=True) + + # Check remote branches + result = run_command( + ["git", "branch", "-r"], + check=True, + capture_output=True, + text=True, + dry_run_override=False, + ) + remote_branches = result.stdout + + test_branch_exists = f"{remote_name}/{test_branch}" in remote_branches + stable_branch_exists = f"{remote_name}/{stable_branch}" in remote_branches + + if not test_branch_exists: + console_print(f"[error]Test branch '{remote_name}/{test_branch}' does not exist!") + console_print("Available remote branches:") + run_command(["git", "branch", "-r"]) + exit(1) + console_print(f"[success]Test branch '{remote_name}/{test_branch}' exists") + + if not stable_branch_exists: + console_print(f"[error]Stable branch '{remote_name}/{stable_branch}' does not exist!") + console_print("Available remote branches:") + run_command(["git", "branch", "-r"]) + exit(1) + console_print(f"[success]Stable branch '{remote_name}/{stable_branch}' exists") + + +def validate_tag_does_not_exist(version, remote_name): + """Validate that the release tag doesn't already exist.""" + console_print(f"[info]Checking if tag '{version}' already exists...") + + # Check if tag exists locally + local_result = run_command( + ["git", "tag", "-l", version], + check=True, + capture_output=True, + text=True, + ) + + # Check if tag exists on remote using ls-remote with --exit-code + remote_result = run_command( + ["git", "ls-remote", "--exit-code", "--tags", remote_name, f"refs/tags/{version}"], + check=False, + ) + + tag_exists_locally = bool(local_result.stdout.strip()) + tag_exists_remotely = remote_result.returncode == 0 + + if not tag_exists_locally and not tag_exists_remotely: + console_print(f"[success]Tag '{version}' does not exist yet") + return + + location = [] + if tag_exists_locally: + location.append("locally") + if tag_exists_remotely: + location.append("remotely") + + console_print(f"[error]Tag '{version}' already exists {' and '.join(location)}!") + + if tag_exists_locally: + console_print(f"Use 'git tag -d {version}' to delete it locally if needed") + if tag_exists_remotely: + console_print(f"Use 'git push {remote_name} --delete {version}' to delete it remotely if needed") + + if not confirm_action("Do you want to continue anyway? This may cause issues."): + exit(1) + + +def validate_on_correct_branch_for_tagging(version_branch): + """Validate that we're on the correct branch for tagging (stable branch).""" + console_print("[info]Validating we're on the correct branch for tagging...") + + expected_branch = f"v{version_branch}-stable" + + # Check current branch + result = run_command( + ["git", "branch", "--show-current"], + check=True, + capture_output=True, + text=True, + dry_run_override=False, + ) + current_branch = result.stdout.strip() + + if current_branch != expected_branch: + console_print(f"[error]Currently on branch '{current_branch}', expected '{expected_branch}'!") + console_print("Tags should be created on the stable branch after merging the sync PR.") + console_print("Make sure the PR merge step completed successfully.") + exit(1) + console_print(f"[success]On correct branch '{expected_branch}' for tagging") + + +def merge_pr(version_branch, remote_name): if confirm_action("Do you want to merge the Sync PR?"): run_command( [ @@ -43,7 +209,7 @@ def merge_pr(version_branch): check=True, ) run_command( - ["git", "reset", "--hard", f"origin/v{version_branch}-stable"], + ["git", "reset", "--hard", f"{remote_name}/v{version_branch}-stable"], check=True, ) run_command( @@ -52,18 +218,18 @@ def merge_pr(version_branch): ) if confirm_action("Do you want to push the changes? Pushing the changes closes the PR"): run_command( - ["git", "push", "origin", f"v{version_branch}-stable"], + ["git", "push", remote_name, f"v{version_branch}-stable"], check=True, ) -def git_tag(version): +def git_tag(version, message): if confirm_action(f"Tag {version}?"): run_command( - ["git", "tag", "-s", f"{version}", "-m", f"Apache Airflow {version}"], + ["git", "tag", "-s", f"{version}", "-m", message], check=True, ) - console_print("[success]Tagged") + console_print(f"[success]Tagged {version}!") def git_clean(): @@ -116,12 +282,27 @@ def create_artifacts_with_hatch(source_date_epoch: int): AIRFLOW_DIST_PATH.mkdir(exist_ok=True) env_copy = os.environ.copy() env_copy["SOURCE_DATE_EPOCH"] = str(source_date_epoch) + # Build Airflow packages run_command( ["hatch", "build", "-c", "-t", "custom", "-t", "sdist", "-t", "wheel"], check=True, env=env_copy ) - console_print("[success]Successfully prepared Airflow packages:") + # Build Task SDK packages + run_command( + [ + "breeze", + "release-management", + "prepare-task-sdk-distributions", + "--distribution-format", + "both", + "--use-local-hatch", + ], + check=True, + ) + console_print("[success]Successfully prepared Airflow and Task SDK packages:") for file in sorted(AIRFLOW_DIST_PATH.glob("apache_airflow*")): console_print(print(file.name)) + for file in sorted(AIRFLOW_DIST_PATH.glob("*task*")): + console_print(print(file.name)) console_print() @@ -137,7 +318,17 @@ def create_artifacts_with_docker(): ], check=True, ) - console_print("[success]Artifacts created") + run_command( + [ + "breeze", + "release-management", + "prepare-task-sdk-distributions", + "--distribution-format", + "both", + ], + check=True, + ) + console_print("[success]Airflow and Task SDK artifacts created") def sign_the_release(repo_root): @@ -147,10 +338,10 @@ def sign_the_release(repo_root): console_print("[success]Release signed") -def tag_and_push_constraints(version, version_branch): +def tag_and_push_constraints(version, version_branch, remote_name): if confirm_action("Do you want to tag and push constraints?"): run_command( - ["git", "checkout", f"origin/constraints-{version_branch}"], + ["git", "checkout", f"{remote_name}/constraints-{version_branch}"], check=True, ) run_command( @@ -165,7 +356,7 @@ def tag_and_push_constraints(version, version_branch): check=True, ) run_command( - ["git", "push", "origin", "tag", f"constraints-{version}"], + ["git", "push", remote_name, "tag", f"constraints-{version}"], check=True, ) console_print("[success]Constraints tagged and pushed") @@ -187,13 +378,18 @@ def clone_asf_repo(version, repo_root): console_print("[success]Cloned ASF repo successfully") -def move_artifacts_to_svn(version, repo_root): +def move_artifacts_to_svn(version, task_sdk_version, repo_root): if confirm_action("Do you want to move artifacts to SVN?"): os.chdir(f"{repo_root}/asf-dist/dev/airflow") run_command(["svn", "mkdir", f"{version}"], check=True) - run_command(f"mv {repo_root}/dist/* {version}/", check=True, shell=True) + run_command(f"mv {repo_root}/dist/*{version}* {version}/", check=True, shell=True) + run_command( + f"mv {repo_root}/dist/*{task_sdk_version}* task-sdk/{task_sdk_version}/", check=True, shell=True + ) console_print("[success]Moved artifacts to SVN:") run_command(["ls"]) + run_command([f"ls {version}"]) + run_command([f"ls task-sdk/{task_sdk_version}"]) def push_artifacts_to_asf_repo(version, repo_root): @@ -237,6 +433,19 @@ def prepare_pypi_packages(version, version_suffix, repo_root): ], check=True, ) + # Task SDK + run_command( + [ + "breeze", + "release-management", + "prepare-task-sdk-distributions", + "--version-suffix", + f"{version_suffix}", + "--distribution-format", + "both", + ], + check=True, + ) files_to_check = [] for files in Path(AIRFLOW_DIST_PATH).glob("apache_airflow*"): if "-sources" not in files.name: @@ -273,8 +482,8 @@ def push_packages_to_pypi(version): ) -def push_release_candidate_tag_to_github(version): - if confirm_action("Do you want to push release candidate tag to GitHub?"): +def push_release_candidate_tag_to_github(version, remote_name): + if confirm_action("Do you want to push release candidate tags to GitHub?"): console_print( """ This step should only be done now and not before, because it triggers an automated @@ -283,7 +492,7 @@ def push_release_candidate_tag_to_github(version): """ ) confirm_action(f"Confirm that {version} is pushed to PyPI(not PyPI test). Is it pushed?", abort=True) - run_command(["git", "push", "origin", "tag", f"{version}"], check=True) + run_command(["git", "push", remote_name, "tag", f"{version}"], check=True) console_print("[success]Release candidate tag pushed to GitHub") @@ -345,13 +554,15 @@ def prepare_airflow_tarball(version: str): ) @click.option("--version", required=True, help="The release candidate version e.g. 2.4.3rc1") @click.option("--previous-version", required=True, help="Previous version released e.g. 2.4.2") +@click.option("--task-sdk-version", required=True, help="The task SDK version e.g. 1.0.6rc1.") @click.option( "--github-token", help="GitHub token to use in generating issue for testing of release candidate" ) +@click.option("--remote-name", default="origin", help="Git remote name to push to (default: origin)") @option_answer @option_dry_run @option_verbose -def publish_release_candidate(version, previous_version, github_token): +def publish_release_candidate(version, previous_version, task_sdk_version, github_token, remote_name): from packaging.version import Version airflow_version = Version(version) @@ -368,29 +579,43 @@ def publish_release_candidate(version, previous_version, github_token): version_suffix = airflow_version.pre[0] + str(airflow_version.pre[1]) version_branch = str(airflow_version.release[0]) + "-" + str(airflow_version.release[1]) version_without_rc = airflow_version.base_version + + task_sdk_version_obj = Version(task_sdk_version) + task_sdk_version_without_rc = task_sdk_version_obj.base_version + os.chdir(AIRFLOW_ROOT_PATH) airflow_repo_root = os.getcwd() + validate_remote_tracks_apache_airflow(remote_name) + validate_git_status() + validate_version_branches_exist(version_branch, remote_name) + validate_tag_does_not_exist(version, remote_name) + validate_tag_does_not_exist(f"task-sdk/{task_sdk_version}", remote_name) + # List the above variables and ask for confirmation console_print() console_print(f"Previous version: {previous_version}") - console_print(f"version: {version}") + console_print(f"Airflow version: {version}") + console_print(f"Task SDK version: {task_sdk_version}") console_print(f"version_suffix: {version_suffix}") console_print(f"version_branch: {version_branch}") console_print(f"version_without_rc: {version_without_rc}") + console_print(f"task_sdk_version_without_rc: {task_sdk_version_without_rc}") console_print(f"airflow_repo_root: {airflow_repo_root}") + console_print(f"remote_name: {remote_name}") console_print() - console_print("Below are your git remotes. We will push to origin:") + console_print(f"Below are your git remotes. We will push to {remote_name}:") run_command(["git", "remote", "-v"]) console_print() confirm_action("Verify that the above information is correct. Do you want to continue?", abort=True) - # Final confirmation - confirm_action("Pushes will be made to origin. Do you want to continue?", abort=True) # Merge the sync PR - merge_pr(version_branch) + merge_pr(version_branch, remote_name) # # # Tag & clean the repo - git_tag(version) + # Validate we're on the correct branch before tagging + validate_on_correct_branch_for_tagging(version_branch) + git_tag(version, f"Apache Airflow {version}") + git_tag(f"task-sdk/{task_sdk_version}", f"Airflow Task SDK {task_sdk_version}") git_clean() source_date_epoch = get_source_date_epoch(AIRFLOW_ROOT_PATH) shutil.rmtree(AIRFLOW_DIST_PATH, ignore_errors=True) @@ -407,11 +632,11 @@ def publish_release_candidate(version, previous_version, github_token): # Sign the release sign_the_release(airflow_repo_root) # Tag and push constraints - tag_and_push_constraints(version, version_branch) + tag_and_push_constraints(version, version_branch, remote_name) # Clone the asf repo clone_asf_repo(version, airflow_repo_root) # Move artifacts to SVN - move_artifacts_to_svn(version, airflow_repo_root) + move_artifacts_to_svn(version, task_sdk_version, airflow_repo_root) # Push the artifacts to the asf repo push_artifacts_to_asf_repo(version, airflow_repo_root) @@ -428,7 +653,8 @@ def publish_release_candidate(version, previous_version, github_token): push_packages_to_pypi(version) # Push the release candidate tag to gitHub - push_release_candidate_tag_to_github(version) + push_release_candidate_tag_to_github(version, remote_name) + push_release_candidate_tag_to_github(f"task-sdk/{task_sdk_version}", remote_name) # Create issue for testing os.chdir(airflow_repo_root) diff --git a/dev/breeze/src/airflow_breeze/commands/release_management_commands_config.py b/dev/breeze/src/airflow_breeze/commands/release_management_commands_config.py index 7dee7bca4db..09eae51533c 100644 --- a/dev/breeze/src/airflow_breeze/commands/release_management_commands_config.py +++ b/dev/breeze/src/airflow_breeze/commands/release_management_commands_config.py @@ -421,7 +421,9 @@ RELEASE_MANAGEMENT_PARAMETERS: dict[str, list[dict[str, str | list[str]]]] = { "options": [ "--version", "--previous-version", + "--task-sdk-version", "--github-token", + "--remote-name", ], } ],