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

potiuk pushed a commit to branch mirror-hardened-images-more-often
in repository https://gitbox.apache.org/repos/asf/airflow.git


The following commit(s) were added to 
refs/heads/mirror-hardened-images-more-often by this push:
     new 1f37210cb59 Dispatch the mirror from a job of its own in the upgrade 
check
1f37210cb59 is described below

commit 1f37210cb59f5926b280da4e2ebb21bc4d446310
Author: Jarek Potiuk <[email protected]>
AuthorDate: Fri Oct 30 13:33:11 2026 +0100

    Dispatch the mirror from a job of its own in the upgrade check
    
    The upgrade job runs the third-party tools and hooks it upgrades, with a
    token that may push. Giving that token "actions: write" as well would let
    any of that code dispatch, re-run or cancel workflows. A separate job that
    only dispatches the mirror holds that permission now, and the upgrade job
    waits for it.
    
    Generated-by: Claude Opus 5
---
 .github/workflows/scheduled-upgrade-check-main.yml |  2 +-
 .../scheduled-upgrade-check-v3-4-test.yml          |  2 +-
 .github/workflows/upgrade-check.yml                | 37 +++++++++++++++++++---
 dev/breeze/doc/09_release_management_tasks.rst     |  7 ++--
 .../output_workflow-run_mirror-base-images.svg     | 32 +++++++++++++++----
 .../output_workflow-run_mirror-base-images.txt     |  2 +-
 .../airflow_breeze/commands/workflow_commands.py   | 11 +++++--
 .../commands/workflow_commands_config.py           |  9 +++++-
 dev/breeze/tests/test_workflow_commands.py         | 13 ++++++--
 9 files changed, 93 insertions(+), 22 deletions(-)

diff --git a/.github/workflows/scheduled-upgrade-check-main.yml 
b/.github/workflows/scheduled-upgrade-check-main.yml
index 198bb856f53..ee0ab72a15a 100644
--- a/.github/workflows/scheduled-upgrade-check-main.yml
+++ b/.github/workflows/scheduled-upgrade-check-main.yml
@@ -25,7 +25,7 @@ on:  # yamllint disable-line rule:truthy
 permissions:
   contents: write
   pull-requests: write
-  # Dispatches the "Mirror hardened base images" workflow before the upgrade 
pins its images.
+  # Only the job of upgrade-check.yml that dispatches the "Mirror hardened 
base images" workflow gets it.
   actions: write
 jobs:
   upgrade-main:
diff --git a/.github/workflows/scheduled-upgrade-check-v3-4-test.yml 
b/.github/workflows/scheduled-upgrade-check-v3-4-test.yml
index 753cde39c86..9d51e87cec4 100644
--- a/.github/workflows/scheduled-upgrade-check-v3-4-test.yml
+++ b/.github/workflows/scheduled-upgrade-check-v3-4-test.yml
@@ -25,7 +25,7 @@ on:  # yamllint disable-line rule:truthy
 permissions:
   contents: write
   pull-requests: write
-  # Dispatches the "Mirror hardened base images" workflow before the upgrade 
pins its images.
+  # Only the job of upgrade-check.yml that dispatches the "Mirror hardened 
base images" workflow gets it.
   actions: write
 jobs:
   upgrade-v3-4-test:
diff --git a/.github/workflows/upgrade-check.yml 
b/.github/workflows/upgrade-check.yml
index b6693bb620b..88591e5cc6a 100644
--- a/.github/workflows/upgrade-check.yml
+++ b/.github/workflows/upgrade-check.yml
@@ -30,20 +30,48 @@ on:  # yamllint disable-line rule:truthy
         description: "Slack bot token for notifications"
         required: true
 permissions:
-  contents: write
-  pull-requests: write
-  # Dispatches the "Mirror hardened base images" workflow before the upgrade 
pins its images.
-  actions: write
+  contents: read
 env:
   GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
   SLACK_BOT_TOKEN: ${{ secrets.SLACK_BOT_TOKEN }}
   TARGET_BRANCH: ${{ inputs.target-branch }}
 jobs:
