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 271604fa772 Allow manual triggering of constraints refresh workflow 
(#69457)
271604fa772 is described below

commit 271604fa772551eee6fa67f834908fddff325cbc
Author: Jarek Potiuk <[email protected]>
AuthorDate: Mon Jul 6 13:14:41 2026 +0200

    Allow manual triggering of constraints refresh workflow (#69457)
    
    Constraints in the constraints-* branches only refresh automatically when
    uv.lock changes on a stable branch. After a providers release there is no 
such
    push, so refreshing constraints - for example to pick up newly released
    providers before promoting an RC - required running the breeze
    constraint-generation commands locally.
    
    Add a workflow_dispatch trigger to the update-constraints workflow. The 
manual
    run is launched from main and takes a `ref` input (branch, tag or commit 
hash)
    selecting the sources to refresh constraints from; the target 
constraints-X-Y
    branch is derived from that ref's branch_defaults.py. This avoids 
cherry-picking
    the workflow to vX-Y-test / vX-Y-stable, which matters because those 
branches
    diverge while RCs are being voted on. A toggle re-resolves to the newest
    matching dependencies from PyPI (default on). The ref is threaded through 
the
    reusable ci-image-build and generate-constraints workflows via a new 
optional
    checkout-ref input (backward compatible for existing callers).
    
    Because the RC constraints are frozen when the RC is cut, start-release now 
asks
    whether to base the final constraints-<version> tag on the latest 
constraints-X-Y
    branch tip (when refreshed after the last RC) instead of the RC tag.
    
    The manual procedure and release-guide note are documented accordingly.
---
 .github/workflows/ci-image-build.yml               |  6 ++
 .github/workflows/generate-constraints.yml         |  8 ++-
 .github/workflows/update-constraints-on-push.yml   | 76 +++++++++++++++++-----
 ...UALLY_GENERATING_IMAGE_CACHE_AND_CONSTRAINTS.md | 49 ++++++++++++++
 dev/README_RELEASE_AIRFLOW.md                      | 10 +++
 .../src/airflow_breeze/commands/release_command.py | 21 +++++-
 dev/breeze/tests/test_release_command.py           | 52 +++++++++++++++
 7 files changed, 204 insertions(+), 18 deletions(-)

diff --git a/.github/workflows/ci-image-build.yml 
b/.github/workflows/ci-image-build.yml
index 7f1fa61fe60..980c9c7a223 100644
--- a/.github/workflows/ci-image-build.yml
+++ b/.github/workflows/ci-image-build.yml
@@ -29,6 +29,11 @@ on:  # yamllint disable-line rule:truthy
         required: false
         default: ""
         type: string
+      checkout-ref:
+        description: "Commit-ish to checkout sources from (empty = workflow 
ref)."
+        required: false
+        default: ""
+        type: string
       pull-request-target:
         description: "Whether we are running this from pull-request-target 
workflow (true/false)"
         required: false
@@ -120,6 +125,7 @@ jobs:
       - name: "Checkout target branch"
         uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0  # 
v7.0.0
         with:
+          ref: ${{ inputs.checkout-ref }}
           persist-credentials: false
       - name: "Free up disk space"
         shell: bash
diff --git a/.github/workflows/generate-constraints.yml 
b/.github/workflows/generate-constraints.yml
index 55604039f77..2a8d8f4f72d 100644
--- a/.github/workflows/generate-constraints.yml
+++ b/.github/workflows/generate-constraints.yml
@@ -52,6 +52,11 @@ on:  # yamllint disable-line rule:truthy
         description: "Whether to use uvloop (true/false)"
         required: true
         type: string
+      checkout-ref:
+        description: "Commit-ish to checkout sources from (empty = workflow 
ref)."
+        required: false
+        default: ""
+        type: string
 jobs:
   generate-constraints-matrix:
     permissions:
