Pin the hardened base images to digests the CI has passed on (#76023)

* Refresh the hardened base image mirror every six hours

Docker rebuilds the hardened images in place and moves the dhi.io apt
repository on at the same time. With a weekly mirror, the base images
Airflow builds on lag the repository by up to a week, and any apt
install whose dependencies were rebuilt in between fails: build-essential
currently cannot be installed because the mirrored base has an older
libbz2 than the bzip2 the repository offers
(docker-hardened-images/catalog#689). Mirroring copies manifests between
registries without pulling layers, so running it several times a day is
cheap.

Generated-by: Claude Opus 5

* Pin the hardened base images to digests the CI has passed on

Building on the mirror's moving tags made CI non-reproducible: any
refresh of the mirror changed the image every build started from,
without a change in the repository and without the full CI having run
on it. A CI run could pass or fail depending only on when it ran, and
rebuilding the same commit later gave a different image.

The images now build on a digest, and only the upgrade check moves it,
in a PR that must pass the full CI before the new base is used. The
mirror refreshes every six hours and triggers that upgrade check when it
moves a tag past its pin, so the pins follow Docker's rebuilds - and the
dhi.io apt repository moving with them - within hours rather than drift
behind it. Every pinned and mirrored digest keeps a tag of its own, so a
pinned image never becomes untagged.

The hardened images keep stock Debian's apt sources beside dhi.io, and
apt took whichever offered the higher version, mixing Debian builds into
an image whose dependencies were built by Docker. dhi.io is now
preferred whenever it publishes the package.

Generated-by: Claude Opus 5

* Let dhi.io's package versions win over the image's own

Docker renumbered its builds ("1.0.8-6+dhi2" became
"1.0.8-6+deb13u0+dhi2", which sorts lower) and its apt repository moved
to the new numbers before its images did. Every package depending on a
renumbered build - bzip2, libffi-dev, libgpg-error-dev among them - is
then uninstallable, because apt will not replace the image's
higher-numbered copy with the repository's lower-numbered one, and the
CI image build fails (docker-hardened-images/catalog#689). Every package
in the image comes from dhi.io, so letting its repository decide their
versions is safe, and it keeps the build working through such a
renumbering.

Generated-by: Claude Opus 5

* Move the base images to Docker's current Python patch releases

Docker stops rebuilding a Python patch release once it publishes the
next, so the 3.11.17, 3.12.15 and 3.13.16 images still carry the old
package numbering that the dhi.io apt repository has moved past, and
installing packages on them fails (docker-hardened-images/catalog#689).
Docker fixed it in the images of the current patch releases.

Generated with the Python part of the upgrade check, which also pins
the new images to the digests the mirror serves for them.

Generated-by: Claude Opus 5

* Compare the registry host exactly in the mirror test

Generated-by: Claude Opus 5

* Lower the dhi.io apt pin below 1000 so dependencies can resolve

At priority 1001 the Package:* dhi.io preference forces dhi.io's build even as a downgrade, so apt keeps dhi.io's libpq5 17.11 and postgresql-client-18 (which needs libpq5 >= 18.6 from PGDG) cannot be installed. Below 1000 the preference still favours dhi.io for the packages it publishes but lets apt take a newer version from another repository to satisfy a dependency. The forced "downgrade" the >1000 priority handled no longer arises now the base image is pinned to a digest CI has passed on.

* Rank the PostgreSQL apt repository above the dhi.io preference

install_postgres adds the PGDG repository, but the hardened image's Package:* dhi.io preference outranks Debian's default, so apt kept dhi.io's libpq and postgresql-client-18 (which needs a newer libpq5 than dhi.io carries) failed with held broken packages. Pinning the PostgreSQL origin above the dhi.io preference lets apt take libpq from PGDG to satisfy the client.

* Use US spelling favors to pass the docs spellcheck

The docker-stack docs spellcheck rejects the British spelling favours in build.rst; use the US spelling there and in the matching install-script comments.

---------

Co-authored-by: Rahul Vats <43964496+vatsrahul1001@users.noreply.github.com>
This commit is contained in:
Jarek PotiukandRahul Vats authored and GitHub committed 2026-10-30 13:52:53 +00:00
1 parent acb2208f15
commit 48435351c4
59 files changed
+1920 -228

No files matched your search

+6
View File
@@ -22,6 +22,11 @@ inputs:
python-version:
description: 'Python version to use'
default: "3.11"
free-space:
description: >-
Whether to free disk space on the runner. It needs swap control and docker, which the ubuntu-slim
container runner does not have, so jobs on ubuntu-slim pass "false".
default: "true"
outputs:
host-python-version:
description: Python version used in host
@@ -55,6 +60,7 @@ runs:
env:
PYTHON_VERSION: "${{ inputs.python-version }}"
- name: "Free space"
if: inputs.free-space == 'true'
shell: bash
run: breeze ci free-space
env:
+5 -5
View File
@@ -263,8 +263,9 @@ jobs:
if: >-
needs.gate.outputs.project-ci == 'true' &&
needs.check-run-needed.outputs.run-needed == 'true'
# At build-info stage we do not yet have outputs so we need to hard-code the runs-on to public runners
runs-on: ["ubuntu-22.04"]
# At build-info stage we do not yet have outputs so the runs-on is hard-coded. ubuntu-slim is enough:
# it only installs breeze and runs selective checks, which need neither docker nor freeing disk space.
runs-on: ["ubuntu-slim"]
outputs:
all-python-versions-list-as-string: >-
${{ steps.selective-checks.outputs.all-python-versions-list-as-string }}
@@ -378,9 +379,6 @@ jobs:
upgrade-to-newer-dependencies: ${{ steps.selective-checks.outputs.upgrade-to-newer-dependencies }}
is-uv-lock-update: ${{ steps.selective-checks.outputs.is-uv-lock-update }}
steps:
- name: "Cleanup repo"
shell: bash
run: sudo rm -rf ${GITHUB_WORKSPACE}/*
- name: "Checkout ${{ github.ref }} ( ${{ github.sha }} )"
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
@@ -403,6 +401,8 @@ jobs:
|| echo "::warning::Could not fetch ${PUSH_BASE_COMMIT}, all tests will run"
- name: "Install Breeze"
uses: ./.github/actions/breeze
with:
free-space: "false"
id: breeze
- name: "Save github context to file"
# The github context can be very large for PRs that touch many files (e.g. Dependabot
+5 -5
View File
@@ -144,8 +144,9 @@ jobs:
name: "Build info"
needs: [check-run-needed]
if: needs.check-run-needed.outputs.run-needed == 'true'
# At build-info stage we do not yet have outputs so we need to hard-code the runs-on to public runners
runs-on: ["ubuntu-22.04"]
# At build-info stage we do not yet have outputs so the runs-on is hard-coded. ubuntu-slim is enough:
# it only installs breeze and runs selective checks, which need neither docker nor freeing disk space.
runs-on: ["ubuntu-slim"]
outputs:
all-python-versions-list-as-string: >-
${{ steps.selective-checks.outputs.all-python-versions-list-as-string }}
@@ -259,9 +260,6 @@ jobs:
upgrade-to-newer-dependencies: ${{ steps.selective-checks.outputs.upgrade-to-newer-dependencies }}
is-uv-lock-update: ${{ steps.selective-checks.outputs.is-uv-lock-update }}
steps:
- name: "Cleanup repo"
shell: bash
run: sudo rm -rf ${GITHUB_WORKSPACE}/*
- name: "Checkout ${{ github.ref }} ( ${{ github.sha }} )"
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
@@ -284,6 +282,8 @@ jobs:
|| echo "::warning::Could not fetch ${PUSH_BASE_COMMIT}, all tests will run"
- name: "Install Breeze"
uses: ./.github/actions/breeze
with:
free-space: "false"
id: breeze
- name: "Save github context to file"
# The github context can be very large for PRs that touch many files (e.g. Dependabot
@@ -27,31 +27,80 @@ name: Mirror hardened base images
#
# `docker buildx imagetools create` copies the multi-platform manifest between registries without
# pulling the layers into the runner, so a refresh of all Python versions is cheap.
#
# The builds do not use these moving tags: they are pinned to digests that only "breeze ci upgrade"
# moves, in a PR that has to pass the full CI. Docker rebuilds the images in place and moves the
# dhi.io apt repository on together with them, so a pin left behind soon stops matching what apt
# installs. A run that moves a tag past its pin therefore triggers that upgrade check, and the pins
# follow Docker's rebuilds within hours.
on: # yamllint disable-line rule:truthy
workflow_dispatch:
inputs:
trigger-upgrade-check:
description: >-
Run the main upgrade check when a tag moves past its pin. "breeze ci upgrade" for main turns
it off, as it is the upgrade that pins the images.
type: boolean
default: true
schedule:
# Docker rebuilds the hardened images as CVEs are fixed, keeping the same tags. Re-running
# weekly is what picks those rebuilds up.
- cron: '27 4 * * 1'
- cron: '27 */6 * * *'
permissions:
contents: read
concurrency:
group: mirror-hardened-base-images
cancel-in-progress: false
jobs:
# Picks the runner the way CI does: RunsOn in apache/airflow, public runners when the repository
# variable USE_PUBLIC_RUNNERS is "true".
build-info:
name: "Build info"
runs-on: ["ubuntu-slim"]
timeout-minutes: 10
if: github.repository == 'apache/airflow' && github.ref == 'refs/heads/main'
permissions:
contents: read
outputs:
runner-type: ${{ steps.selective-checks.outputs.runner-type }}
steps:
- name: "Checkout ${{ github.ref }} ( ${{ github.sha }} )"
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
fetch-depth: 2
persist-credentials: false
- name: "Install Breeze"
uses: ./.github/actions/breeze
with:
free-space: "false"
- name: "Save github context to file"
# Same as in ci-amd.yml: a scheduled or dispatched run has no attacker-controlled context, and
# the single-quoted heredoc disables every shell expansion of the JSON anyway.
shell: bash
run: | # zizmor: ignore[template-injection]
cat > "${RUNNER_TEMP}/github_context.json" << '__GITHUB_CONTEXT_END__'
${{ toJson(github) }}
__GITHUB_CONTEXT_END__
- name: "Selective checks"
id: selective-checks
env:
COMMIT_REF: "${{ github.sha }}"
VERBOSE: "false"
GITHUB_CONTEXT_INPUT: "${{ runner.temp }}/github_context.json"
PLATFORM: "linux/amd64"
USE_PUBLIC_RUNNERS: "${{ vars.USE_PUBLIC_RUNNERS }}"
run: breeze ci selective-check 2>> ${GITHUB_OUTPUT}
mirror:
name: "Mirror hardened base images"
runs-on: ["ubuntu-22.04"]
needs: [build-info]
# Copies manifests registry-to-registry without pulling layers, so the smallest class is enough.
runs-on: ${{ format(fromJSON(needs.build-info.outputs.runner-type)[0], '2+4', '8+16') }}
timeout-minutes: 30
# Never runs on forks - they have neither the credentials nor the package to push to. Only main
# and the v3-*-test release branches may push: the mirror is the default BASE_IMAGE of every
# build, so a manual dispatch from any other branch must not be able to replace it.
if: >-
github.repository == 'apache/airflow' && (
github.ref == 'refs/heads/main' || (
startsWith(github.ref, 'refs/heads/v3-') && endsWith(github.ref, '-test')
)
)
# pushes: the mirror is the default BASE_IMAGE of every build, so a manual dispatch from any other
# branch must not be able to replace it. Main mirrors every Python version an active release
# branch still builds on, so the release branches need no runs of their own.
if: github.repository == 'apache/airflow' && github.ref == 'refs/heads/main'
permissions:
actions: write
contents: read
packages: write
steps:
@@ -77,4 +126,15 @@ jobs:
ACTOR: ${{ github.actor }}
run: echo "${GITHUB_TOKEN}" | docker login ghcr.io -u "${ACTOR}" --password-stdin
- name: "Mirror base images"
id: mirror
run: breeze release-management mirror-base-images
# Only main's pins: release branches move theirs on their own upgrade schedule. Also after a
# failed mirror step: a tag it moved before failing stays moved, so a re-run would not see it
# move again.
- name: "Trigger the upgrade check to move the pinned base images"
if: >-
!cancelled() && steps.mirror.outputs.moved-past-pins == 'true' &&
(github.event_name == 'schedule' || inputs.trigger-upgrade-check)
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: gh workflow run scheduled-upgrade-check-main.yml --repo "${GITHUB_REPOSITORY}" --ref main
+1 -1
View File
@@ -75,7 +75,7 @@ jobs:
build-info:
timeout-minutes: 10
name: "Build Info"
runs-on: ["ubuntu-24.04"]
runs-on: ["ubuntu-slim"]
env:
VERBOSE: true
REF: ${{ inputs.ref }}
@@ -65,7 +65,7 @@ jobs:
build-info:
name: "Build info"
# No outputs yet at this stage, so the runner has to be hard-coded to a public one.
runs-on: ["ubuntu-22.04"]
runs-on: ["ubuntu-slim"]
outputs:
default-branch: ${{ steps.selective-checks.outputs.default-branch }}
default-constraints-branch: ${{ steps.selective-checks.outputs.default-constraints-branch }}
@@ -75,15 +75,14 @@ jobs:
python-versions: ${{ steps.selective-checks.outputs.python-versions }}
use-uv: ${{ steps.selective-checks.outputs.force-pip == 'true' && 'false' || 'true' }}
steps:
- name: "Cleanup repo"
shell: bash
run: sudo rm -rf ${GITHUB_WORKSPACE}/*
- name: "Checkout ${{ github.ref }} ( ${{ github.sha }} )"
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- name: "Install Breeze"
uses: ./.github/actions/breeze
with:
free-space: "false"
- name: Selective checks
id: selective-checks
env:
+3 -4
View File
@@ -52,7 +52,7 @@ jobs:
build-info:
timeout-minutes: 10
name: "Build info"
runs-on: ["ubuntu-22.04"]
runs-on: ["ubuntu-slim"]
if: contains(fromJSON('[
"ashb",
"eladkal",
@@ -74,9 +74,6 @@ jobs:
target-branch: ${{ steps.stage.outputs.target-branch }}
allow-pre-releases: ${{ steps.stage.outputs.allow-pre-releases }}
steps:
- name: "Cleanup repo"
shell: bash
run: sudo rm -rf ${GITHUB_WORKSPACE}/*
- name: "Checkout ${{ inputs.ref }}"
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
@@ -85,6 +82,8 @@ jobs:
persist-credentials: false
- name: "Install Breeze"
uses: ./.github/actions/breeze
with:
free-space: "false"
id: breeze
- name: "Save github context to file"
# See ci-amd.yml for the full rationale: avoids ARG_MAX on big PRs by writing the
@@ -44,7 +44,7 @@ jobs:
build-info:
timeout-minutes: 10
name: "Build Info"
runs-on: ["ubuntu-24.04"]
runs-on: ["ubuntu-slim"]
outputs:
pythonVersions: ${{ steps.determine-python-versions.outputs.python-versions }}
platformMatrix: ${{ steps.determine-matrix.outputs.platformMatrix }}
@@ -81,17 +81,14 @@ jobs:
echo "Airflow version: '${AIRFLOW_VERSION}'"
echo "AMD only: '${AMD_ONLY}'"
echo "Limit python versions: '${LIMIT_PYTHON_VERSIONS}'"
- name: "Cleanup repo"
shell: bash
run: >
sudo rm -rf ${GITHUB_WORKSPACE}/*
- name: "Checkout ${{ github.ref }} ( ${{ github.sha }} )"
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- name: "Install Breeze"
uses: ./.github/actions/breeze
with:
free-space: "false"
- name: "Save github context to file"
# See ci-amd.yml for the full rationale: avoids ARG_MAX on big PRs by writing the
# github context to a file, and the single-quoted heredoc makes the zizmor
@@ -36,7 +36,7 @@ concurrency:
jobs:
build-info:
name: "Build info"
runs-on: ["ubuntu-22.04"]
runs-on: ["ubuntu-slim"]
if: github.repository == 'apache/airflow' || github.event_name == 'workflow_dispatch'
timeout-minutes: 15
outputs:
@@ -72,9 +72,6 @@ jobs:
run-ui-e2e-tests: ${{ steps.selective-checks.outputs.run-ui-e2e-tests }}
use-uv: ${{ steps.selective-checks.outputs.force-pip == 'true' && 'false' || 'true' }}
steps:
- name: "Cleanup repo"
shell: bash
run: sudo rm -rf ${GITHUB_WORKSPACE}/*
- name: "Checkout ${{ github.ref }} ( ${{ github.sha }} )"
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
@@ -82,6 +79,8 @@ jobs:
persist-credentials: false
- name: "Install Breeze"
uses: ./.github/actions/breeze
with:
free-space: "false"
- name: "Save github context to file"
# Same as in ci-amd.yml: a scheduled or dispatched run has no attacker-controlled context, and
# the single-quoted heredoc disables every shell expansion of the JSON anyway.
@@ -25,6 +25,8 @@ on: # yamllint disable-line rule:truthy
permissions:
contents: write
pull-requests: write
# Only the job of upgrade-check.yml that dispatches the "Mirror hardened base images" workflow gets it.
actions: write
jobs:
upgrade-main:
name: "[main] Upgrade"
@@ -25,6 +25,8 @@ on: # yamllint disable-line rule:truthy
permissions:
contents: write
pull-requests: write
# Only the job of upgrade-check.yml that dispatches the "Mirror hardened base images" workflow gets it.
actions: write
jobs:
upgrade-v3-4-test:
name: "[v3-4-test] Upgrade"
@@ -35,7 +35,7 @@ env:
jobs:
build-info:
name: "Build info"
runs-on: ["ubuntu-22.04"]
runs-on: ["ubuntu-slim"]
outputs:
default-branch: ${{ steps.selective-checks.outputs.default-branch }}
default-constraints-branch: ${{ steps.selective-checks.outputs.default-constraints-branch }}
@@ -43,9 +43,6 @@ jobs:
python-versions: ${{ steps.selective-checks.outputs.python-versions }}
python-versions-list-as-string: ${{ steps.selective-checks.outputs.python-versions-list-as-string }}
steps:
- name: "Cleanup repo"
shell: bash
run: sudo rm -rf ${GITHUB_WORKSPACE}/*
- name: "Checkout ${{ github.ref }} ( ${{ github.sha }} )"
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
@@ -58,6 +55,8 @@ jobs:
persist-credentials: false
- name: "Install Breeze"
uses: ./.github/actions/breeze
with:
free-space: "false"
id: breeze
- name: "Save github context to file"
# See ci-amd.yml for the full rationale: avoids ARG_MAX on big PRs by writing the
@@ -73,7 +73,7 @@ env:
jobs:
build-info:
name: "Build info"
runs-on: ["ubuntu-22.04"]
runs-on: ["ubuntu-slim"]
# 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.
@@ -101,9 +101,6 @@ jobs:
python-versions-list-as-string: ${{ steps.selective-checks.outputs.python-versions-list-as-string }}
constraints-baseline-sha: ${{ steps.constraints-baseline.outputs.sha }}
steps:
- name: "Cleanup repo"
shell: bash
run: sudo rm -rf ${GITHUB_WORKSPACE}/*
- name: "Checkout ${{ inputs.ref || github.ref }} ( ${{ inputs.ref || github.sha }} )"
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
@@ -112,6 +109,8 @@ jobs:
persist-credentials: false
- name: "Install Breeze"
uses: ./.github/actions/breeze
with:
free-space: "false"
id: breeze
- name: "Save github context to file"
# See ci-amd.yml for the full rationale: avoids ARG_MAX on big PRs by writing the
+75 -3
View File
@@ -30,18 +30,89 @@ on: # yamllint disable-line rule:truthy
description: "Slack bot token for notifications"
required: true
permissions:
contents: write
pull-requests: write
contents: read
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
SLACK_BOT_TOKEN: ${{ secrets.SLACK_BOT_TOKEN }}
TARGET_BRANCH: ${{ inputs.target-branch }}
jobs:
# Picks the runner of the upgrade the way CI does: RunsOn in apache/airflow, public runners in forks
# or when the repository variable USE_PUBLIC_RUNNERS is "true".
build-info:
name: >-
[${{ inputs.target-branch }}] Build info
runs-on: ["ubuntu-slim"]
timeout-minutes: 10
permissions:
contents: read
outputs:
runner-type: ${{ steps.selective-checks.outputs.runner-type }}
steps:
- name: "Checkout ${{ github.ref }} ( ${{ github.sha }} )"
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
fetch-depth: 2
persist-credentials: false
- name: "Install Breeze"
uses: ./.github/actions/breeze
with:
free-space: "false"
- name: "Save github context to file"
# Same as in ci-amd.yml: a scheduled or dispatched run has no attacker-controlled context, and
# the single-quoted heredoc disables every shell expansion of the JSON anyway.
shell: bash
run: | # zizmor: ignore[template-injection]
cat > "${RUNNER_TEMP}/github_context.json" << '__GITHUB_CONTEXT_END__'
${{ toJson(github) }}
__GITHUB_CONTEXT_END__
- name: "Selective checks"
id: selective-checks
env:
COMMIT_REF: "${{ github.sha }}"
VERBOSE: "false"
GITHUB_CONTEXT_INPUT: "${{ runner.temp }}/github_context.json"
PLATFORM: "linux/amd64"
USE_PUBLIC_RUNNERS: "${{ vars.USE_PUBLIC_RUNNERS }}"
run: breeze ci selective-check 2>> ${GITHUB_OUTPUT}
# 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. It only dispatches the mirror and waits for it.
mirror-base-images:
timeout-minutes: 30
name: >-
[${{ inputs.target-branch }}] Mirror hardened base images
runs-on: ["ubuntu-slim"]
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
with:
free-space: "false"
# 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"]
# Builds the CI image for the uv.lock refresh and runs every hook, like the CI image builds.
runs-on: ${{ format(fromJSON(needs.build-info.outputs.runner-type)[0], '8+16', '32+64') }}
needs: [build-info, mirror-base-images]
permissions:
contents: write
pull-requests: write
steps:
- name: >-
[${{ inputs.target-branch }}] Cleanup repo
@@ -85,6 +156,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
+77 -6
View File
@@ -54,18 +54,25 @@ ARG AIRFLOW_VERSION="3.3.2"
# image are based on it, so Python is never compiled during the build.
#
# The default is Airflow's public mirror of the upstream "dhi.io/python" image, so building needs no
# registry credentials. The upstream image itself requires a "docker login dhi.io".
# registry credentials. The upstream image itself requires a "docker login dhi.io". It is pinned to a
# digest, because the mirror's tags move several times a day: "breeze ci upgrade" moves the pin once
# the full CI has passed on the new image.
#
# The FIPS-validated variants require a paid Docker subscription and cannot be mirrored or made the
# default, but you can build a FIPS-compliant image by pointing the build at one:
# docker build . --build-arg BASE_IMAGE="dhi.io/python:3.13.16-debian13-fips-dev"
# docker build . --build-arg BASE_IMAGE="dhi.io/python:3.13.18-debian13-fips-dev"
#
# AIRFLOW_IMAGE_FLAVOR="legacy" builds the image the way it was built before the hardened base images:
# BASE_IMAGE is then a plain Debian image (for example "debian:bookworm-slim") and Python
# AIRFLOW_PYTHON_VERSION is downloaded from python.org, verified and compiled from sources.
# AIRFLOW_PYTHON_VERSION is downloaded from python.org, verified and compiled from sources. A
# MAJOR.MINOR AIRFLOW_PYTHON_VERSION builds the latest release of that line.
#
# AIRFLOW_PYTHON_VERSION is empty by default: the hardened flavor takes Python from BASE_IMAGE, and the
# build fails if a version passed here does not match it. Breeze passes the Python patchlevel that
# "breeze ci upgrade" last moved, so a new patch release also rebuilds the legacy flavor.
ARG AIRFLOW_IMAGE_FLAVOR="hardened"
ARG BASE_IMAGE="ghcr.io/apache/airflow/base/python:3.13.16-debian13-dev"
ARG AIRFLOW_PYTHON_VERSION="3.13.16"
ARG BASE_IMAGE="ghcr.io/apache/airflow/base/python:3.13.18-debian13-dev@sha256:9ed60ac490ee5038920b1e69ea9adb256f9f95ca285b65800d2ddab73b6e3a1e"
ARG AIRFLOW_PYTHON_VERSION=""
# You can swap comments between those two args to test pip from the main version
# When you attempt to test if the version of `pip` from specified branch works for our builds
@@ -125,7 +132,7 @@ PNPM_VERSION=${PNPM_VERSION:-10.28.1}
RUSTUP_DEFAULT_TOOLCHAIN=${RUSTUP_DEFAULT_TOOLCHAIN:-stable}
RUSTUP_VERSION=${RUSTUP_VERSION:-1.29.0}
AIRFLOW_IMAGE_FLAVOR=${AIRFLOW_IMAGE_FLAVOR:-hardened}
AIRFLOW_PYTHON_VERSION=${AIRFLOW_PYTHON_VERSION:-3.13.16}
AIRFLOW_PYTHON_VERSION=${AIRFLOW_PYTHON_VERSION:-}
COSIGN_VERSION=${COSIGN_VERSION:-3.0.5}
if [[ "${AIRFLOW_IMAGE_FLAVOR}" == "hardened" ]]; then
# The Debian 12 hardened images ship Python under /opt/python, the Debian 13 ones install it as
@@ -133,6 +140,13 @@ if [[ "${AIRFLOW_IMAGE_FLAVOR}" == "hardened" ]]; then
PYTHON_HOME=${PYTHON_HOME:-$(python3 -c 'import sys; print(sys.base_prefix)')}
# Read before apt runs: a package depending on python3 can pull another Python version in.
BASE_PYTHON_MAJOR_MINOR=$(python3 -c 'import sys; print(f"{sys.version_info[0]}.{sys.version_info[1]}")')
if [[ -n "${AIRFLOW_PYTHON_VERSION}" && "${AIRFLOW_PYTHON_VERSION}." != "${BASE_PYTHON_MAJOR_MINOR}."* ]]; then
echo
echo "ERROR! AIRFLOW_PYTHON_VERSION is ${AIRFLOW_PYTHON_VERSION}, but the base image has Python ${BASE_PYTHON_MAJOR_MINOR}."
echo "The hardened flavor takes Python from BASE_IMAGE: leave AIRFLOW_PYTHON_VERSION empty or pick a BASE_IMAGE with that Python."
echo
exit 1
fi
elif [[ "${AIRFLOW_IMAGE_FLAVOR}" == "legacy" ]]; then
PYTHON_HOME=${PYTHON_HOME:-/usr/python}
else
@@ -307,6 +321,24 @@ function keep_image_conffiles() {
printf '%s\n' force-confdef force-confold > /etc/dpkg/dpkg.cfg.d/airflow-keep-conffiles
}
function prefer_hardened_apt_repository() {
# The hardened base images take their packages from dhi.io, but stock Debian's sources stay
# configured beside it and apt installs whichever repository offers the higher version. Debian's
# build of a package then replaces Docker's, and its dependencies no longer match the packages the
# image ships (docker-hardened-images/catalog#610). Ranking dhi.io above the default priority of
# 500 makes apt take Docker's build whenever dhi.io publishes the package, and fall back to Debian
# only when it does not.
# The priority stays below 1000 so the preference never forces a downgrade: apt still favors
# dhi.io's build of a package it publishes, but may take a newer version from another repository
# when a dependency requires it - for example PGDG's libpq5, which postgresql-client-18 needs at
# ">= 18.6" and dhi.io does not carry. Pinning the base image to a digest CI has passed on keeps
# dhi.io and the image consistent, so the renumbering that once needed a forced "downgrade" of the
# image's copy (docker-hardened-images/catalog#689) no longer arises within a build.
# The preference covers every apt run - this build's and those of images extending it.
mkdir -p /etc/apt/preferences.d
printf 'Package: *\nPin: origin "dhi.io"\nPin-Priority: 999\n' > /etc/apt/preferences.d/airflow-prefer-dhi
}
function refresh_ldconfig_after_dpkg() {
# The hardened base images ship libc-bin without its dpkg trigger, so installing a shared library
# does not refresh the ldconfig cache and ctypes.util.find_library() - which reads that cache -
@@ -366,6 +398,7 @@ function align_perl_base_with_perl() {
function install_debian_dev_dependencies() {
if [[ "${AIRFLOW_IMAGE_FLAVOR}" == "hardened" ]]; then
keep_image_conffiles
prefer_hardened_apt_repository
refresh_ldconfig_after_dpkg
fi
apt-get update
@@ -548,8 +581,39 @@ function install_cosign() {
chmod +x /tmp/cosign
}
function resolve_python_version() {
if [[ -z "${AIRFLOW_PYTHON_VERSION}" ]]; then
echo
echo "ERROR! The legacy flavor compiles Python from sources, so AIRFLOW_PYTHON_VERSION has to name it:"
echo "MAJOR.MINOR for the latest release of that line, or MAJOR.MINOR.PATCH."
echo
exit 1
fi
if [[ ! "${AIRFLOW_PYTHON_VERSION}" =~ ^[0-9]+\.[0-9]+$ ]]; then
return
fi
# A release directory appears on python.org with its first pre-release, so take the newest one
# that already holds the final tarball.
local candidate
for candidate in $(wget --tries=3 --waitretry=5 -qO- https://www.python.org/ftp/python/ \
| grep -oE "href=\"${AIRFLOW_PYTHON_VERSION//./\\.}\.[0-9]+/\"" \
| grep -oE '[0-9]+\.[0-9]+\.[0-9]+' | sort -V -r); do
if wget --tries=3 --waitretry=5 -q --spider \
"https://www.python.org/ftp/python/${candidate}/Python-${candidate}.tar.xz"; then
echo "Python ${AIRFLOW_PYTHON_VERSION} resolved to its latest release ${candidate}"
AIRFLOW_PYTHON_VERSION="${candidate}"
return
fi
done
echo
echo "ERROR! No release of Python ${AIRFLOW_PYTHON_VERSION} found on python.org."
echo
exit 1
}
function install_python() {
# Only the legacy image flavor compiles Python - the hardened flavor gets it from the base image.
resolve_python_version
wget --tries=3 --waitretry=5 -O python.tar.xz "https://www.python.org/ftp/python/${AIRFLOW_PYTHON_VERSION%%[a-z]*}/Python-${AIRFLOW_PYTHON_VERSION}.tar.xz"
local major_minor_version
major_minor_version="${AIRFLOW_PYTHON_VERSION%.*}"
@@ -630,6 +694,7 @@ function install_python() {
function install_debian_runtime_dependencies() {
if [[ "${AIRFLOW_IMAGE_FLAVOR}" == "hardened" ]]; then
keep_image_conffiles
prefer_hardened_apt_repository
refresh_ldconfig_after_dpkg
fi
apt-get update
@@ -971,6 +1036,12 @@ install_postgres_client() {
echo "deb [arch=amd64,arm64] https://apt.postgresql.org/pub/repos/apt/ $(common::debian_codename)-pgdg main" > \
/etc/apt/sources.list.d/pgdg.list
# The hardened image's airflow-prefer-dhi pin outranks Debian's default, so apt would otherwise keep
# dhi.io's libpq (e.g. 17.11) and postgresql-client (which needs a matching, newer libpq5) could not
# install. PostgreSQL's own repository is the authority for its packages - rank it above the dhi.io
# preference so apt takes libpq from PGDG.
mkdir -p /etc/apt/preferences.d
printf 'Package: *\nPin: origin "apt.postgresql.org"\nPin-Priority: 1001\n' > /etc/apt/preferences.d/airflow-prefer-pgdg
apt-get update
apt-get install --no-install-recommends -y "${packages[@]}"
apt-get autoremove -yqq --purge
+72 -5
View File
@@ -22,12 +22,14 @@
# image are based on it, so Python is never compiled during the build.
#
# The default is Airflow's public mirror of the upstream "dhi.io/python" image, so building needs no
# registry credentials. The upstream image itself requires a "docker login dhi.io".
# registry credentials. The upstream image itself requires a "docker login dhi.io". It is pinned to a
# digest, because the mirror's tags move several times a day: "breeze ci upgrade" moves the pin once
# the full CI has passed on the new image.
#
# The FIPS-validated variants require a paid Docker subscription and cannot be mirrored or made the
# default, but you can build a FIPS-compliant image by pointing the build at one:
# docker build . --build-arg BASE_IMAGE="dhi.io/python:3.13.16-debian13-fips-dev"
ARG BASE_IMAGE="ghcr.io/apache/airflow/base/python:3.13.16-debian13-dev"
# docker build . --build-arg BASE_IMAGE="dhi.io/python:3.13.18-debian13-fips-dev"
ARG BASE_IMAGE="ghcr.io/apache/airflow/base/python:3.13.18-debian13-dev@sha256:9ed60ac490ee5038920b1e69ea9adb256f9f95ca285b65800d2ddab73b6e3a1e"
##############################################################################################
# This is the script image where we keep all inlined bash scripts needed in other segments
@@ -61,7 +63,7 @@ PNPM_VERSION=${PNPM_VERSION:-10.28.1}
RUSTUP_DEFAULT_TOOLCHAIN=${RUSTUP_DEFAULT_TOOLCHAIN:-stable}
RUSTUP_VERSION=${RUSTUP_VERSION:-1.29.0}
AIRFLOW_IMAGE_FLAVOR=${AIRFLOW_IMAGE_FLAVOR:-hardened}
AIRFLOW_PYTHON_VERSION=${AIRFLOW_PYTHON_VERSION:-3.13.16}
AIRFLOW_PYTHON_VERSION=${AIRFLOW_PYTHON_VERSION:-}
COSIGN_VERSION=${COSIGN_VERSION:-3.0.5}
if [[ "${AIRFLOW_IMAGE_FLAVOR}" == "hardened" ]]; then
# The Debian 12 hardened images ship Python under /opt/python, the Debian 13 ones install it as
@@ -69,6 +71,13 @@ if [[ "${AIRFLOW_IMAGE_FLAVOR}" == "hardened" ]]; then
PYTHON_HOME=${PYTHON_HOME:-$(python3 -c 'import sys; print(sys.base_prefix)')}
# Read before apt runs: a package depending on python3 can pull another Python version in.
BASE_PYTHON_MAJOR_MINOR=$(python3 -c 'import sys; print(f"{sys.version_info[0]}.{sys.version_info[1]}")')
if [[ -n "${AIRFLOW_PYTHON_VERSION}" && "${AIRFLOW_PYTHON_VERSION}." != "${BASE_PYTHON_MAJOR_MINOR}."* ]]; then
echo
echo "ERROR! AIRFLOW_PYTHON_VERSION is ${AIRFLOW_PYTHON_VERSION}, but the base image has Python ${BASE_PYTHON_MAJOR_MINOR}."
echo "The hardened flavor takes Python from BASE_IMAGE: leave AIRFLOW_PYTHON_VERSION empty or pick a BASE_IMAGE with that Python."
echo
exit 1
fi
elif [[ "${AIRFLOW_IMAGE_FLAVOR}" == "legacy" ]]; then
PYTHON_HOME=${PYTHON_HOME:-/usr/python}
else
@@ -243,6 +252,24 @@ function keep_image_conffiles() {
printf '%s\n' force-confdef force-confold > /etc/dpkg/dpkg.cfg.d/airflow-keep-conffiles
}
function prefer_hardened_apt_repository() {
# The hardened base images take their packages from dhi.io, but stock Debian's sources stay
# configured beside it and apt installs whichever repository offers the higher version. Debian's
# build of a package then replaces Docker's, and its dependencies no longer match the packages the
# image ships (docker-hardened-images/catalog#610). Ranking dhi.io above the default priority of
# 500 makes apt take Docker's build whenever dhi.io publishes the package, and fall back to Debian
# only when it does not.
# The priority stays below 1000 so the preference never forces a downgrade: apt still favors
# dhi.io's build of a package it publishes, but may take a newer version from another repository
# when a dependency requires it - for example PGDG's libpq5, which postgresql-client-18 needs at
# ">= 18.6" and dhi.io does not carry. Pinning the base image to a digest CI has passed on keeps
# dhi.io and the image consistent, so the renumbering that once needed a forced "downgrade" of the
# image's copy (docker-hardened-images/catalog#689) no longer arises within a build.
# The preference covers every apt run - this build's and those of images extending it.
mkdir -p /etc/apt/preferences.d
printf 'Package: *\nPin: origin "dhi.io"\nPin-Priority: 999\n' > /etc/apt/preferences.d/airflow-prefer-dhi
}
function refresh_ldconfig_after_dpkg() {
# The hardened base images ship libc-bin without its dpkg trigger, so installing a shared library
# does not refresh the ldconfig cache and ctypes.util.find_library() - which reads that cache -
@@ -302,6 +329,7 @@ function align_perl_base_with_perl() {
function install_debian_dev_dependencies() {
if [[ "${AIRFLOW_IMAGE_FLAVOR}" == "hardened" ]]; then
keep_image_conffiles
prefer_hardened_apt_repository
refresh_ldconfig_after_dpkg
fi
apt-get update
@@ -484,8 +512,39 @@ function install_cosign() {
chmod +x /tmp/cosign
}
function resolve_python_version() {
if [[ -z "${AIRFLOW_PYTHON_VERSION}" ]]; then
echo
echo "ERROR! The legacy flavor compiles Python from sources, so AIRFLOW_PYTHON_VERSION has to name it:"
echo "MAJOR.MINOR for the latest release of that line, or MAJOR.MINOR.PATCH."
echo
exit 1
fi
if [[ ! "${AIRFLOW_PYTHON_VERSION}" =~ ^[0-9]+\.[0-9]+$ ]]; then
return
fi
# A release directory appears on python.org with its first pre-release, so take the newest one
# that already holds the final tarball.
local candidate
for candidate in $(wget --tries=3 --waitretry=5 -qO- https://www.python.org/ftp/python/ \
| grep -oE "href=\"${AIRFLOW_PYTHON_VERSION//./\\.}\.[0-9]+/\"" \
| grep -oE '[0-9]+\.[0-9]+\.[0-9]+' | sort -V -r); do
if wget --tries=3 --waitretry=5 -q --spider \
"https://www.python.org/ftp/python/${candidate}/Python-${candidate}.tar.xz"; then
echo "Python ${AIRFLOW_PYTHON_VERSION} resolved to its latest release ${candidate}"
AIRFLOW_PYTHON_VERSION="${candidate}"
return
fi
done
echo
echo "ERROR! No release of Python ${AIRFLOW_PYTHON_VERSION} found on python.org."
echo
exit 1
}
function install_python() {
# Only the legacy image flavor compiles Python - the hardened flavor gets it from the base image.
resolve_python_version
wget --tries=3 --waitretry=5 -O python.tar.xz "https://www.python.org/ftp/python/${AIRFLOW_PYTHON_VERSION%%[a-z]*}/Python-${AIRFLOW_PYTHON_VERSION}.tar.xz"
local major_minor_version
major_minor_version="${AIRFLOW_PYTHON_VERSION%.*}"
@@ -566,6 +625,7 @@ function install_python() {
function install_debian_runtime_dependencies() {
if [[ "${AIRFLOW_IMAGE_FLAVOR}" == "hardened" ]]; then
keep_image_conffiles
prefer_hardened_apt_repository
refresh_ldconfig_after_dpkg
fi
apt-get update
@@ -907,6 +967,12 @@ install_postgres_client() {
echo "deb [arch=amd64,arm64] https://apt.postgresql.org/pub/repos/apt/ $(common::debian_codename)-pgdg main" > \
/etc/apt/sources.list.d/pgdg.list
# The hardened image's airflow-prefer-dhi pin outranks Debian's default, so apt would otherwise keep
# dhi.io's libpq (e.g. 17.11) and postgresql-client (which needs a matching, newer libpq5) could not
# install. PostgreSQL's own repository is the authority for its packages - rank it above the dhi.io
# preference so apt takes libpq from PGDG.
mkdir -p /etc/apt/preferences.d
printf 'Package: *\nPin: origin "apt.postgresql.org"\nPin-Priority: 1001\n' > /etc/apt/preferences.d/airflow-prefer-pgdg
apt-get update
apt-get install --no-install-recommends -y "${packages[@]}"
apt-get autoremove -yqq --purge
@@ -1979,7 +2045,8 @@ ENV DEV_APT_COMMAND=${DEV_APT_COMMAND} \
ADDITIONAL_DEV_APT_COMMAND=${ADDITIONAL_DEV_APT_COMMAND}
ARG AIRFLOW_PYTHON_VERSION="3.13.16"
# Empty takes the Python of BASE_IMAGE, breeze passes the patchlevel of the image it builds on.
ARG AIRFLOW_PYTHON_VERSION=""
ENV AIRFLOW_PYTHON_VERSION=${AIRFLOW_PYTHON_VERSION}
ENV GOLANG_MAJOR_MINOR_VERSION="1.27.2"
ENV TEMURIN_VERSION="11"
+49 -2
View File
@@ -129,6 +129,42 @@ introduced have it already. An older release branch only behaves this way once t
`ci-amd.yml` / `ci-arm.yml` that introduced it has been backported to it; until then it keeps
running the full CI on every push.
### Before cutting a beta or an RC: refresh the base images and the important CI versions
The images are built on hardened Python base images pinned to digests, and the legacy images compile
a pinned Python patch release. Only the "Upgrade important CI environment" upgrade moves those pins,
so a release ships with the latest base images, Python patch releases and their security fixes only
when the upgrade ran right before it. It is one command, which first mirrors the latest hardened base
images (a workflow run on `main`, as the mirror is shared by all branches) and waits for it. It needs
`gh` credentials that may dispatch `apache/airflow` workflows, and checks that first.
- **Beta** - before you fast-forward `vX-Y-test` to `main` for the beta (see
[Beta releases](#beta-releases-fast-forwarding-vx-y-test-to-main)), as during the beta phase
`vX-Y-test` takes its changes only from `main`:
```shell script
breeze workflow-run upgrade-check --branch main
```
It runs the upgrade check workflow for `main` and waits for it. When anything moved, the workflow
opens (or updates) the `[main] Upgrade important CI environment` PR as a draft. Mark it ready for
review, which starts its CI, and merge it once the CI has passed.
- **RC** - before you open the PR syncing `vX-Y-test` into `vX-Y-stable`, and before the full-run
check in the next section:
```shell script
git switch vX-Y-test
git pull --ff-only upstream vX-Y-test
breeze workflow-run upgrade-check --branch vX-Y-test
```
It runs the upgrade in your checkout and pushes the `[vX-Y-test] CI: Upgrade important CI
environment` commit straight to `vX-Y-test`, the head of the test-to-stable PR, so the PR's CI tests
it. This runs locally because the release branch rules let only committers push to `vX-Y-test`
without CI having passed. It never switches, resets or cleans your checkout: it fails and tells you
what to do when you are not on `vX-Y-test` at the tip of `apache/airflow`, or have local changes.
### Before cutting an RC: check the full run of the branch HEAD
The incremental push runs do not replace the full run. Before you cut an RC (or open the PR syncing
@@ -171,9 +207,12 @@ released from `vX-Y-test` only: there is no `vX-Y-stable` branch yet and therefo
PR exists only to carry changes from `vX-Y-test` into `vX-Y-stable` for an `rc`/final), and there is
no vote.
1. Push the version-bump and release-notes commits to `vX-Y-test` through normal PRs, so the tip of
1. Run the upgrade check for `main` and merge its PR, as described in
[Before cutting a beta or an RC](#before-cutting-a-beta-or-an-rc-refresh-the-base-images-and-the-important-ci-versions),
then fast-forward `vX-Y-test` to `main`.
2. Push the version-bump and release-notes commits to `vX-Y-test` through normal PRs, so the tip of
`vX-Y-test` is exactly what the beta should ship.
2. Run:
3. Run:
```shell script
breeze release-management start-beta-process \
@@ -468,6 +507,9 @@ Before cutting an RC, we should look at the milestone and merge anything ready,
The Release Candidate artifacts we vote upon should be the exact ones we vote against, without any modification other than renaming – i.e. the contents of the files must be the same between voted release candidate and final release. Because of this the version in the built artifacts that will become the official Apache releases must not include the rcN suffix.
**Before you build the artifacts**, run the upgrade check, as described in
[Before cutting a beta or an RC](#before-cutting-a-beta-or-an-rc-refresh-the-base-images-and-the-important-ci-versions).
> [!IMPORTANT]
> When you start the release candidates of a major/minor release that went through beta releases
> (`X.Y.0rc1`), `vX-Y-test` stops being fast-forwarded to `main` and changes reach it only by
@@ -635,6 +677,11 @@ still works but is no longer recommended.
- PR from the 'test' branch to the 'stable' branch
Before you open it, run `breeze workflow-run upgrade-check --branch vX-Y-test`, which pushes the
upgrade onto `vX-Y-test`, as described in
[Before cutting a beta or an RC](#before-cutting-a-beta-or-an-rc-refresh-the-base-images-and-the-important-ci-versions),
so the release is built on the latest base images.
Cherry-picked commits often include provider dependency bumps (changes to `>=` constraints on
`apache-airflow-providers-*` packages in `pyproject.toml`). CI blocks such changes by default —
only Release Managers should perform them. To allow the PR to pass, add the
@@ -1159,6 +1159,42 @@ These are all available flags of ``workflow-run release-constraints`` command:
:width: 100%
:alt: Breeze workflow-run release-constraints
Refreshing the base images before a release
"""""""""""""""""""""""""""""""""""""""""""
The images are built on hardened Python base images pinned to digests, and the legacy images on pinned
Python patch releases. Only the "Upgrade important CI environment" upgrade moves those pins, to images the
mirror already has, so the release manager runs it before every beta (for ``main``) and before every
test-to-stable PR (for the release branch), with one command.
``breeze workflow-run upgrade-check`` mirrors the latest hardened base images first and waits for it, then
upgrades the branch - by default the branch breeze comes from. For ``main`` it runs the upgrade check
workflow, which opens an upgrade PR, and waits for it. For a ``vX-Y-test`` branch it runs
``breeze ci upgrade --push-to-target-branch`` in your checkout, which pushes the upgrade straight to the
branch, the head of the test-to-stable PR. That runs locally, as the release branch rules let only
committers push without CI having passed. It fails, rather than switching, resetting or cleaning anything,
when the checkout is not at the tip of the branch or has local changes. Both need ``gh`` credentials that
may dispatch ``apache/airflow`` workflows, and check that first.
These are all available flags of ``workflow-run upgrade-check`` command:
.. image:: ./images/output_workflow-run_upgrade-check.svg
:target: https://raw.githubusercontent.com/apache/airflow/main/dev/breeze/doc/images/output_workflow-run_upgrade-check.svg
:width: 100%
:alt: Breeze workflow-run upgrade-check
``breeze workflow-run mirror-base-images`` runs only the mirror on ``main`` and waits for it. The mirror is
shared by all branches, and ``main`` mirrors every Python version an active release branch still builds
on. The upgrade check workflow runs it in a job of its own, so the token that may dispatch workflows never
reaches the job running the upgraded tools.
These are all available flags of ``workflow-run mirror-base-images`` command:
.. image:: ./images/output_workflow-run_mirror-base-images.svg
:target: https://raw.githubusercontent.com/apache/airflow/main/dev/breeze/doc/images/output_workflow-run_mirror-base-images.svg
:width: 100%
:alt: Breeze workflow-run mirror-base-images
Publishing the schema files to S3
"""""""""""""""""""""""""""""""""
+1 -1
View File
@@ -429,7 +429,7 @@ can be used for CI images:
| Build argument | Default value | Description |
|-----------------------------------|-----------------------------|-------------------------------------------------------------------------------------------------------------------|
| `BASE_IMAGE` | `ghcr.io/apache/airflow/base/python:<AIRFLOW_PYTHON_VERSION>-debian13-dev` | Mirror of the hardened base image Python comes from |
| `BASE_IMAGE` | `ghcr.io/apache/airflow/base/python:<X.Y.Z>-debian13-dev@<digest>` | Mirror of the hardened base image Python comes from, pinned to a digest by `breeze ci upgrade` |
| `PYTHON_MAJOR_MINOR_VERSION` | `3.11` | major/minor version of Python (should match base image) |
| `DEPENDENCIES_EPOCH_NUMBER` | `2` | increasing this number will reinstall all apt dependencies |
| `ADDITIONAL_PIP_INSTALL_FLAGS` | | additional `pip` flags passed to the installation commands (except when reinstalling `pip` itself) |
+75 -39
View File
@@ -1,4 +1,4 @@
<svg class="rich-terminal" viewBox="0 0 1482 1172.3999999999999" xmlns="http://www.w3.org/2000/svg">
<svg class="rich-terminal" viewBox="0 0 1482 1392.0" xmlns="http://www.w3.org/2000/svg">
<!-- Generated with Rich https://www.textualize.io -->
<style>
@@ -43,7 +43,7 @@
<defs>
<clipPath id="breeze-ci-upgrade-clip-terminal">
<rect x="0" y="0" width="1463.0" height="1121.3999999999999" />
<rect x="0" y="0" width="1463.0" height="1341.0" />
</clipPath>
<clipPath id="breeze-ci-upgrade-line-0">
<rect x="0" y="1.5" width="1464" height="24.65"/>
@@ -180,9 +180,36 @@
<clipPath id="breeze-ci-upgrade-line-44">
<rect x="0" y="1075.1" width="1464" height="24.65"/>
</clipPath>
<clipPath id="breeze-ci-upgrade-line-45">
<rect x="0" y="1099.5" width="1464" height="24.65"/>
</clipPath>
<clipPath id="breeze-ci-upgrade-line-46">
<rect x="0" y="1123.9" width="1464" height="24.65"/>
</clipPath>
<clipPath id="breeze-ci-upgrade-line-47">
<rect x="0" y="1148.3" width="1464" height="24.65"/>
</clipPath>
<clipPath id="breeze-ci-upgrade-line-48">
<rect x="0" y="1172.7" width="1464" height="24.65"/>
</clipPath>
<clipPath id="breeze-ci-upgrade-line-49">
<rect x="0" y="1197.1" width="1464" height="24.65"/>
</clipPath>
<clipPath id="breeze-ci-upgrade-line-50">
<rect x="0" y="1221.5" width="1464" height="24.65"/>
</clipPath>
<clipPath id="breeze-ci-upgrade-line-51">
<rect x="0" y="1245.9" width="1464" height="24.65"/>
</clipPath>
<clipPath id="breeze-ci-upgrade-line-52">
<rect x="0" y="1270.3" width="1464" height="24.65"/>
</clipPath>
<clipPath id="breeze-ci-upgrade-line-53">
<rect x="0" y="1294.7" 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="1170.4" rx="8"/><text class="breeze-ci-upgrade-title" fill="#c5c8c6" text-anchor="middle" x="740" y="27">Command:&#160;ci&#160;upgrade</text>
<rect fill="#292929" stroke="rgba(255,255,255,0.35)" stroke-width="1" x="1" y="1" width="1480" height="1390" rx="8"/><text class="breeze-ci-upgrade-title" fill="#c5c8c6" text-anchor="middle" x="740" y="27">Command:&#160;ci&#160;upgrade</text>
<g transform="translate(26,22)">
<circle cx="0" cy="0" r="7" fill="#ff5f57"/>
<circle cx="22" cy="0" r="7" fill="#febc2e"/>
@@ -202,42 +229,51 @@
</text><text class="breeze-ci-upgrade-r5" x="0" y="190.8" textLength="12.2" clip-path="url(#breeze-ci-upgrade-line-7)">│</text><text class="breeze-ci-upgrade-r1" x="707.6" y="190.8" textLength="158.6" clip-path="url(#breeze-ci-upgrade-line-7)">&#x27;vX-Y-test&#x27;)&#160;</text><text class="breeze-ci-upgrade-r5" x="866.2" y="190.8" textLength="183" clip-path="url(#breeze-ci-upgrade-line-7)">[default:&#160;main]</text><text class="breeze-ci-upgrade-r6" x="1061.4" y="190.8" textLength="73.2" clip-path="url(#breeze-ci-upgrade-line-7)">(TEXT)</text><text class="breeze-ci-upgrade-r5" x="1451.8" y="190.8" textLength="12.2" clip-path="url(#breeze-ci-upgrade-line-7)">│</text><text class="breeze-ci-upgrade-r1" x="1464" y="190.8" textLength="12.2" clip-path="url(#breeze-ci-upgrade-line-7)">
</text><text class="breeze-ci-upgrade-r5" x="0" y="215.2" textLength="12.2" clip-path="url(#breeze-ci-upgrade-line-8)">│</text><text class="breeze-ci-upgrade-r4" x="24.4" y="215.2" textLength="134.2" clip-path="url(#breeze-ci-upgrade-line-8)">--create-pr</text><text class="breeze-ci-upgrade-r1" x="158.6" y="215.2" textLength="12.2" clip-path="url(#breeze-ci-upgrade-line-8)">/</text><text class="breeze-ci-upgrade-r4" x="170.8" y="215.2" textLength="170.8" clip-path="url(#breeze-ci-upgrade-line-8)">--no-create-pr</text><text class="breeze-ci-upgrade-r1" x="707.6" y="215.2" textLength="732" clip-path="url(#breeze-ci-upgrade-line-8)">Automatically&#160;create&#160;a&#160;PR&#160;with&#160;the&#160;upgrade&#160;changes&#160;(if&#160;not&#160;&#160;</text><text class="breeze-ci-upgrade-r5" x="1451.8" y="215.2" textLength="12.2" clip-path="url(#breeze-ci-upgrade-line-8)">│</text><text class="breeze-ci-upgrade-r1" x="1464" y="215.2" textLength="12.2" clip-path="url(#breeze-ci-upgrade-line-8)">
</text><text class="breeze-ci-upgrade-r5" x="0" y="239.6" textLength="12.2" clip-path="url(#breeze-ci-upgrade-line-9)">│</text><text class="breeze-ci-upgrade-r1" x="707.6" y="239.6" textLength="732" clip-path="url(#breeze-ci-upgrade-line-9)">specified,&#160;will&#160;ask)&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;</text><text class="breeze-ci-upgrade-r5" x="1451.8" y="239.6" textLength="12.2" clip-path="url(#breeze-ci-upgrade-line-9)">│</text><text class="breeze-ci-upgrade-r1" x="1464" y="239.6" textLength="12.2" clip-path="url(#breeze-ci-upgrade-line-9)">
</text><text class="breeze-ci-upgrade-r5" x="0" y="264" textLength="12.2" clip-path="url(#breeze-ci-upgrade-line-10)">│</text><text class="breeze-ci-upgrade-r4" x="24.4" y="264" textLength="85.4" clip-path="url(#breeze-ci-upgrade-line-10)">--draft</text><text class="breeze-ci-upgrade-r1" x="109.8" y="264" textLength="12.2" clip-path="url(#breeze-ci-upgrade-line-10)">/</text><text class="breeze-ci-upgrade-r4" x="122" y="264" textLength="122" clip-path="url(#breeze-ci-upgrade-line-10)">--no-draft</text><text class="breeze-ci-upgrade-r1" x="707.6" y="264" textLength="732" clip-path="url(#breeze-ci-upgrade-line-10)">Create&#160;the&#160;PR&#160;as&#160;a&#160;draft&#160;(useful&#160;for&#160;scheduled&#160;CI&#160;runs&#160;where</text><text class="breeze-ci-upgrade-r5" x="1451.8" y="264" textLength="12.2" clip-path="url(#breeze-ci-upgrade-line-10)">│</text><text class="breeze-ci-upgrade-r1" x="1464" y="264" textLength="12.2" clip-path="url(#breeze-ci-upgrade-line-10)">
</text><text class="breeze-ci-upgrade-r5" x="0" y="288.4" textLength="12.2" clip-path="url(#breeze-ci-upgrade-line-11)">│</text><text class="breeze-ci-upgrade-r1" x="707.6" y="288.4" textLength="390.4" clip-path="url(#breeze-ci-upgrade-line-11)">a&#160;human&#160;undrafts&#160;to&#160;trigger&#160;CI)&#160;</text><text class="breeze-ci-upgrade-r5" x="1098" y="288.4" textLength="231.8" clip-path="url(#breeze-ci-upgrade-line-11)">[default:&#160;no-draft]</text><text class="breeze-ci-upgrade-r5" x="1451.8" y="288.4" textLength="12.2" clip-path="url(#breeze-ci-upgrade-line-11)">│</text><text class="breeze-ci-upgrade-r1" x="1464" y="288.4" textLength="12.2" clip-path="url(#breeze-ci-upgrade-line-11)">
</text><text class="breeze-ci-upgrade-r5" x="0" y="312.8" textLength="12.2" clip-path="url(#breeze-ci-upgrade-line-12)">│</text><text class="breeze-ci-upgrade-r4" x="24.4" y="312.8" textLength="195.2" clip-path="url(#breeze-ci-upgrade-line-12)">--switch-to-base</text><text class="breeze-ci-upgrade-r1" x="219.6" y="312.8" textLength="12.2" clip-path="url(#breeze-ci-upgrade-line-12)">/</text><text class="breeze-ci-upgrade-r4" x="231.8" y="312.8" textLength="231.8" clip-path="url(#breeze-ci-upgrade-line-12)">--no-switch-to-base</text><text class="breeze-ci-upgrade-r1" x="707.6" y="312.8" textLength="732" clip-path="url(#breeze-ci-upgrade-line-12)">Automatically&#160;switch&#160;to&#160;the&#160;base&#160;branch&#160;if&#160;not&#160;already&#160;on&#160;it</text><text class="breeze-ci-upgrade-r5" x="1451.8" y="312.8" textLength="12.2" clip-path="url(#breeze-ci-upgrade-line-12)">│</text><text class="breeze-ci-upgrade-r1" x="1464" y="312.8" textLength="12.2" clip-path="url(#breeze-ci-upgrade-line-12)">
</text><text class="breeze-ci-upgrade-r5" x="0" y="337.2" textLength="12.2" clip-path="url(#breeze-ci-upgrade-line-13)">│</text><text class="breeze-ci-upgrade-r1" x="707.6" y="337.2" textLength="732" clip-path="url(#breeze-ci-upgrade-line-13)">(if&#160;not&#160;specified,&#160;will&#160;ask)&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;</text><text class="breeze-ci-upgrade-r5" x="1451.8" y="337.2" textLength="12.2" clip-path="url(#breeze-ci-upgrade-line-13)">│</text><text class="breeze-ci-upgrade-r1" x="1464" y="337.2" textLength="12.2" clip-path="url(#breeze-ci-upgrade-line-13)">
</text><text class="breeze-ci-upgrade-r5" x="0" y="361.6" textLength="12.2" clip-path="url(#breeze-ci-upgrade-line-14)">│</text><text class="breeze-ci-upgrade-r4" x="24.4" y="361.6" textLength="658.8" clip-path="url(#breeze-ci-upgrade-line-14)">--airflow-site&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;</text><text class="breeze-ci-upgrade-r1" x="707.6" y="361.6" textLength="732" clip-path="url(#breeze-ci-upgrade-line-14)">Path&#160;to&#160;airflow-site&#160;checkout&#160;for&#160;publishing&#160;K8s&#160;schemas&#160;&#160;&#160;&#160;</text><text class="breeze-ci-upgrade-r5" x="1451.8" y="361.6" textLength="12.2" clip-path="url(#breeze-ci-upgrade-line-14)">│</text><text class="breeze-ci-upgrade-r1" x="1464" y="361.6" textLength="12.2" clip-path="url(#breeze-ci-upgrade-line-14)">
</text><text class="breeze-ci-upgrade-r5" x="0" y="386" textLength="12.2" clip-path="url(#breeze-ci-upgrade-line-15)">│</text><text class="breeze-ci-upgrade-r5" x="707.6" y="386" textLength="317.2" clip-path="url(#breeze-ci-upgrade-line-15)">[default:&#160;../airflow-site]</text><text class="breeze-ci-upgrade-r6" x="1037" y="386" textLength="134.2" clip-path="url(#breeze-ci-upgrade-line-15)">(DIRECTORY)</text><text class="breeze-ci-upgrade-r5" x="1451.8" y="386" textLength="12.2" clip-path="url(#breeze-ci-upgrade-line-15)">│</text><text class="breeze-ci-upgrade-r1" x="1464" y="386" textLength="12.2" clip-path="url(#breeze-ci-upgrade-line-15)">
</text><text class="breeze-ci-upgrade-r5" x="0" y="410.4" textLength="12.2" clip-path="url(#breeze-ci-upgrade-line-16)">│</text><text class="breeze-ci-upgrade-r4" x="24.4" y="410.4" textLength="658.8" clip-path="url(#breeze-ci-upgrade-line-16)">--force-k8s-schema-sync&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;</text><text class="breeze-ci-upgrade-r1" x="707.6" y="410.4" textLength="732" clip-path="url(#breeze-ci-upgrade-line-16)">Force&#160;syncing&#160;K8s&#160;schemas&#160;to&#160;airflow-site&#160;even&#160;if&#160;all&#160;&#160;&#160;&#160;&#160;&#160;&#160;</text><text class="breeze-ci-upgrade-r5" x="1451.8" y="410.4" textLength="12.2" clip-path="url(#breeze-ci-upgrade-line-16)">│</text><text class="breeze-ci-upgrade-r1" x="1464" y="410.4" textLength="12.2" clip-path="url(#breeze-ci-upgrade-line-16)">
</text><text class="breeze-ci-upgrade-r5" x="0" y="434.8" textLength="12.2" clip-path="url(#breeze-ci-upgrade-line-17)">│</text><text class="breeze-ci-upgrade-r1" x="707.6" y="434.8" textLength="732" clip-path="url(#breeze-ci-upgrade-line-17)">versions&#160;appear&#160;published&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;</text><text class="breeze-ci-upgrade-r5" x="1451.8" y="434.8" textLength="12.2" clip-path="url(#breeze-ci-upgrade-line-17)">│</text><text class="breeze-ci-upgrade-r1" x="1464" y="434.8" textLength="12.2" clip-path="url(#breeze-ci-upgrade-line-17)">
</text><text class="breeze-ci-upgrade-r5" x="0" y="459.2" textLength="12.2" clip-path="url(#breeze-ci-upgrade-line-18)">│</text><text class="breeze-ci-upgrade-r4" x="24.4" y="459.2" textLength="305" clip-path="url(#breeze-ci-upgrade-line-18)">--latest-package-managers</text><text class="breeze-ci-upgrade-r1" x="329.4" y="459.2" textLength="12.2" clip-path="url(#breeze-ci-upgrade-line-18)">/</text><text class="breeze-ci-upgrade-r4" x="341.6" y="459.2" textLength="341.6" clip-path="url(#breeze-ci-upgrade-line-18)">--no-latest-package-managers</text><text class="breeze-ci-upgrade-r1" x="707.6" y="459.2" textLength="732" clip-path="url(#breeze-ci-upgrade-line-18)">Bypass&#160;cooldown&#160;when&#160;upgrading&#160;package&#160;managers&#160;(pip&#160;and&#160;uv)</text><text class="breeze-ci-upgrade-r5" x="1451.8" y="459.2" textLength="12.2" clip-path="url(#breeze-ci-upgrade-line-18)">│</text><text class="breeze-ci-upgrade-r1" x="1464" y="459.2" textLength="12.2" clip-path="url(#breeze-ci-upgrade-line-18)">
</text><text class="breeze-ci-upgrade-r5" x="0" y="483.6" textLength="12.2" clip-path="url(#breeze-ci-upgrade-line-19)">│</text><text class="breeze-ci-upgrade-r1" x="707.6" y="483.6" textLength="353.8" clip-path="url(#breeze-ci-upgrade-line-19)">to&#160;use&#160;their&#160;latest&#160;versions&#160;</text><text class="breeze-ci-upgrade-r5" x="1061.4" y="483.6" textLength="122" clip-path="url(#breeze-ci-upgrade-line-19)">[default:&#160;</text><text class="breeze-ci-upgrade-r5" x="1451.8" y="483.6" textLength="12.2" clip-path="url(#breeze-ci-upgrade-line-19)">│</text><text class="breeze-ci-upgrade-r1" x="1464" y="483.6" textLength="12.2" clip-path="url(#breeze-ci-upgrade-line-19)">
</text><text class="breeze-ci-upgrade-r5" x="0" y="508" textLength="12.2" clip-path="url(#breeze-ci-upgrade-line-20)">│</text><text class="breeze-ci-upgrade-r5" x="707.6" y="508" textLength="329.4" clip-path="url(#breeze-ci-upgrade-line-20)">no-latest-package-managers]</text><text class="breeze-ci-upgrade-r5" x="1451.8" y="508" textLength="12.2" clip-path="url(#breeze-ci-upgrade-line-20)">│</text><text class="breeze-ci-upgrade-r1" x="1464" y="508" textLength="12.2" clip-path="url(#breeze-ci-upgrade-line-20)">
</text><text class="breeze-ci-upgrade-r5" x="0" y="532.4" textLength="12.2" clip-path="url(#breeze-ci-upgrade-line-21)">│</text><text class="breeze-ci-upgrade-r4" x="24.4" y="532.4" textLength="658.8" clip-path="url(#breeze-ci-upgrade-line-21)">--github-token&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;</text><text class="breeze-ci-upgrade-r1" x="707.6" y="532.4" textLength="512.4" clip-path="url(#breeze-ci-upgrade-line-21)">The&#160;token&#160;used&#160;to&#160;authenticate&#160;to&#160;GitHub.&#160;</text><text class="breeze-ci-upgrade-r6" x="1220" y="532.4" textLength="73.2" clip-path="url(#breeze-ci-upgrade-line-21)">(TEXT)</text><text class="breeze-ci-upgrade-r5" x="1451.8" y="532.4" textLength="12.2" clip-path="url(#breeze-ci-upgrade-line-21)">│</text><text class="breeze-ci-upgrade-r1" x="1464" y="532.4" textLength="12.2" clip-path="url(#breeze-ci-upgrade-line-21)">
</text><text class="breeze-ci-upgrade-r5" x="0" y="556.8" textLength="1464" clip-path="url(#breeze-ci-upgrade-line-22)">╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯</text><text class="breeze-ci-upgrade-r1" x="1464" y="556.8" textLength="12.2" clip-path="url(#breeze-ci-upgrade-line-22)">
</text><text class="breeze-ci-upgrade-r5" x="0" y="581.2" textLength="24.4" clip-path="url(#breeze-ci-upgrade-line-23)">╭─</text><text class="breeze-ci-upgrade-r5" x="24.4" y="581.2" textLength="183" clip-path="url(#breeze-ci-upgrade-line-23)">&#160;Upgrade&#160;steps&#160;</text><text class="breeze-ci-upgrade-r5" x="207.4" y="581.2" textLength="1232.2" clip-path="url(#breeze-ci-upgrade-line-23)">─────────────────────────────────────────────────────────────────────────────────────────────────────</text><text class="breeze-ci-upgrade-r5" x="1439.6" y="581.2" textLength="24.4" clip-path="url(#breeze-ci-upgrade-line-23)">─╮</text><text class="breeze-ci-upgrade-r1" x="1464" y="581.2" textLength="12.2" clip-path="url(#breeze-ci-upgrade-line-23)">
</text><text class="breeze-ci-upgrade-r5" x="0" y="605.6" textLength="12.2" clip-path="url(#breeze-ci-upgrade-line-24)">│</text><text class="breeze-ci-upgrade-r4" x="24.4" y="605.6" textLength="146.4" clip-path="url(#breeze-ci-upgrade-line-24)">--autoupdate</text><text class="breeze-ci-upgrade-r1" x="170.8" y="605.6" textLength="12.2" clip-path="url(#breeze-ci-upgrade-line-24)">/</text><text class="breeze-ci-upgrade-r4" x="183" y="605.6" textLength="183" clip-path="url(#breeze-ci-upgrade-line-24)">--no-autoupdate</text><text class="breeze-ci-upgrade-r1" x="744.2" y="605.6" textLength="549" clip-path="url(#breeze-ci-upgrade-line-24)">Run&#160;prek&#160;autoupdate&#160;to&#160;update&#160;hook&#160;revisions&#160;</text><text class="breeze-ci-upgrade-r5" x="1293.2" y="605.6" textLength="122" clip-path="url(#breeze-ci-upgrade-line-24)">[default:&#160;</text><text class="breeze-ci-upgrade-r5" x="1451.8" y="605.6" textLength="12.2" clip-path="url(#breeze-ci-upgrade-line-24)">│</text><text class="breeze-ci-upgrade-r1" x="1464" y="605.6" textLength="12.2" clip-path="url(#breeze-ci-upgrade-line-24)">
</text><text class="breeze-ci-upgrade-r5" x="0" y="630" textLength="12.2" clip-path="url(#breeze-ci-upgrade-line-25)">│</text><text class="breeze-ci-upgrade-r5" x="744.2" y="630" textLength="134.2" clip-path="url(#breeze-ci-upgrade-line-25)">autoupdate]</text><text class="breeze-ci-upgrade-r5" x="1451.8" y="630" textLength="12.2" clip-path="url(#breeze-ci-upgrade-line-25)">│</text><text class="breeze-ci-upgrade-r1" x="1464" y="630" textLength="12.2" clip-path="url(#breeze-ci-upgrade-line-25)">
</text><text class="breeze-ci-upgrade-r5" x="0" y="654.4" textLength="12.2" clip-path="url(#breeze-ci-upgrade-line-26)">│</text><text class="breeze-ci-upgrade-r4" x="24.4" y="654.4" textLength="329.4" clip-path="url(#breeze-ci-upgrade-line-26)">--update-chart-dependencies</text><text class="breeze-ci-upgrade-r1" x="353.8" y="654.4" textLength="12.2" clip-path="url(#breeze-ci-upgrade-line-26)">/</text><text class="breeze-ci-upgrade-r4" x="366" y="654.4" textLength="353.8" clip-path="url(#breeze-ci-upgrade-line-26)">--no-update-chart-dependencie</text><text class="breeze-ci-upgrade-r1" x="744.2" y="654.4" textLength="695.4" clip-path="url(#breeze-ci-upgrade-line-26)">Run&#160;update-chart-dependencies&#160;to&#160;update&#160;Helm&#160;chart&#160;&#160;&#160;&#160;&#160;&#160;&#160;</text><text class="breeze-ci-upgrade-r5" x="1451.8" y="654.4" textLength="12.2" clip-path="url(#breeze-ci-upgrade-line-26)">│</text><text class="breeze-ci-upgrade-r1" x="1464" y="654.4" textLength="12.2" clip-path="url(#breeze-ci-upgrade-line-26)">
</text><text class="breeze-ci-upgrade-r5" x="0" y="678.8" textLength="12.2" clip-path="url(#breeze-ci-upgrade-line-27)">│</text><text class="breeze-ci-upgrade-r4" x="24.4" y="678.8" textLength="12.2" clip-path="url(#breeze-ci-upgrade-line-27)">s</text><text class="breeze-ci-upgrade-r1" x="744.2" y="678.8" textLength="158.6" clip-path="url(#breeze-ci-upgrade-line-27)">dependencies&#160;</text><text class="breeze-ci-upgrade-r5" x="902.8" y="678.8" textLength="439.2" clip-path="url(#breeze-ci-upgrade-line-27)">[default:&#160;update-chart-dependencies]</text><text class="breeze-ci-upgrade-r5" x="1451.8" y="678.8" textLength="12.2" clip-path="url(#breeze-ci-upgrade-line-27)">│</text><text class="breeze-ci-upgrade-r1" x="1464" y="678.8" textLength="12.2" clip-path="url(#breeze-ci-upgrade-line-27)">
</text><text class="breeze-ci-upgrade-r5" x="0" y="703.2" textLength="12.2" clip-path="url(#breeze-ci-upgrade-line-28)">│</text><text class="breeze-ci-upgrade-r4" x="24.4" y="703.2" textLength="341.6" clip-path="url(#breeze-ci-upgrade-line-28)">--upgrade-important-versions</text><text class="breeze-ci-upgrade-r1" x="366" y="703.2" textLength="12.2" clip-path="url(#breeze-ci-upgrade-line-28)">/</text><text class="breeze-ci-upgrade-r4" x="378.2" y="703.2" textLength="341.6" clip-path="url(#breeze-ci-upgrade-line-28)">--no-upgrade-important-versi</text><text class="breeze-ci-upgrade-r1" x="744.2" y="703.2" textLength="695.4" clip-path="url(#breeze-ci-upgrade-line-28)">Run&#160;upgrade-important-versions&#160;to&#160;bump&#160;key&#160;dependency&#160;&#160;&#160;&#160;</text><text class="breeze-ci-upgrade-r5" x="1451.8" y="703.2" textLength="12.2" clip-path="url(#breeze-ci-upgrade-line-28)">│</text><text class="breeze-ci-upgrade-r1" x="1464" y="703.2" textLength="12.2" clip-path="url(#breeze-ci-upgrade-line-28)">
</text><text class="breeze-ci-upgrade-r5" x="0" y="727.6" textLength="12.2" clip-path="url(#breeze-ci-upgrade-line-29)">│</text><text class="breeze-ci-upgrade-r4" x="24.4" y="727.6" textLength="36.6" clip-path="url(#breeze-ci-upgrade-line-29)">ons</text><text class="breeze-ci-upgrade-r1" x="744.2" y="727.6" textLength="109.8" clip-path="url(#breeze-ci-upgrade-line-29)">versions&#160;</text><text class="breeze-ci-upgrade-r5" x="854" y="727.6" textLength="451.4" clip-path="url(#breeze-ci-upgrade-line-29)">[default:&#160;upgrade-important-versions]</text><text class="breeze-ci-upgrade-r5" x="1451.8" y="727.6" textLength="12.2" clip-path="url(#breeze-ci-upgrade-line-29)">│</text><text class="breeze-ci-upgrade-r1" x="1464" y="727.6" textLength="12.2" clip-path="url(#breeze-ci-upgrade-line-29)">
</text><text class="breeze-ci-upgrade-r5" x="0" y="752" textLength="12.2" clip-path="url(#breeze-ci-upgrade-line-30)">│</text><text class="breeze-ci-upgrade-r4" x="24.4" y="752" textLength="329.4" clip-path="url(#breeze-ci-upgrade-line-30)">--upgrade-dependency-floors</text><text class="breeze-ci-upgrade-r1" x="353.8" y="752" textLength="12.2" clip-path="url(#breeze-ci-upgrade-line-30)">/</text><text class="breeze-ci-upgrade-r4" x="366" y="752" textLength="353.8" clip-path="url(#breeze-ci-upgrade-line-30)">--no-upgrade-dependency-floor</text><text class="breeze-ci-upgrade-r1" x="744.2" y="752" textLength="695.4" clip-path="url(#breeze-ci-upgrade-line-30)">Raise&#160;the&#160;floors&#160;of&#160;the&#160;curated&#160;dependencies&#160;in&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;</text><text class="breeze-ci-upgrade-r5" x="1451.8" y="752" textLength="12.2" clip-path="url(#breeze-ci-upgrade-line-30)">│</text><text class="breeze-ci-upgrade-r1" x="1464" y="752" textLength="12.2" clip-path="url(#breeze-ci-upgrade-line-30)">
</text><text class="breeze-ci-upgrade-r5" x="0" y="776.4" textLength="12.2" clip-path="url(#breeze-ci-upgrade-line-31)">│</text><text class="breeze-ci-upgrade-r4" x="24.4" y="776.4" textLength="12.2" clip-path="url(#breeze-ci-upgrade-line-31)">s</text><text class="breeze-ci-upgrade-r1" x="744.2" y="776.4" textLength="402.6" clip-path="url(#breeze-ci-upgrade-line-31)">[tool.airflow.dependency-floors]&#160;</text><text class="breeze-ci-upgrade-r5" x="1146.8" y="776.4" textLength="122" clip-path="url(#breeze-ci-upgrade-line-31)">[default:&#160;</text><text class="breeze-ci-upgrade-r5" x="1451.8" y="776.4" textLength="12.2" clip-path="url(#breeze-ci-upgrade-line-31)">│</text><text class="breeze-ci-upgrade-r1" x="1464" y="776.4" textLength="12.2" clip-path="url(#breeze-ci-upgrade-line-31)">
</text><text class="breeze-ci-upgrade-r5" x="0" y="800.8" textLength="12.2" clip-path="url(#breeze-ci-upgrade-line-32)">│</text><text class="breeze-ci-upgrade-r5" x="744.2" y="800.8" textLength="317.2" clip-path="url(#breeze-ci-upgrade-line-32)">upgrade-dependency-floors]</text><text class="breeze-ci-upgrade-r5" x="1451.8" y="800.8" textLength="12.2" clip-path="url(#breeze-ci-upgrade-line-32)">│</text><text class="breeze-ci-upgrade-r1" x="1464" y="800.8" textLength="12.2" clip-path="url(#breeze-ci-upgrade-line-32)">
</text><text class="breeze-ci-upgrade-r5" x="0" y="825.2" textLength="12.2" clip-path="url(#breeze-ci-upgrade-line-33)">│</text><text class="breeze-ci-upgrade-r4" x="24.4" y="825.2" textLength="195.2" clip-path="url(#breeze-ci-upgrade-line-33)">--update-uv-lock</text><text class="breeze-ci-upgrade-r1" x="219.6" y="825.2" textLength="12.2" clip-path="url(#breeze-ci-upgrade-line-33)">/</text><text class="breeze-ci-upgrade-r4" x="231.8" y="825.2" textLength="231.8" clip-path="url(#breeze-ci-upgrade-line-33)">--no-update-uv-lock</text><text class="breeze-ci-upgrade-r1" x="744.2" y="825.2" textLength="695.4" clip-path="url(#breeze-ci-upgrade-line-33)">Run&#160;update-uv-lock&#160;to&#160;regenerate&#160;uv.lock&#160;with&#160;latest&#160;&#160;&#160;&#160;&#160;</text><text class="breeze-ci-upgrade-r5" x="1451.8" y="825.2" textLength="12.2" clip-path="url(#breeze-ci-upgrade-line-33)">│</text><text class="breeze-ci-upgrade-r1" x="1464" y="825.2" textLength="12.2" clip-path="url(#breeze-ci-upgrade-line-33)">
</text><text class="breeze-ci-upgrade-r5" x="0" y="849.6" textLength="12.2" clip-path="url(#breeze-ci-upgrade-line-34)">│</text><text class="breeze-ci-upgrade-r1" x="744.2" y="849.6" textLength="695.4" clip-path="url(#breeze-ci-upgrade-line-34)">resolutions&#160;inside&#160;Breeze&#160;CI&#160;image,&#160;then&#160;regenerate&#160;the&#160;&#160;</text><text class="breeze-ci-upgrade-r5" x="1451.8" y="849.6" textLength="12.2" clip-path="url(#breeze-ci-upgrade-line-34)">│</text><text class="breeze-ci-upgrade-r1" x="1464" y="849.6" textLength="12.2" clip-path="url(#breeze-ci-upgrade-line-34)">
</text><text class="breeze-ci-upgrade-r5" x="0" y="874" textLength="12.2" clip-path="url(#breeze-ci-upgrade-line-35)">│</text><text class="breeze-ci-upgrade-r1" x="744.2" y="874" textLength="695.4" clip-path="url(#breeze-ci-upgrade-line-35)">API&#160;datamodels&#160;the&#160;resolved&#160;code&#160;generator&#160;stamps&#160;its&#160;&#160;&#160;&#160;</text><text class="breeze-ci-upgrade-r5" x="1451.8" y="874" textLength="12.2" clip-path="url(#breeze-ci-upgrade-line-35)">│</text><text class="breeze-ci-upgrade-r1" x="1464" y="874" textLength="12.2" clip-path="url(#breeze-ci-upgrade-line-35)">
</text><text class="breeze-ci-upgrade-r5" x="0" y="898.4" textLength="12.2" clip-path="url(#breeze-ci-upgrade-line-36)">│</text><text class="breeze-ci-upgrade-r1" x="744.2" y="898.4" textLength="158.6" clip-path="url(#breeze-ci-upgrade-line-36)">version&#160;into&#160;</text><text class="breeze-ci-upgrade-r5" x="902.8" y="898.4" textLength="305" clip-path="url(#breeze-ci-upgrade-line-36)">[default:&#160;update-uv-lock]</text><text class="breeze-ci-upgrade-r5" x="1451.8" y="898.4" textLength="12.2" clip-path="url(#breeze-ci-upgrade-line-36)">│</text><text class="breeze-ci-upgrade-r1" x="1464" y="898.4" textLength="12.2" clip-path="url(#breeze-ci-upgrade-line-36)">
</text><text class="breeze-ci-upgrade-r5" x="0" y="922.8" textLength="12.2" clip-path="url(#breeze-ci-upgrade-line-37)">│</text><text class="breeze-ci-upgrade-r4" x="24.4" y="922.8" textLength="207.4" clip-path="url(#breeze-ci-upgrade-line-37)">--k8s-schema-sync</text><text class="breeze-ci-upgrade-r1" x="231.8" y="922.8" textLength="12.2" clip-path="url(#breeze-ci-upgrade-line-37)">/</text><text class="breeze-ci-upgrade-r4" x="244" y="922.8" textLength="244" clip-path="url(#breeze-ci-upgrade-line-37)">--no-k8s-schema-sync</text><text class="breeze-ci-upgrade-r1" x="744.2" y="922.8" textLength="463.6" clip-path="url(#breeze-ci-upgrade-line-37)">Sync&#160;K8s&#160;JSON&#160;schemas&#160;to&#160;airflow-site&#160;</text><text class="breeze-ci-upgrade-r5" x="1207.8" y="922.8" textLength="122" clip-path="url(#breeze-ci-upgrade-line-37)">[default:&#160;</text><text class="breeze-ci-upgrade-r5" x="1451.8" y="922.8" textLength="12.2" clip-path="url(#breeze-ci-upgrade-line-37)">│</text><text class="breeze-ci-upgrade-r1" x="1464" y="922.8" textLength="12.2" clip-path="url(#breeze-ci-upgrade-line-37)">
</text><text class="breeze-ci-upgrade-r5" x="0" y="947.2" textLength="12.2" clip-path="url(#breeze-ci-upgrade-line-38)">│</text><text class="breeze-ci-upgrade-r5" x="744.2" y="947.2" textLength="195.2" clip-path="url(#breeze-ci-upgrade-line-38)">k8s-schema-sync]</text><text class="breeze-ci-upgrade-r5" x="1451.8" y="947.2" textLength="12.2" clip-path="url(#breeze-ci-upgrade-line-38)">│</text><text class="breeze-ci-upgrade-r1" x="1464" y="947.2" textLength="12.2" clip-path="url(#breeze-ci-upgrade-line-38)">
</text><text class="breeze-ci-upgrade-r5" x="0" y="971.6" textLength="1464" clip-path="url(#breeze-ci-upgrade-line-39)">╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯</text><text class="breeze-ci-upgrade-r1" x="1464" y="971.6" textLength="12.2" clip-path="url(#breeze-ci-upgrade-line-39)">
</text><text class="breeze-ci-upgrade-r5" x="0" y="996" textLength="24.4" clip-path="url(#breeze-ci-upgrade-line-40)">╭─</text><text class="breeze-ci-upgrade-r5" x="24.4" y="996" textLength="195.2" clip-path="url(#breeze-ci-upgrade-line-40)">&#160;Common&#160;options&#160;</text><text class="breeze-ci-upgrade-r5" x="219.6" y="996" textLength="1220" clip-path="url(#breeze-ci-upgrade-line-40)">────────────────────────────────────────────────────────────────────────────────────────────────────</text><text class="breeze-ci-upgrade-r5" x="1439.6" y="996" textLength="24.4" clip-path="url(#breeze-ci-upgrade-line-40)">─╮</text><text class="breeze-ci-upgrade-r1" x="1464" y="996" textLength="12.2" clip-path="url(#breeze-ci-upgrade-line-40)">
</text><text class="breeze-ci-upgrade-r5" x="0" y="1020.4" textLength="12.2" clip-path="url(#breeze-ci-upgrade-line-41)">│</text><text class="breeze-ci-upgrade-r4" x="24.4" y="1020.4" textLength="109.8" clip-path="url(#breeze-ci-upgrade-line-41)">--answer&#160;</text><text class="breeze-ci-upgrade-r7" x="158.6" y="1020.4" textLength="24.4" clip-path="url(#breeze-ci-upgrade-line-41)">-a</text><text class="breeze-ci-upgrade-r1" x="207.4" y="1020.4" textLength="329.4" clip-path="url(#breeze-ci-upgrade-line-41)">Force&#160;answer&#160;to&#160;questions.&#160;</text><text class="breeze-ci-upgrade-r6" x="536.8" y="1020.4" textLength="353.8" clip-path="url(#breeze-ci-upgrade-line-41)">(y&#160;|&#160;n&#160;|&#160;q&#160;|&#160;yes&#160;|&#160;no&#160;|&#160;quit)</text><text class="breeze-ci-upgrade-r5" x="1451.8" y="1020.4" textLength="12.2" clip-path="url(#breeze-ci-upgrade-line-41)">│</text><text class="breeze-ci-upgrade-r1" x="1464" y="1020.4" textLength="12.2" clip-path="url(#breeze-ci-upgrade-line-41)">
</text><text class="breeze-ci-upgrade-r5" x="0" y="1044.8" textLength="12.2" clip-path="url(#breeze-ci-upgrade-line-42)">│</text><text class="breeze-ci-upgrade-r4" x="24.4" y="1044.8" textLength="109.8" clip-path="url(#breeze-ci-upgrade-line-42)">--verbose</text><text class="breeze-ci-upgrade-r7" x="158.6" y="1044.8" textLength="24.4" clip-path="url(#breeze-ci-upgrade-line-42)">-v</text><text class="breeze-ci-upgrade-r1" x="207.4" y="1044.8" textLength="585.6" clip-path="url(#breeze-ci-upgrade-line-42)">Print&#160;verbose&#160;information&#160;about&#160;performed&#160;steps.</text><text class="breeze-ci-upgrade-r5" x="1451.8" y="1044.8" textLength="12.2" clip-path="url(#breeze-ci-upgrade-line-42)">│</text><text class="breeze-ci-upgrade-r1" x="1464" y="1044.8" textLength="12.2" clip-path="url(#breeze-ci-upgrade-line-42)">
</text><text class="breeze-ci-upgrade-r5" x="0" y="1069.2" textLength="12.2" clip-path="url(#breeze-ci-upgrade-line-43)">│</text><text class="breeze-ci-upgrade-r4" x="24.4" y="1069.2" textLength="109.8" clip-path="url(#breeze-ci-upgrade-line-43)">--dry-run</text><text class="breeze-ci-upgrade-r7" x="158.6" y="1069.2" textLength="24.4" clip-path="url(#breeze-ci-upgrade-line-43)">-D</text><text class="breeze-ci-upgrade-r1" x="207.4" y="1069.2" textLength="719.8" clip-path="url(#breeze-ci-upgrade-line-43)">If&#160;dry-run&#160;is&#160;set,&#160;commands&#160;are&#160;only&#160;printed,&#160;not&#160;executed.</text><text class="breeze-ci-upgrade-r5" x="1451.8" y="1069.2" textLength="12.2" clip-path="url(#breeze-ci-upgrade-line-43)">│</text><text class="breeze-ci-upgrade-r1" x="1464" y="1069.2" textLength="12.2" clip-path="url(#breeze-ci-upgrade-line-43)">
</text><text class="breeze-ci-upgrade-r5" x="0" y="1093.6" textLength="12.2" clip-path="url(#breeze-ci-upgrade-line-44)">│</text><text class="breeze-ci-upgrade-r4" x="24.4" y="1093.6" textLength="109.8" clip-path="url(#breeze-ci-upgrade-line-44)">--help&#160;&#160;&#160;</text><text class="breeze-ci-upgrade-r7" x="158.6" y="1093.6" textLength="24.4" clip-path="url(#breeze-ci-upgrade-line-44)">-h</text><text class="breeze-ci-upgrade-r1" x="207.4" y="1093.6" textLength="329.4" clip-path="url(#breeze-ci-upgrade-line-44)">Show&#160;this&#160;message&#160;and&#160;exit.</text><text class="breeze-ci-upgrade-r5" x="1451.8" y="1093.6" textLength="12.2" clip-path="url(#breeze-ci-upgrade-line-44)">│</text><text class="breeze-ci-upgrade-r1" x="1464" y="1093.6" textLength="12.2" clip-path="url(#breeze-ci-upgrade-line-44)">
</text><text class="breeze-ci-upgrade-r5" x="0" y="1118" textLength="1464" clip-path="url(#breeze-ci-upgrade-line-45)">╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯</text><text class="breeze-ci-upgrade-r1" x="1464" y="1118" textLength="12.2" clip-path="url(#breeze-ci-upgrade-line-45)">
</text><text class="breeze-ci-upgrade-r5" x="0" y="264" textLength="12.2" clip-path="url(#breeze-ci-upgrade-line-10)">│</text><text class="breeze-ci-upgrade-r4" x="24.4" y="264" textLength="658.8" clip-path="url(#breeze-ci-upgrade-line-10)">--push-to-target-branch&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;</text><text class="breeze-ci-upgrade-r1" x="707.6" y="264" textLength="732" clip-path="url(#breeze-ci-upgrade-line-10)">Commit&#160;the&#160;upgrade&#160;onto&#160;the&#160;target&#160;release&#160;branch&#160;and&#160;push&#160;&#160;</text><text class="breeze-ci-upgrade-r5" x="1451.8" y="264" textLength="12.2" clip-path="url(#breeze-ci-upgrade-line-10)">│</text><text class="breeze-ci-upgrade-r1" x="1464" y="264" textLength="12.2" clip-path="url(#breeze-ci-upgrade-line-10)">
</text><text class="breeze-ci-upgrade-r5" x="0" y="288.4" textLength="12.2" clip-path="url(#breeze-ci-upgrade-line-11)">│</text><text class="breeze-ci-upgrade-r1" x="707.6" y="288.4" textLength="732" clip-path="url(#breeze-ci-upgrade-line-11)">it&#160;there&#160;instead&#160;of&#160;opening&#160;a&#160;PR&#160;-&#160;what&#160;the&#160;release&#160;manager&#160;</text><text class="breeze-ci-upgrade-r5" x="1451.8" y="288.4" textLength="12.2" clip-path="url(#breeze-ci-upgrade-line-11)">│</text><text class="breeze-ci-upgrade-r1" x="1464" y="288.4" textLength="12.2" clip-path="url(#breeze-ci-upgrade-line-11)">
</text><text class="breeze-ci-upgrade-r5" x="0" y="312.8" textLength="12.2" clip-path="url(#breeze-ci-upgrade-line-12)">│</text><text class="breeze-ci-upgrade-r1" x="707.6" y="312.8" textLength="732" clip-path="url(#breeze-ci-upgrade-line-12)">does&#160;before&#160;the&#160;test-to-stable&#160;PR,&#160;whose&#160;head&#160;is&#160;that&#160;&#160;&#160;&#160;&#160;&#160;&#160;</text><text class="breeze-ci-upgrade-r5" x="1451.8" y="312.8" textLength="12.2" clip-path="url(#breeze-ci-upgrade-line-12)">│</text><text class="breeze-ci-upgrade-r1" x="1464" y="312.8" textLength="12.2" clip-path="url(#breeze-ci-upgrade-line-12)">
</text><text class="breeze-ci-upgrade-r5" x="0" y="337.2" textLength="12.2" clip-path="url(#breeze-ci-upgrade-line-13)">│</text><text class="breeze-ci-upgrade-r1" x="707.6" y="337.2" textLength="732" clip-path="url(#breeze-ci-upgrade-line-13)">branch.&#160;Needs&#160;committer&#160;rights,&#160;as&#160;the&#160;release&#160;branch&#160;rules&#160;</text><text class="breeze-ci-upgrade-r5" x="1451.8" y="337.2" textLength="12.2" clip-path="url(#breeze-ci-upgrade-line-13)">│</text><text class="breeze-ci-upgrade-r1" x="1464" y="337.2" textLength="12.2" clip-path="url(#breeze-ci-upgrade-line-13)">
</text><text class="breeze-ci-upgrade-r5" x="0" y="361.6" textLength="12.2" clip-path="url(#breeze-ci-upgrade-line-14)">│</text><text class="breeze-ci-upgrade-r1" x="707.6" y="361.6" textLength="732" clip-path="url(#breeze-ci-upgrade-line-14)">let&#160;only&#160;committers&#160;push&#160;without&#160;CI&#160;having&#160;passed&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;</text><text class="breeze-ci-upgrade-r5" x="1451.8" y="361.6" textLength="12.2" clip-path="url(#breeze-ci-upgrade-line-14)">│</text><text class="breeze-ci-upgrade-r1" x="1464" y="361.6" textLength="12.2" clip-path="url(#breeze-ci-upgrade-line-14)">
</text><text class="breeze-ci-upgrade-r5" x="0" y="386" textLength="12.2" clip-path="url(#breeze-ci-upgrade-line-15)">│</text><text class="breeze-ci-upgrade-r4" x="24.4" y="386" textLength="85.4" clip-path="url(#breeze-ci-upgrade-line-15)">--draft</text><text class="breeze-ci-upgrade-r1" x="109.8" y="386" textLength="12.2" clip-path="url(#breeze-ci-upgrade-line-15)">/</text><text class="breeze-ci-upgrade-r4" x="122" y="386" textLength="122" clip-path="url(#breeze-ci-upgrade-line-15)">--no-draft</text><text class="breeze-ci-upgrade-r1" x="707.6" y="386" textLength="732" clip-path="url(#breeze-ci-upgrade-line-15)">Create&#160;the&#160;PR&#160;as&#160;a&#160;draft&#160;(useful&#160;for&#160;scheduled&#160;CI&#160;runs&#160;where</text><text class="breeze-ci-upgrade-r5" x="1451.8" y="386" textLength="12.2" clip-path="url(#breeze-ci-upgrade-line-15)">│</text><text class="breeze-ci-upgrade-r1" x="1464" y="386" textLength="12.2" clip-path="url(#breeze-ci-upgrade-line-15)">
</text><text class="breeze-ci-upgrade-r5" x="0" y="410.4" textLength="12.2" clip-path="url(#breeze-ci-upgrade-line-16)">│</text><text class="breeze-ci-upgrade-r1" x="707.6" y="410.4" textLength="390.4" clip-path="url(#breeze-ci-upgrade-line-16)">a&#160;human&#160;undrafts&#160;to&#160;trigger&#160;CI)&#160;</text><text class="breeze-ci-upgrade-r5" x="1098" y="410.4" textLength="231.8" clip-path="url(#breeze-ci-upgrade-line-16)">[default:&#160;no-draft]</text><text class="breeze-ci-upgrade-r5" x="1451.8" y="410.4" textLength="12.2" clip-path="url(#breeze-ci-upgrade-line-16)">│</text><text class="breeze-ci-upgrade-r1" x="1464" y="410.4" textLength="12.2" clip-path="url(#breeze-ci-upgrade-line-16)">
</text><text class="breeze-ci-upgrade-r5" x="0" y="434.8" textLength="12.2" clip-path="url(#breeze-ci-upgrade-line-17)">│</text><text class="breeze-ci-upgrade-r4" x="24.4" y="434.8" textLength="195.2" clip-path="url(#breeze-ci-upgrade-line-17)">--switch-to-base</text><text class="breeze-ci-upgrade-r1" x="219.6" y="434.8" textLength="12.2" clip-path="url(#breeze-ci-upgrade-line-17)">/</text><text class="breeze-ci-upgrade-r4" x="231.8" y="434.8" textLength="231.8" clip-path="url(#breeze-ci-upgrade-line-17)">--no-switch-to-base</text><text class="breeze-ci-upgrade-r1" x="707.6" y="434.8" textLength="732" clip-path="url(#breeze-ci-upgrade-line-17)">Automatically&#160;switch&#160;to&#160;the&#160;base&#160;branch&#160;if&#160;not&#160;already&#160;on&#160;it</text><text class="breeze-ci-upgrade-r5" x="1451.8" y="434.8" textLength="12.2" clip-path="url(#breeze-ci-upgrade-line-17)">│</text><text class="breeze-ci-upgrade-r1" x="1464" y="434.8" textLength="12.2" clip-path="url(#breeze-ci-upgrade-line-17)">
</text><text class="breeze-ci-upgrade-r5" x="0" y="459.2" textLength="12.2" clip-path="url(#breeze-ci-upgrade-line-18)">│</text><text class="breeze-ci-upgrade-r1" x="707.6" y="459.2" textLength="732" clip-path="url(#breeze-ci-upgrade-line-18)">(if&#160;not&#160;specified,&#160;will&#160;ask)&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;</text><text class="breeze-ci-upgrade-r5" x="1451.8" y="459.2" textLength="12.2" clip-path="url(#breeze-ci-upgrade-line-18)">│</text><text class="breeze-ci-upgrade-r1" x="1464" y="459.2" textLength="12.2" clip-path="url(#breeze-ci-upgrade-line-18)">
</text><text class="breeze-ci-upgrade-r5" x="0" y="483.6" textLength="12.2" clip-path="url(#breeze-ci-upgrade-line-19)">│</text><text class="breeze-ci-upgrade-r4" x="24.4" y="483.6" textLength="658.8" clip-path="url(#breeze-ci-upgrade-line-19)">--airflow-site&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;</text><text class="breeze-ci-upgrade-r1" x="707.6" y="483.6" textLength="732" clip-path="url(#breeze-ci-upgrade-line-19)">Path&#160;to&#160;airflow-site&#160;checkout&#160;for&#160;publishing&#160;K8s&#160;schemas&#160;&#160;&#160;&#160;</text><text class="breeze-ci-upgrade-r5" x="1451.8" y="483.6" textLength="12.2" clip-path="url(#breeze-ci-upgrade-line-19)">│</text><text class="breeze-ci-upgrade-r1" x="1464" y="483.6" textLength="12.2" clip-path="url(#breeze-ci-upgrade-line-19)">
</text><text class="breeze-ci-upgrade-r5" x="0" y="508" textLength="12.2" clip-path="url(#breeze-ci-upgrade-line-20)">│</text><text class="breeze-ci-upgrade-r5" x="707.6" y="508" textLength="317.2" clip-path="url(#breeze-ci-upgrade-line-20)">[default:&#160;../airflow-site]</text><text class="breeze-ci-upgrade-r6" x="1037" y="508" textLength="134.2" clip-path="url(#breeze-ci-upgrade-line-20)">(DIRECTORY)</text><text class="breeze-ci-upgrade-r5" x="1451.8" y="508" textLength="12.2" clip-path="url(#breeze-ci-upgrade-line-20)">│</text><text class="breeze-ci-upgrade-r1" x="1464" y="508" textLength="12.2" clip-path="url(#breeze-ci-upgrade-line-20)">
</text><text class="breeze-ci-upgrade-r5" x="0" y="532.4" textLength="12.2" clip-path="url(#breeze-ci-upgrade-line-21)">│</text><text class="breeze-ci-upgrade-r4" x="24.4" y="532.4" textLength="658.8" clip-path="url(#breeze-ci-upgrade-line-21)">--force-k8s-schema-sync&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;</text><text class="breeze-ci-upgrade-r1" x="707.6" y="532.4" textLength="732" clip-path="url(#breeze-ci-upgrade-line-21)">Force&#160;syncing&#160;K8s&#160;schemas&#160;to&#160;airflow-site&#160;even&#160;if&#160;all&#160;&#160;&#160;&#160;&#160;&#160;&#160;</text><text class="breeze-ci-upgrade-r5" x="1451.8" y="532.4" textLength="12.2" clip-path="url(#breeze-ci-upgrade-line-21)">│</text><text class="breeze-ci-upgrade-r1" x="1464" y="532.4" textLength="12.2" clip-path="url(#breeze-ci-upgrade-line-21)">
</text><text class="breeze-ci-upgrade-r5" x="0" y="556.8" textLength="12.2" clip-path="url(#breeze-ci-upgrade-line-22)">│</text><text class="breeze-ci-upgrade-r1" x="707.6" y="556.8" textLength="732" clip-path="url(#breeze-ci-upgrade-line-22)">versions&#160;appear&#160;published&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;</text><text class="breeze-ci-upgrade-r5" x="1451.8" y="556.8" textLength="12.2" clip-path="url(#breeze-ci-upgrade-line-22)">│</text><text class="breeze-ci-upgrade-r1" x="1464" y="556.8" textLength="12.2" clip-path="url(#breeze-ci-upgrade-line-22)">
</text><text class="breeze-ci-upgrade-r5" x="0" y="581.2" textLength="12.2" clip-path="url(#breeze-ci-upgrade-line-23)">│</text><text class="breeze-ci-upgrade-r4" x="24.4" y="581.2" textLength="305" clip-path="url(#breeze-ci-upgrade-line-23)">--latest-package-managers</text><text class="breeze-ci-upgrade-r1" x="329.4" y="581.2" textLength="12.2" clip-path="url(#breeze-ci-upgrade-line-23)">/</text><text class="breeze-ci-upgrade-r4" x="341.6" y="581.2" textLength="341.6" clip-path="url(#breeze-ci-upgrade-line-23)">--no-latest-package-managers</text><text class="breeze-ci-upgrade-r1" x="707.6" y="581.2" textLength="732" clip-path="url(#breeze-ci-upgrade-line-23)">Bypass&#160;cooldown&#160;when&#160;upgrading&#160;package&#160;managers&#160;(pip&#160;and&#160;uv)</text><text class="breeze-ci-upgrade-r5" x="1451.8" y="581.2" textLength="12.2" clip-path="url(#breeze-ci-upgrade-line-23)">│</text><text class="breeze-ci-upgrade-r1" x="1464" y="581.2" textLength="12.2" clip-path="url(#breeze-ci-upgrade-line-23)">
</text><text class="breeze-ci-upgrade-r5" x="0" y="605.6" textLength="12.2" clip-path="url(#breeze-ci-upgrade-line-24)">│</text><text class="breeze-ci-upgrade-r1" x="707.6" y="605.6" textLength="353.8" clip-path="url(#breeze-ci-upgrade-line-24)">to&#160;use&#160;their&#160;latest&#160;versions&#160;</text><text class="breeze-ci-upgrade-r5" x="1061.4" y="605.6" textLength="122" clip-path="url(#breeze-ci-upgrade-line-24)">[default:&#160;</text><text class="breeze-ci-upgrade-r5" x="1451.8" y="605.6" textLength="12.2" clip-path="url(#breeze-ci-upgrade-line-24)">│</text><text class="breeze-ci-upgrade-r1" x="1464" y="605.6" textLength="12.2" clip-path="url(#breeze-ci-upgrade-line-24)">
</text><text class="breeze-ci-upgrade-r5" x="0" y="630" textLength="12.2" clip-path="url(#breeze-ci-upgrade-line-25)">│</text><text class="breeze-ci-upgrade-r5" x="707.6" y="630" textLength="329.4" clip-path="url(#breeze-ci-upgrade-line-25)">no-latest-package-managers]</text><text class="breeze-ci-upgrade-r5" x="1451.8" y="630" textLength="12.2" clip-path="url(#breeze-ci-upgrade-line-25)">│</text><text class="breeze-ci-upgrade-r1" x="1464" y="630" textLength="12.2" clip-path="url(#breeze-ci-upgrade-line-25)">
</text><text class="breeze-ci-upgrade-r5" x="0" y="654.4" textLength="12.2" clip-path="url(#breeze-ci-upgrade-line-26)">│</text><text class="breeze-ci-upgrade-r4" x="24.4" y="654.4" textLength="658.8" clip-path="url(#breeze-ci-upgrade-line-26)">--github-token&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;</text><text class="breeze-ci-upgrade-r1" x="707.6" y="654.4" textLength="512.4" clip-path="url(#breeze-ci-upgrade-line-26)">The&#160;token&#160;used&#160;to&#160;authenticate&#160;to&#160;GitHub.&#160;</text><text class="breeze-ci-upgrade-r6" x="1220" y="654.4" textLength="73.2" clip-path="url(#breeze-ci-upgrade-line-26)">(TEXT)</text><text class="breeze-ci-upgrade-r5" x="1451.8" y="654.4" textLength="12.2" clip-path="url(#breeze-ci-upgrade-line-26)">│</text><text class="breeze-ci-upgrade-r1" x="1464" y="654.4" textLength="12.2" clip-path="url(#breeze-ci-upgrade-line-26)">
</text><text class="breeze-ci-upgrade-r5" x="0" y="678.8" textLength="1464" clip-path="url(#breeze-ci-upgrade-line-27)">╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯</text><text class="breeze-ci-upgrade-r1" x="1464" y="678.8" textLength="12.2" clip-path="url(#breeze-ci-upgrade-line-27)">
</text><text class="breeze-ci-upgrade-r5" x="0" y="703.2" textLength="24.4" clip-path="url(#breeze-ci-upgrade-line-28)">╭─</text><text class="breeze-ci-upgrade-r5" x="24.4" y="703.2" textLength="183" clip-path="url(#breeze-ci-upgrade-line-28)">&#160;Upgrade&#160;steps&#160;</text><text class="breeze-ci-upgrade-r5" x="207.4" y="703.2" textLength="1232.2" clip-path="url(#breeze-ci-upgrade-line-28)">─────────────────────────────────────────────────────────────────────────────────────────────────────</text><text class="breeze-ci-upgrade-r5" x="1439.6" y="703.2" textLength="24.4" clip-path="url(#breeze-ci-upgrade-line-28)">─╮</text><text class="breeze-ci-upgrade-r1" x="1464" y="703.2" textLength="12.2" clip-path="url(#breeze-ci-upgrade-line-28)">
</text><text class="breeze-ci-upgrade-r5" x="0" y="727.6" textLength="12.2" clip-path="url(#breeze-ci-upgrade-line-29)">│</text><text class="breeze-ci-upgrade-r4" x="24.4" y="727.6" textLength="146.4" clip-path="url(#breeze-ci-upgrade-line-29)">--autoupdate</text><text class="breeze-ci-upgrade-r1" x="170.8" y="727.6" textLength="12.2" clip-path="url(#breeze-ci-upgrade-line-29)">/</text><text class="breeze-ci-upgrade-r4" x="183" y="727.6" textLength="183" clip-path="url(#breeze-ci-upgrade-line-29)">--no-autoupdate</text><text class="breeze-ci-upgrade-r1" x="744.2" y="727.6" textLength="549" clip-path="url(#breeze-ci-upgrade-line-29)">Run&#160;prek&#160;autoupdate&#160;to&#160;update&#160;hook&#160;revisions&#160;</text><text class="breeze-ci-upgrade-r5" x="1293.2" y="727.6" textLength="122" clip-path="url(#breeze-ci-upgrade-line-29)">[default:&#160;</text><text class="breeze-ci-upgrade-r5" x="1451.8" y="727.6" textLength="12.2" clip-path="url(#breeze-ci-upgrade-line-29)">│</text><text class="breeze-ci-upgrade-r1" x="1464" y="727.6" textLength="12.2" clip-path="url(#breeze-ci-upgrade-line-29)">
</text><text class="breeze-ci-upgrade-r5" x="0" y="752" textLength="12.2" clip-path="url(#breeze-ci-upgrade-line-30)">│</text><text class="breeze-ci-upgrade-r5" x="744.2" y="752" textLength="134.2" clip-path="url(#breeze-ci-upgrade-line-30)">autoupdate]</text><text class="breeze-ci-upgrade-r5" x="1451.8" y="752" textLength="12.2" clip-path="url(#breeze-ci-upgrade-line-30)">│</text><text class="breeze-ci-upgrade-r1" x="1464" y="752" textLength="12.2" clip-path="url(#breeze-ci-upgrade-line-30)">
</text><text class="breeze-ci-upgrade-r5" x="0" y="776.4" textLength="12.2" clip-path="url(#breeze-ci-upgrade-line-31)">│</text><text class="breeze-ci-upgrade-r4" x="24.4" y="776.4" textLength="329.4" clip-path="url(#breeze-ci-upgrade-line-31)">--update-chart-dependencies</text><text class="breeze-ci-upgrade-r1" x="353.8" y="776.4" textLength="12.2" clip-path="url(#breeze-ci-upgrade-line-31)">/</text><text class="breeze-ci-upgrade-r4" x="366" y="776.4" textLength="353.8" clip-path="url(#breeze-ci-upgrade-line-31)">--no-update-chart-dependencie</text><text class="breeze-ci-upgrade-r1" x="744.2" y="776.4" textLength="695.4" clip-path="url(#breeze-ci-upgrade-line-31)">Run&#160;update-chart-dependencies&#160;to&#160;update&#160;Helm&#160;chart&#160;&#160;&#160;&#160;&#160;&#160;&#160;</text><text class="breeze-ci-upgrade-r5" x="1451.8" y="776.4" textLength="12.2" clip-path="url(#breeze-ci-upgrade-line-31)">│</text><text class="breeze-ci-upgrade-r1" x="1464" y="776.4" textLength="12.2" clip-path="url(#breeze-ci-upgrade-line-31)">
</text><text class="breeze-ci-upgrade-r5" x="0" y="800.8" textLength="12.2" clip-path="url(#breeze-ci-upgrade-line-32)">│</text><text class="breeze-ci-upgrade-r4" x="24.4" y="800.8" textLength="12.2" clip-path="url(#breeze-ci-upgrade-line-32)">s</text><text class="breeze-ci-upgrade-r1" x="744.2" y="800.8" textLength="158.6" clip-path="url(#breeze-ci-upgrade-line-32)">dependencies&#160;</text><text class="breeze-ci-upgrade-r5" x="902.8" y="800.8" textLength="439.2" clip-path="url(#breeze-ci-upgrade-line-32)">[default:&#160;update-chart-dependencies]</text><text class="breeze-ci-upgrade-r5" x="1451.8" y="800.8" textLength="12.2" clip-path="url(#breeze-ci-upgrade-line-32)">│</text><text class="breeze-ci-upgrade-r1" x="1464" y="800.8" textLength="12.2" clip-path="url(#breeze-ci-upgrade-line-32)">
</text><text class="breeze-ci-upgrade-r5" x="0" y="825.2" textLength="12.2" clip-path="url(#breeze-ci-upgrade-line-33)">│</text><text class="breeze-ci-upgrade-r4" x="24.4" y="825.2" textLength="341.6" clip-path="url(#breeze-ci-upgrade-line-33)">--upgrade-important-versions</text><text class="breeze-ci-upgrade-r1" x="366" y="825.2" textLength="12.2" clip-path="url(#breeze-ci-upgrade-line-33)">/</text><text class="breeze-ci-upgrade-r4" x="378.2" y="825.2" textLength="341.6" clip-path="url(#breeze-ci-upgrade-line-33)">--no-upgrade-important-versi</text><text class="breeze-ci-upgrade-r1" x="744.2" y="825.2" textLength="695.4" clip-path="url(#breeze-ci-upgrade-line-33)">Run&#160;upgrade-important-versions&#160;to&#160;bump&#160;key&#160;dependency&#160;&#160;&#160;&#160;</text><text class="breeze-ci-upgrade-r5" x="1451.8" y="825.2" textLength="12.2" clip-path="url(#breeze-ci-upgrade-line-33)">│</text><text class="breeze-ci-upgrade-r1" x="1464" y="825.2" textLength="12.2" clip-path="url(#breeze-ci-upgrade-line-33)">
</text><text class="breeze-ci-upgrade-r5" x="0" y="849.6" textLength="12.2" clip-path="url(#breeze-ci-upgrade-line-34)">│</text><text class="breeze-ci-upgrade-r4" x="24.4" y="849.6" textLength="36.6" clip-path="url(#breeze-ci-upgrade-line-34)">ons</text><text class="breeze-ci-upgrade-r1" x="744.2" y="849.6" textLength="109.8" clip-path="url(#breeze-ci-upgrade-line-34)">versions&#160;</text><text class="breeze-ci-upgrade-r5" x="854" y="849.6" textLength="451.4" clip-path="url(#breeze-ci-upgrade-line-34)">[default:&#160;upgrade-important-versions]</text><text class="breeze-ci-upgrade-r5" x="1451.8" y="849.6" textLength="12.2" clip-path="url(#breeze-ci-upgrade-line-34)">│</text><text class="breeze-ci-upgrade-r1" x="1464" y="849.6" textLength="12.2" clip-path="url(#breeze-ci-upgrade-line-34)">
</text><text class="breeze-ci-upgrade-r5" x="0" y="874" textLength="12.2" clip-path="url(#breeze-ci-upgrade-line-35)">│</text><text class="breeze-ci-upgrade-r4" x="24.4" y="874" textLength="329.4" clip-path="url(#breeze-ci-upgrade-line-35)">--upgrade-dependency-floors</text><text class="breeze-ci-upgrade-r1" x="353.8" y="874" textLength="12.2" clip-path="url(#breeze-ci-upgrade-line-35)">/</text><text class="breeze-ci-upgrade-r4" x="366" y="874" textLength="353.8" clip-path="url(#breeze-ci-upgrade-line-35)">--no-upgrade-dependency-floor</text><text class="breeze-ci-upgrade-r1" x="744.2" y="874" textLength="695.4" clip-path="url(#breeze-ci-upgrade-line-35)">Raise&#160;the&#160;floors&#160;of&#160;the&#160;curated&#160;dependencies&#160;in&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;</text><text class="breeze-ci-upgrade-r5" x="1451.8" y="874" textLength="12.2" clip-path="url(#breeze-ci-upgrade-line-35)">│</text><text class="breeze-ci-upgrade-r1" x="1464" y="874" textLength="12.2" clip-path="url(#breeze-ci-upgrade-line-35)">
</text><text class="breeze-ci-upgrade-r5" x="0" y="898.4" textLength="12.2" clip-path="url(#breeze-ci-upgrade-line-36)">│</text><text class="breeze-ci-upgrade-r4" x="24.4" y="898.4" textLength="12.2" clip-path="url(#breeze-ci-upgrade-line-36)">s</text><text class="breeze-ci-upgrade-r1" x="744.2" y="898.4" textLength="402.6" clip-path="url(#breeze-ci-upgrade-line-36)">[tool.airflow.dependency-floors]&#160;</text><text class="breeze-ci-upgrade-r5" x="1146.8" y="898.4" textLength="122" clip-path="url(#breeze-ci-upgrade-line-36)">[default:&#160;</text><text class="breeze-ci-upgrade-r5" x="1451.8" y="898.4" textLength="12.2" clip-path="url(#breeze-ci-upgrade-line-36)">│</text><text class="breeze-ci-upgrade-r1" x="1464" y="898.4" textLength="12.2" clip-path="url(#breeze-ci-upgrade-line-36)">
</text><text class="breeze-ci-upgrade-r5" x="0" y="922.8" textLength="12.2" clip-path="url(#breeze-ci-upgrade-line-37)">│</text><text class="breeze-ci-upgrade-r5" x="744.2" y="922.8" textLength="317.2" clip-path="url(#breeze-ci-upgrade-line-37)">upgrade-dependency-floors]</text><text class="breeze-ci-upgrade-r5" x="1451.8" y="922.8" textLength="12.2" clip-path="url(#breeze-ci-upgrade-line-37)">│</text><text class="breeze-ci-upgrade-r1" x="1464" y="922.8" textLength="12.2" clip-path="url(#breeze-ci-upgrade-line-37)">
</text><text class="breeze-ci-upgrade-r5" x="0" y="947.2" textLength="12.2" clip-path="url(#breeze-ci-upgrade-line-38)">│</text><text class="breeze-ci-upgrade-r4" x="24.4" y="947.2" textLength="244" clip-path="url(#breeze-ci-upgrade-line-38)">--mirror-base-images</text><text class="breeze-ci-upgrade-r1" x="268.4" y="947.2" textLength="12.2" clip-path="url(#breeze-ci-upgrade-line-38)">/</text><text class="breeze-ci-upgrade-r4" x="280.6" y="947.2" textLength="280.6" clip-path="url(#breeze-ci-upgrade-line-38)">--no-mirror-base-images</text><text class="breeze-ci-upgrade-r1" x="744.2" y="947.2" textLength="695.4" clip-path="url(#breeze-ci-upgrade-line-38)">Mirror&#160;the&#160;latest&#160;hardened&#160;base&#160;images&#160;(a&#160;workflow&#160;run&#160;on</text><text class="breeze-ci-upgrade-r5" x="1451.8" y="947.2" textLength="12.2" clip-path="url(#breeze-ci-upgrade-line-38)">│</text><text class="breeze-ci-upgrade-r1" x="1464" y="947.2" textLength="12.2" clip-path="url(#breeze-ci-upgrade-line-38)">
</text><text class="breeze-ci-upgrade-r5" x="0" y="971.6" textLength="12.2" clip-path="url(#breeze-ci-upgrade-line-39)">│</text><text class="breeze-ci-upgrade-r1" x="744.2" y="971.6" textLength="695.4" clip-path="url(#breeze-ci-upgrade-line-39)">main)&#160;before&#160;upgrading&#160;important&#160;versions,&#160;so&#160;the&#160;upgrade</text><text class="breeze-ci-upgrade-r5" x="1451.8" y="971.6" textLength="12.2" clip-path="url(#breeze-ci-upgrade-line-39)">│</text><text class="breeze-ci-upgrade-r1" x="1464" y="971.6" textLength="12.2" clip-path="url(#breeze-ci-upgrade-line-39)">
</text><text class="breeze-ci-upgrade-r5" x="0" y="996" textLength="12.2" clip-path="url(#breeze-ci-upgrade-line-40)">│</text><text class="breeze-ci-upgrade-r1" x="744.2" y="996" textLength="695.4" clip-path="url(#breeze-ci-upgrade-line-40)">can&#160;pin&#160;them.&#160;Needs&#160;gh&#160;credentials&#160;that&#160;may&#160;dispatch&#160;&#160;&#160;&#160;&#160;</text><text class="breeze-ci-upgrade-r5" x="1451.8" y="996" textLength="12.2" clip-path="url(#breeze-ci-upgrade-line-40)">│</text><text class="breeze-ci-upgrade-r1" x="1464" y="996" textLength="12.2" clip-path="url(#breeze-ci-upgrade-line-40)">
</text><text class="breeze-ci-upgrade-r5" x="0" y="1020.4" textLength="12.2" clip-path="url(#breeze-ci-upgrade-line-41)">│</text><text class="breeze-ci-upgrade-r1" x="744.2" y="1020.4" textLength="305" clip-path="url(#breeze-ci-upgrade-line-41)">apache/airflow&#160;workflows&#160;</text><text class="breeze-ci-upgrade-r5" x="1049.2" y="1020.4" textLength="353.8" clip-path="url(#breeze-ci-upgrade-line-41)">[default:&#160;mirror-base-images]</text><text class="breeze-ci-upgrade-r5" x="1451.8" y="1020.4" textLength="12.2" clip-path="url(#breeze-ci-upgrade-line-41)">│</text><text class="breeze-ci-upgrade-r1" x="1464" y="1020.4" textLength="12.2" clip-path="url(#breeze-ci-upgrade-line-41)">
</text><text class="breeze-ci-upgrade-r5" x="0" y="1044.8" textLength="12.2" clip-path="url(#breeze-ci-upgrade-line-42)">│</text><text class="breeze-ci-upgrade-r4" x="24.4" y="1044.8" textLength="195.2" clip-path="url(#breeze-ci-upgrade-line-42)">--update-uv-lock</text><text class="breeze-ci-upgrade-r1" x="219.6" y="1044.8" textLength="12.2" clip-path="url(#breeze-ci-upgrade-line-42)">/</text><text class="breeze-ci-upgrade-r4" x="231.8" y="1044.8" textLength="231.8" clip-path="url(#breeze-ci-upgrade-line-42)">--no-update-uv-lock</text><text class="breeze-ci-upgrade-r1" x="744.2" y="1044.8" textLength="695.4" clip-path="url(#breeze-ci-upgrade-line-42)">Run&#160;update-uv-lock&#160;to&#160;regenerate&#160;uv.lock&#160;with&#160;latest&#160;&#160;&#160;&#160;&#160;</text><text class="breeze-ci-upgrade-r5" x="1451.8" y="1044.8" textLength="12.2" clip-path="url(#breeze-ci-upgrade-line-42)">│</text><text class="breeze-ci-upgrade-r1" x="1464" y="1044.8" textLength="12.2" clip-path="url(#breeze-ci-upgrade-line-42)">
</text><text class="breeze-ci-upgrade-r5" x="0" y="1069.2" textLength="12.2" clip-path="url(#breeze-ci-upgrade-line-43)">│</text><text class="breeze-ci-upgrade-r1" x="744.2" y="1069.2" textLength="695.4" clip-path="url(#breeze-ci-upgrade-line-43)">resolutions&#160;inside&#160;Breeze&#160;CI&#160;image,&#160;then&#160;regenerate&#160;the&#160;&#160;</text><text class="breeze-ci-upgrade-r5" x="1451.8" y="1069.2" textLength="12.2" clip-path="url(#breeze-ci-upgrade-line-43)">│</text><text class="breeze-ci-upgrade-r1" x="1464" y="1069.2" textLength="12.2" clip-path="url(#breeze-ci-upgrade-line-43)">
</text><text class="breeze-ci-upgrade-r5" x="0" y="1093.6" textLength="12.2" clip-path="url(#breeze-ci-upgrade-line-44)">│</text><text class="breeze-ci-upgrade-r1" x="744.2" y="1093.6" textLength="695.4" clip-path="url(#breeze-ci-upgrade-line-44)">API&#160;datamodels&#160;the&#160;resolved&#160;code&#160;generator&#160;stamps&#160;its&#160;&#160;&#160;&#160;</text><text class="breeze-ci-upgrade-r5" x="1451.8" y="1093.6" textLength="12.2" clip-path="url(#breeze-ci-upgrade-line-44)">│</text><text class="breeze-ci-upgrade-r1" x="1464" y="1093.6" textLength="12.2" clip-path="url(#breeze-ci-upgrade-line-44)">
</text><text class="breeze-ci-upgrade-r5" x="0" y="1118" textLength="12.2" clip-path="url(#breeze-ci-upgrade-line-45)">│</text><text class="breeze-ci-upgrade-r1" x="744.2" y="1118" textLength="158.6" clip-path="url(#breeze-ci-upgrade-line-45)">version&#160;into&#160;</text><text class="breeze-ci-upgrade-r5" x="902.8" y="1118" textLength="305" clip-path="url(#breeze-ci-upgrade-line-45)">[default:&#160;update-uv-lock]</text><text class="breeze-ci-upgrade-r5" x="1451.8" y="1118" textLength="12.2" clip-path="url(#breeze-ci-upgrade-line-45)">│</text><text class="breeze-ci-upgrade-r1" x="1464" y="1118" textLength="12.2" clip-path="url(#breeze-ci-upgrade-line-45)">
</text><text class="breeze-ci-upgrade-r5" x="0" y="1142.4" textLength="12.2" clip-path="url(#breeze-ci-upgrade-line-46)">│</text><text class="breeze-ci-upgrade-r4" x="24.4" y="1142.4" textLength="207.4" clip-path="url(#breeze-ci-upgrade-line-46)">--k8s-schema-sync</text><text class="breeze-ci-upgrade-r1" x="231.8" y="1142.4" textLength="12.2" clip-path="url(#breeze-ci-upgrade-line-46)">/</text><text class="breeze-ci-upgrade-r4" x="244" y="1142.4" textLength="244" clip-path="url(#breeze-ci-upgrade-line-46)">--no-k8s-schema-sync</text><text class="breeze-ci-upgrade-r1" x="744.2" y="1142.4" textLength="463.6" clip-path="url(#breeze-ci-upgrade-line-46)">Sync&#160;K8s&#160;JSON&#160;schemas&#160;to&#160;airflow-site&#160;</text><text class="breeze-ci-upgrade-r5" x="1207.8" y="1142.4" textLength="122" clip-path="url(#breeze-ci-upgrade-line-46)">[default:&#160;</text><text class="breeze-ci-upgrade-r5" x="1451.8" y="1142.4" textLength="12.2" clip-path="url(#breeze-ci-upgrade-line-46)">│</text><text class="breeze-ci-upgrade-r1" x="1464" y="1142.4" textLength="12.2" clip-path="url(#breeze-ci-upgrade-line-46)">
</text><text class="breeze-ci-upgrade-r5" x="0" y="1166.8" textLength="12.2" clip-path="url(#breeze-ci-upgrade-line-47)">│</text><text class="breeze-ci-upgrade-r5" x="744.2" y="1166.8" textLength="195.2" clip-path="url(#breeze-ci-upgrade-line-47)">k8s-schema-sync]</text><text class="breeze-ci-upgrade-r5" x="1451.8" y="1166.8" textLength="12.2" clip-path="url(#breeze-ci-upgrade-line-47)">│</text><text class="breeze-ci-upgrade-r1" x="1464" y="1166.8" textLength="12.2" clip-path="url(#breeze-ci-upgrade-line-47)">
</text><text class="breeze-ci-upgrade-r5" x="0" y="1191.2" textLength="1464" clip-path="url(#breeze-ci-upgrade-line-48)">╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯</text><text class="breeze-ci-upgrade-r1" x="1464" y="1191.2" textLength="12.2" clip-path="url(#breeze-ci-upgrade-line-48)">
</text><text class="breeze-ci-upgrade-r5" x="0" y="1215.6" textLength="24.4" clip-path="url(#breeze-ci-upgrade-line-49)">╭─</text><text class="breeze-ci-upgrade-r5" x="24.4" y="1215.6" textLength="195.2" clip-path="url(#breeze-ci-upgrade-line-49)">&#160;Common&#160;options&#160;</text><text class="breeze-ci-upgrade-r5" x="219.6" y="1215.6" textLength="1220" clip-path="url(#breeze-ci-upgrade-line-49)">────────────────────────────────────────────────────────────────────────────────────────────────────</text><text class="breeze-ci-upgrade-r5" x="1439.6" y="1215.6" textLength="24.4" clip-path="url(#breeze-ci-upgrade-line-49)">─╮</text><text class="breeze-ci-upgrade-r1" x="1464" y="1215.6" textLength="12.2" clip-path="url(#breeze-ci-upgrade-line-49)">
</text><text class="breeze-ci-upgrade-r5" x="0" y="1240" textLength="12.2" clip-path="url(#breeze-ci-upgrade-line-50)">│</text><text class="breeze-ci-upgrade-r4" x="24.4" y="1240" textLength="109.8" clip-path="url(#breeze-ci-upgrade-line-50)">--answer&#160;</text><text class="breeze-ci-upgrade-r7" x="158.6" y="1240" textLength="24.4" clip-path="url(#breeze-ci-upgrade-line-50)">-a</text><text class="breeze-ci-upgrade-r1" x="207.4" y="1240" textLength="329.4" clip-path="url(#breeze-ci-upgrade-line-50)">Force&#160;answer&#160;to&#160;questions.&#160;</text><text class="breeze-ci-upgrade-r6" x="536.8" y="1240" textLength="353.8" clip-path="url(#breeze-ci-upgrade-line-50)">(y&#160;|&#160;n&#160;|&#160;q&#160;|&#160;yes&#160;|&#160;no&#160;|&#160;quit)</text><text class="breeze-ci-upgrade-r5" x="1451.8" y="1240" textLength="12.2" clip-path="url(#breeze-ci-upgrade-line-50)">│</text><text class="breeze-ci-upgrade-r1" x="1464" y="1240" textLength="12.2" clip-path="url(#breeze-ci-upgrade-line-50)">
</text><text class="breeze-ci-upgrade-r5" x="0" y="1264.4" textLength="12.2" clip-path="url(#breeze-ci-upgrade-line-51)">│</text><text class="breeze-ci-upgrade-r4" x="24.4" y="1264.4" textLength="109.8" clip-path="url(#breeze-ci-upgrade-line-51)">--verbose</text><text class="breeze-ci-upgrade-r7" x="158.6" y="1264.4" textLength="24.4" clip-path="url(#breeze-ci-upgrade-line-51)">-v</text><text class="breeze-ci-upgrade-r1" x="207.4" y="1264.4" textLength="585.6" clip-path="url(#breeze-ci-upgrade-line-51)">Print&#160;verbose&#160;information&#160;about&#160;performed&#160;steps.</text><text class="breeze-ci-upgrade-r5" x="1451.8" y="1264.4" textLength="12.2" clip-path="url(#breeze-ci-upgrade-line-51)">│</text><text class="breeze-ci-upgrade-r1" x="1464" y="1264.4" textLength="12.2" clip-path="url(#breeze-ci-upgrade-line-51)">
</text><text class="breeze-ci-upgrade-r5" x="0" y="1288.8" textLength="12.2" clip-path="url(#breeze-ci-upgrade-line-52)">│</text><text class="breeze-ci-upgrade-r4" x="24.4" y="1288.8" textLength="109.8" clip-path="url(#breeze-ci-upgrade-line-52)">--dry-run</text><text class="breeze-ci-upgrade-r7" x="158.6" y="1288.8" textLength="24.4" clip-path="url(#breeze-ci-upgrade-line-52)">-D</text><text class="breeze-ci-upgrade-r1" x="207.4" y="1288.8" textLength="719.8" clip-path="url(#breeze-ci-upgrade-line-52)">If&#160;dry-run&#160;is&#160;set,&#160;commands&#160;are&#160;only&#160;printed,&#160;not&#160;executed.</text><text class="breeze-ci-upgrade-r5" x="1451.8" y="1288.8" textLength="12.2" clip-path="url(#breeze-ci-upgrade-line-52)">│</text><text class="breeze-ci-upgrade-r1" x="1464" y="1288.8" textLength="12.2" clip-path="url(#breeze-ci-upgrade-line-52)">
</text><text class="breeze-ci-upgrade-r5" x="0" y="1313.2" textLength="12.2" clip-path="url(#breeze-ci-upgrade-line-53)">│</text><text class="breeze-ci-upgrade-r4" x="24.4" y="1313.2" textLength="109.8" clip-path="url(#breeze-ci-upgrade-line-53)">--help&#160;&#160;&#160;</text><text class="breeze-ci-upgrade-r7" x="158.6" y="1313.2" textLength="24.4" clip-path="url(#breeze-ci-upgrade-line-53)">-h</text><text class="breeze-ci-upgrade-r1" x="207.4" y="1313.2" textLength="329.4" clip-path="url(#breeze-ci-upgrade-line-53)">Show&#160;this&#160;message&#160;and&#160;exit.</text><text class="breeze-ci-upgrade-r5" x="1451.8" y="1313.2" textLength="12.2" clip-path="url(#breeze-ci-upgrade-line-53)">│</text><text class="breeze-ci-upgrade-r1" x="1464" y="1313.2" textLength="12.2" clip-path="url(#breeze-ci-upgrade-line-53)">
</text><text class="breeze-ci-upgrade-r5" x="0" y="1337.6" textLength="1464" clip-path="url(#breeze-ci-upgrade-line-54)">╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯</text><text class="breeze-ci-upgrade-r1" x="1464" y="1337.6" textLength="12.2" clip-path="url(#breeze-ci-upgrade-line-54)">
</text>
</g>
</g>

Before

Width:  |  Height:  |  Size: 43 KiB

After

Width:  |  Height:  |  Size: 50 KiB

+1 -1
View File
@@ -1 +1 @@
1d72b501b2cc60a65a2788e1c467efe5
36a6bfb20d5908eaf319d41aabb4899a
@@ -1,4 +1,4 @@
<svg class="rich-terminal" viewBox="0 0 1482 1318.8" xmlns="http://www.w3.org/2000/svg">
<svg class="rich-terminal" viewBox="0 0 1482 1343.1999999999998" xmlns="http://www.w3.org/2000/svg">
<!-- Generated with Rich https://www.textualize.io -->
<style>
@@ -43,7 +43,7 @@
<defs>
<clipPath id="breeze-setup-check-all-params-in-groups-clip-terminal">
<rect x="0" y="0" width="1463.0" height="1267.8" />
<rect x="0" y="0" width="1463.0" height="1292.1999999999998" />
</clipPath>
<clipPath id="breeze-setup-check-all-params-in-groups-line-0">
<rect x="0" y="1.5" width="1464" height="24.65"/>
@@ -198,9 +198,12 @@
<clipPath id="breeze-setup-check-all-params-in-groups-line-50">
<rect x="0" y="1221.5" width="1464" height="24.65"/>
</clipPath>
<clipPath id="breeze-setup-check-all-params-in-groups-line-51">
<rect x="0" y="1245.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="1316.8" rx="8"/><text class="breeze-setup-check-all-params-in-groups-title" fill="#c5c8c6" text-anchor="middle" x="740" y="27">Command:&#160;setup&#160;check-all-params-in-groups</text>
<rect fill="#292929" stroke="rgba(255,255,255,0.35)" stroke-width="1" x="1" y="1" width="1480" height="1341.2" rx="8"/><text class="breeze-setup-check-all-params-in-groups-title" fill="#c5c8c6" text-anchor="middle" x="740" y="27">Command:&#160;setup&#160;check-all-params-in-groups</text>
<g transform="translate(26,22)">
<circle cx="0" cy="0" r="7" fill="#ff5f57"/>
<circle cx="22" cy="0" r="7" fill="#febc2e"/>
@@ -255,13 +258,14 @@
</text><text class="breeze-setup-check-all-params-in-groups-r5" x="0" y="1044.8" textLength="12.2" clip-path="url(#breeze-setup-check-all-params-in-groups-line-42)">│</text><text class="breeze-setup-check-all-params-in-groups-r6" x="158.6" y="1044.8" textLength="1195.6" clip-path="url(#breeze-setup-check-all-params-in-groups-line-42)">testing:providers-integration-tests&#160;|&#160;testing:providers-tests&#160;|&#160;testing:python-api-client-tests&#160;|&#160;</text><text class="breeze-setup-check-all-params-in-groups-r5" x="1451.8" y="1044.8" textLength="12.2" clip-path="url(#breeze-setup-check-all-params-in-groups-line-42)">│</text><text class="breeze-setup-check-all-params-in-groups-r1" x="1464" y="1044.8" textLength="12.2" clip-path="url(#breeze-setup-check-all-params-in-groups-line-42)">
</text><text class="breeze-setup-check-all-params-in-groups-r5" x="0" y="1069.2" textLength="12.2" clip-path="url(#breeze-setup-check-all-params-in-groups-line-43)">│</text><text class="breeze-setup-check-all-params-in-groups-r6" x="158.6" y="1069.2" textLength="1281" clip-path="url(#breeze-setup-check-all-params-in-groups-line-43)">testing:system-tests&#160;|&#160;testing:task-sdk-integration-tests&#160;|&#160;testing:task-sdk-tests&#160;|&#160;testing:ui-e2e-tests</text><text class="breeze-setup-check-all-params-in-groups-r5" x="1451.8" y="1069.2" textLength="12.2" clip-path="url(#breeze-setup-check-all-params-in-groups-line-43)">│</text><text class="breeze-setup-check-all-params-in-groups-r1" x="1464" y="1069.2" textLength="12.2" clip-path="url(#breeze-setup-check-all-params-in-groups-line-43)">
</text><text class="breeze-setup-check-all-params-in-groups-r5" x="0" y="1093.6" textLength="12.2" clip-path="url(#breeze-setup-check-all-params-in-groups-line-44)">│</text><text class="breeze-setup-check-all-params-in-groups-r6" x="158.6" y="1093.6" textLength="1061.4" clip-path="url(#breeze-setup-check-all-params-in-groups-line-44)">|&#160;ui&#160;|&#160;ui:check-translation-completeness&#160;|&#160;ui:compile-assets&#160;|&#160;verify&#160;|&#160;workflow-run&#160;|&#160;</text><text class="breeze-setup-check-all-params-in-groups-r5" x="1451.8" y="1093.6" textLength="12.2" clip-path="url(#breeze-setup-check-all-params-in-groups-line-44)">│</text><text class="breeze-setup-check-all-params-in-groups-r1" x="1464" y="1093.6" textLength="12.2" clip-path="url(#breeze-setup-check-all-params-in-groups-line-44)">
</text><text class="breeze-setup-check-all-params-in-groups-r5" x="0" y="1118" textLength="12.2" clip-path="url(#breeze-setup-check-all-params-in-groups-line-45)">│</text><text class="breeze-setup-check-all-params-in-groups-r6" x="158.6" y="1118" textLength="1183.4" clip-path="url(#breeze-setup-check-all-params-in-groups-line-45)">workflow-run:publish-docs&#160;|&#160;workflow-run:release-constraints&#160;|&#160;workflow-run:sync-staging-to-main)</text><text class="breeze-setup-check-all-params-in-groups-r5" x="1451.8" y="1118" textLength="12.2" clip-path="url(#breeze-setup-check-all-params-in-groups-line-45)">│</text><text class="breeze-setup-check-all-params-in-groups-r1" x="1464" y="1118" textLength="12.2" clip-path="url(#breeze-setup-check-all-params-in-groups-line-45)">
</text><text class="breeze-setup-check-all-params-in-groups-r5" x="0" y="1142.4" textLength="1464" clip-path="url(#breeze-setup-check-all-params-in-groups-line-46)">╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯</text><text class="breeze-setup-check-all-params-in-groups-r1" x="1464" y="1142.4" textLength="12.2" clip-path="url(#breeze-setup-check-all-params-in-groups-line-46)">
</text><text class="breeze-setup-check-all-params-in-groups-r5" x="0" y="1166.8" textLength="24.4" clip-path="url(#breeze-setup-check-all-params-in-groups-line-47)">╭─</text><text class="breeze-setup-check-all-params-in-groups-r5" x="24.4" y="1166.8" textLength="195.2" clip-path="url(#breeze-setup-check-all-params-in-groups-line-47)">&#160;Common&#160;options&#160;</text><text class="breeze-setup-check-all-params-in-groups-r5" x="219.6" y="1166.8" textLength="1220" clip-path="url(#breeze-setup-check-all-params-in-groups-line-47)">────────────────────────────────────────────────────────────────────────────────────────────────────</text><text class="breeze-setup-check-all-params-in-groups-r5" x="1439.6" y="1166.8" textLength="24.4" clip-path="url(#breeze-setup-check-all-params-in-groups-line-47)">─╮</text><text class="breeze-setup-check-all-params-in-groups-r1" x="1464" y="1166.8" textLength="12.2" clip-path="url(#breeze-setup-check-all-params-in-groups-line-47)">
</text><text class="breeze-setup-check-all-params-in-groups-r5" x="0" y="1191.2" textLength="12.2" clip-path="url(#breeze-setup-check-all-params-in-groups-line-48)">│</text><text class="breeze-setup-check-all-params-in-groups-r4" x="24.4" y="1191.2" textLength="109.8" clip-path="url(#breeze-setup-check-all-params-in-groups-line-48)">--verbose</text><text class="breeze-setup-check-all-params-in-groups-r7" x="158.6" y="1191.2" textLength="24.4" clip-path="url(#breeze-setup-check-all-params-in-groups-line-48)">-v</text><text class="breeze-setup-check-all-params-in-groups-r1" x="207.4" y="1191.2" textLength="585.6" clip-path="url(#breeze-setup-check-all-params-in-groups-line-48)">Print&#160;verbose&#160;information&#160;about&#160;performed&#160;steps.</text><text class="breeze-setup-check-all-params-in-groups-r5" x="1451.8" y="1191.2" textLength="12.2" clip-path="url(#breeze-setup-check-all-params-in-groups-line-48)">│</text><text class="breeze-setup-check-all-params-in-groups-r1" x="1464" y="1191.2" textLength="12.2" clip-path="url(#breeze-setup-check-all-params-in-groups-line-48)">
</text><text class="breeze-setup-check-all-params-in-groups-r5" x="0" y="1215.6" textLength="12.2" clip-path="url(#breeze-setup-check-all-params-in-groups-line-49)">│</text><text class="breeze-setup-check-all-params-in-groups-r4" x="24.4" y="1215.6" textLength="109.8" clip-path="url(#breeze-setup-check-all-params-in-groups-line-49)">--dry-run</text><text class="breeze-setup-check-all-params-in-groups-r7" x="158.6" y="1215.6" textLength="24.4" clip-path="url(#breeze-setup-check-all-params-in-groups-line-49)">-D</text><text class="breeze-setup-check-all-params-in-groups-r1" x="207.4" y="1215.6" textLength="719.8" clip-path="url(#breeze-setup-check-all-params-in-groups-line-49)">If&#160;dry-run&#160;is&#160;set,&#160;commands&#160;are&#160;only&#160;printed,&#160;not&#160;executed.</text><text class="breeze-setup-check-all-params-in-groups-r5" x="1451.8" y="1215.6" textLength="12.2" clip-path="url(#breeze-setup-check-all-params-in-groups-line-49)">│</text><text class="breeze-setup-check-all-params-in-groups-r1" x="1464" y="1215.6" textLength="12.2" clip-path="url(#breeze-setup-check-all-params-in-groups-line-49)">
</text><text class="breeze-setup-check-all-params-in-groups-r5" x="0" y="1240" textLength="12.2" clip-path="url(#breeze-setup-check-all-params-in-groups-line-50)">│</text><text class="breeze-setup-check-all-params-in-groups-r4" x="24.4" y="1240" textLength="109.8" clip-path="url(#breeze-setup-check-all-params-in-groups-line-50)">--help&#160;&#160;&#160;</text><text class="breeze-setup-check-all-params-in-groups-r7" x="158.6" y="1240" textLength="24.4" clip-path="url(#breeze-setup-check-all-params-in-groups-line-50)">-h</text><text class="breeze-setup-check-all-params-in-groups-r1" x="207.4" y="1240" textLength="329.4" clip-path="url(#breeze-setup-check-all-params-in-groups-line-50)">Show&#160;this&#160;message&#160;and&#160;exit.</text><text class="breeze-setup-check-all-params-in-groups-r5" x="1451.8" y="1240" textLength="12.2" clip-path="url(#breeze-setup-check-all-params-in-groups-line-50)">│</text><text class="breeze-setup-check-all-params-in-groups-r1" x="1464" y="1240" textLength="12.2" clip-path="url(#breeze-setup-check-all-params-in-groups-line-50)">
</text><text class="breeze-setup-check-all-params-in-groups-r5" x="0" y="1264.4" textLength="1464" clip-path="url(#breeze-setup-check-all-params-in-groups-line-51)">╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯</text><text class="breeze-setup-check-all-params-in-groups-r1" x="1464" y="1264.4" textLength="12.2" clip-path="url(#breeze-setup-check-all-params-in-groups-line-51)">
</text><text class="breeze-setup-check-all-params-in-groups-r5" x="0" y="1118" textLength="12.2" clip-path="url(#breeze-setup-check-all-params-in-groups-line-45)">│</text><text class="breeze-setup-check-all-params-in-groups-r6" x="158.6" y="1118" textLength="1183.4" clip-path="url(#breeze-setup-check-all-params-in-groups-line-45)">workflow-run:mirror-base-images&#160;|&#160;workflow-run:publish-docs&#160;|&#160;workflow-run:release-constraints&#160;|&#160;</text><text class="breeze-setup-check-all-params-in-groups-r5" x="1451.8" y="1118" textLength="12.2" clip-path="url(#breeze-setup-check-all-params-in-groups-line-45)">│</text><text class="breeze-setup-check-all-params-in-groups-r1" x="1464" y="1118" textLength="12.2" clip-path="url(#breeze-setup-check-all-params-in-groups-line-45)">
</text><text class="breeze-setup-check-all-params-in-groups-r5" x="0" y="1142.4" textLength="12.2" clip-path="url(#breeze-setup-check-all-params-in-groups-line-46)">│</text><text class="breeze-setup-check-all-params-in-groups-r6" x="158.6" y="1142.4" textLength="768.6" clip-path="url(#breeze-setup-check-all-params-in-groups-line-46)">workflow-run:sync-staging-to-main&#160;|&#160;workflow-run:upgrade-check)</text><text class="breeze-setup-check-all-params-in-groups-r5" x="1451.8" y="1142.4" textLength="12.2" clip-path="url(#breeze-setup-check-all-params-in-groups-line-46)">│</text><text class="breeze-setup-check-all-params-in-groups-r1" x="1464" y="1142.4" textLength="12.2" clip-path="url(#breeze-setup-check-all-params-in-groups-line-46)">
</text><text class="breeze-setup-check-all-params-in-groups-r5" x="0" y="1166.8" textLength="1464" clip-path="url(#breeze-setup-check-all-params-in-groups-line-47)">╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯</text><text class="breeze-setup-check-all-params-in-groups-r1" x="1464" y="1166.8" textLength="12.2" clip-path="url(#breeze-setup-check-all-params-in-groups-line-47)">
</text><text class="breeze-setup-check-all-params-in-groups-r5" x="0" y="1191.2" textLength="24.4" clip-path="url(#breeze-setup-check-all-params-in-groups-line-48)">╭─</text><text class="breeze-setup-check-all-params-in-groups-r5" x="24.4" y="1191.2" textLength="195.2" clip-path="url(#breeze-setup-check-all-params-in-groups-line-48)">&#160;Common&#160;options&#160;</text><text class="breeze-setup-check-all-params-in-groups-r5" x="219.6" y="1191.2" textLength="1220" clip-path="url(#breeze-setup-check-all-params-in-groups-line-48)">────────────────────────────────────────────────────────────────────────────────────────────────────</text><text class="breeze-setup-check-all-params-in-groups-r5" x="1439.6" y="1191.2" textLength="24.4" clip-path="url(#breeze-setup-check-all-params-in-groups-line-48)">─╮</text><text class="breeze-setup-check-all-params-in-groups-r1" x="1464" y="1191.2" textLength="12.2" clip-path="url(#breeze-setup-check-all-params-in-groups-line-48)">
</text><text class="breeze-setup-check-all-params-in-groups-r5" x="0" y="1215.6" textLength="12.2" clip-path="url(#breeze-setup-check-all-params-in-groups-line-49)">│</text><text class="breeze-setup-check-all-params-in-groups-r4" x="24.4" y="1215.6" textLength="109.8" clip-path="url(#breeze-setup-check-all-params-in-groups-line-49)">--verbose</text><text class="breeze-setup-check-all-params-in-groups-r7" x="158.6" y="1215.6" textLength="24.4" clip-path="url(#breeze-setup-check-all-params-in-groups-line-49)">-v</text><text class="breeze-setup-check-all-params-in-groups-r1" x="207.4" y="1215.6" textLength="585.6" clip-path="url(#breeze-setup-check-all-params-in-groups-line-49)">Print&#160;verbose&#160;information&#160;about&#160;performed&#160;steps.</text><text class="breeze-setup-check-all-params-in-groups-r5" x="1451.8" y="1215.6" textLength="12.2" clip-path="url(#breeze-setup-check-all-params-in-groups-line-49)">│</text><text class="breeze-setup-check-all-params-in-groups-r1" x="1464" y="1215.6" textLength="12.2" clip-path="url(#breeze-setup-check-all-params-in-groups-line-49)">
</text><text class="breeze-setup-check-all-params-in-groups-r5" x="0" y="1240" textLength="12.2" clip-path="url(#breeze-setup-check-all-params-in-groups-line-50)">│</text><text class="breeze-setup-check-all-params-in-groups-r4" x="24.4" y="1240" textLength="109.8" clip-path="url(#breeze-setup-check-all-params-in-groups-line-50)">--dry-run</text><text class="breeze-setup-check-all-params-in-groups-r7" x="158.6" y="1240" textLength="24.4" clip-path="url(#breeze-setup-check-all-params-in-groups-line-50)">-D</text><text class="breeze-setup-check-all-params-in-groups-r1" x="207.4" y="1240" textLength="719.8" clip-path="url(#breeze-setup-check-all-params-in-groups-line-50)">If&#160;dry-run&#160;is&#160;set,&#160;commands&#160;are&#160;only&#160;printed,&#160;not&#160;executed.</text><text class="breeze-setup-check-all-params-in-groups-r5" x="1451.8" y="1240" textLength="12.2" clip-path="url(#breeze-setup-check-all-params-in-groups-line-50)">│</text><text class="breeze-setup-check-all-params-in-groups-r1" x="1464" y="1240" textLength="12.2" clip-path="url(#breeze-setup-check-all-params-in-groups-line-50)">
</text><text class="breeze-setup-check-all-params-in-groups-r5" x="0" y="1264.4" textLength="12.2" clip-path="url(#breeze-setup-check-all-params-in-groups-line-51)">│</text><text class="breeze-setup-check-all-params-in-groups-r4" x="24.4" y="1264.4" textLength="109.8" clip-path="url(#breeze-setup-check-all-params-in-groups-line-51)">--help&#160;&#160;&#160;</text><text class="breeze-setup-check-all-params-in-groups-r7" x="158.6" y="1264.4" textLength="24.4" clip-path="url(#breeze-setup-check-all-params-in-groups-line-51)">-h</text><text class="breeze-setup-check-all-params-in-groups-r1" x="207.4" y="1264.4" textLength="329.4" clip-path="url(#breeze-setup-check-all-params-in-groups-line-51)">Show&#160;this&#160;message&#160;and&#160;exit.</text><text class="breeze-setup-check-all-params-in-groups-r5" x="1451.8" y="1264.4" textLength="12.2" clip-path="url(#breeze-setup-check-all-params-in-groups-line-51)">│</text><text class="breeze-setup-check-all-params-in-groups-r1" x="1464" y="1264.4" textLength="12.2" clip-path="url(#breeze-setup-check-all-params-in-groups-line-51)">
</text><text class="breeze-setup-check-all-params-in-groups-r5" x="0" y="1288.8" textLength="1464" clip-path="url(#breeze-setup-check-all-params-in-groups-line-52)">╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯</text><text class="breeze-setup-check-all-params-in-groups-r1" x="1464" y="1288.8" textLength="12.2" clip-path="url(#breeze-setup-check-all-params-in-groups-line-52)">
</text>
</g>
</g>

Before

Width:  |  Height:  |  Size: 50 KiB

After

Width:  |  Height:  |  Size: 51 KiB

@@ -1 +1 @@
f171eb2c2e78f67a3ccd1bf977faf841
e8d8ba34d1d98a7d50149e5814e9d6a6
@@ -272,8 +272,8 @@
</text><text class="breeze-setup-regenerate-command-images-r5" x="0" y="1093.6" textLength="12.2" clip-path="url(#breeze-setup-regenerate-command-images-line-44)">│</text><text class="breeze-setup-regenerate-command-images-r6" x="195.2" y="1093.6" textLength="1195.6" clip-path="url(#breeze-setup-regenerate-command-images-line-44)">testing:providers-integration-tests&#160;|&#160;testing:providers-tests&#160;|&#160;testing:python-api-client-tests&#160;|&#160;</text><text class="breeze-setup-regenerate-command-images-r5" x="1451.8" y="1093.6" textLength="12.2" clip-path="url(#breeze-setup-regenerate-command-images-line-44)">│</text><text class="breeze-setup-regenerate-command-images-r1" x="1464" y="1093.6" textLength="12.2" clip-path="url(#breeze-setup-regenerate-command-images-line-44)">
</text><text class="breeze-setup-regenerate-command-images-r5" x="0" y="1118" textLength="12.2" clip-path="url(#breeze-setup-regenerate-command-images-line-45)">│</text><text class="breeze-setup-regenerate-command-images-r6" x="195.2" y="1118" textLength="1037" clip-path="url(#breeze-setup-regenerate-command-images-line-45)">testing:system-tests&#160;|&#160;testing:task-sdk-integration-tests&#160;|&#160;testing:task-sdk-tests&#160;|&#160;</text><text class="breeze-setup-regenerate-command-images-r5" x="1451.8" y="1118" textLength="12.2" clip-path="url(#breeze-setup-regenerate-command-images-line-45)">│</text><text class="breeze-setup-regenerate-command-images-r1" x="1464" y="1118" textLength="12.2" clip-path="url(#breeze-setup-regenerate-command-images-line-45)">
</text><text class="breeze-setup-regenerate-command-images-r5" x="0" y="1142.4" textLength="12.2" clip-path="url(#breeze-setup-regenerate-command-images-line-46)">│</text><text class="breeze-setup-regenerate-command-images-r6" x="195.2" y="1142.4" textLength="1134.6" clip-path="url(#breeze-setup-regenerate-command-images-line-46)">testing:ui-e2e-tests&#160;|&#160;ui&#160;|&#160;ui:check-translation-completeness&#160;|&#160;ui:compile-assets&#160;|&#160;verify&#160;|&#160;</text><text class="breeze-setup-regenerate-command-images-r5" x="1451.8" y="1142.4" textLength="12.2" clip-path="url(#breeze-setup-regenerate-command-images-line-46)">│</text><text class="breeze-setup-regenerate-command-images-r1" x="1464" y="1142.4" textLength="12.2" clip-path="url(#breeze-setup-regenerate-command-images-line-46)">
</text><text class="breeze-setup-regenerate-command-images-r5" x="0" y="1166.8" textLength="12.2" clip-path="url(#breeze-setup-regenerate-command-images-line-47)">│</text><text class="breeze-setup-regenerate-command-images-r6" x="195.2" y="1166.8" textLength="951.6" clip-path="url(#breeze-setup-regenerate-command-images-line-47)">workflow-run&#160;|&#160;workflow-run:publish-docs&#160;|&#160;workflow-run:release-constraints&#160;|&#160;</text><text class="breeze-setup-regenerate-command-images-r5" x="1451.8" y="1166.8" textLength="12.2" clip-path="url(#breeze-setup-regenerate-command-images-line-47)">│</text><text class="breeze-setup-regenerate-command-images-r1" x="1464" y="1166.8" textLength="12.2" clip-path="url(#breeze-setup-regenerate-command-images-line-47)">
</text><text class="breeze-setup-regenerate-command-images-r5" x="0" y="1191.2" textLength="12.2" clip-path="url(#breeze-setup-regenerate-command-images-line-48)">│</text><text class="breeze-setup-regenerate-command-images-r6" x="195.2" y="1191.2" textLength="414.8" clip-path="url(#breeze-setup-regenerate-command-images-line-48)">workflow-run:sync-staging-to-main)</text><text class="breeze-setup-regenerate-command-images-r5" x="1451.8" y="1191.2" textLength="12.2" clip-path="url(#breeze-setup-regenerate-command-images-line-48)">│</text><text class="breeze-setup-regenerate-command-images-r1" x="1464" y="1191.2" textLength="12.2" clip-path="url(#breeze-setup-regenerate-command-images-line-48)">
</text><text class="breeze-setup-regenerate-command-images-r5" x="0" y="1166.8" textLength="12.2" clip-path="url(#breeze-setup-regenerate-command-images-line-47)">│</text><text class="breeze-setup-regenerate-command-images-r6" x="195.2" y="1166.8" textLength="939.4" clip-path="url(#breeze-setup-regenerate-command-images-line-47)">workflow-run&#160;|&#160;workflow-run:mirror-base-images&#160;|&#160;workflow-run:publish-docs&#160;|&#160;</text><text class="breeze-setup-regenerate-command-images-r5" x="1451.8" y="1166.8" textLength="12.2" clip-path="url(#breeze-setup-regenerate-command-images-line-47)">│</text><text class="breeze-setup-regenerate-command-images-r1" x="1464" y="1166.8" textLength="12.2" clip-path="url(#breeze-setup-regenerate-command-images-line-47)">
</text><text class="breeze-setup-regenerate-command-images-r5" x="0" y="1191.2" textLength="12.2" clip-path="url(#breeze-setup-regenerate-command-images-line-48)">│</text><text class="breeze-setup-regenerate-command-images-r6" x="195.2" y="1191.2" textLength="1195.6" clip-path="url(#breeze-setup-regenerate-command-images-line-48)">workflow-run:release-constraints&#160;|&#160;workflow-run:sync-staging-to-main&#160;|&#160;workflow-run:upgrade-check)</text><text class="breeze-setup-regenerate-command-images-r5" x="1451.8" y="1191.2" textLength="12.2" clip-path="url(#breeze-setup-regenerate-command-images-line-48)">│</text><text class="breeze-setup-regenerate-command-images-r1" x="1464" y="1191.2" textLength="12.2" clip-path="url(#breeze-setup-regenerate-command-images-line-48)">
</text><text class="breeze-setup-regenerate-command-images-r5" x="0" y="1215.6" textLength="12.2" clip-path="url(#breeze-setup-regenerate-command-images-line-49)">│</text><text class="breeze-setup-regenerate-command-images-r4" x="24.4" y="1215.6" textLength="146.4" clip-path="url(#breeze-setup-regenerate-command-images-line-49)">--check-only</text><text class="breeze-setup-regenerate-command-images-r1" x="195.2" y="1215.6" textLength="1244.4" clip-path="url(#breeze-setup-regenerate-command-images-line-49)">Only&#160;check&#160;if&#160;some&#160;images&#160;need&#160;to&#160;be&#160;regenerated.&#160;Return&#160;0&#160;if&#160;no&#160;need&#160;or&#160;1&#160;if&#160;needed.&#160;Cannot&#160;be&#160;used&#160;&#160;</text><text class="breeze-setup-regenerate-command-images-r5" x="1451.8" y="1215.6" textLength="12.2" clip-path="url(#breeze-setup-regenerate-command-images-line-49)">│</text><text class="breeze-setup-regenerate-command-images-r1" x="1464" y="1215.6" textLength="12.2" clip-path="url(#breeze-setup-regenerate-command-images-line-49)">
</text><text class="breeze-setup-regenerate-command-images-r5" x="0" y="1240" textLength="12.2" clip-path="url(#breeze-setup-regenerate-command-images-line-50)">│</text><text class="breeze-setup-regenerate-command-images-r1" x="195.2" y="1240" textLength="170.8" clip-path="url(#breeze-setup-regenerate-command-images-line-50)">together&#160;with&#160;</text><text class="breeze-setup-regenerate-command-images-r4" x="366" y="1240" textLength="109.8" clip-path="url(#breeze-setup-regenerate-command-images-line-50)">--command</text><text class="breeze-setup-regenerate-command-images-r1" x="475.8" y="1240" textLength="109.8" clip-path="url(#breeze-setup-regenerate-command-images-line-50)">&#160;flag&#160;or&#160;</text><text class="breeze-setup-regenerate-command-images-r4" x="585.6" y="1240" textLength="85.4" clip-path="url(#breeze-setup-regenerate-command-images-line-50)">--force</text><text class="breeze-setup-regenerate-command-images-r1" x="671" y="1240" textLength="768.6" clip-path="url(#breeze-setup-regenerate-command-images-line-50)">.&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;</text><text class="breeze-setup-regenerate-command-images-r5" x="1451.8" y="1240" textLength="12.2" clip-path="url(#breeze-setup-regenerate-command-images-line-50)">│</text><text class="breeze-setup-regenerate-command-images-r1" x="1464" y="1240" textLength="12.2" clip-path="url(#breeze-setup-regenerate-command-images-line-50)">
</text><text class="breeze-setup-regenerate-command-images-r5" x="0" y="1264.4" textLength="1464" clip-path="url(#breeze-setup-regenerate-command-images-line-51)">╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯</text><text class="breeze-setup-regenerate-command-images-r1" x="1464" y="1264.4" textLength="12.2" clip-path="url(#breeze-setup-regenerate-command-images-line-51)">

Before

Width:  |  Height:  |  Size: 55 KiB

After

Width:  |  Height:  |  Size: 56 KiB

@@ -1 +1 @@
17b27f3a6bfb16a9119e47440e68e7f3
26c3bb5c5b4fac683d96921e5b9a2676
+27 -7
View File
@@ -1,4 +1,4 @@
<svg class="rich-terminal" viewBox="0 0 1482 367.2" 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>
@@ -42,7 +42,7 @@
<defs>
<clipPath id="breeze-workflow-run-clip-terminal">
<rect x="0" y="0" width="1463.0" height="316.2" />
<rect x="0" y="0" width="1463.0" height="438.2" />
</clipPath>
<clipPath id="breeze-workflow-run-line-0">
<rect x="0" y="1.5" width="1464" height="24.65"/>
@@ -80,9 +80,24 @@
<clipPath id="breeze-workflow-run-line-11">
<rect x="0" y="269.9" width="1464" height="24.65"/>
</clipPath>
<clipPath id="breeze-workflow-run-line-12">
<rect x="0" y="294.3" width="1464" height="24.65"/>
</clipPath>
<clipPath id="breeze-workflow-run-line-13">
<rect x="0" y="318.7" width="1464" height="24.65"/>
</clipPath>
<clipPath id="breeze-workflow-run-line-14">
<rect x="0" y="343.1" width="1464" height="24.65"/>
</clipPath>
<clipPath id="breeze-workflow-run-line-15">
<rect x="0" y="367.5" width="1464" height="24.65"/>
</clipPath>
<clipPath id="breeze-workflow-run-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="365.2" rx="8"/><text class="breeze-workflow-run-title" fill="#c5c8c6" text-anchor="middle" x="740" y="27">Command:&#160;workflow-run</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-workflow-run-title" fill="#c5c8c6" text-anchor="middle" x="740" y="27">Command:&#160;workflow-run</text>
<g transform="translate(26,22)">
<circle cx="0" cy="0" r="7" fill="#ff5f57"/>
<circle cx="22" cy="0" r="7" fill="#febc2e"/>
@@ -101,10 +116,15 @@
</text><text class="breeze-workflow-run-r5" x="0" y="166.4" textLength="12.2" clip-path="url(#breeze-workflow-run-line-6)">│</text><text class="breeze-workflow-run-r4" x="24.4" y="166.4" textLength="73.2" clip-path="url(#breeze-workflow-run-line-6)">--help</text><text class="breeze-workflow-run-r6" x="122" y="166.4" textLength="24.4" clip-path="url(#breeze-workflow-run-line-6)">-h</text><text class="breeze-workflow-run-r1" x="170.8" y="166.4" textLength="329.4" clip-path="url(#breeze-workflow-run-line-6)">Show&#160;this&#160;message&#160;and&#160;exit.</text><text class="breeze-workflow-run-r5" x="1451.8" y="166.4" textLength="12.2" clip-path="url(#breeze-workflow-run-line-6)">│</text><text class="breeze-workflow-run-r1" x="1464" y="166.4" textLength="12.2" clip-path="url(#breeze-workflow-run-line-6)">
</text><text class="breeze-workflow-run-r5" x="0" y="190.8" textLength="1464" clip-path="url(#breeze-workflow-run-line-7)">╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯</text><text class="breeze-workflow-run-r1" x="1464" y="190.8" textLength="12.2" clip-path="url(#breeze-workflow-run-line-7)">
</text><text class="breeze-workflow-run-r5" x="0" y="215.2" textLength="24.4" clip-path="url(#breeze-workflow-run-line-8)">╭─</text><text class="breeze-workflow-run-r5" x="24.4" y="215.2" textLength="512.4" clip-path="url(#breeze-workflow-run-line-8)">&#160;Airflow&#160;github&#160;actions&#160;workflow&#160;commands&#160;</text><text class="breeze-workflow-run-r5" x="536.8" y="215.2" textLength="902.8" clip-path="url(#breeze-workflow-run-line-8)">──────────────────────────────────────────────────────────────────────────</text><text class="breeze-workflow-run-r5" x="1439.6" y="215.2" textLength="24.4" clip-path="url(#breeze-workflow-run-line-8)">─╮</text><text class="breeze-workflow-run-r1" x="1464" y="215.2" textLength="12.2" clip-path="url(#breeze-workflow-run-line-8)">
</text><text class="breeze-workflow-run-r5" x="0" y="239.6" textLength="12.2" clip-path="url(#breeze-workflow-run-line-9)">│</text><text class="breeze-workflow-run-r4" x="24.4" y="239.6" textLength="256.2" clip-path="url(#breeze-workflow-run-line-9)">publish-docs&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;</text><text class="breeze-workflow-run-r1" x="305" y="239.6" textLength="1134.6" clip-path="url(#breeze-workflow-run-line-9)">Trigger&#160;publish&#160;docs&#160;to&#160;S3&#160;workflow&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;</text><text class="breeze-workflow-run-r5" x="1451.8" y="239.6" textLength="12.2" clip-path="url(#breeze-workflow-run-line-9)">│</text><text class="breeze-workflow-run-r1" x="1464" y="239.6" textLength="12.2" clip-path="url(#breeze-workflow-run-line-9)">
</text><text class="breeze-workflow-run-r5" x="0" y="264" textLength="12.2" clip-path="url(#breeze-workflow-run-line-10)">│</text><text class="breeze-workflow-run-r4" x="24.4" y="264" textLength="256.2" clip-path="url(#breeze-workflow-run-line-10)">sync-staging-to-main&#160;</text><text class="breeze-workflow-run-r1" x="305" y="264" textLength="1134.6" clip-path="url(#breeze-workflow-run-line-10)">Reset&#160;the&#160;staging&#160;branches&#160;of&#160;apache/airflow-site&#160;and&#160;apache/airflow-site-archive&#160;to&#160;main.&#160;&#160;&#160;</text><text class="breeze-workflow-run-r5" x="1451.8" y="264" textLength="12.2" clip-path="url(#breeze-workflow-run-line-10)">│</text><text class="breeze-workflow-run-r1" x="1464" y="264" textLength="12.2" clip-path="url(#breeze-workflow-run-line-10)">
</text><text class="breeze-workflow-run-r5" x="0" y="288.4" textLength="12.2" clip-path="url(#breeze-workflow-run-line-11)">│</text><text class="breeze-workflow-run-r4" x="24.4" y="288.4" textLength="256.2" clip-path="url(#breeze-workflow-run-line-11)">release-constraints&#160;&#160;</text><text class="breeze-workflow-run-r1" x="305" y="288.4" textLength="1134.6" clip-path="url(#breeze-workflow-run-line-11)">Trigger&#160;the&#160;workflow&#160;that&#160;resolves,&#160;publishes&#160;and&#160;tags&#160;the&#160;constraints&#160;for&#160;a&#160;release.&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;</text><text class="breeze-workflow-run-r5" x="1451.8" y="288.4" textLength="12.2" clip-path="url(#breeze-workflow-run-line-11)">│</text><text class="breeze-workflow-run-r1" x="1464" y="288.4" textLength="12.2" clip-path="url(#breeze-workflow-run-line-11)">
</text><text class="breeze-workflow-run-r5" x="0" y="312.8" textLength="1464" clip-path="url(#breeze-workflow-run-line-12)">╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯</text><text class="breeze-workflow-run-r1" x="1464" y="312.8" textLength="12.2" clip-path="url(#breeze-workflow-run-line-12)">
</text><text class="breeze-workflow-run-r5" x="0" y="239.6" textLength="12.2" clip-path="url(#breeze-workflow-run-line-9)">│</text><text class="breeze-workflow-run-r4" x="24.4" y="239.6" textLength="244" clip-path="url(#breeze-workflow-run-line-9)">publish-docs&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;</text><text class="breeze-workflow-run-r1" x="292.8" y="239.6" textLength="1146.8" clip-path="url(#breeze-workflow-run-line-9)">Trigger&#160;publish&#160;docs&#160;to&#160;S3&#160;workflow&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;</text><text class="breeze-workflow-run-r5" x="1451.8" y="239.6" textLength="12.2" clip-path="url(#breeze-workflow-run-line-9)">│</text><text class="breeze-workflow-run-r1" x="1464" y="239.6" textLength="12.2" clip-path="url(#breeze-workflow-run-line-9)">
</text><text class="breeze-workflow-run-r5" x="0" y="264" textLength="12.2" clip-path="url(#breeze-workflow-run-line-10)">│</text><text class="breeze-workflow-run-r4" x="24.4" y="264" textLength="244" clip-path="url(#breeze-workflow-run-line-10)">sync-staging-to-main</text><text class="breeze-workflow-run-r1" x="292.8" y="264" textLength="1146.8" clip-path="url(#breeze-workflow-run-line-10)">Reset&#160;the&#160;staging&#160;branches&#160;of&#160;apache/airflow-site&#160;and&#160;apache/airflow-site-archive&#160;to&#160;main.&#160;&#160;&#160;&#160;</text><text class="breeze-workflow-run-r5" x="1451.8" y="264" textLength="12.2" clip-path="url(#breeze-workflow-run-line-10)">│</text><text class="breeze-workflow-run-r1" x="1464" y="264" textLength="12.2" clip-path="url(#breeze-workflow-run-line-10)">
</text><text class="breeze-workflow-run-r5" x="0" y="288.4" textLength="12.2" clip-path="url(#breeze-workflow-run-line-11)">│</text><text class="breeze-workflow-run-r4" x="24.4" y="288.4" textLength="244" clip-path="url(#breeze-workflow-run-line-11)">release-constraints&#160;</text><text class="breeze-workflow-run-r1" x="292.8" y="288.4" textLength="1146.8" clip-path="url(#breeze-workflow-run-line-11)">Trigger&#160;the&#160;workflow&#160;that&#160;resolves,&#160;publishes&#160;and&#160;tags&#160;the&#160;constraints&#160;for&#160;a&#160;release.&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;</text><text class="breeze-workflow-run-r5" x="1451.8" y="288.4" textLength="12.2" clip-path="url(#breeze-workflow-run-line-11)">│</text><text class="breeze-workflow-run-r1" x="1464" y="288.4" textLength="12.2" clip-path="url(#breeze-workflow-run-line-11)">
</text><text class="breeze-workflow-run-r5" x="0" y="312.8" textLength="12.2" clip-path="url(#breeze-workflow-run-line-12)">│</text><text class="breeze-workflow-run-r4" x="24.4" y="312.8" textLength="244" clip-path="url(#breeze-workflow-run-line-12)">mirror-base-images&#160;&#160;</text><text class="breeze-workflow-run-r1" x="292.8" y="312.8" textLength="1146.8" clip-path="url(#breeze-workflow-run-line-12)">Mirror&#160;the&#160;latest&#160;hardened&#160;base&#160;images&#160;and&#160;wait&#160;until&#160;they&#160;are&#160;mirrored.&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;</text><text class="breeze-workflow-run-r5" x="1451.8" y="312.8" textLength="12.2" clip-path="url(#breeze-workflow-run-line-12)">│</text><text class="breeze-workflow-run-r1" x="1464" y="312.8" textLength="12.2" clip-path="url(#breeze-workflow-run-line-12)">
</text><text class="breeze-workflow-run-r5" x="0" y="337.2" textLength="12.2" clip-path="url(#breeze-workflow-run-line-13)">│</text><text class="breeze-workflow-run-r4" x="24.4" y="337.2" textLength="244" clip-path="url(#breeze-workflow-run-line-13)">upgrade-check&#160;&#160;&#160;&#160;&#160;&#160;&#160;</text><text class="breeze-workflow-run-r1" x="292.8" y="337.2" textLength="1146.8" clip-path="url(#breeze-workflow-run-line-13)">Upgrade&#160;the&#160;important&#160;CI&#160;environment&#160;of&#160;a&#160;branch&#160;before&#160;a&#160;release,&#160;mirroring&#160;the&#160;latest&#160;&#160;&#160;&#160;&#160;&#160;&#160;</text><text class="breeze-workflow-run-r5" x="1451.8" y="337.2" textLength="12.2" clip-path="url(#breeze-workflow-run-line-13)">│</text><text class="breeze-workflow-run-r1" x="1464" y="337.2" textLength="12.2" clip-path="url(#breeze-workflow-run-line-13)">
</text><text class="breeze-workflow-run-r5" x="0" y="361.6" textLength="12.2" clip-path="url(#breeze-workflow-run-line-14)">│</text><text class="breeze-workflow-run-r1" x="292.8" y="361.6" textLength="1146.8" clip-path="url(#breeze-workflow-run-line-14)">hardened&#160;base&#160;images&#160;first.&#160;For&#160;main&#160;(before&#160;a&#160;beta)&#160;it&#160;runs&#160;the&#160;upgrade&#160;check&#160;workflow,&#160;which</text><text class="breeze-workflow-run-r5" x="1451.8" y="361.6" textLength="12.2" clip-path="url(#breeze-workflow-run-line-14)">│</text><text class="breeze-workflow-run-r1" x="1464" y="361.6" textLength="12.2" clip-path="url(#breeze-workflow-run-line-14)">
</text><text class="breeze-workflow-run-r5" x="0" y="386" textLength="12.2" clip-path="url(#breeze-workflow-run-line-15)">│</text><text class="breeze-workflow-run-r1" x="292.8" y="386" textLength="1146.8" clip-path="url(#breeze-workflow-run-line-15)">opens&#160;an&#160;upgrade&#160;PR,&#160;and&#160;waits&#160;for&#160;it.&#160;For&#160;a&#160;vX-Y-test&#160;branch&#160;(before&#160;its&#160;test-to-stable&#160;PR)&#160;&#160;</text><text class="breeze-workflow-run-r5" x="1451.8" y="386" textLength="12.2" clip-path="url(#breeze-workflow-run-line-15)">│</text><text class="breeze-workflow-run-r1" x="1464" y="386" textLength="12.2" clip-path="url(#breeze-workflow-run-line-15)">
</text><text class="breeze-workflow-run-r5" x="0" y="410.4" textLength="12.2" clip-path="url(#breeze-workflow-run-line-16)">│</text><text class="breeze-workflow-run-r1" x="292.8" y="410.4" textLength="1146.8" clip-path="url(#breeze-workflow-run-line-16)">it&#160;runs&#160;the&#160;upgrade&#160;locally&#160;and&#160;pushes&#160;it&#160;to&#160;the&#160;branch,&#160;which&#160;needs&#160;committer&#160;rights.&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;</text><text class="breeze-workflow-run-r5" x="1451.8" y="410.4" textLength="12.2" clip-path="url(#breeze-workflow-run-line-16)">│</text><text class="breeze-workflow-run-r1" x="1464" y="410.4" textLength="12.2" clip-path="url(#breeze-workflow-run-line-16)">
</text><text class="breeze-workflow-run-r5" x="0" y="434.8" textLength="1464" clip-path="url(#breeze-workflow-run-line-17)">╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯</text><text class="breeze-workflow-run-r1" x="1464" y="434.8" textLength="12.2" clip-path="url(#breeze-workflow-run-line-17)">
</text>
</g>
</g>

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 17 KiB

@@ -1 +1 @@
f09cd8e8609d8136229196ae83fb8580
88b9c3c624c11fa2f1c3d596d1a23770
@@ -0,0 +1,111 @@
<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>
@font-face {
font-family: "Fira Code";
src: local("FiraCode-Regular"),
url("https://cdnjs.cloudflare.com/ajax/libs/firacode/6.2.0/woff2/FiraCode-Regular.woff2") format("woff2"),
url("https://cdnjs.cloudflare.com/ajax/libs/firacode/6.2.0/woff/FiraCode-Regular.woff") format("woff");
font-style: normal;
font-weight: 400;
}
@font-face {
font-family: "Fira Code";
src: local("FiraCode-Bold"),
url("https://cdnjs.cloudflare.com/ajax/libs/firacode/6.2.0/woff2/FiraCode-Bold.woff2") format("woff2"),
url("https://cdnjs.cloudflare.com/ajax/libs/firacode/6.2.0/woff/FiraCode-Bold.woff") format("woff");
font-style: bold;
font-weight: 700;
}
.breeze-workflow-run-mirror-base-images-matrix {
font-family: Fira Code, monospace;
font-size: 20px;
line-height: 24.4px;
font-variant-east-asian: full-width;
}
.breeze-workflow-run-mirror-base-images-title {
font-size: 18px;
font-weight: bold;
font-family: arial;
}
.breeze-workflow-run-mirror-base-images-r1 { fill: #c5c8c6 }
.breeze-workflow-run-mirror-base-images-r2 { fill: #d0b344 }
.breeze-workflow-run-mirror-base-images-r3 { fill: #c5c8c6;font-weight: bold }
.breeze-workflow-run-mirror-base-images-r4 { fill: #68a0b3;font-weight: bold }
.breeze-workflow-run-mirror-base-images-r5 { fill: #868887 }
.breeze-workflow-run-mirror-base-images-r6 { fill: #98a84b;font-weight: bold }
</style>
<defs>
<clipPath id="breeze-workflow-run-mirror-base-images-clip-terminal">
<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"/>
</clipPath>
<clipPath id="breeze-workflow-run-mirror-base-images-line-1">
<rect x="0" y="25.9" width="1464" height="24.65"/>
</clipPath>
<clipPath id="breeze-workflow-run-mirror-base-images-line-2">
<rect x="0" y="50.3" width="1464" height="24.65"/>
</clipPath>
<clipPath id="breeze-workflow-run-mirror-base-images-line-3">
<rect x="0" y="74.7" width="1464" height="24.65"/>
</clipPath>
<clipPath id="breeze-workflow-run-mirror-base-images-line-4">
<rect x="0" y="99.1" width="1464" height="24.65"/>
</clipPath>
<clipPath id="breeze-workflow-run-mirror-base-images-line-5">
<rect x="0" y="123.5" width="1464" height="24.65"/>
</clipPath>
<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="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"/>
<circle cx="44" cy="0" r="7" fill="#28c840"/>
</g>
<g transform="translate(9, 41)" clip-path="url(#breeze-workflow-run-mirror-base-images-clip-terminal)">
<g class="breeze-workflow-run-mirror-base-images-matrix">
<text class="breeze-workflow-run-mirror-base-images-r1" x="1464" y="20" textLength="12.2" clip-path="url(#breeze-workflow-run-mirror-base-images-line-0)">
</text><text class="breeze-workflow-run-mirror-base-images-r2" x="12.2" y="44.4" textLength="73.2" clip-path="url(#breeze-workflow-run-mirror-base-images-line-1)">Usage:</text><text class="breeze-workflow-run-mirror-base-images-r3" x="97.6" y="44.4" textLength="463.6" clip-path="url(#breeze-workflow-run-mirror-base-images-line-1)">breeze&#160;workflow-run&#160;mirror-base-images</text><text class="breeze-workflow-run-mirror-base-images-r1" x="573.4" y="44.4" textLength="12.2" clip-path="url(#breeze-workflow-run-mirror-base-images-line-1)">[</text><text class="breeze-workflow-run-mirror-base-images-r4" x="585.6" y="44.4" textLength="85.4" clip-path="url(#breeze-workflow-run-mirror-base-images-line-1)">OPTIONS</text><text class="breeze-workflow-run-mirror-base-images-r1" x="671" y="44.4" textLength="12.2" clip-path="url(#breeze-workflow-run-mirror-base-images-line-1)">]</text><text class="breeze-workflow-run-mirror-base-images-r1" x="1464" y="44.4" textLength="12.2" clip-path="url(#breeze-workflow-run-mirror-base-images-line-1)">
</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="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-mirror-base-images-line-5)">──────────────────────────────────────────────────────────────────────────────────────────────────────</text><text class="breeze-workflow-run-mirror-base-images-r5" x="1439.6" 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-r1" x="1464" y="142" textLength="12.2" clip-path="url(#breeze-workflow-run-mirror-base-images-line-5)">
</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-base-images-line-6)">/</text><text class="breeze-workflow-run-mirror-base-images-r4" x="317.2" y="166.4" textLength="317.2" clip-path="url(#breeze-workflow-run-mirror-base-images-line-6)">--no-trigger-upgrade-check</text><text class="breeze-workflow-run-mirror-base-images-r1" x="658.8" y="166.4" textLength="780.8" clip-path="url(#breeze-workflow-run-mirror-base-images-line-6)">Let&#160;the&#160;mirror&#160;run&#160;the&#160;main&#160;upgrade&#160;check&#160;when&#160;a&#160;tag&#160;moves&#160;past&#160;</text><text class="breeze-workflow-run-mirror-base-images-r5" x="1451.8" 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-r1" x="1464" 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-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="breeze-workflow-run-mirror-base-images-r5" x="1451.8" 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="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="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-mirror-base-images-line-8)">[default:&#160;trigger-upgrade-check]</text><text class="breeze-workflow-run-mirror-base-images-r5" x="1451.8" 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="1464" 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-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-mirror-base-images-line-10)">────────────────────────────────────────────────────────────────────────────────────────────────────</text><text class="breeze-workflow-run-mirror-base-images-r5" x="1439.6" 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-r1" x="1464" y="264" textLength="12.2" clip-path="url(#breeze-workflow-run-mirror-base-images-line-10)">
</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-11)">-h</text><text class="breeze-workflow-run-mirror-base-images-r1" x="170.8" y="288.4" textLength="329.4" clip-path="url(#breeze-workflow-run-mirror-base-images-line-11)">Show&#160;this&#160;message&#160;and&#160;exit.</text><text class="breeze-workflow-run-mirror-base-images-r5" x="1451.8" 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-r1" x="1464" 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-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>
</svg>

After

Width:  |  Height:  |  Size: 14 KiB

@@ -0,0 +1 @@
ff6777b06b1340e4e0d47c58e828aae6
@@ -0,0 +1,116 @@
<svg class="rich-terminal" viewBox="0 0 1482 391.59999999999997" xmlns="http://www.w3.org/2000/svg">
<!-- Generated with Rich https://www.textualize.io -->
<style>
@font-face {
font-family: "Fira Code";
src: local("FiraCode-Regular"),
url("https://cdnjs.cloudflare.com/ajax/libs/firacode/6.2.0/woff2/FiraCode-Regular.woff2") format("woff2"),
url("https://cdnjs.cloudflare.com/ajax/libs/firacode/6.2.0/woff/FiraCode-Regular.woff") format("woff");
font-style: normal;
font-weight: 400;
}
@font-face {
font-family: "Fira Code";
src: local("FiraCode-Bold"),
url("https://cdnjs.cloudflare.com/ajax/libs/firacode/6.2.0/woff2/FiraCode-Bold.woff2") format("woff2"),
url("https://cdnjs.cloudflare.com/ajax/libs/firacode/6.2.0/woff/FiraCode-Bold.woff") format("woff");
font-style: bold;
font-weight: 700;
}
.breeze-workflow-run-upgrade-check-matrix {
font-family: Fira Code, monospace;
font-size: 20px;
line-height: 24.4px;
font-variant-east-asian: full-width;
}
.breeze-workflow-run-upgrade-check-title {
font-size: 18px;
font-weight: bold;
font-family: arial;
}
.breeze-workflow-run-upgrade-check-r1 { fill: #c5c8c6 }
.breeze-workflow-run-upgrade-check-r2 { fill: #d0b344 }
.breeze-workflow-run-upgrade-check-r3 { fill: #c5c8c6;font-weight: bold }
.breeze-workflow-run-upgrade-check-r4 { fill: #68a0b3;font-weight: bold }
.breeze-workflow-run-upgrade-check-r5 { fill: #868887 }
.breeze-workflow-run-upgrade-check-r6 { fill: #8d7b39 }
.breeze-workflow-run-upgrade-check-r7 { fill: #98a84b;font-weight: bold }
</style>
<defs>
<clipPath id="breeze-workflow-run-upgrade-check-clip-terminal">
<rect x="0" y="0" width="1463.0" height="340.59999999999997" />
</clipPath>
<clipPath id="breeze-workflow-run-upgrade-check-line-0">
<rect x="0" y="1.5" width="1464" height="24.65"/>
</clipPath>
<clipPath id="breeze-workflow-run-upgrade-check-line-1">
<rect x="0" y="25.9" width="1464" height="24.65"/>
</clipPath>
<clipPath id="breeze-workflow-run-upgrade-check-line-2">
<rect x="0" y="50.3" width="1464" height="24.65"/>
</clipPath>
<clipPath id="breeze-workflow-run-upgrade-check-line-3">
<rect x="0" y="74.7" width="1464" height="24.65"/>
</clipPath>
<clipPath id="breeze-workflow-run-upgrade-check-line-4">
<rect x="0" y="99.1" width="1464" height="24.65"/>
</clipPath>
<clipPath id="breeze-workflow-run-upgrade-check-line-5">
<rect x="0" y="123.5" width="1464" height="24.65"/>
</clipPath>
<clipPath id="breeze-workflow-run-upgrade-check-line-6">
<rect x="0" y="147.9" width="1464" height="24.65"/>
</clipPath>
<clipPath id="breeze-workflow-run-upgrade-check-line-7">
<rect x="0" y="172.3" width="1464" height="24.65"/>
</clipPath>
<clipPath id="breeze-workflow-run-upgrade-check-line-8">
<rect x="0" y="196.7" width="1464" height="24.65"/>
</clipPath>
<clipPath id="breeze-workflow-run-upgrade-check-line-9">
<rect x="0" y="221.1" width="1464" height="24.65"/>
</clipPath>
<clipPath id="breeze-workflow-run-upgrade-check-line-10">
<rect x="0" y="245.5" width="1464" height="24.65"/>
</clipPath>
<clipPath id="breeze-workflow-run-upgrade-check-line-11">
<rect x="0" y="269.9" width="1464" height="24.65"/>
</clipPath>
<clipPath id="breeze-workflow-run-upgrade-check-line-12">
<rect x="0" y="294.3" 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="389.6" rx="8"/><text class="breeze-workflow-run-upgrade-check-title" fill="#c5c8c6" text-anchor="middle" x="740" y="27">Command:&#160;workflow-run&#160;upgrade-check</text>
<g transform="translate(26,22)">
<circle cx="0" cy="0" r="7" fill="#ff5f57"/>
<circle cx="22" cy="0" r="7" fill="#febc2e"/>
<circle cx="44" cy="0" r="7" fill="#28c840"/>
</g>
<g transform="translate(9, 41)" clip-path="url(#breeze-workflow-run-upgrade-check-clip-terminal)">
<g class="breeze-workflow-run-upgrade-check-matrix">
<text class="breeze-workflow-run-upgrade-check-r1" x="1464" y="20" textLength="12.2" clip-path="url(#breeze-workflow-run-upgrade-check-line-0)">
</text><text class="breeze-workflow-run-upgrade-check-r2" x="12.2" y="44.4" textLength="73.2" clip-path="url(#breeze-workflow-run-upgrade-check-line-1)">Usage:</text><text class="breeze-workflow-run-upgrade-check-r3" x="97.6" y="44.4" textLength="402.6" clip-path="url(#breeze-workflow-run-upgrade-check-line-1)">breeze&#160;workflow-run&#160;upgrade-check</text><text class="breeze-workflow-run-upgrade-check-r1" x="512.4" y="44.4" textLength="12.2" clip-path="url(#breeze-workflow-run-upgrade-check-line-1)">[</text><text class="breeze-workflow-run-upgrade-check-r4" x="524.6" y="44.4" textLength="85.4" clip-path="url(#breeze-workflow-run-upgrade-check-line-1)">OPTIONS</text><text class="breeze-workflow-run-upgrade-check-r1" x="610" y="44.4" textLength="12.2" clip-path="url(#breeze-workflow-run-upgrade-check-line-1)">]</text><text class="breeze-workflow-run-upgrade-check-r1" x="1464" y="44.4" textLength="12.2" clip-path="url(#breeze-workflow-run-upgrade-check-line-1)">
</text><text class="breeze-workflow-run-upgrade-check-r1" x="1464" y="68.8" textLength="12.2" clip-path="url(#breeze-workflow-run-upgrade-check-line-2)">
</text><text class="breeze-workflow-run-upgrade-check-r1" x="12.2" y="93.2" textLength="1415.2" clip-path="url(#breeze-workflow-run-upgrade-check-line-3)">Upgrade&#160;the&#160;important&#160;CI&#160;environment&#160;of&#160;a&#160;branch&#160;before&#160;a&#160;release,&#160;mirroring&#160;the&#160;latest&#160;hardened&#160;base&#160;images&#160;first.&#160;</text><text class="breeze-workflow-run-upgrade-check-r1" x="1464" y="93.2" textLength="12.2" clip-path="url(#breeze-workflow-run-upgrade-check-line-3)">
</text><text class="breeze-workflow-run-upgrade-check-r1" x="12.2" y="117.6" textLength="1366.4" clip-path="url(#breeze-workflow-run-upgrade-check-line-4)">For&#160;main&#160;(before&#160;a&#160;beta)&#160;it&#160;runs&#160;the&#160;upgrade&#160;check&#160;workflow,&#160;which&#160;opens&#160;an&#160;upgrade&#160;PR,&#160;and&#160;waits&#160;for&#160;it.&#160;For&#160;a&#160;</text><text class="breeze-workflow-run-upgrade-check-r1" x="1464" y="117.6" textLength="12.2" clip-path="url(#breeze-workflow-run-upgrade-check-line-4)">
</text><text class="breeze-workflow-run-upgrade-check-r1" x="12.2" y="142" textLength="1427.4" clip-path="url(#breeze-workflow-run-upgrade-check-line-5)">vX-Y-test&#160;branch&#160;(before&#160;its&#160;test-to-stable&#160;PR)&#160;it&#160;runs&#160;the&#160;upgrade&#160;locally&#160;and&#160;pushes&#160;it&#160;to&#160;the&#160;branch,&#160;which&#160;needs&#160;</text><text class="breeze-workflow-run-upgrade-check-r1" x="1464" y="142" textLength="12.2" clip-path="url(#breeze-workflow-run-upgrade-check-line-5)">
</text><text class="breeze-workflow-run-upgrade-check-r1" x="12.2" y="166.4" textLength="207.4" clip-path="url(#breeze-workflow-run-upgrade-check-line-6)">committer&#160;rights.</text><text class="breeze-workflow-run-upgrade-check-r1" x="1464" y="166.4" textLength="12.2" clip-path="url(#breeze-workflow-run-upgrade-check-line-6)">
</text><text class="breeze-workflow-run-upgrade-check-r1" x="1464" y="190.8" textLength="12.2" clip-path="url(#breeze-workflow-run-upgrade-check-line-7)">
</text><text class="breeze-workflow-run-upgrade-check-r5" x="0" y="215.2" textLength="24.4" clip-path="url(#breeze-workflow-run-upgrade-check-line-8)">╭─</text><text class="breeze-workflow-run-upgrade-check-r5" x="24.4" y="215.2" textLength="231.8" clip-path="url(#breeze-workflow-run-upgrade-check-line-8)">&#160;Branch&#160;to&#160;upgrade&#160;</text><text class="breeze-workflow-run-upgrade-check-r5" x="256.2" y="215.2" textLength="1183.4" clip-path="url(#breeze-workflow-run-upgrade-check-line-8)">─────────────────────────────────────────────────────────────────────────────────────────────────</text><text class="breeze-workflow-run-upgrade-check-r5" x="1439.6" y="215.2" textLength="24.4" clip-path="url(#breeze-workflow-run-upgrade-check-line-8)">─╮</text><text class="breeze-workflow-run-upgrade-check-r1" x="1464" y="215.2" textLength="12.2" clip-path="url(#breeze-workflow-run-upgrade-check-line-8)">
</text><text class="breeze-workflow-run-upgrade-check-r5" x="0" y="239.6" textLength="12.2" clip-path="url(#breeze-workflow-run-upgrade-check-line-9)">│</text><text class="breeze-workflow-run-upgrade-check-r4" x="24.4" y="239.6" textLength="97.6" clip-path="url(#breeze-workflow-run-upgrade-check-line-9)">--branch</text><text class="breeze-workflow-run-upgrade-check-r1" x="146.4" y="239.6" textLength="231.8" clip-path="url(#breeze-workflow-run-upgrade-check-line-9)">Branch&#160;to&#160;upgrade.&#160;</text><text class="breeze-workflow-run-upgrade-check-r5" x="378.2" y="239.6" textLength="183" clip-path="url(#breeze-workflow-run-upgrade-check-line-9)">[default:&#160;main]</text><text class="breeze-workflow-run-upgrade-check-r6" x="573.4" y="239.6" textLength="219.6" clip-path="url(#breeze-workflow-run-upgrade-check-line-9)">(main&#160;|&#160;v3-4-test)</text><text class="breeze-workflow-run-upgrade-check-r5" x="1451.8" y="239.6" textLength="12.2" clip-path="url(#breeze-workflow-run-upgrade-check-line-9)">│</text><text class="breeze-workflow-run-upgrade-check-r1" x="1464" y="239.6" textLength="12.2" clip-path="url(#breeze-workflow-run-upgrade-check-line-9)">
</text><text class="breeze-workflow-run-upgrade-check-r5" x="0" y="264" textLength="1464" clip-path="url(#breeze-workflow-run-upgrade-check-line-10)">╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯</text><text class="breeze-workflow-run-upgrade-check-r1" x="1464" y="264" textLength="12.2" clip-path="url(#breeze-workflow-run-upgrade-check-line-10)">
</text><text class="breeze-workflow-run-upgrade-check-r5" x="0" y="288.4" textLength="24.4" clip-path="url(#breeze-workflow-run-upgrade-check-line-11)">╭─</text><text class="breeze-workflow-run-upgrade-check-r5" x="24.4" y="288.4" textLength="195.2" clip-path="url(#breeze-workflow-run-upgrade-check-line-11)">&#160;Common&#160;options&#160;</text><text class="breeze-workflow-run-upgrade-check-r5" x="219.6" y="288.4" textLength="1220" clip-path="url(#breeze-workflow-run-upgrade-check-line-11)">────────────────────────────────────────────────────────────────────────────────────────────────────</text><text class="breeze-workflow-run-upgrade-check-r5" x="1439.6" y="288.4" textLength="24.4" clip-path="url(#breeze-workflow-run-upgrade-check-line-11)">─╮</text><text class="breeze-workflow-run-upgrade-check-r1" x="1464" y="288.4" textLength="12.2" clip-path="url(#breeze-workflow-run-upgrade-check-line-11)">
</text><text class="breeze-workflow-run-upgrade-check-r5" x="0" y="312.8" textLength="12.2" clip-path="url(#breeze-workflow-run-upgrade-check-line-12)">│</text><text class="breeze-workflow-run-upgrade-check-r4" x="24.4" y="312.8" textLength="73.2" clip-path="url(#breeze-workflow-run-upgrade-check-line-12)">--help</text><text class="breeze-workflow-run-upgrade-check-r7" x="122" y="312.8" textLength="24.4" clip-path="url(#breeze-workflow-run-upgrade-check-line-12)">-h</text><text class="breeze-workflow-run-upgrade-check-r1" x="170.8" y="312.8" textLength="329.4" clip-path="url(#breeze-workflow-run-upgrade-check-line-12)">Show&#160;this&#160;message&#160;and&#160;exit.</text><text class="breeze-workflow-run-upgrade-check-r5" x="1451.8" y="312.8" textLength="12.2" clip-path="url(#breeze-workflow-run-upgrade-check-line-12)">│</text><text class="breeze-workflow-run-upgrade-check-r1" x="1464" y="312.8" textLength="12.2" clip-path="url(#breeze-workflow-run-upgrade-check-line-12)">
</text><text class="breeze-workflow-run-upgrade-check-r5" x="0" y="337.2" textLength="1464" clip-path="url(#breeze-workflow-run-upgrade-check-line-13)">╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯</text><text class="breeze-workflow-run-upgrade-check-r1" x="1464" y="337.2" textLength="12.2" clip-path="url(#breeze-workflow-run-upgrade-check-line-13)">
</text>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 13 KiB

@@ -0,0 +1 @@
b2395e1d455cbc62fd5f684ae56a0700
@@ -63,6 +63,7 @@ from airflow_breeze.utils.docker_command_utils import (
perform_environment_checks,
)
from airflow_breeze.utils.fork_compare import get_fork_changes, get_fork_pull_request_labels, is_fork_push
from airflow_breeze.utils.gh_workflow_utils import mirror_hardened_base_images
from airflow_breeze.utils.git_remotes import find_remote, find_remote_for_repo, get_remotes
from airflow_breeze.utils.github import format_github_token_scope_guidance, retrieve_github_token
from airflow_breeze.utils.path_utils import AIRFLOW_HOME_PATH, AIRFLOW_ROOT_PATH
@@ -717,6 +718,57 @@ def build_upgrade_pr_body(floors_report: str | None) -> str:
return f"{UPGRADE_PR_BODY}\n\n{floors_report}"
def commit_upgrade_changes(target_branch: str) -> None:
run_command(["git", "add", "--all"])
try:
run_command(["git", "commit", "--message", f"[{target_branch}] CI: Upgrade important CI environment"])
except subprocess.CalledProcessError:
console_print("[info]Commit failed, assume some auto-fixes might have been made...[/]")
run_command(["git", "add", "--all"])
run_command(
[
"git",
"commit",
# postpone pre-commit checks to CI, not to fail in automation if e.g. mypy changes force code checks
"--no-verify",
"--message",
f"[{target_branch}] CI: Upgrade important CI environment",
]
)
def push_upgrade_to_target_branch(remote: str | None, target_branch: str, floors_report: str | None) -> None:
if not remote:
console_print("[error]Pushing to the target branch needs a remote pointing to apache/airflow.[/]")
sys.exit(1)
commit_upgrade_changes(target_branch)
console_print(f"[info]Pushing the upgrade to {remote}/{target_branch}...[/]")
push_result = run_command(
["git", "push", remote, f"HEAD:refs/heads/{target_branch}"],
capture_output=True,
text=True,
check=False,
)
if push_result.returncode != 0:
console_print(f"[error]Failed to push:\n{push_result.stdout}\n{push_result.stderr}[/]")
sys.exit(1)
console_print(f"[success]The upgrade is pushed to {target_branch}.[/]")
if floors_report:
console_print(floors_report)
def mirror_base_images_before_upgrade(target_branch: str, github_token: str | None) -> None:
if github_token:
# The mirror's gh calls read the token from the process environment, not the upgrade's env.
os.environ["GITHUB_TOKEN"] = github_token
mirror_hardened_base_images(
"apache/airflow",
hint="Or skip it with --no-mirror-base-images.",
# The mirror would otherwise start a second main upgrade next to this one.
trigger_upgrade_check=target_branch != "main",
)
def get_step_enabled(
*,
autoupdate: bool,
@@ -763,6 +815,14 @@ def build_update_pr_body_command(branch_name: str, pr_body: str) -> list[str]:
help="Automatically create a PR with the upgrade changes (if not specified, will ask)",
is_flag=True,
)
@click.option(
"--push-to-target-branch",
is_flag=True,
default=False,
help="Commit the upgrade onto the target release branch and push it there instead of opening a PR - "
"what the release manager does before the test-to-stable PR, whose head is that branch. Needs "
"committer rights, as the release branch rules let only committers push without CI having passed",
)
@click.option(
"--draft/--no-draft",
default=False,
@@ -813,6 +873,14 @@ def build_update_pr_body_command(branch_name: str, pr_body: str) -> list[str]:
show_default=True,
help="Raise the floors of the curated dependencies in [tool.airflow.dependency-floors]",
)
@click.option(
"--mirror-base-images/--no-mirror-base-images",
default=True,
show_default=True,
help="Mirror the latest hardened base images (a workflow run on main) before upgrading important "
"versions, so the upgrade can pin them. Needs gh credentials that may dispatch apache/airflow "
"workflows",
)
@click.option(
"--update-uv-lock/--no-update-uv-lock",
default=True,
@@ -839,6 +907,7 @@ def build_update_pr_body_command(branch_name: str, pr_body: str) -> list[str]:
def upgrade(
target_branch: str,
create_pr: bool | None,
push_to_target_branch: bool,
draft: bool,
switch_to_base: bool | None,
airflow_site: Path,
@@ -847,6 +916,7 @@ def upgrade(
update_chart_dependencies: bool,
upgrade_important_versions: bool,
upgrade_dependency_floors: bool,
mirror_base_images: bool,
update_uv_lock: bool,
k8s_schema_sync: bool,
latest_package_managers: bool,
@@ -860,6 +930,9 @@ def upgrade(
"Must be 'main' or follow pattern 'vX-Y-test' where X and Y are numbers (e.g., 'v2-10-test').[/]"
)
sys.exit(1)
if push_to_target_branch and target_branch == "main":
console_print("[error]main takes changes only through PRs - drop --push-to-target-branch.[/]")
sys.exit(1)
# Check if we're on the main branch
branch_result = run_command(
@@ -928,6 +1001,25 @@ def upgrade(
"run `git remote add upstream https://github.com/apache/airflow.git` to add it.[/]"
)
# Pushing to the branch must neither take local work along nor wipe it, so it never switches,
# resets or cleans the checkout.
if push_to_target_branch and (not at_apache_branch or not is_clean):
remote = apache_remote_name or "upstream"
console_print(
f"[error]--push-to-target-branch pushes the upgrade straight onto {target_branch}, so it runs "
f"only on a clean checkout of {target_branch} at the tip of apache/airflow {target_branch}, "
"and it never switches, resets or cleans your checkout for you.[/]\n"
f"Current branch: {current_branch}"
f"{'' if is_clean else ', with uncommitted or untracked changes'}.\n"
"Commit or stash your changes, then bring the branch to the tip and run the command again:\n\n"
f" git switch {target_branch}\n"
f" git pull --ff-only {remote} {target_branch}\n\n"
f"If the pull refuses, your local {target_branch} has commits apache/airflow does not - push "
"them through a PR or move them to another branch first. Or run the command from a separate "
f"worktree: git worktree add ../airflow-{target_branch} {target_branch}"
)
sys.exit(1)
# Track whether user chose to reset to target branch
user_switched_to_target = False
@@ -1008,6 +1100,10 @@ def upgrade(
update_uv_lock=update_uv_lock,
)
# The upgrade pins the base images to the digests the mirror serves, so the mirror has to be current.
if step_enabled["upgrade-important-versions"] and mirror_base_images:
mirror_base_images_before_upgrade(target_branch, github_token)
# Execute upgrade commands
for step_name, command in UPGRADE_COMMANDS:
if step_enabled[step_name]:
@@ -1027,6 +1123,10 @@ def upgrade(
console_print("[success]No changes were made during the upgrade. Exiting[/]")
sys.exit(0)
if push_to_target_branch:
push_upgrade_to_target_branch(apache_remote_name, target_branch, floors_report)
return
# Determine whether to create a PR
should_create_pr = create_pr
if should_create_pr is None:
@@ -1046,24 +1146,7 @@ def upgrade(
run_command(["git", "branch", "-D", branch_name])
run_command(["git", "checkout", "-b", branch_name])
run_command(["git", "add", "."])
try:
run_command(
["git", "commit", "--message", f"[{target_branch}] CI: Upgrade important CI environment"]
)
except subprocess.CalledProcessError:
console_print("[info]Commit failed, assume some auto-fixes might have been made...[/]")
run_command(["git", "add", "."])
run_command(
[
"git",
"commit",
# postpone pre-commit checks to CI, not to fail in automation if e.g. mypy changes force code checks
"--no-verify",
"--message",
f"[{target_branch}] CI: Upgrade important CI environment",
]
)
commit_upgrade_changes(target_branch)
# Push the branch to origin (use detected origin or fallback to 'origin')
push_remote = origin_remote_name if origin_remote_name else "origin"
@@ -81,6 +81,7 @@ CI_PARAMETERS: dict[str, list[dict[str, str | list[str]]]] = {
"options": [
"--target-branch",
"--create-pr",
"--push-to-target-branch",
"--draft",
"--switch-to-base",
"--airflow-site",
@@ -96,6 +97,7 @@ CI_PARAMETERS: dict[str, list[dict[str, str | list[str]]]] = {
"--update-chart-dependencies",
"--upgrade-important-versions",
"--upgrade-dependency-floors",
"--mirror-base-images",
"--update-uv-lock",
"--k8s-schema-sync",
],
@@ -19,6 +19,7 @@ from __future__ import annotations
import ast
import glob
import itertools
import json
import operator
import os
import random
@@ -91,6 +92,7 @@ from airflow_breeze.commands.common_package_installation_options import (
)
from airflow_breeze.commands.release_management_group import release_management_group
from airflow_breeze.global_constants import (
ALL_PYTHON_MAJOR_MINOR_VERSIONS,
ALL_PYTHON_VERSION_TO_PATCHLEVEL_VERSION,
ALLOWED_DEBIAN_VERSIONS,
ALLOWED_DISTRIBUTION_FORMATS,
@@ -102,6 +104,7 @@ from airflow_breeze.global_constants import (
DEFAULT_PYTHON_MAJOR_MINOR_VERSION,
DEFAULT_PYTHON_MAJOR_MINOR_VERSION_FOR_IMAGES,
DESTINATION_LOCATIONS,
HARDENED_PYTHON_IMAGE_DIGESTS,
HARDENED_PYTHON_IMAGE_MIRROR,
HARDENED_PYTHON_IMAGE_SOURCE,
MULTI_PLATFORM,
@@ -111,6 +114,7 @@ from airflow_breeze.global_constants import (
get_airflow_version,
get_airflowctl_version,
get_hardened_python_base_image,
get_hardened_python_image_retention_tag,
get_hardened_python_image_tag,
get_task_sdk_version,
get_ts_sdk_version,
@@ -5261,6 +5265,28 @@ def get_latest_hardened_python_patchlevel(
return max(versions, key=lambda version: tuple(int(part) for part in version.split(".")))
def get_image_digest(image: str) -> str | None:
"""Return the digest of an image's (multi-platform) manifest in its registry, or None if absent."""
result = run_command(
["docker", "buildx", "imagetools", "inspect", image, "--format", "{{json .Manifest}}"],
check=False,
capture_output=True,
text=True,
)
if result.returncode != 0 or not result.stdout:
return None
return json.loads(result.stdout)["digest"]
def copy_image(source: str, targets: list[str]) -> bool:
console_print(f"[info]Mirroring {source} -> {', '.join(targets)}[/]")
# imagetools copies the multi-platform manifest registry-to-registry, so the layers never travel
# through the machine running this.
tag_flags = [flag for target in targets for flag in ("--tag", target)]
result = run_command(["docker", "buildx", "imagetools", "create", *tag_flags, source], check=False)
return result.returncode == 0
@release_management_group.command(
name="mirror-base-images",
help="Mirror the hardened Python base images Airflow builds on to Airflow's public registry.",
@@ -5269,41 +5295,65 @@ def get_latest_hardened_python_patchlevel(
@option_verbose
@option_dry_run
def mirror_base_images(python: str | None):
python_versions = CURRENT_PYTHON_MAJOR_MINOR_VERSIONS if python is None else [python]
# Only main mirrors, so it covers every Python version an active release branch still builds on,
# not only the ones main itself uses.
python_versions = ALL_PYTHON_MAJOR_MINOR_VERSIONS if python is None else [python]
failed: list[str] = []
# Tags this run moved to an image other than the one the builds are pinned to. Only a move counts:
# a pin that was already behind has had its upgrade triggered by the run that moved the tag.
moved: list[str] = []
# Every Debian version is mirrored: the CI image and the default PROD image use the first one, the
# others are still available to build PROD images on.
for python_version, debian_version in itertools.product(python_versions, ALLOWED_DEBIAN_VERSIONS):
tag = get_hardened_python_image_tag(python_version, debian_version)
source = f"{HARDENED_PYTHON_IMAGE_SOURCE}:{tag}"
# The floating major/minor tag is what documentation and ad-hoc builds refer to, so that they
# do not have to be edited on every Python patch release.
floating_tag = tag.replace(
ALL_PYTHON_VERSION_TO_PATCHLEVEL_VERSION.get(python_version, python_version), python_version, 1
)
targets = [f"{HARDENED_PYTHON_IMAGE_MIRROR}:{tag}", f"{HARDENED_PYTHON_IMAGE_MIRROR}:{floating_tag}"]
copies = [(source, targets)]
copies = [(tag, [floating_tag])]
pinned = ALL_PYTHON_VERSION_TO_PATCHLEVEL_VERSION.get(python_version, python_version)
latest = get_latest_hardened_python_patchlevel(python_version, debian_version)
if latest and latest != pinned:
latest_tag = tag.replace(pinned, latest, 1)
copies.append(
(
f"{HARDENED_PYTHON_IMAGE_SOURCE}:{latest_tag}",
[f"{HARDENED_PYTHON_IMAGE_MIRROR}:{latest_tag}"],
)
)
for copy_source, copy_targets in copies:
console_print(f"[info]Mirroring {copy_source} -> {', '.join(copy_targets)}[/]")
# imagetools copies the multi-platform manifest registry-to-registry, so the layers never
# travel through the machine running this.
tag_flags = [flag for target in copy_targets for flag in ("--tag", target)]
result = run_command(
["docker", "buildx", "imagetools", "create", *tag_flags, copy_source],
check=False,
)
if result.returncode != 0:
failed.append(copy_source)
copies.append((tag.replace(pinned, latest, 1), []))
for copy_tag, extra_tags in copies:
mirrored = f"{HARDENED_PYTHON_IMAGE_MIRROR}:{copy_tag}"
targets = [mirrored, *(f"{HARDENED_PYTHON_IMAGE_MIRROR}:{extra}" for extra in extra_tags)]
previous_digest = get_image_digest(mirrored)
if not copy_image(f"{HARDENED_PYTHON_IMAGE_SOURCE}:{copy_tag}", targets):
failed.append(copy_tag)
continue
digest = get_image_digest(mirrored)
if digest is None:
continue
if digest != previous_digest and digest != HARDENED_PYTHON_IMAGE_DIGESTS.get(copy_tag):
moved.append(copy_tag)
# The moving tag is replaced by the next refresh, which would leave the digest an upgrade
# PR is testing untagged.
if not copy_image(
f"{mirrored}@{digest}",
[
f"{HARDENED_PYTHON_IMAGE_MIRROR}:{get_hardened_python_image_retention_tag(copy_tag, digest)}"
],
):
failed.append(f"{copy_tag}@{digest}")
# The builds keep using the pinned digest after the moving tag has moved on, so it stays tagged
# too - also when it was pinned before this command started tagging digests.
pinned_digest = HARDENED_PYTHON_IMAGE_DIGESTS.get(tag)
if pinned_digest and not copy_image(
f"{HARDENED_PYTHON_IMAGE_MIRROR}:{tag}@{pinned_digest}",
[f"{HARDENED_PYTHON_IMAGE_MIRROR}:{get_hardened_python_image_retention_tag(tag, pinned_digest)}"],
):
failed.append(f"{tag}@{pinned_digest}")
if moved:
console_print(
f"[warning]The mirror moved past the pinned base images for: {', '.join(moved)}. "
"Run 'breeze ci upgrade' to move the pins.[/]"
)
github_output = os.environ.get("GITHUB_OUTPUT")
if github_output:
with open(github_output, "a") as output_file:
output_file.write(f"moved-past-pins={'true' if moved else 'false'}\n")
if failed:
console_print(f"[error]Failed to mirror: {', '.join(failed)}[/]")
sys.exit(1)
@@ -24,19 +24,26 @@ import sys
import click
from airflow_breeze.branch_defaults import AIRFLOW_BRANCH
from airflow_breeze.commands.common_options import argument_doc_packages, option_answer
from airflow_breeze.global_constants import REGULAR_DOC_PACKAGES
from airflow_breeze.utils.click_utils import BreezeGroup
from airflow_breeze.utils.confirm import Answer, user_confirm
from airflow_breeze.utils.console import console_print
from airflow_breeze.utils.custom_param_types import BetterChoice
from airflow_breeze.utils.gh_workflow_utils import get_staging_only_commits, trigger_workflow_and_monitor
from airflow_breeze.utils.gh_workflow_utils import (
get_staging_only_commits,
mirror_hardened_base_images,
trigger_workflow_and_monitor,
)
from airflow_breeze.utils.github import run_gh_command
from airflow_breeze.utils.packages import (
expand_all_provider_distributions,
get_available_distributions,
get_long_package_name,
)
from airflow_breeze.utils.path_utils import AIRFLOW_ROOT_PATH
from airflow_breeze.utils.run_utils import run_command
from airflow_breeze.utils.shared_options import get_dry_run
WORKFLOW_NAME_MAPS = {
@@ -47,6 +54,18 @@ WORKFLOW_NAME_MAPS = {
"reset-staging": "reset-staging.yml",
}
# Every branch the upgrade check runs for has a scheduled workflow of its own on main.
UPGRADE_CHECK_WORKFLOW_PREFIX = "scheduled-upgrade-check-"
UPGRADE_CHECK_BRANCHES = sorted(
{AIRFLOW_BRANCH}
| {
path.stem.removeprefix(UPGRADE_CHECK_WORKFLOW_PREFIX)
for path in (AIRFLOW_ROOT_PATH / ".github" / "workflows").glob(
f"{UPGRADE_CHECK_WORKFLOW_PREFIX}*.yml"
)
}
)
# X.Y.Z or X.Y.ZrcN - the workflow derives the release stage from which of the two it is given,
# so there is no separate switch that could disagree with the version.
RELEASE_VERSION_PATTERN = re.compile(r"^\d+\.\d+\.\d+(rc\d+)?$")
@@ -477,6 +496,57 @@ def workflow_run_release_constraints(version: str, ref: str, workflow_branch: st
)
@workflow_run_group.command(
name="mirror-base-images",
help="Mirror the latest hardened base images and wait until they are mirrored.",
)
@click.option(
"--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(
name="upgrade-check",
help="Upgrade the important CI environment of a branch before a release, mirroring the latest "
"hardened base images first. For main (before a beta) it runs the upgrade check workflow, which opens "
"an upgrade PR, and waits for it. For a vX-Y-test branch (before its test-to-stable PR) it runs the "
"upgrade locally and pushes it to the branch, which needs committer rights.",
)
@click.option(
"--branch",
help="Branch to upgrade.",
type=BetterChoice(UPGRADE_CHECK_BRANCHES),
default=AIRFLOW_BRANCH,
show_default=True,
)
def workflow_run_upgrade_check(branch: str):
if branch == "main":
console_print("[blue]Running the upgrade check for main[/blue]")
# The scheduled workflow lives on main and checks the branch out itself.
trigger_workflow_and_monitor(
workflow_name=f"{UPGRADE_CHECK_WORKFLOW_PREFIX}{branch}.yml",
repo=APACHE_AIRFLOW_REPO,
branch="main",
)
return
# The release branch rules let only committers push without CI having passed, and the workflow's
# token is not one, so the upgrade that goes straight onto the branch runs here.
console_print(f"[blue]Upgrading {branch} locally and pushing the upgrade to it[/blue]")
result = run_command(
["breeze", "ci", "upgrade", "--target-branch", branch, "--push-to-target-branch"],
check=False,
cwd=AIRFLOW_ROOT_PATH,
)
if result.returncode != 0:
sys.exit(result.returncode)
@workflow_run_group.command(
name="sync-staging-to-main",
help="Reset the staging branches of apache/airflow-site and apache/airflow-site-archive to main.",
@@ -18,7 +18,13 @@ from __future__ import annotations
WORKFLOW_RUN_COMMANDS: dict[str, str | list[str]] = {
"name": "Airflow github actions workflow commands",
"commands": ["publish-docs", "sync-staging-to-main", "release-constraints"],
"commands": [
"publish-docs",
"sync-staging-to-main",
"release-constraints",
"mirror-base-images",
"upgrade-check",
],
}
WORKFLOW_RUN_PARAMETERS: dict[str, list[dict[str, str | list[str]]]] = {
@@ -74,3 +80,19 @@ WORKFLOW_RUN_PARAMETERS["breeze workflow-run sync-staging-to-main"] = [
],
},
]
WORKFLOW_RUN_PARAMETERS["breeze workflow-run upgrade-check"] = [
{
"name": "Branch to upgrade",
"options": [
"--branch",
],
},
]
WORKFLOW_RUN_PARAMETERS["breeze workflow-run mirror-base-images"] = [
{
"name": "Mirror flags",
"options": [
"--trigger-upgrade-check",
],
},
]
@@ -0,0 +1,10 @@
{
"3.11.18-debian13-dev": "sha256:080a9c1e8be5aba44c02219dd6095729e384b63682742b680ca2625305db9328",
"3.11.18-debian12-dev": "sha256:615754e4e8869d260a285d54cc7ba28a521f387dc419fc26b070f42f70d4cb1d",
"3.12.16-debian13-dev": "sha256:d9f6001073bab0942d40d259bfb42d83090c1a9028f3b9cce161465a9c368872",
"3.12.16-debian12-dev": "sha256:a023760fdf3dbe4b14eac79dde641bbc002736b7c2a7179c584fc1a12ef53af8",
"3.13.18-debian13-dev": "sha256:9ed60ac490ee5038920b1e69ea9adb256f9f95ca285b65800d2ddab73b6e3a1e",
"3.13.18-debian12-dev": "sha256:fcc30bbd1dd311935537297f1322dffa8559fe4d610b351ca7db06ba32212613",
"3.14.8-debian13-dev": "sha256:ce0302a81b4f7e78b6ac935098f1aa8af8921fa2375fe2d1e9179df09b4bc90b",
"3.14.8-debian12-dev": "sha256:4bd030dab376b001202b15dfe8e4ce493e53ea2842f0f9335c4df2df24c4183a"
}
@@ -0,0 +1,6 @@
{
"3.11": "3.11.18",
"3.12": "3.12.16",
"3.13": "3.13.18",
"3.14": "3.14.10"
}
@@ -824,6 +824,7 @@ DEVEL_DEPS_PATH = AIRFLOW_ROOT_PATH / "generated" / "devel_deps.txt"
# Initialize files for rebuild check
FILES_FOR_REBUILD_CHECK = [
"Dockerfile.ci",
"dev/breeze/src/airflow_breeze/files/hardened_python_image_digests.json",
".dockerignore",
"scripts/docker/common.sh",
"scripts/docker/install_additional_dependencies.sh",
@@ -933,9 +934,9 @@ PROVIDERS_COMPATIBILITY_TESTS_MATRIX: list[dict[str, str | list[str]]] = [
ALL_PYTHON_VERSION_TO_PATCHLEVEL_VERSION: dict[str, str] = {
"3.10": "3.10.22",
"3.11": "3.11.17",
"3.12": "3.12.15",
"3.13": "3.13.16",
"3.11": "3.11.18",
"3.12": "3.12.16",
"3.13": "3.13.18",
"3.14": "3.14.8",
}
@@ -954,14 +955,47 @@ def get_hardened_python_image_tag(python: str, debian_version: str = ALLOWED_DEB
return f"{ALL_PYTHON_VERSION_TO_PATCHLEVEL_VERSION.get(python, python)}-{distro}-dev"
# The mirror refreshes its tags several times a day, so images are built on the digest each tag had
# when "breeze ci upgrade" last moved it. A new base image therefore reaches the builds only through
# the upgrade PR, after the full CI has passed on it. The digests live in a file of their own, which
# scripts/ci/prek/upgrade_important_versions.py rewrites and the CI image rebuild check watches.
HARDENED_PYTHON_IMAGE_DIGESTS_PATH = (
AIRFLOW_ROOT_PATH
/ "dev"
/ "breeze"
/ "src"
/ "airflow_breeze"
/ "files"
/ "hardened_python_image_digests.json"
)
HARDENED_PYTHON_IMAGE_DIGESTS: dict[str, str] = json.loads(HARDENED_PYTHON_IMAGE_DIGESTS_PATH.read_text())
def get_hardened_python_base_image(python: str, debian_version: str = ALLOWED_DEBIAN_VERSIONS[0]) -> str:
return f"{HARDENED_PYTHON_IMAGE_MIRROR}:{get_hardened_python_image_tag(python, debian_version)}"
tag = get_hardened_python_image_tag(python, debian_version)
image = f"{HARDENED_PYTHON_IMAGE_MIRROR}:{tag}"
digest = HARDENED_PYTHON_IMAGE_DIGESTS.get(tag)
return f"{image}@{digest}" if digest else image
def get_hardened_python_image_retention_tag(tag: str, digest: str) -> str:
"""Return the mirror tag that keeps a pinned digest tagged after its moving tag has moved on."""
return f"{tag}-{digest.removeprefix('sha256:')[:12]}"
def get_legacy_base_image(debian_version: str = ALLOWED_DEBIAN_VERSIONS[0]) -> str:
return f"debian:{debian_version}-slim"
# The legacy flavor compiles Python from python.org sources, which get a patch release days before the
# hardened images do, so it has patchlevels of its own. "breeze ci upgrade" moves them, which changes
# the AIRFLOW_PYTHON_VERSION build arg and so rebuilds the legacy images on the new patch release.
LEGACY_PYTHON_VERSIONS_PATH = (
AIRFLOW_ROOT_PATH / "dev" / "breeze" / "src" / "airflow_breeze" / "files" / "legacy_python_versions.json"
)
LEGACY_PYTHON_VERSIONS: dict[str, str] = json.loads(LEGACY_PYTHON_VERSIONS_PATH.read_text())
# Number of slices for low dep tests
NUMBER_OF_LOW_DEP_SLICES = 5
@@ -27,6 +27,7 @@ from airflow_breeze.global_constants import (
AIRFLOW_SOURCES_TO,
ALL_PYTHON_VERSION_TO_PATCHLEVEL_VERSION,
ALLOWED_IMAGE_FLAVORS,
LEGACY_PYTHON_VERSIONS,
get_airflow_extras,
get_legacy_base_image,
)
@@ -68,6 +69,12 @@ class BuildProdParams(CommonBuildParams):
return get_legacy_base_image(self.debian_version)
return super().python_base_image
@property
def python_patchlevel_version(self) -> str:
if self.image_flavor == "legacy":
return LEGACY_PYTHON_VERSIONS.get(self.python, self.python)
return ALL_PYTHON_VERSION_TO_PATCHLEVEL_VERSION.get(self.python, self.python)
@property
def airflow_version(self) -> str:
if self.install_airflow_version:
@@ -244,9 +251,7 @@ class BuildProdParams(CommonBuildParams):
self._req_arg("INSTALL_POSTGRES_CLIENT", self.install_postgres_client)
self._req_arg("AIRFLOW_IMAGE_FLAVOR", self.image_flavor)
self._req_arg("BASE_IMAGE", self.python_base_image)
self._req_arg(
"AIRFLOW_PYTHON_VERSION", ALL_PYTHON_VERSION_TO_PATCHLEVEL_VERSION.get(self.python, self.python)
)
self._req_arg("AIRFLOW_PYTHON_VERSION", self.python_patchlevel_version)
# optional build args
self._set_common_opt_args()
self._opt_arg("ADDITIONAL_RUNTIME_APT_COMMAND", self.additional_runtime_apt_command)
@@ -17,6 +17,7 @@
from __future__ import annotations
import json
import os
import re
import subprocess
import sys
@@ -290,3 +291,44 @@ def get_staging_only_commits(repo: str) -> list[str]:
f"{commit['commit']['message'].splitlines()[0]}"
for commit in commits
]
HARDENED_BASE_IMAGES_MIRROR_WORKFLOW = "mirror-hardened-base-images.yml"
def check_can_dispatch_workflows(repo: str, hint: str) -> None:
"""Fail unless the gh credentials may dispatch the workflows of the repository."""
# In GitHub Actions the job token gets "actions: write" from the workflow, and the repository API
# reports no permissions for it.
if get_dry_run() or os.environ.get("GITHUB_ACTIONS") == "true":
return
result = run_gh_command(
["gh", "api", f"repos/{repo}", "--jq", ".permissions.push"], capture_output=True, text=True
)
if result.returncode != 0 or result.stdout.strip() != "true":
console_print(
f"[red]Your gh credentials cannot dispatch workflows in {repo}. Mirroring the hardened base "
f"images runs the '{HARDENED_BASE_IMAGES_MIRROR_WORKFLOW}' workflow on main, which pushes "
"the images to ghcr.io, and dispatching it needs write access to the repository. Log in "
f"with 'gh auth login' as an account with that access. {hint}[/red]"
)
sys.exit(1)
def mirror_hardened_base_images(repo: str, hint: str = "", trigger_upgrade_check: bool = True) -> None:
"""
Mirror the latest hardened base images by running the mirror workflow on main, and wait for it.
:param trigger_upgrade_check: whether the mirror runs the main upgrade check when a tag moves past
its pin - off when the caller is that upgrade, which would otherwise run twice.
"""
check_can_dispatch_workflows(repo, hint)
console_print("[blue]Mirroring the latest hardened base images[/blue]")
# The mirror is shared by every branch, and main mirrors every Python version an active release
# branch still builds on.
trigger_workflow_and_monitor(
workflow_name=HARDENED_BASE_IMAGES_MIRROR_WORKFLOW,
repo=repo,
branch="main",
**{"trigger-upgrade-check": trigger_upgrade_check},
)
+38 -6
View File
@@ -16,12 +16,21 @@
# under the License.
from __future__ import annotations
from unittest import mock
import pytest
from airflow_breeze.global_constants import ALL_PYTHON_VERSION_TO_PATCHLEVEL_VERSION
from airflow_breeze.global_constants import (
ALL_PYTHON_VERSION_TO_PATCHLEVEL_VERSION,
HARDENED_PYTHON_IMAGE_DIGESTS,
LEGACY_PYTHON_VERSIONS,
)
from airflow_breeze.params.build_ci_params import BuildCiParams
from airflow_breeze.params.build_prod_params import BuildProdParams
PYTHON_3_12_TRIXIE_TAG = f"{ALL_PYTHON_VERSION_TO_PATCHLEVEL_VERSION['3.12']}-debian13-dev"
PYTHON_3_12_BOOKWORM_TAG = f"{ALL_PYTHON_VERSION_TO_PATCHLEVEL_VERSION['3.12']}-debian12-dev"
def _build_args(params) -> dict[str, str]:
values = params.prepare_arguments_for_docker_build_command()
@@ -34,13 +43,15 @@ def _build_args(params) -> dict[str, str]:
pytest.param(
"hardened",
"trixie",
f"ghcr.io/apache/airflow/base/python:{ALL_PYTHON_VERSION_TO_PATCHLEVEL_VERSION['3.12']}-debian13-dev",
f"ghcr.io/apache/airflow/base/python:{PYTHON_3_12_TRIXIE_TAG}"
f"@{HARDENED_PYTHON_IMAGE_DIGESTS[PYTHON_3_12_TRIXIE_TAG]}",
id="hardened-trixie",
),
pytest.param(
"hardened",
"bookworm",
f"ghcr.io/apache/airflow/base/python:{ALL_PYTHON_VERSION_TO_PATCHLEVEL_VERSION['3.12']}-debian12-dev",
f"ghcr.io/apache/airflow/base/python:{PYTHON_3_12_BOOKWORM_TAG}"
f"@{HARDENED_PYTHON_IMAGE_DIGESTS[PYTHON_3_12_BOOKWORM_TAG]}",
id="hardened-bookworm",
),
pytest.param("legacy", "trixie", "debian:trixie-slim", id="legacy-trixie"),
@@ -56,7 +67,20 @@ def test_prod_image_base_image_follows_flavor_and_debian_version(
assert build_args["BASE_IMAGE"] == expected_base_image
assert build_args["AIRFLOW_IMAGE_FLAVOR"] == image_flavor
assert build_args["AIRFLOW_PYTHON_VERSION"] == ALL_PYTHON_VERSION_TO_PATCHLEVEL_VERSION["3.12"]
@pytest.mark.parametrize(
("image_flavor", "expected_python_version"),
[
pytest.param("hardened", ALL_PYTHON_VERSION_TO_PATCHLEVEL_VERSION["3.12"], id="hardened"),
pytest.param("legacy", "3.12.99", id="legacy"),
],
)
@mock.patch.dict(LEGACY_PYTHON_VERSIONS, {"3.12": "3.12.99"})
def test_prod_image_python_version_follows_flavor(image_flavor, expected_python_version):
build_args = _build_args(BuildProdParams(python="3.12", image_flavor=image_flavor))
assert build_args["AIRFLOW_PYTHON_VERSION"] == expected_python_version
@pytest.mark.parametrize("image_flavor", ["hardened", "legacy"])
@@ -70,13 +94,21 @@ def test_prod_image_defaults_to_hardened_trixie():
build_args = _build_args(BuildProdParams(python="3.12"))
assert build_args["AIRFLOW_IMAGE_FLAVOR"] == "hardened"
assert build_args["BASE_IMAGE"].endswith("-debian13-dev")
assert build_args["BASE_IMAGE"].split("@")[0].endswith("-debian13-dev")
def test_ci_image_is_hardened_trixie():
build_args = _build_args(BuildCiParams(python="3.12"))
assert build_args["BASE_IMAGE"] == (
f"ghcr.io/apache/airflow/base/python:{ALL_PYTHON_VERSION_TO_PATCHLEVEL_VERSION['3.12']}-debian13-dev"
f"ghcr.io/apache/airflow/base/python:{PYTHON_3_12_TRIXIE_TAG}"
f"@{HARDENED_PYTHON_IMAGE_DIGESTS[PYTHON_3_12_TRIXIE_TAG]}"
)
assert "AIRFLOW_IMAGE_FLAVOR" not in build_args
def test_base_image_without_a_pinned_digest_uses_the_tag():
# A Python version Airflow does not pin - for example one passed in to try a new release.
build_args = _build_args(BuildCiParams(python="3.99"))
assert build_args["BASE_IMAGE"] == "ghcr.io/apache/airflow/base/python:3.99-debian13-dev"
+100
View File
@@ -16,16 +16,24 @@
# under the License.
from __future__ import annotations
import os
from subprocess import CompletedProcess
from unittest import mock
import pytest
from click.testing import CliRunner
from airflow_breeze.commands.ci_commands import (
UPGRADE_COMMANDS,
build_update_pr_body_command,
build_upgrade_pr_body,
get_step_enabled,
mirror_base_images_before_upgrade,
push_upgrade_to_target_branch,
read_floors_report,
upgrade,
)
from airflow_breeze.utils.git_remotes import Remote
STEP_NAMES = [name for name, _ in UPGRADE_COMMANDS]
@@ -71,6 +79,11 @@ def test_upgrade_has_floor_flag():
assert option.default is True
def test_upgrade_mirrors_the_base_images_by_default():
option = next(p for p in upgrade.params if p.name == "mirror_base_images")
assert option.default is True
def test_upgrade_has_latest_package_managers_flag():
option = next(p for p in upgrade.params if p.name == "latest_package_managers")
assert option.default is False
@@ -126,3 +139,90 @@ def test_read_floors_report_removes_its_temp_dir(tmp_path, content, expected):
report_path.write_text(content)
assert read_floors_report(report_path) == expected
assert not report_dir.exists()
@pytest.mark.parametrize(
("target_branch", "trigger_upgrade_check"),
[
pytest.param("main", False, id="main-upgrade-runs-once"),
pytest.param("v3-4-test", True, id="release-branch-also-upgrades-main"),
],
)
@mock.patch("airflow_breeze.commands.ci_commands.mirror_hardened_base_images", autospec=True)
def test_upgrade_mirrors_the_base_images_with_its_token(
mock_mirror, monkeypatch, target_branch, trigger_upgrade_check
):
monkeypatch.setenv("GITHUB_TOKEN", "ambient-token")
mirror_base_images_before_upgrade(target_branch, "explicit-token")
assert os.environ["GITHUB_TOKEN"] == "explicit-token"
mock_mirror.assert_called_once_with(
"apache/airflow",
hint="Or skip it with --no-mirror-base-images.",
trigger_upgrade_check=trigger_upgrade_check,
)
APACHE_REMOTE = Remote(name="upstream", url="https://github.com/apache/airflow.git", repo="apache/airflow")
class FakeGit:
"""Answers the git calls of the upgrade for a checkout on a branch, with or without local changes."""
def __init__(self, branch: str, head: str, upstream_head: str, porcelain: str):
self.answers = {
("rev-parse", "--abbrev-ref"): branch,
("rev-parse", "HEAD"): head,
("status", "--porcelain"): porcelain,
}
self.upstream_head = upstream_head
self.calls: list[list[str]] = []
def __call__(self, cmd, **kwargs):
self.calls.append(cmd)
if cmd[:2] == ["git", "rev-parse"] and cmd[-1].startswith("upstream/"):
return CompletedProcess(cmd, 0, stdout=self.upstream_head)
return CompletedProcess(cmd, 0, stdout=self.answers.get(tuple(cmd[1:3]), ""))
@pytest.mark.parametrize(
("branch", "head", "porcelain"),
[
pytest.param("v3-4-test", "abc", " M Dockerfile\n", id="uncommitted-changes"),
pytest.param("v3-4-test", "abc", "?? notes.txt\n", id="untracked-files"),
pytest.param("my-feature", "abc", "", id="other-branch"),
pytest.param("v3-4-test", "old", "", id="behind-the-tip"),
],
)
@mock.patch("airflow_breeze.commands.ci_commands.get_remotes", autospec=True, return_value=[APACHE_REMOTE])
def test_push_to_target_branch_fails_instead_of_touching_the_checkout(mock_remotes, branch, head, porcelain):
git = FakeGit(branch=branch, head=head, upstream_head="abc", porcelain=porcelain)
with mock.patch("airflow_breeze.commands.ci_commands.run_command", autospec=True, side_effect=git):
result = CliRunner().invoke(
upgrade, ["--target-branch", "v3-4-test", "--push-to-target-branch", "--switch-to-base"]
)
assert result.exit_code == 1
assert "git pull --ff-only upstream" in result.output
assert not [
call for call in git.calls if call[:2] in (["git", "checkout"], ["git", "reset"], ["git", "clean"])
]
def test_push_to_target_branch_refuses_main():
result = CliRunner().invoke(upgrade, ["--target-branch", "main", "--push-to-target-branch"])
assert result.exit_code == 1
@mock.patch("airflow_breeze.commands.ci_commands.run_command", autospec=True)
def test_push_upgrade_to_target_branch_pushes_the_commit_onto_the_branch(mock_run_command):
mock_run_command.return_value = CompletedProcess([], 0, stdout="", stderr="")
push_upgrade_to_target_branch("upstream", "v3-4-test", floors_report=None)
commands = [call.args[0] for call in mock_run_command.call_args_list]
assert commands[-1] == ["git", "push", "upstream", "HEAD:refs/heads/v3-4-test"]
assert ["git", "add", "--all"] in commands
assert ["git", "commit", "--message", "[v3-4-test] CI: Upgrade important CI environment"] in commands
@@ -25,8 +25,10 @@ import pytest
from airflow_breeze.utils.gh_workflow_utils import (
NEW_RUN_TIMEOUT_SECONDS,
check_can_dispatch_workflows,
get_latest_workflow_run_id,
get_staging_only_commits,
mirror_hardened_base_images,
monitor_workflow_run,
trigger_workflow_and_monitor,
wait_for_new_workflow_run,
@@ -163,3 +165,55 @@ def test_get_staging_only_commits_formats_sha_date_and_subject(mock_run_gh_comma
"api",
"repos/apache/airflow-site/compare/main...staging",
]
@pytest.mark.parametrize(
("returncode", "stdout", "allowed"),
[
pytest.param(0, "true\n", True, id="write-access"),
pytest.param(0, "false\n", False, id="read-only"),
pytest.param(1, "", False, id="not-logged-in"),
],
)
@mock.patch("airflow_breeze.utils.gh_workflow_utils.run_gh_command", autospec=True)
def test_check_can_dispatch_workflows_needs_write_access(
mock_run_gh_command, monkeypatch, returncode, stdout, allowed
):
monkeypatch.delenv("GITHUB_ACTIONS", raising=False)
mock_run_gh_command.return_value = subprocess.CompletedProcess(
args=[], returncode=returncode, stdout=stdout, stderr=""
)
with contextlib.nullcontext() if allowed else pytest.raises(SystemExit):
check_can_dispatch_workflows("apache/airflow", hint="")
@mock.patch("airflow_breeze.utils.gh_workflow_utils.run_gh_command", autospec=True)
def test_check_can_dispatch_workflows_trusts_the_job_permissions_in_github_actions(
mock_run_gh_command, monkeypatch
):
monkeypatch.setenv("GITHUB_ACTIONS", "true")
check_can_dispatch_workflows("apache/airflow", hint="")
mock_run_gh_command.assert_not_called()
@mock.patch("airflow_breeze.utils.gh_workflow_utils.trigger_workflow_and_monitor", autospec=True)
@mock.patch("airflow_breeze.utils.gh_workflow_utils.check_can_dispatch_workflows", autospec=True)
def test_mirror_hardened_base_images_runs_the_mirror_on_main_after_the_check(mock_check, mock_trigger):
calls = mock.Mock()
calls.attach_mock(mock_check, "check")
calls.attach_mock(mock_trigger, "trigger")
mirror_hardened_base_images("apache/airflow", hint="hint", trigger_upgrade_check=False)
assert calls.mock_calls == [
mock.call.check("apache/airflow", "hint"),
mock.call.trigger(
workflow_name="mirror-hardened-base-images.yml",
repo="apache/airflow",
branch="main",
**{"trigger-upgrade-check": False},
),
]
+134 -8
View File
@@ -16,6 +16,7 @@
# under the License.
from __future__ import annotations
import json
from subprocess import CompletedProcess
from unittest import mock
@@ -27,7 +28,41 @@ from airflow_breeze.commands.release_management_commands import (
get_latest_hardened_python_patchlevel,
mirror_base_images,
)
from airflow_breeze.global_constants import ALL_PYTHON_VERSION_TO_PATCHLEVEL_VERSION
from airflow_breeze.global_constants import (
ALL_PYTHON_VERSION_TO_PATCHLEVEL_VERSION,
HARDENED_PYTHON_IMAGE_DIGESTS,
)
MIRROR = "ghcr.io/apache/airflow/base/python"
PINNED_TAG = f"{ALL_PYTHON_VERSION_TO_PATCHLEVEL_VERSION['3.14']}-debian13-dev"
PINNED_DIGEST = HARDENED_PYTHON_IMAGE_DIGESTS[PINNED_TAG]
NEW_DIGEST = "sha256:" + "ab" * 32
class FakeRegistry:
"""Answers run_command for imagetools, moving each mirrored tag to the digest given for it."""
def __init__(self, digests_after_copy: dict[str, str]):
self.digests: dict[str, str] = {}
self.digests_after_copy = digests_after_copy
self.copies: list[tuple[str, list[str]]] = []
def __call__(self, cmd, **kwargs):
if cmd[3] == "inspect":
digest = self.digests.get(cmd[4])
stdout = json.dumps({"digest": digest}) if digest else ""
return CompletedProcess(args=cmd, returncode=0 if digest else 1, stdout=stdout)
targets = [cmd[i + 1] for i, arg in enumerate(cmd) if arg == "--tag"]
self.copies.append((cmd[-1], targets))
for target in targets:
tag = target.split(":", 1)[1]
if tag in self.digests_after_copy:
self.digests[target] = self.digests_after_copy[tag]
return CompletedProcess(args=cmd, returncode=0)
@property
def sources(self) -> list[str]:
return [source for source, _ in self.copies if source.partition("/")[0] == "dhi.io"]
def _catalog_response(text: str) -> mock.MagicMock:
@@ -70,6 +105,7 @@ def test_latest_hardened_python_patchlevel_is_none_when_the_catalog_is_unreachab
def test_mirror_also_copies_a_newer_published_patchlevel(newer_available):
pinned = ALL_PYTHON_VERSION_TO_PATCHLEVEL_VERSION["3.14"]
latest = "3.14.99" if newer_available else pinned
registry = FakeRegistry({})
with (
mock.patch(
"airflow_breeze.commands.release_management_commands.get_latest_hardened_python_patchlevel",
@@ -79,20 +115,110 @@ def test_mirror_also_copies_a_newer_published_patchlevel(newer_available):
mock.patch(
"airflow_breeze.commands.release_management_commands.run_command",
autospec=True,
return_value=CompletedProcess(args=[], returncode=0),
) as run,
side_effect=registry,
),
):
result = CliRunner().invoke(mirror_base_images, ["--python", "3.14"], catch_exceptions=False)
result = CliRunner().invoke(
mirror_base_images, ["--python", "3.14"], env={"GITHUB_OUTPUT": None}, catch_exceptions=False
)
assert result.exit_code == 0
sources = [call.args[0][-1] for call in run.call_args_list]
if newer_available:
assert sources == [
assert registry.sources == [
f"dhi.io/python:{pinned}-debian13-dev",
"dhi.io/python:3.14.99-debian13-dev",
f"dhi.io/python:{pinned}-debian12-dev",
"dhi.io/python:3.14.99-debian12-dev",
]
assert "ghcr.io/apache/airflow/base/python:3.14.99-debian13-dev" in run.call_args_list[1].args[0]
assert (
"dhi.io/python:3.14.99-debian13-dev",
[f"{MIRROR}:3.14.99-debian13-dev"],
) in registry.copies
else:
assert sources == [f"dhi.io/python:{pinned}-debian13-dev", f"dhi.io/python:{pinned}-debian12-dev"]
assert registry.sources == [
f"dhi.io/python:{pinned}-debian13-dev",
f"dhi.io/python:{pinned}-debian12-dev",
]
@mock.patch(
"airflow_breeze.commands.release_management_commands.get_latest_hardened_python_patchlevel",
autospec=True,
return_value=None,
)
def test_mirror_keeps_the_pinned_and_the_mirrored_digests_tagged(mock_latest):
registry = FakeRegistry({PINNED_TAG: NEW_DIGEST})
with mock.patch(
"airflow_breeze.commands.release_management_commands.run_command",
autospec=True,
side_effect=registry,
):
result = CliRunner().invoke(
mirror_base_images, ["--python", "3.14"], env={"GITHUB_OUTPUT": None}, catch_exceptions=False
)
assert result.exit_code == 0
assert (f"{MIRROR}:{PINNED_TAG}@{NEW_DIGEST}", [f"{MIRROR}:{PINNED_TAG}-{'ab' * 6}"]) in registry.copies
assert (
f"{MIRROR}:{PINNED_TAG}@{PINNED_DIGEST}",
[f"{MIRROR}:{PINNED_TAG}-{PINNED_DIGEST.removeprefix('sha256:')[:12]}"],
) in registry.copies
@pytest.mark.parametrize(
("previous_digest", "new_digest", "expected"),
[
pytest.param(PINNED_DIGEST, NEW_DIGEST, "true", id="moved-past-the-pin"),
pytest.param(NEW_DIGEST, NEW_DIGEST, "false", id="already-behind-not-moved"),
pytest.param(NEW_DIGEST, PINNED_DIGEST, "false", id="moved-back-to-the-pin"),
],
)
@mock.patch(
"airflow_breeze.commands.release_management_commands.get_latest_hardened_python_patchlevel",
autospec=True,
return_value=None,
)
def test_mirror_reports_moving_past_the_pins(mock_latest, previous_digest, new_digest, expected, tmp_path):
registry = FakeRegistry({PINNED_TAG: new_digest})
registry.digests[f"{MIRROR}:{PINNED_TAG}"] = previous_digest
github_output = tmp_path / "github_output"
with mock.patch(
"airflow_breeze.commands.release_management_commands.run_command",
autospec=True,
side_effect=registry,
):
result = CliRunner().invoke(
mirror_base_images,
["--python", "3.14"],
env={"GITHUB_OUTPUT": str(github_output)},
catch_exceptions=False,
)
assert result.exit_code == 0
assert github_output.read_text() == f"moved-past-pins={expected}\n"
@mock.patch(
"airflow_breeze.commands.release_management_commands.CURRENT_PYTHON_MAJOR_MINOR_VERSIONS", ["3.14"]
)
@mock.patch(
"airflow_breeze.commands.release_management_commands.ALL_PYTHON_MAJOR_MINOR_VERSIONS", ["3.13", "3.14"]
)
@mock.patch(
"airflow_breeze.commands.release_management_commands.get_latest_hardened_python_patchlevel",
autospec=True,
return_value=None,
)
def test_mirror_covers_the_python_versions_release_branches_still_use(mock_latest):
registry = FakeRegistry({})
with mock.patch(
"airflow_breeze.commands.release_management_commands.run_command",
autospec=True,
side_effect=registry,
):
result = CliRunner().invoke(
mirror_base_images, [], env={"GITHUB_OUTPUT": None}, catch_exceptions=False
)
assert result.exit_code == 0
assert {source.split(":")[1].rsplit(".", 1)[0] for source in registry.sources} == {"3.13", "3.14"}
@@ -28,10 +28,13 @@ from click.testing import CliRunner
from airflow_breeze.commands.workflow_commands import (
find_providers_missing_from_site_index,
get_lines_added_to_site_index,
workflow_run_mirror_base_images,
workflow_run_publish,
workflow_run_sync_staging_to_main,
workflow_run_upgrade_check,
)
from airflow_breeze.utils.confirm import Answer
from airflow_breeze.utils.path_utils import AIRFLOW_ROOT_PATH
def _make_gh_response(ref: str | None) -> CompletedProcess:
@@ -389,3 +392,57 @@ class TestSyncStagingToMain:
assert "Could not compare staging to main in apache/airflow-site" in result.output
assert "commit(s)" not in mock_confirm.call_args.args[0]
assert mock_trigger.call_count == 2
@pytest.mark.parametrize(
("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, 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", trigger_upgrade_check=trigger_upgrade_check)
@patch("airflow_breeze.commands.workflow_commands.run_command", autospec=True)
@patch("airflow_breeze.commands.workflow_commands.trigger_workflow_and_monitor", autospec=True)
def test_upgrade_check_for_main_runs_the_workflow_that_opens_a_pr(mock_trigger, mock_run_command):
result = CliRunner().invoke(workflow_run_upgrade_check, ["--branch", "main"], catch_exceptions=False)
assert result.exit_code == 0
mock_trigger.assert_called_once_with(
workflow_name="scheduled-upgrade-check-main.yml", repo="apache/airflow", branch="main"
)
mock_run_command.assert_not_called()
@pytest.mark.parametrize("returncode", [0, 1])
@patch("airflow_breeze.commands.workflow_commands.run_command", autospec=True)
@patch("airflow_breeze.commands.workflow_commands.trigger_workflow_and_monitor", autospec=True)
def test_upgrade_check_for_a_release_branch_pushes_the_upgrade_from_here(
mock_trigger, mock_run_command, returncode
):
mock_run_command.return_value = CompletedProcess([], returncode)
result = CliRunner().invoke(workflow_run_upgrade_check, ["--branch", "v3-4-test"], catch_exceptions=False)
assert result.exit_code == returncode
mock_run_command.assert_called_once_with(
["breeze", "ci", "upgrade", "--target-branch", "v3-4-test", "--push-to-target-branch"],
check=False,
cwd=AIRFLOW_ROOT_PATH,
)
mock_trigger.assert_not_called()
@patch("airflow_breeze.commands.workflow_commands.trigger_workflow_and_monitor", autospec=True)
def test_upgrade_check_offers_only_branches_with_an_upgrade_workflow(mock_trigger):
result = CliRunner().invoke(workflow_run_upgrade_check, ["--branch", "v3-4-stable"])
assert result.exit_code == 2
mock_trigger.assert_not_called()
+16 -10
View File
@@ -38,11 +38,16 @@ Those are the most common arguments that you use when you want to build a custom
| | | a plain Debian ``BASE_IMAGE`` (see below |
| | | the table). |
+------------------------------------------+-------------------------------------------+---------------------------------------------+
| ``AIRFLOW_PYTHON_VERSION`` | ``3.13.16`` | Python version recorded in the image |
| | | labels. In the hardened flavor the Python |
| | | itself comes from ``BASE_IMAGE``, so change |
| | | both together. In the legacy flavor it is |
| | | the version compiled from sources. |
| ``AIRFLOW_PYTHON_VERSION`` | | Python version recorded, as passed, in the |
| | | image labels (the published images record |
| | | the full patch release). In the hardened |
| | | flavor Python comes from ``BASE_IMAGE``: |
| | | leave it empty, and a version that does not |
| | | match ``BASE_IMAGE`` fails the build. In |
| | | the legacy flavor it is |
| | | the version compiled from sources: |
| | | ``MAJOR.MINOR`` builds the latest release |
| | | of that line. |
+------------------------------------------+-------------------------------------------+---------------------------------------------+
| ``BASE_IMAGE`` | (see below the table) | Airflow's public mirror of the Docker |
| | | Hardened Image that Python comes from. |
@@ -97,17 +102,18 @@ Those are the most common arguments that you use when you want to build a custom
functionality - only added confusion - so it has been removed.
The default ``BASE_IMAGE`` is Airflow's public mirror of the `Docker Hardened Image <https://dhi.io>`_ for
Python, tagged with the same Python version as ``AIRFLOW_PYTHON_VERSION`` - for Airflow |airflow-version| that
is ``ghcr.io/apache/airflow/base/python:3.13.16-debian13-dev``. Pulling the mirror needs no credentials;
pulling ``dhi.io`` directly requires a ``docker login dhi.io``.
Python - for Airflow |airflow-version| that is ``ghcr.io/apache/airflow/base/python:3.13.18-debian13-dev``,
pinned to the digest the Airflow CI last passed on (see :ref:`the hardened base images <image-hardened-base>`). Pulling the mirror needs no
credentials; pulling ``dhi.io`` directly requires a ``docker login dhi.io``.
For the 3.4.x line Airflow also publishes ``legacy`` images, built the way all images were built before
3.4.0: Python is downloaded from python.org, its signature verified and compiled on a ``debian:bookworm-slim``
base. They are deprecated and will be removed in Airflow 3.5.0. To build one yourself, set both arguments:
base. They are deprecated and will be removed in Airflow 3.5.0. To build one yourself, set these arguments:
.. code-block:: bash
docker build . --build-arg AIRFLOW_IMAGE_FLAVOR="legacy" --build-arg BASE_IMAGE="debian:bookworm-slim"
docker build . --build-arg AIRFLOW_IMAGE_FLAVOR="legacy" --build-arg BASE_IMAGE="debian:bookworm-slim" \
--build-arg AIRFLOW_PYTHON_VERSION="3.13"
List of default extras in the production Dockerfile:
+16 -5
View File
@@ -963,11 +963,18 @@ previous images that can matter when you extend or run the image are:
``dpkg`` keeps the image's copy of a configuration file without asking - also in images that extend
it. A package upgrade therefore does not replace a configuration file that differs from the
package's version.
* **The tags are rebuilt in place.** Docker republishes the same tags as CVEs are fixed, and Airflow
refreshes its mirror weekly, so rebuilding an image from the same ``BASE_IMAGE`` tag can pick up a newer
base. The weekly mirror also copies the newest Python patch release Docker publishes, and Airflow's
regular dependency upgrade moves the pinned patch level to it, so the images follow Docker's releases
rather than python.org's.
* **The tags are rebuilt in place, so the default is pinned to a digest.** Docker republishes the same tags
as CVEs are fixed, and Airflow refreshes its mirror every six hours, so building from a tag alone can pick
up a newer base at any time. The default ``BASE_IMAGE`` therefore names the digest as well, and Airflow's
regular dependency upgrade moves it - in a pull request that has to pass the full CI before the new base
is used. Pass a tag without a digest to build on the newest mirrored base instead. The mirror also copies
the newest Python patch release Docker publishes, and the same upgrade moves the pinned patch level to it,
so the images follow Docker's releases rather than python.org's.
* **Packages come from Docker's apt repository first.** The image keeps stock Debian's apt sources beside
``dhi.io``, and ``/etc/apt/preferences.d/airflow-prefer-dhi`` ranks ``dhi.io`` higher, so apt installs
Docker's build of a package whenever ``dhi.io`` publishes it and falls back to Debian's otherwise - also
in images that extend it. The priority stays below 1000, so apt still favors ``dhi.io``'s build but can
take a newer version from another repository when a dependency needs it (for example PGDG's ``libpq5``).
.. _image-legacy-flavor:
@@ -993,8 +1000,12 @@ They are deprecated and will be removed in Airflow 3.5.0. You can build one with
docker build . \
--build-arg AIRFLOW_IMAGE_FLAVOR="legacy" \
--build-arg BASE_IMAGE="debian:bookworm-slim" \
--build-arg AIRFLOW_PYTHON_VERSION="3.13" \
--tag my-image:my-tag
A ``MAJOR.MINOR`` ``AIRFLOW_PYTHON_VERSION`` builds the latest python.org release of that Python line;
pass ``MAJOR.MINOR.PATCH`` to build a specific one.
The flavor an image was built with is recorded in its ``org.apache.airflow.image.flavor`` label.
.. _image-build-fips:
@@ -31,9 +31,8 @@ export DOCKER_BUILDKIT=1
docker build . \
--pull \
--build-arg BASE_IMAGE="ghcr.io/apache/airflow/base/python:3.13.16-debian13-dev" \
--build-arg BASE_IMAGE="${BASE_IMAGE:-ghcr.io/apache/airflow/base/python:3.13.18-debian13-dev}" \
--build-arg AIRFLOW_VERSION="${AIRFLOW_VERSION}" \
--build-arg AIRFLOW_PYTHON_VERSION="3.13.16" \
--build-arg ADDITIONAL_PYTHON_DEPS="mpi4py==4.1.1" \
--build-arg ADDITIONAL_DEV_APT_DEPS="libopenmpi-dev" \
--build-arg ADDITIONAL_RUNTIME_APT_DEPS="openmpi-common" \
@@ -32,8 +32,7 @@ export DOCKER_BUILDKIT=1
docker build . -f Dockerfile \
--pull \
--platform 'linux/amd64' \
--build-arg BASE_IMAGE="ghcr.io/apache/airflow/base/python:3.13.16-debian13-dev" \
--build-arg AIRFLOW_PYTHON_VERSION="3.13.16" \
--build-arg BASE_IMAGE="${BASE_IMAGE:-ghcr.io/apache/airflow/base/python:3.13.18-debian13-dev}" \
--build-arg AIRFLOW_VERSION="${AIRFLOW_VERSION}" \
--build-arg ADDITIONAL_AIRFLOW_EXTRAS="slack,odbc" \
--build-arg ADDITIONAL_PYTHON_DEPS=" \
@@ -32,8 +32,7 @@ export DOCKER_BUILDKIT=1
docker build . \
--pull \
--build-arg BASE_IMAGE="ghcr.io/apache/airflow/base/python:3.13.16-debian13-dev" \
--build-arg AIRFLOW_PYTHON_VERSION="3.13.16" \
--build-arg BASE_IMAGE="${BASE_IMAGE:-ghcr.io/apache/airflow/base/python:3.13.18-debian13-dev}" \
--build-arg AIRFLOW_VERSION="${AIRFLOW_VERSION}" \
--build-arg ADDITIONAL_AIRFLOW_EXTRAS="jdbc" \
--build-arg ADDITIONAL_PYTHON_DEPS="pandas==2.1.2" \
@@ -31,8 +31,7 @@ export DOCKER_BUILDKIT=1
docker build . \
--pull \
--build-arg BASE_IMAGE="ghcr.io/apache/airflow/base/python:3.13.16-debian13-dev" \
--build-arg AIRFLOW_PYTHON_VERSION="3.13.16" \
--build-arg BASE_IMAGE="${BASE_IMAGE:-ghcr.io/apache/airflow/base/python:3.13.18-debian13-dev}" \
--build-arg AIRFLOW_VERSION="${AIRFLOW_VERSION}" \
--build-arg ADDITIONAL_AIRFLOW_EXTRAS="mssql,hdfs" \
--build-arg ADDITIONAL_PYTHON_DEPS="oauth2client" \
@@ -30,8 +30,7 @@ export AIRFLOW_VERSION=3.0.3
export DOCKER_BUILDKIT=1
docker build . \
--build-arg BASE_IMAGE="ghcr.io/apache/airflow/base/python:3.13.16-debian13-dev" \
--build-arg AIRFLOW_PYTHON_VERSION="3.13.16" \
--build-arg BASE_IMAGE="${BASE_IMAGE:-ghcr.io/apache/airflow/base/python:3.13.18-debian13-dev}" \
--build-arg AIRFLOW_VERSION="${AIRFLOW_VERSION}" \
--tag "my-pypi-selected-version:0.0.1"
# [END build]
@@ -47,8 +47,7 @@ export DOCKER_BUILDKIT=1
docker build . \
--pull \
--build-arg BASE_IMAGE="ghcr.io/apache/airflow/base/python:3.13.16-debian13-dev" \
--build-arg AIRFLOW_PYTHON_VERSION="3.13.16" \
--build-arg BASE_IMAGE="${BASE_IMAGE:-ghcr.io/apache/airflow/base/python:3.13.18-debian13-dev}" \
--build-arg AIRFLOW_INSTALLATION_METHOD="apache-airflow" \
--build-arg AIRFLOW_VERSION="${AIRFLOW_VERSION}" \
--build-arg INSTALL_MYSQL_CLIENT="false" \
@@ -53,6 +53,8 @@ def get_latest_airflow_image():
)
@pytest.mark.parametrize("script_file", glob.glob(f"{DOCKER_EXAMPLES_DIR}/**/*.sh", recursive=True))
def test_shell_script_example(script_file):
# The examples build on the floating base image tag unless BASE_IMAGE is set - set it to the pinned
# image (`breeze` builds use it) to build them on the base the CI passed on.
run_command(["bash", script_file])
+132 -9
View File
@@ -33,6 +33,7 @@
from __future__ import annotations
import json
import os
import re
import subprocess
@@ -442,6 +443,116 @@ def get_latest_python_version(python_major_minor: str) -> str:
return str(latest_version)
HARDENED_PYTHON_IMAGE_MIRROR_REPOSITORY = "apache/airflow/base/python"
MANIFEST_LIST_MEDIA_TYPES = (
"application/vnd.oci.image.index.v1+json, application/vnd.docker.distribution.manifest.list.v2+json"
)
# The digests the images built on the mirror are pinned to - breeze reads them from this file.
PYTHON_IMAGE_DIGESTS_PATH = (
AIRFLOW_ROOT_PATH
/ "dev"
/ "breeze"
/ "src"
/ "airflow_breeze"
/ "files"
/ "hardened_python_image_digests.json"
)
# Only references that already carry a digest are re-pinned - documentation that shows the tag alone
# stays readable.
PINNED_BASE_IMAGE_PATTERN = re.compile(
r"(ghcr\.io/apache/airflow/base/python:)([0-9]+\.[0-9]+\.[0-9]+-debian[0-9]+-dev)@sha256:[0-9a-f]{64}"
)
def get_mirrored_python_image_digest(tag: str) -> str:
"""Return the digest the mirror's moving tag currently points at - the image the pin moves to."""
token = requests.get(
"https://ghcr.io/token",
params={"scope": f"repository:{HARDENED_PYTHON_IMAGE_MIRROR_REPOSITORY}:pull"},
timeout=30,
).json()["token"]
response = requests.head(
f"https://ghcr.io/v2/{HARDENED_PYTHON_IMAGE_MIRROR_REPOSITORY}/manifests/{tag}",
headers={"Authorization": f"Bearer {token}", "Accept": MANIFEST_LIST_MEDIA_TYPES},
timeout=30,
)
if response.status_code == 404:
console.print(
f"[bright_red]ghcr.io/{HARDENED_PYTHON_IMAGE_MIRROR_REPOSITORY}:{tag} is not mirrored yet. "
"Run the 'Mirror hardened base images' workflow and try again."
)
sys.exit(1)
response.raise_for_status()
return response.headers["Docker-Content-Digest"]
def fetch_python_image_digests(latest_python_versions: dict[str, str]) -> dict[str, str]:
"""Fetch the digest to pin for every hardened base image tag the images are built on."""
return {
tag: get_mirrored_python_image_digest(tag)
for python_version in latest_python_versions.values()
for debian in HARDENED_PYTHON_DEBIAN_RELEASES
for tag in [f"{python_version}-debian{debian}-dev"]
}
def write_pins_file(path: Path, pins: dict[str, str]) -> bool:
"""Write one of the JSON files breeze reads its pins from, returning whether they changed."""
new_content = json.dumps(pins, indent=2) + "\n"
if path.read_text() == new_content:
return False
path.write_text(new_content)
console.print(f"[bright_blue]Updated {path}")
return True
PYTHON_ORG_FTP_URL = "https://www.python.org/ftp/python/"
# The patchlevels the legacy flavor compiles from python.org sources - breeze reads them from this file.
# They follow python.org rather than the hardened images, which publish a patch release days later.
LEGACY_PYTHON_VERSIONS_PATH = (
AIRFLOW_ROOT_PATH / "dev" / "breeze" / "src" / "airflow_breeze" / "files" / "legacy_python_versions.json"
)
def get_latest_released_python_version(python_major_minor: str, ftp_listing: str) -> str:
"""Return the newest python.org release of a major.minor, given the python.org FTP listing."""
pattern = rf'href="({re.escape(python_major_minor)}\.\d+)/"'
for candidate in sorted((Version(v) for v in re.findall(pattern, ftp_listing)), reverse=True):
# A release directory appears with the first pre-release, before the final tarball is in it.
response = requests.head(
f"{PYTHON_ORG_FTP_URL}{candidate}/Python-{candidate}.tar.xz",
headers={"User-Agent": "Python requests"},
allow_redirects=True,
timeout=30,
)
# Any other error must not fall through to an older release, which would downgrade the pin.
if response.status_code != 404:
response.raise_for_status()
return str(candidate)
console.print(f"[bright_red]No released Python {python_major_minor} found on {PYTHON_ORG_FTP_URL}")
sys.exit(1)
def fetch_legacy_python_versions() -> dict[str, str]:
"""Fetch the latest python.org release of every supported major.minor for the legacy flavor."""
if not UPGRADE_PYTHON:
return {}
response = requests.get(PYTHON_ORG_FTP_URL, headers={"User-Agent": "Python requests"}, timeout=30)
response.raise_for_status()
return {
python_major_minor: get_latest_released_python_version(python_major_minor, response.text)
for python_major_minor in ALL_PYTHON_MAJOR_MINOR_VERSIONS
}
def update_python_image_digests(content: str, python_image_digests: dict[str, str]) -> str:
def repin(match: re.Match[str]) -> str:
digest = python_image_digests.get(match.group(2))
return f"{match.group(1)}{match.group(2)}@{digest}" if digest else match.group(0)
return PINNED_BASE_IMAGE_PATTERN.sub(repin, content)
def get_latest_golang_version() -> str:
if not UPGRADE_GOLANG:
return ""
@@ -664,17 +775,12 @@ GOLANG_PATTERNS: list[tuple[re.Pattern, Quoting]] = [
),
]
# AIRFLOW_PYTHON_VERSION is not here: the builds derive it from the base image, or breeze passes it.
AIRFLOW_IMAGE_PYTHON_PATTERNS: list[tuple[re.Pattern, Quoting]] = [
(re.compile(r"(AIRFLOW_PYTHON_VERSION=)(\"[0-9.abrc]+\")"), Quoting.DOUBLE_QUOTED),
# Base image tags pin the same Python patchlevel as AIRFLOW_PYTHON_VERSION and have to move with it
(
re.compile(r"((?:dhi\.io|ghcr\.io/apache/airflow/base)/python:)([0-9]+\.[0-9]+\.[0-9.abrc]+)"),
Quoting.UNQUOTED,
),
(
re.compile(r"(\| ``AIRFLOW_PYTHON_VERSION`` *\| )(``[0-9.abrc]+``)( *\|)"),
Quoting.REVERSE_DOUBLE_QUOTED,
),
]
UV_PATTERNS: list[tuple[re.Pattern, Quoting]] = [
@@ -979,6 +1085,7 @@ def update_file_with_versions(
keep_length: bool,
versions: dict[str, str],
latest_python_versions: dict[str, str],
python_image_digests: dict[str, str] | None = None,
) -> str:
"""Update file content with all version replacements."""
new_content = file_content
@@ -1005,6 +1112,9 @@ def update_file_with_versions(
new_content = apply_pattern_replacements(
new_content, latest_python_version, AIRFLOW_IMAGE_PYTHON_PATTERNS, keep_length
)
# After the patchlevel replacements above, so the references name the tags the digests are for.
if python_image_digests:
new_content = update_python_image_digests(new_content, python_image_digests)
return _apply_simple_regexp_replacements(new_content, versions)
@@ -1019,7 +1129,11 @@ def _apply_simple_regexp_replacements(new_content: str, versions: dict[str, str]
return new_content
def process_all_files(versions: dict[str, str], latest_python_versions: dict[str, str]) -> bool:
def process_all_files(
versions: dict[str, str],
latest_python_versions: dict[str, str],
python_image_digests: dict[str, str] | None = None,
) -> bool:
"""
Process all files and apply version updates.
@@ -1030,7 +1144,9 @@ def process_all_files(versions: dict[str, str], latest_python_versions: dict[str
for file_to_update, keep_length in FILES_TO_UPDATE:
console.print(f"[bright_blue]Updating {file_to_update}")
file_content = file_to_update.read_text()
new_content = update_file_with_versions(file_content, keep_length, versions, latest_python_versions)
new_content = update_file_with_versions(
file_content, keep_length, versions, latest_python_versions, python_image_digests
)
if new_content != file_content:
file_to_update.write_text(new_content)
@@ -1254,8 +1370,15 @@ def main() -> None:
versions = fetch_all_package_versions()
log_special_versions(versions)
latest_python_versions = fetch_python_versions()
python_image_digests = fetch_python_image_digests(latest_python_versions)
changed = process_all_files(versions, latest_python_versions)
legacy_python_versions = fetch_legacy_python_versions()
changed = process_all_files(versions, latest_python_versions, python_image_digests)
if python_image_digests:
changed = write_pins_file(PYTHON_IMAGE_DIGESTS_PATH, python_image_digests) or changed
if legacy_python_versions:
changed = write_pins_file(LEGACY_PYTHON_VERSIONS_PATH, legacy_python_versions) or changed
# Resolve hatchling transitive dependencies and update all pyproject.toml build-system sections
if UPGRADE_HATCHLING or UPGRADE_FLIT_CORE:
+61 -1
View File
@@ -36,7 +36,9 @@ RUSTUP_VERSION=${RUSTUP_VERSION:-1.29.0}
# "hardened" images get Python from a Docker Hardened Image base, "legacy" ones compile it from
# source on top of a plain debian-slim base.
AIRFLOW_IMAGE_FLAVOR=${AIRFLOW_IMAGE_FLAVOR:-hardened}
AIRFLOW_PYTHON_VERSION=${AIRFLOW_PYTHON_VERSION:-3.13.16}
# Empty by default: the hardened flavor takes Python from the base image, and the legacy flavor
# needs it named (MAJOR.MINOR builds the latest release of that line).
AIRFLOW_PYTHON_VERSION=${AIRFLOW_PYTHON_VERSION:-}
COSIGN_VERSION=${COSIGN_VERSION:-3.0.5}
if [[ "${AIRFLOW_IMAGE_FLAVOR}" == "hardened" ]]; then
# The Debian 12 hardened images ship Python under /opt/python, the Debian 13 ones install it as
@@ -44,6 +46,13 @@ if [[ "${AIRFLOW_IMAGE_FLAVOR}" == "hardened" ]]; then
PYTHON_HOME=${PYTHON_HOME:-$(python3 -c 'import sys; print(sys.base_prefix)')}
# Read before apt runs: a package depending on python3 can pull another Python version in.
BASE_PYTHON_MAJOR_MINOR=$(python3 -c 'import sys; print(f"{sys.version_info[0]}.{sys.version_info[1]}")')
if [[ -n "${AIRFLOW_PYTHON_VERSION}" && "${AIRFLOW_PYTHON_VERSION}." != "${BASE_PYTHON_MAJOR_MINOR}."* ]]; then
echo
echo "ERROR! AIRFLOW_PYTHON_VERSION is ${AIRFLOW_PYTHON_VERSION}, but the base image has Python ${BASE_PYTHON_MAJOR_MINOR}."
echo "The hardened flavor takes Python from BASE_IMAGE: leave AIRFLOW_PYTHON_VERSION empty or pick a BASE_IMAGE with that Python."
echo
exit 1
fi
elif [[ "${AIRFLOW_IMAGE_FLAVOR}" == "legacy" ]]; then
PYTHON_HOME=${PYTHON_HOME:-/usr/python}
else
@@ -218,6 +227,24 @@ function keep_image_conffiles() {
printf '%s\n' force-confdef force-confold > /etc/dpkg/dpkg.cfg.d/airflow-keep-conffiles
}
function prefer_hardened_apt_repository() {
# The hardened base images take their packages from dhi.io, but stock Debian's sources stay
# configured beside it and apt installs whichever repository offers the higher version. Debian's
# build of a package then replaces Docker's, and its dependencies no longer match the packages the
# image ships (docker-hardened-images/catalog#610). Ranking dhi.io above the default priority of
# 500 makes apt take Docker's build whenever dhi.io publishes the package, and fall back to Debian
# only when it does not.
# The priority stays below 1000 so the preference never forces a downgrade: apt still favors
# dhi.io's build of a package it publishes, but may take a newer version from another repository
# when a dependency requires it - for example PGDG's libpq5, which postgresql-client-18 needs at
# ">= 18.6" and dhi.io does not carry. Pinning the base image to a digest CI has passed on keeps
# dhi.io and the image consistent, so the renumbering that once needed a forced "downgrade" of the
# image's copy (docker-hardened-images/catalog#689) no longer arises within a build.
# The preference covers every apt run - this build's and those of images extending it.
mkdir -p /etc/apt/preferences.d
printf 'Package: *\nPin: origin "dhi.io"\nPin-Priority: 999\n' > /etc/apt/preferences.d/airflow-prefer-dhi
}
function refresh_ldconfig_after_dpkg() {
# The hardened base images ship libc-bin without its dpkg trigger, so installing a shared library
# does not refresh the ldconfig cache and ctypes.util.find_library() - which reads that cache -
@@ -277,6 +304,7 @@ function align_perl_base_with_perl() {
function install_debian_dev_dependencies() {
if [[ "${AIRFLOW_IMAGE_FLAVOR}" == "hardened" ]]; then
keep_image_conffiles
prefer_hardened_apt_repository
refresh_ldconfig_after_dpkg
fi
apt-get update
@@ -459,8 +487,39 @@ function install_cosign() {
chmod +x /tmp/cosign
}
function resolve_python_version() {
if [[ -z "${AIRFLOW_PYTHON_VERSION}" ]]; then
echo
echo "ERROR! The legacy flavor compiles Python from sources, so AIRFLOW_PYTHON_VERSION has to name it:"
echo "MAJOR.MINOR for the latest release of that line, or MAJOR.MINOR.PATCH."
echo
exit 1
fi
if [[ ! "${AIRFLOW_PYTHON_VERSION}" =~ ^[0-9]+\.[0-9]+$ ]]; then
return
fi
# A release directory appears on python.org with its first pre-release, so take the newest one
# that already holds the final tarball.
local candidate
for candidate in $(wget --tries=3 --waitretry=5 -qO- https://www.python.org/ftp/python/ \
| grep -oE "href=\"${AIRFLOW_PYTHON_VERSION//./\\.}\.[0-9]+/\"" \
| grep -oE '[0-9]+\.[0-9]+\.[0-9]+' | sort -V -r); do
if wget --tries=3 --waitretry=5 -q --spider \
"https://www.python.org/ftp/python/${candidate}/Python-${candidate}.tar.xz"; then
echo "Python ${AIRFLOW_PYTHON_VERSION} resolved to its latest release ${candidate}"
AIRFLOW_PYTHON_VERSION="${candidate}"
return
fi
done
echo
echo "ERROR! No release of Python ${AIRFLOW_PYTHON_VERSION} found on python.org."
echo
exit 1
}
function install_python() {
# Only the legacy image flavor compiles Python - the hardened flavor gets it from the base image.
resolve_python_version
wget --tries=3 --waitretry=5 -O python.tar.xz "https://www.python.org/ftp/python/${AIRFLOW_PYTHON_VERSION%%[a-z]*}/Python-${AIRFLOW_PYTHON_VERSION}.tar.xz"
local major_minor_version
major_minor_version="${AIRFLOW_PYTHON_VERSION%.*}"
@@ -541,6 +600,7 @@ function install_python() {
function install_debian_runtime_dependencies() {
if [[ "${AIRFLOW_IMAGE_FLAVOR}" == "hardened" ]]; then
keep_image_conffiles
prefer_hardened_apt_repository
refresh_ldconfig_after_dpkg
fi
apt-get update
+6
View File
@@ -45,6 +45,12 @@ install_postgres_client() {
echo "deb [arch=amd64,arm64] https://apt.postgresql.org/pub/repos/apt/ $(common::debian_codename)-pgdg main" > \
/etc/apt/sources.list.d/pgdg.list
# The hardened image's airflow-prefer-dhi pin outranks Debian's default, so apt would otherwise keep
# dhi.io's libpq (e.g. 17.11) and postgresql-client (which needs a matching, newer libpq5) could not
# install. PostgreSQL's own repository is the authority for its packages - rank it above the dhi.io
# preference so apt takes libpq from PGDG.
mkdir -p /etc/apt/preferences.d
printf 'Package: *\nPin: origin "apt.postgresql.org"\nPin-Priority: 1001\n' > /etc/apt/preferences.d/airflow-prefer-pgdg
apt-get update
apt-get install --no-install-recommends -y "${packages[@]}"
apt-get autoremove -yqq --purge
@@ -19,6 +19,7 @@
from __future__ import annotations
import json
from datetime import UTC
from unittest import mock
@@ -349,3 +350,155 @@ def test_get_latest_pypi_version_latest_package_managers(
monkeypatch.setattr(uiv.requests, "get", mock.MagicMock(return_value=response))
assert uiv.get_latest_pypi_version(package_name, should_upgrade=True) == expected_version
OLD_DIGEST = "sha256:" + "0" * 64
NEW_DIGEST = "sha256:" + "1" * 64
def test_python_image_digests_repin_only_the_pinned_references():
from ci.prek import upgrade_important_versions as uiv
content = (
f'ARG BASE_IMAGE="ghcr.io/apache/airflow/base/python:3.13.17-debian13-dev@{OLD_DIGEST}"\n'
"See ghcr.io/apache/airflow/base/python:3.13.17-debian13-dev for the tag.\n"
)
new_content = uiv.update_python_image_digests(content, {"3.13.17-debian13-dev": NEW_DIGEST})
assert new_content == (
f'ARG BASE_IMAGE="ghcr.io/apache/airflow/base/python:3.13.17-debian13-dev@{NEW_DIGEST}"\n'
"See ghcr.io/apache/airflow/base/python:3.13.17-debian13-dev for the tag.\n"
)
@pytest.mark.parametrize(
("existing", "expected_changed"),
[
pytest.param({"3.13.16-debian13-dev": OLD_DIGEST}, True, id="moved"),
pytest.param({"3.13.17-debian13-dev": NEW_DIGEST}, False, id="unchanged"),
],
)
def test_python_image_digests_are_written_to_the_pins_file(monkeypatch, tmp_path, existing, expected_changed):
from ci.prek import upgrade_important_versions as uiv
pins_file = tmp_path / "hardened_python_image_digests.json"
pins_file.write_text(json.dumps(existing, indent=2) + "\n")
assert uiv.write_pins_file(pins_file, {"3.13.17-debian13-dev": NEW_DIGEST}) is expected_changed
assert json.loads(pins_file.read_text()) == {"3.13.17-debian13-dev": NEW_DIGEST}
@pytest.mark.parametrize("path_name", ["PYTHON_IMAGE_DIGESTS_PATH", "LEGACY_PYTHON_VERSIONS_PATH"])
def test_pins_file_is_the_one_breeze_reads(path_name):
from ci.prek import upgrade_important_versions as uiv
assert getattr(uiv, path_name).is_file()
FTP_LISTING = (
'<a href="3.13.9/">3.13.9/</a>\n<a href="3.13.10/">3.13.10/</a>\n'
'<a href="3.13.11/">3.13.11/</a>\n<a href="3.14.2/">3.14.2/</a>\n'
)
@pytest.mark.parametrize(
("released", "expected"),
[
pytest.param({"3.13.9", "3.13.10", "3.13.11"}, "3.13.11", id="newest-release"),
pytest.param({"3.13.9", "3.13.10"}, "3.13.10", id="skips-pre-release-only-directory"),
],
)
def test_legacy_python_version_is_the_newest_python_org_release(released, expected):
from ci.prek import upgrade_important_versions as uiv
def head(url, **kwargs):
response = mock.MagicMock(spec=uiv.requests.Response)
version = url.split("/")[-2]
response.status_code = 200 if version in released else 404
return response
with mock.patch.object(uiv.requests, "head", autospec=True, side_effect=head):
assert uiv.get_latest_released_python_version("3.13", FTP_LISTING) == expected
def test_legacy_python_version_exits_without_a_release():
from ci.prek import upgrade_important_versions as uiv
with pytest.raises(SystemExit):
uiv.get_latest_released_python_version("3.15", FTP_LISTING)
def test_python_patchlevel_upgrade_also_repins_the_base_image(monkeypatch):
from ci.prek import upgrade_important_versions as uiv
monkeypatch.setattr(uiv, "UPGRADE_PYTHON", True)
monkeypatch.setattr(uiv, "ALL_PYTHON_MAJOR_MINOR_VERSIONS", ["3.13"])
monkeypatch.setattr(uiv, "DEFAULT_PROD_IMAGE_PYTHON_VERSION", "3.13")
content = (
f'ARG BASE_IMAGE="ghcr.io/apache/airflow/base/python:3.13.16-debian13-dev@{OLD_DIGEST}"\n'
'ARG AIRFLOW_PYTHON_VERSION="3.13"\n'
)
new_content = uiv.update_file_with_versions(
content,
False,
{},
latest_python_versions={"3.13": "3.13.17"},
python_image_digests={"3.13.17-debian13-dev": NEW_DIGEST},
)
assert new_content == (
f'ARG BASE_IMAGE="ghcr.io/apache/airflow/base/python:3.13.17-debian13-dev@{NEW_DIGEST}"\n'
'ARG AIRFLOW_PYTHON_VERSION="3.13"\n'
)
def _ghcr_responses(uiv, status_code: int):
token_response = mock.MagicMock(spec=uiv.requests.Response)
token_response.json.return_value = {"token": "anonymous"}
manifest_response = mock.MagicMock(spec=uiv.requests.Response)
manifest_response.status_code = status_code
manifest_response.headers = {"Docker-Content-Digest": NEW_DIGEST}
get = mock.create_autospec(uiv.requests.get, return_value=token_response)
head = mock.create_autospec(uiv.requests.head, return_value=manifest_response)
return get, head
def test_python_image_digests_are_read_from_the_mirror(monkeypatch):
from ci.prek import upgrade_important_versions as uiv
get, head = _ghcr_responses(uiv, 200)
monkeypatch.setattr(uiv.requests, "get", get)
monkeypatch.setattr(uiv.requests, "head", head)
assert uiv.fetch_python_image_digests({"3.13": "3.13.17"}) == {
"3.13.17-debian13-dev": NEW_DIGEST,
"3.13.17-debian12-dev": NEW_DIGEST,
}
assert [call.args[0] for call in head.call_args_list] == [
"https://ghcr.io/v2/apache/airflow/base/python/manifests/3.13.17-debian13-dev",
"https://ghcr.io/v2/apache/airflow/base/python/manifests/3.13.17-debian12-dev",
]
def test_python_image_digest_exits_when_the_tag_is_not_mirrored(monkeypatch):
from ci.prek import upgrade_important_versions as uiv
get, head = _ghcr_responses(uiv, 404)
monkeypatch.setattr(uiv.requests, "get", get)
monkeypatch.setattr(uiv.requests, "head", head)
with pytest.raises(SystemExit):
uiv.get_mirrored_python_image_digest("3.13.17-debian13-dev")
def test_legacy_python_version_fails_on_a_server_error_instead_of_picking_an_older_release():
from ci.prek import upgrade_important_versions as uiv
response = mock.MagicMock(spec=uiv.requests.Response)
response.status_code = 503
response.raise_for_status.side_effect = uiv.requests.HTTPError("503")
with mock.patch.object(uiv.requests, "head", autospec=True, return_value=response):
with pytest.raises(uiv.requests.HTTPError):
uiv.get_latest_released_python_version("3.13", FTP_LISTING)