+  # A job of its own, so the token that may dispatch workflows never reaches 
the upgrade job, which
+  # runs the third-party tools and hooks it upgrades.
+  mirror-base-images:
+    timeout-minutes: 30
+    name: >-
+      [${{ inputs.target-branch }}] Mirror hardened base images
+    runs-on: ["ubuntu-22.04"]
+    permissions:
+      actions: write
+      contents: read
+    steps:
+      - name: >-
+          [${{ inputs.target-branch }}] Checkout main
+        uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1  # 
v7.0.1
+        with:
+          ref: main
+          persist-credentials: false
+      - name: >-
+          [${{ inputs.target-branch }}] Install Breeze
+        uses: ./.github/actions/breeze
+      # The upgrade for main is the one the mirror would otherwise start a 
second time.
+      - name: >-
+          [${{ inputs.target-branch }}] Mirror the base images and wait for 
the run
+        env:
+          TRIGGER_UPGRADE_CHECK_FLAG: >-
+            ${{ inputs.target-branch == 'main' && '--no-trigger-upgrade-check' 
|| '--trigger-upgrade-check' }}
+        run: breeze workflow-run mirror-base-images 
"${TRIGGER_UPGRADE_CHECK_FLAG}"
   createupgrade-check:
     timeout-minutes: 45
     name: >-
       [${{ inputs.target-branch }}] Upgrade checks and PR
     runs-on: ["ubuntu-22.04"]
+    needs: [mirror-base-images]
+    permissions:
+      contents: write
+      pull-requests: write
     steps:
       - name: >-
           [${{ inputs.target-branch }}] Cleanup repo
@@ -87,6 +115,7 @@ jobs:
           --switch-to-base
           --no-k8s-schema-sync
           --latest-package-managers
+          --no-mirror-base-images
           --answer yes
       - name: >-
           [${{ inputs.target-branch }}] Find upgrade PR
diff --git a/dev/breeze/doc/09_release_management_tasks.rst 
b/dev/breeze/doc/09_release_management_tasks.rst
index 273ed9808b0..cd638c2cbcf 100644
--- a/dev/breeze/doc/09_release_management_tasks.rst
+++ b/dev/breeze/doc/09_release_management_tasks.rst
@@ -1168,9 +1168,10 @@ These are all available flags of ``workflow-run 
mirror-base-images`` command:
   :alt: Breeze workflow-run mirror-base-images
 
 ``breeze workflow-run upgrade-check`` runs the upgrade check for a branch, by 
default the branch breeze
-comes from. The check mirrors the base images first - ``breeze ci upgrade`` 
does that before it upgrades
-the important versions, locally as well as in CI, unless 
``--no-mirror-base-images`` is passed. When
-anything moved, it opens an upgrade PR against the branch.
+comes from. The check mirrors the base images first: in CI a job of its own 
does it, so the token that may
+dispatch workflows never reaches the job running the upgraded tools, and a 
local ``breeze ci upgrade`` does
+it before it upgrades the important versions, unless 
``--no-mirror-base-images`` is passed. When anything
+moved, it opens an upgrade PR against the branch.
 
 These are all available flags of ``workflow-run upgrade-check`` command:
 
diff --git a/dev/breeze/doc/images/output_workflow-run_mirror-base-images.svg 
b/dev/breeze/doc/images/output_workflow-run_mirror-base-images.svg
index c5804aa80ea..9c61d5dd5f0 100644
--- a/dev/breeze/doc/images/output_workflow-run_mirror-base-images.svg
+++ b/dev/breeze/doc/images/output_workflow-run_mirror-base-images.svg
@@ -1,4 +1,4 @@
-<svg class="rich-terminal" viewBox="0 0 1482 245.2" 
xmlns="http://www.w3.org/2000/svg";>
+<svg class="rich-terminal" viewBox="0 0 1482 367.2" 
xmlns="http://www.w3.org/2000/svg";>
     <!-- Generated with Rich https://www.textualize.io -->
     <style>
 
@@ -42,7 +42,7 @@
 
     <defs>
     <clipPath id="breeze-workflow-run-mirror-base-images-clip-terminal">
-      <rect x="0" y="0" width="1463.0" height="194.2" />
+      <rect x="0" y="0" width="1463.0" height="316.2" />
     </clipPath>
     <clipPath id="breeze-workflow-run-mirror-base-images-line-0">
     <rect x="0" y="1.5" width="1464" height="24.65"/>
@@ -65,9 +65,24 @@
 <clipPath id="breeze-workflow-run-mirror-base-images-line-6">
     <rect x="0" y="147.9" width="1464" height="24.65"/>
             </clipPath>