@@ -76,9 +81,10 @@ jobs:
       - name: "Cleanup repo"
         shell: bash
         run: sudo rm -rf ${GITHUB_WORKSPACE}/*
-      - name: "Checkout ${{ github.ref }} ( ${{ github.sha }} )"
+      - name: "Checkout ${{ inputs.checkout-ref || github.ref }} ( ${{ 
github.sha }} )"
         uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0  # 
v7.0.0
         with:
+          ref: ${{ inputs.checkout-ref }}
           persist-credentials: false
       - name: "Install prek"
         uses: ./.github/actions/install-prek
diff --git a/.github/workflows/update-constraints-on-push.yml 
b/.github/workflows/update-constraints-on-push.yml
index 16460bd89cb..b65168c53da 100644
--- a/.github/workflows/update-constraints-on-push.yml
+++ b/.github/workflows/update-constraints-on-push.yml
@@ -16,7 +16,20 @@
 # under the License.
 #
 ---
-name: Update constraints on push for main (only when uv.lock changes)
+name: Update constraints (on uv.lock push or manual dispatch)
+# This workflow refreshes the pinned constraint files stored in the
+# `constraints-*` branches. It runs automatically whenever `uv.lock` changes on
+# `main` or a `vX-Y-test` branch, and can also be triggered manually via the
+# "Run workflow" button (workflow_dispatch) - for example to pick up newly
+# released providers/dependencies from PyPI just before promoting an RC.
+#
+# The manual run is always launched from `main` (so the workflow definition and
+# `breeze` come from `main`), and the `ref` input selects the commit-ish
+# (branch, tag or commit hash) whose sources the constraints are refreshed 
from.
+# The `constraints-X-Y` branch to push to is derived from that ref's
+# `dev/breeze/src/airflow_breeze/branch_defaults.py`, so no cherry-pick to the
+# `vX-Y-test` / `vX-Y-stable` branch is needed.
+# See dev/MANUALLY_GENERATING_IMAGE_CACHE_AND_CONSTRAINTS.md for details.
 on:  # yamllint disable-line rule:truthy
   push:
     branches:
@@ -24,11 +37,27 @@ on:  # yamllint disable-line rule:truthy
       - v[0-9]+-[0-9]+-test
     paths:
       - 'uv.lock'
+  workflow_dispatch:
+    inputs:
+      ref:
+        description: >-
+          Commit-ish to refresh constraints from (branch, tag or commit hash),
+          e.g. `v3-3-test`, `v3-3-stable`, `constraints-3-3` or a tag/hash.
+          The matching `constraints-X-Y` branch is derived from that ref.
+        required: true
+        type: string
+      upgrade-to-newer-dependencies:
+        description: >-
+          Re-resolve to the newest matching dependencies (picks up newly
+          released providers/dependencies from PyPI). Leave enabled when
+          refreshing constraints before promoting an RC.
+        type: boolean
+        default: true
 permissions:
   contents: read
 
 concurrency:
-  group: ${{ github.workflow }}-${{ github.ref }}
+  group: ${{ github.workflow }}-${{ github.ref }}-${{ inputs.ref }}
   cancel-in-progress: true
 env:
   GITHUB_REPOSITORY: ${{ github.repository }}
@@ -39,6 +68,25 @@ jobs:
   build-info:
     name: "Build info"
     runs-on: ["ubuntu-22.04"]
+    # Automatic uv.lock-push runs are unrestricted; manual (workflow_dispatch)
+    # runs are limited to release managers (same allowlist as the prod image
+    # release workflow) because they push to the protected constraints-* 
branches.
+    if: >-
+      github.event_name != 'workflow_dispatch' ||
+      contains(fromJSON('[
+      "ashb",
+      "bugraoz93",
+      "eladkal",
+      "ephraimbuddy",
+      "jedcunningham",
+      "jscheffl",
+      "kaxil",
+      "pierrejeambrun",
+      "potiuk",
+      "utkarsharma2",
+      "vincbeck",
+      "vatsrahul1001",
+      ]'), github.event.sender.login)
     outputs:
       default-branch: ${{ steps.selective-checks.outputs.default-branch }}
       default-constraints-branch: ${{ 
steps.selective-checks.outputs.default-constraints-branch }}
@@ -49,14 +97,10 @@ jobs:
       - name: "Cleanup repo"
         shell: bash
         run: sudo rm -rf ${GITHUB_WORKSPACE}/*
-      - name: "Checkout ${{ github.ref }} ( ${{ github.sha }} )"
-        uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0  # 
v7.0.0
-        with:
-          persist-credentials: false
-      - name: Fetch incoming commit ${{ github.sha }} with its parent
+      - name: "Checkout ${{ inputs.ref || github.ref }} ( ${{ inputs.ref || 
github.sha }} )"
         uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0  # 
v7.0.0
         with:
-          ref: ${{ github.sha }}
+          ref: ${{ inputs.ref || github.sha }}
           fetch-depth: 2
           persist-credentials: false
       - name: "Install Breeze"
@@ -75,7 +119,7 @@ jobs:
         id: selective-checks
         env:
           PR_LABELS: "[]"
-          COMMIT_REF: "${{ github.sha }}"
+          COMMIT_REF: "${{ inputs.ref || github.sha }}"
           VERBOSE: "false"
           GITHUB_CONTEXT_INPUT: "${{ runner.temp }}/github_context.json"
         run: breeze ci selective-check 2>> ${GITHUB_OUTPUT}
@@ -96,8 +140,11 @@ jobs:
       python-versions: ${{ needs.build-info.outputs.python-versions }}
       branch: ${{ needs.build-info.outputs.default-branch }}
       constraints-branch: ${{ 
needs.build-info.outputs.default-constraints-branch }}
+      checkout-ref: ${{ inputs.ref }}
       use-uv: "true"
-      upgrade-to-newer-dependencies: "false"
+      upgrade-to-newer-dependencies: >-
+        ${{ github.event_name == 'workflow_dispatch'
+            && inputs.upgrade-to-newer-dependencies && 'true' || 'false' }}
       docker-cache: "registry"
       disable-airflow-repo-cache: "false"
 
@@ -113,6 +160,7 @@ jobs:
       generate-pypi-constraints: "true"
       generate-no-providers-constraints: "true"
       debug-resources: "false"
+      checkout-ref: ${{ inputs.ref }}
       use-uv: "true"
 
   update-constraints:
@@ -125,6 +173,7 @@ jobs:
       packages: read
     env:
       PYTHON_VERSIONS: ${{ 
needs.build-info.outputs.python-versions-list-as-string }}
+      CONSTRAINTS_BRANCH: ${{ 
needs.build-info.outputs.default-constraints-branch }}
     steps:
       - name: "Cleanup repo"
         shell: bash
@@ -133,14 +182,11 @@ jobs:
         uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0  # 
v7.0.0
         with:
           persist-credentials: false
-      - name: "Set constraints branch name"
-        id: constraints-branch
-        run: ./scripts/ci/constraints/ci_branch_constraints.sh >> 
${GITHUB_OUTPUT}
-      - name: Checkout ${{ steps.constraints-branch.outputs.branch }}
+      - name: Checkout ${{ env.CONSTRAINTS_BRANCH }}
         uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0  # 
v7.0.0
         with:
           path: "constraints"
-          ref: ${{ steps.constraints-branch.outputs.branch }}
+          ref: ${{ env.CONSTRAINTS_BRANCH }}
           persist-credentials: true
           fetch-depth: 0
       - name: "Download constraints from the generate-constraints job"
diff --git a/dev/MANUALLY_GENERATING_IMAGE_CACHE_AND_CONSTRAINTS.md 
b/dev/MANUALLY_GENERATING_IMAGE_CACHE_AND_CONSTRAINTS.md
index 68104fee02d..4a794333ab6 100644
--- a/dev/MANUALLY_GENERATING_IMAGE_CACHE_AND_CONSTRAINTS.md
+++ b/dev/MANUALLY_GENERATING_IMAGE_CACHE_AND_CONSTRAINTS.md
@@ -31,6 +31,7 @@
   - [What the command does](#what-the-command-does)
 - [Manually generating constraint files](#manually-generating-constraint-files)
   - [Why we need to generate constraint files 
manually](#why-we-need-to-generate-constraint-files-manually)
+  - [How to refresh constraints via the CI workflow 
(recommended)](#how-to-refresh-constraints-via-the-ci-workflow-recommended)
   - [How to generate constraint files](#how-to-generate-constraint-files)
   - [Is it safe to generate constraints 
manually?](#is-it-safe-to-generate-constraints-manually)
 - [Manually updating already tagged constraint 
files](#manually-updating-already-tagged-constraint-files)
@@ -213,6 +214,54 @@ with tests, but we KNOW that the tip of the branch is good 
and we want to releas
 we want to move the PRs of contributors to start using the new constraints. 
This should be done with caution
 and you need to be sure what you are doing, but you can always do it manually 
if you want.
 
+## How to refresh constraints via the CI workflow (recommended)
+
+The easiest way to refresh the constraints - for example to pick up newly 
released
+providers/dependencies from PyPI just before promoting an RC - is to trigger 
the
+[`Update constraints`](../.github/workflows/update-constraints-on-push.yml) 
workflow manually
+instead of running the `breeze` commands locally. The workflow runs exactly 
the same steps
+that run automatically when `uv.lock` changes, builds the CI images, generates 
all constraint
+flavours and commits/pushes them to the matching `constraints-*` branch.
+
+The manual run is always launched from `main` and takes a `ref` input that 
selects the
+commit-ish (branch, tag or commit hash) whose sources the constraints are 
refreshed from. You
+do **not** select the release branch in GitHub's branch dropdown, and you do 
**not** need to
+cherry-pick anything to the `vX-Y-test` / `vX-Y-stable` branch first - the 
workflow definition
+and `breeze` come from `main`, while the sources come from the `ref` you pass. 
The only
+requirement is that the `breeze` constraint-generation commands work against 
that `ref` (they
+are stable across recent branches).
+
+Manual runs are restricted to release managers (the same allowlist as the prod 
image release
+workflow); the automatic `uv.lock`-push runs are not restricted.
+
+To run it:
+
+1. Go to the [`Update 
constraints`](https://github.com/apache/airflow/actions/workflows/update-constraints-on-push.yml)
+   workflow in the Actions tab.
+2. Click **Run workflow** and keep the branch set to `main` (this is where the 
workflow runs
+   from - it is not the branch whose constraints get refreshed).
+3. In the **ref** field, enter the commit-ish to refresh constraints from - 
for example
+   `v3-3-test`, `v3-3-stable`, `constraints-3-3`, an RC tag, or a commit hash. 
The matching
+   `constraints-X-Y` branch to push to is derived automatically from that ref's
+   `dev/breeze/src/airflow_breeze/branch_defaults.py`, so pointing at anything 
on the 3.3 line
+   refreshes `constraints-3-3`.
+4. Keep **Re-resolve to the newest matching dependencies** enabled (the 
default) so the run
+   picks up the latest released providers/dependencies from PyPI. Disable it 
only if you want
+   to regenerate constraints strictly from that ref's `uv.lock` without 
upgrading.
+5. Once the run finishes, verify the new commit on the matching constraints 
branch
+   (for example `constraints-3-3` for a 3.3 refresh):
+
+   https://github.com/apache/airflow/commits/constraints-<major>-<minor>/
+
+> [!NOTE]
+> Because `v3-3-test` and `v3-3-stable` can diverge while RCs are being voted 
on (fixes are
+> cherry-picked to `v3-3-test`), be deliberate about which `ref` you refresh 
from. Refresh from
+> the ref that matches the artifacts you are about to promote - typically 
`v3-3-stable` (or the
+> RC tag) for a release, not `v3-3-test`.
+
+If you cannot or do not want to use the workflow, you can still generate the 
constraints
+locally with the `breeze` commands below.
+
 ## How to generate constraint files
 
 ```bash
diff --git a/dev/README_RELEASE_AIRFLOW.md b/dev/README_RELEASE_AIRFLOW.md
index f7d8dc7961d..7e6005e75bb 100644
--- a/dev/README_RELEASE_AIRFLOW.md
+++ b/dev/README_RELEASE_AIRFLOW.md
@@ -1372,6 +1372,16 @@ breeze release-management start-release \
 
 Note: The `--task-sdk-version` parameter is optional. If you are releasing 
Airflow without a corresponding Task SDK release, you can omit this parameter.
 
+Note: When it reaches the constraints step, `start-release` asks whether to 
base the final
+`constraints-${VERSION}` tag on the latest `constraints-X-Y` branch tip 
instead of the RC
+constraints tag. The RC constraints are frozen when the RC is cut, so if any 
providers were
+released (or constraints were otherwise refreshed - see
+[MANUALLY_GENERATING_IMAGE_CACHE_AND_CONSTRAINTS.md](MANUALLY_GENERATING_IMAGE_CACHE_AND_CONSTRAINTS.md))
+after the last RC and you want the released constraints to reflect that, 
answer **yes** to tag the
+`constraints-X-Y` branch tip. Otherwise (the default) the final tag matches 
the RC exactly. If you
+do refresh, run the `Update constraints` workflow from `main` with `ref` set 
to the ref you are
+releasing (typically `v3-*-stable`) **before** running `start-release`.
+
 
 4. Make sure to update Airflow version in ``v3-*-test`` branch after 
cherry-picking to X.Y.1 in
    ``airflow/__init__.py``
diff --git a/dev/breeze/src/airflow_breeze/commands/release_command.py 
b/dev/breeze/src/airflow_breeze/commands/release_command.py
index 5b183e765d9..1b93f9c28ac 100644
--- a/dev/breeze/src/airflow_breeze/commands/release_command.py
+++ b/dev/breeze/src/airflow_breeze/commands/release_command.py
@@ -343,10 +343,27 @@ def upload_to_pypi(version, task_sdk_version=None):
         )
 
 
+def get_constraints_branch_for_version(version: str) -> str:
+    major, minor = version.split(".")[:2]
+    return f"constraints-{major}-{minor}"
+
+
 def retag_constraints(release_candidate, version):
-    if confirm_action(f"Retag constraints for {release_candidate} as 
{version}?"):
+    # By default the final ``constraints-<version>`` tag is created from the 
RC constraints tag.
+    # If the constraints were refreshed after the last RC (e.g. to pick up 
newly released
+    # providers - see dev/MANUALLY_GENERATING_IMAGE_CACHE_AND_CONSTRAINTS.md) 
the tip of the
+    # ``constraints-X-Y`` branch is newer than the RC tag and should be tagged 
instead.
+    constraints_branch = get_constraints_branch_for_version(version)
+    source_ref = f"constraints-{release_candidate}"
+    if confirm_action(
+        f"Base the final constraints on the latest '{constraints_branch}' 
branch tip instead of the "
+        f"'{source_ref}' tag? Choose yes if you refreshed constraints after 
{release_candidate}."
+    ):
+        run_command(["git", "fetch", "origin", constraints_branch], check=True)
+        source_ref = f"origin/{constraints_branch}"
+    if confirm_action(f"Retag constraints from {source_ref} as {version}?"):
         run_command(
-            ["git", "checkout", f"constraints-{release_candidate}"],
+            ["git", "checkout", source_ref],
             check=True,
         )
         run_command(
diff --git a/dev/breeze/tests/test_release_command.py 
b/dev/breeze/tests/test_release_command.py
index 2745a8387eb..1e023e6bf5b 100644
--- a/dev/breeze/tests/test_release_command.py
+++ b/dev/breeze/tests/test_release_command.py
@@ -538,3 +538,55 @@ def 
test_remove_old_release_no_task_sdk_version(monkeypatch, release_cmd):
     assert "task-sdk" not in " ".join(console_messages).lower()
     assert run_command_calls == []
     assert chdir_calls == [svn_release_repo, "/original/dir"]
+
+
[email protected](
+    ("version", "expected_branch"),
+    [
+        ("3.3.0", "constraints-3-3"),
+        ("3.0.5", "constraints-3-0"),
+        ("3.12.10", "constraints-3-12"),
+    ],
+)
+def test_get_constraints_branch_for_version(release_cmd, version, 
expected_branch):
+    assert release_cmd.get_constraints_branch_for_version(version) == 
expected_branch
+
+
+def test_retag_constraints_from_rc_tag_by_default(monkeypatch, release_cmd):
+    run_command_calls: list[list[str]] = []
+
+    def fake_confirm_action(prompt: str, **_kwargs) -> bool:
+        # Decline basing on the branch tip; accept the retag and the push.
+        return not prompt.startswith("Base the final constraints on the 
latest")
+
+    monkeypatch.setattr(release_cmd, "confirm_action", fake_confirm_action)
+    monkeypatch.setattr(release_cmd, "run_command", lambda cmd, **_kwargs: 
run_command_calls.append(cmd))
+
+    release_cmd.retag_constraints("3.3.0rc2", "3.3.0")
+
+    assert ["git", "checkout", "constraints-3.3.0rc2"] in run_command_calls
+    assert ["git", "tag", "-s", "constraints-3.3.0", "-m", "Constraints for 
Apache Airflow 3.3.0"] in (
+        run_command_calls
+    )
+    assert ["git", "push", "origin", "tag", "constraints-3.3.0"] in 
run_command_calls
+    # The branch tip is not fetched when basing on the RC tag.
+    assert not any(cmd[:2] == ["git", "fetch"] for cmd in run_command_calls)
+
+
+def test_retag_constraints_from_branch_tip_when_confirmed(monkeypatch, 
release_cmd):
+    run_command_calls: list[list[str]] = []
+
+    def fake_confirm_action(prompt: str, **_kwargs) -> bool:
+        return True
+
+    monkeypatch.setattr(release_cmd, "confirm_action", fake_confirm_action)
+    monkeypatch.setattr(release_cmd, "run_command", lambda cmd, **_kwargs: 
run_command_calls.append(cmd))
+
+    release_cmd.retag_constraints("3.3.0rc2", "3.3.0")
+
+    assert ["git", "fetch", "origin", "constraints-3-3"] in run_command_calls
+    assert ["git", "checkout", "origin/constraints-3-3"] in run_command_calls
+    assert ["git", "checkout", "constraints-3.3.0rc2"] not in run_command_calls
+    assert ["git", "tag", "-s", "constraints-3.3.0", "-m", "Constraints for 
Apache Airflow 3.3.0"] in (
+        run_command_calls
+    )

Reply via email to