+<clipPath id="breeze-workflow-run-mirror-base-images-line-7">
+    <rect x="0" y="172.3" width="1464" height="24.65"/>
+            </clipPath>
+<clipPath id="breeze-workflow-run-mirror-base-images-line-8">
+    <rect x="0" y="196.7" width="1464" height="24.65"/>
+            </clipPath>
+<clipPath id="breeze-workflow-run-mirror-base-images-line-9">
+    <rect x="0" y="221.1" width="1464" height="24.65"/>
+            </clipPath>
+<clipPath id="breeze-workflow-run-mirror-base-images-line-10">
+    <rect x="0" y="245.5" width="1464" height="24.65"/>
+            </clipPath>
+<clipPath id="breeze-workflow-run-mirror-base-images-line-11">
+    <rect x="0" y="269.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="243.2" rx="8"/><text 
class="breeze-workflow-run-mirror-base-images-title" fill="#c5c8c6" 
text-anchor="middle" x="740" 
y="27">Command:&#160;workflow-run&#160;mirror-base-images</text>
+    <rect fill="#292929" stroke="rgba(255,255,255,0.35)" stroke-width="1" 
x="1" y="1" width="1480" height="365.2" rx="8"/><text 
class="breeze-workflow-run-mirror-base-images-title" fill="#c5c8c6" 
text-anchor="middle" x="740" 
y="27">Command:&#160;workflow-run&#160;mirror-base-images</text>
             <g transform="translate(26,22)">
             <circle cx="0" cy="0" r="7" fill="#ff5f57"/>
             <circle cx="22" cy="0" r="7" fill="#febc2e"/>
@@ -82,9 +97,14 @@
 </text><text class="breeze-workflow-run-mirror-base-images-r1" x="1464" 
y="68.8" textLength="12.2" 
clip-path="url(#breeze-workflow-run-mirror-base-images-line-2)">
 </text><text class="breeze-workflow-run-mirror-base-images-r1" x="12.2" 
y="93.2" textLength="878.4" 
clip-path="url(#breeze-workflow-run-mirror-base-images-line-3)">Mirror&#160;the&#160;latest&#160;hardened&#160;base&#160;images&#160;and&#160;wait&#160;until&#160;they&#160;are&#160;mirrored.</text><text
 class="breeze-workflow-run-mirror-base-images-r1" x="1464" y="93.2" 
textLength="12.2" 
clip-path="url(#breeze-workflow-run-mirror-base-images-line-3)">
 </text><text class="breeze-workflow-run-mirror-base-images-r1" x="1464" 
y="117.6" textLength="12.2" 
clip-path="url(#breeze-workflow-run-mirror-base-images-line-4)">
-</text><text class="breeze-workflow-run-mirror-base-images-r5" x="0" y="142" 
textLength="24.4" 
clip-path="url(#breeze-workflow-run-mirror-base-images-line-5)">╭─</text><text 
class="breeze-workflow-run-mirror-base-images-r5" x="24.4" y="142" 
textLength="195.2" 
clip-path="url(#breeze-workflow-run-mirror-base-images-line-5)">&#160;Common&#160;options&#160;</text><text
 class="breeze-workflow-run-mirror-base-images-r5" x="219.6" y="142" 
textLength="1220" clip-path="url(#breeze-workflow-run-mi [...]
-</text><text class="breeze-workflow-run-mirror-base-images-r5" x="0" y="166.4" 
textLength="12.2" 
clip-path="url(#breeze-workflow-run-mirror-base-images-line-6)">│</text><text 
class="breeze-workflow-run-mirror-base-images-r4" x="24.4" y="166.4" 
textLength="73.2" 
clip-path="url(#breeze-workflow-run-mirror-base-images-line-6)">--help</text><text
 class="breeze-workflow-run-mirror-base-images-r6" x="122" y="166.4" 
textLength="24.4" clip-path="url(#breeze-workflow-run-mirror-base-images-line-6 
[...]
-</text><text class="breeze-workflow-run-mirror-base-images-r5" x="0" y="190.8" 
textLength="1464" 
clip-path="url(#breeze-workflow-run-mirror-base-images-line-7)">╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯</text><text
 class="breeze-workflow-run-mirror-base-images-r1" x="1464" y="190.8" 
textLength="12.2" 
clip-path="url(#breeze-workflow-run-mirror-base-images-line-7)">
+</text><text class="breeze-workflow-run-mirror-base-images-r5" x="0" y="142" 
textLength="24.4" 
clip-path="url(#breeze-workflow-run-mirror-base-images-line-5)">╭─</text><text 
class="breeze-workflow-run-mirror-base-images-r5" x="24.4" y="142" 
textLength="170.8" 
clip-path="url(#breeze-workflow-run-mirror-base-images-line-5)">&#160;Mirror&#160;flags&#160;</text><text
 class="breeze-workflow-run-mirror-base-images-r5" x="195.2" y="142" 
textLength="1244.4" clip-path="url(#breeze-workflow-run-mi [...]
+</text><text class="breeze-workflow-run-mirror-base-images-r5" x="0" y="166.4" 
textLength="12.2" 
clip-path="url(#breeze-workflow-run-mirror-base-images-line-6)">│</text><text 
class="breeze-workflow-run-mirror-base-images-r4" x="24.4" y="166.4" 
textLength="280.6" 
clip-path="url(#breeze-workflow-run-mirror-base-images-line-6)">--trigger-upgrade-check</text><text
 class="breeze-workflow-run-mirror-base-images-r1" x="305" y="166.4" 
textLength="12.2" clip-path="url(#breeze-workflow-run-mirror- [...]
+</text><text class="breeze-workflow-run-mirror-base-images-r5" x="0" y="190.8" 
textLength="12.2" 
clip-path="url(#breeze-workflow-run-mirror-base-images-line-7)">│</text><text 
class="breeze-workflow-run-mirror-base-images-r1" x="658.8" y="190.8" 
textLength="780.8" 
clip-path="url(#breeze-workflow-run-mirror-base-images-line-7)">its&#160;pin.&#160;Turn&#160;it&#160;off&#160;when&#160;the&#160;main&#160;upgrade&#160;check&#160;is&#160;what&#160;runs&#160;&#160;&#160;</text><text
 class="breez [...]
+</text><text class="breeze-workflow-run-mirror-base-images-r5" x="0" y="215.2" 
textLength="12.2" 
clip-path="url(#breeze-workflow-run-mirror-base-images-line-8)">│</text><text 
class="breeze-workflow-run-mirror-base-images-r1" x="658.8" y="215.2" 
textLength="146.4" 
clip-path="url(#breeze-workflow-run-mirror-base-images-line-8)">the&#160;mirror.&#160;</text><text
 class="breeze-workflow-run-mirror-base-images-r5" x="805.2" y="215.2" 
textLength="390.4" clip-path="url(#breeze-workflow-run-mirr [...]
+</text><text class="breeze-workflow-run-mirror-base-images-r5" x="0" y="239.6" 
textLength="1464" 
clip-path="url(#breeze-workflow-run-mirror-base-images-line-9)">╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯</text><text
 class="breeze-workflow-run-mirror-base-images-r1" x="1464" y="239.6" 
textLength="12.2" 
clip-path="url(#breeze-workflow-run-mirror-base-images-line-9)">
+</text><text class="breeze-workflow-run-mirror-base-images-r5" x="0" y="264" 
textLength="24.4" 
clip-path="url(#breeze-workflow-run-mirror-base-images-line-10)">╭─</text><text 
class="breeze-workflow-run-mirror-base-images-r5" x="24.4" y="264" 
textLength="195.2" 
clip-path="url(#breeze-workflow-run-mirror-base-images-line-10)">&#160;Common&#160;options&#160;</text><text
 class="breeze-workflow-run-mirror-base-images-r5" x="219.6" y="264" 
textLength="1220" clip-path="url(#breeze-workflow-run- [...]
+</text><text class="breeze-workflow-run-mirror-base-images-r5" x="0" y="288.4" 
textLength="12.2" 
clip-path="url(#breeze-workflow-run-mirror-base-images-line-11)">│</text><text 
class="breeze-workflow-run-mirror-base-images-r4" x="24.4" y="288.4" 
textLength="73.2" 
clip-path="url(#breeze-workflow-run-mirror-base-images-line-11)">--help</text><text
 class="breeze-workflow-run-mirror-base-images-r6" x="122" y="288.4" 
textLength="24.4" clip-path="url(#breeze-workflow-run-mirror-base-images-line 
[...]
+</text><text class="breeze-workflow-run-mirror-base-images-r5" x="0" y="312.8" 
textLength="1464" 
clip-path="url(#breeze-workflow-run-mirror-base-images-line-12)">╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯</text><text
 class="breeze-workflow-run-mirror-base-images-r1" x="1464" y="312.8" 
textLength="12.2" 
clip-path="url(#breeze-workflow-run-mirror-base-images-line-12)">
 </text>
     </g>
     </g>
diff --git a/dev/breeze/doc/images/output_workflow-run_mirror-base-images.txt 
b/dev/breeze/doc/images/output_workflow-run_mirror-base-images.txt
index 826f1ebb0cf..35b73c90d6e 100644
--- a/dev/breeze/doc/images/output_workflow-run_mirror-base-images.txt
+++ b/dev/breeze/doc/images/output_workflow-run_mirror-base-images.txt
@@ -1 +1 @@
-4f364945d4e9ce6e6e7bf01b2e1dc0b1
+ff6777b06b1340e4e0d47c58e828aae6
diff --git a/dev/breeze/src/airflow_breeze/commands/workflow_commands.py 
b/dev/breeze/src/airflow_breeze/commands/workflow_commands.py
index 0495511670b..76b7f5d8632 100644
--- a/dev/breeze/src/airflow_breeze/commands/workflow_commands.py
+++ b/dev/breeze/src/airflow_breeze/commands/workflow_commands.py
@@ -499,8 +499,15 @@ def workflow_run_release_constraints(version: str, ref: 
str, workflow_branch: st
     name="mirror-base-images",
     help="Mirror the latest hardened base images and wait until they are 
mirrored.",
 )
-def workflow_run_mirror_base_images():
-    mirror_hardened_base_images(APACHE_AIRFLOW_REPO)
[email protected](
+    "--trigger-upgrade-check/--no-trigger-upgrade-check",
+    default=True,
+    show_default=True,
+    help="Let the mirror run the main upgrade check when a tag moves past its 
pin. Turn it off when the "
+    "main upgrade check is what runs the mirror.",
+)
+def workflow_run_mirror_base_images(trigger_upgrade_check: bool):
+    mirror_hardened_base_images(APACHE_AIRFLOW_REPO, 
trigger_upgrade_check=trigger_upgrade_check)
 
 
 @workflow_run_group.command(
diff --git a/dev/breeze/src/airflow_breeze/commands/workflow_commands_config.py 
b/dev/breeze/src/airflow_breeze/commands/workflow_commands_config.py
index 0475958adbd..5b4eb705780 100644
--- a/dev/breeze/src/airflow_breeze/commands/workflow_commands_config.py
+++ b/dev/breeze/src/airflow_breeze/commands/workflow_commands_config.py
@@ -88,4 +88,11 @@ WORKFLOW_RUN_PARAMETERS["breeze workflow-run upgrade-check"] 
= [
         ],
     },
 ]
-WORKFLOW_RUN_PARAMETERS["breeze workflow-run mirror-base-images"] = []
+WORKFLOW_RUN_PARAMETERS["breeze workflow-run mirror-base-images"] = [
+    {
+        "name": "Mirror flags",
+        "options": [
+            "--trigger-upgrade-check",
+        ],
+    },
+]
diff --git a/dev/breeze/tests/test_workflow_commands.py 
b/dev/breeze/tests/test_workflow_commands.py
index 467b7316321..088a36777c0 100644
--- a/dev/breeze/tests/test_workflow_commands.py
+++ b/dev/breeze/tests/test_workflow_commands.py
@@ -394,12 +394,19 @@ class TestSyncStagingToMain:
         assert mock_trigger.call_count == 2
 
 
[email protected](
+    ("args", "trigger_upgrade_check"),
+    [
+        pytest.param([], True, id="default"),
+        pytest.param(["--no-trigger-upgrade-check"], False, 
id="called-by-the-main-upgrade"),
+    ],
+)
 
@patch("airflow_breeze.commands.workflow_commands.mirror_hardened_base_images", 
autospec=True)
-def test_mirror_base_images_runs_the_mirror(mock_mirror):
-    result = CliRunner().invoke(workflow_run_mirror_base_images, [], 
catch_exceptions=False)
+def test_mirror_base_images_runs_the_mirror(mock_mirror, args, 
trigger_upgrade_check):
+    result = CliRunner().invoke(workflow_run_mirror_base_images, args, 
catch_exceptions=False)
 
     assert result.exit_code == 0
-    mock_mirror.assert_called_once_with("apache/airflow")
+    mock_mirror.assert_called_once_with("apache/airflow", 
trigger_upgrade_check=trigger_upgrade_check)
 
 
 @pytest.mark.parametrize(

Reply via email to