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>
No files matched your search
@@ -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:
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -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:
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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
|
||||
"""""""""""""""""""""""""""""""""
|
||||
|
||||
|
||||
@@ -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) |
|
||||
|
||||
@@ -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: ci 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: ci 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)">'vX-Y-test') </text><text class="breeze-ci-upgrade-r5" x="866.2" y="190.8" textLength="183" clip-path="url(#breeze-ci-upgrade-line-7)">[default: 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 create a PR with the upgrade changes (if not  </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, will ask)                                        </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 the PR as a draft (useful for scheduled CI runs 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 human undrafts to trigger CI) </text><text class="breeze-ci-upgrade-r5" x="1098" y="288.4" textLength="231.8" clip-path="url(#breeze-ci-upgrade-line-11)">[default: 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 switch to the base branch if not already on 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 not specified, will ask)                                </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                                        </text><text class="breeze-ci-upgrade-r1" x="707.6" y="361.6" textLength="732" clip-path="url(#breeze-ci-upgrade-line-14)">Path to airflow-site checkout for publishing K8s schemas    </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: ../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                               </text><text class="breeze-ci-upgrade-r1" x="707.6" y="410.4" textLength="732" clip-path="url(#breeze-ci-upgrade-line-16)">Force syncing K8s schemas to airflow-site even if all       </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 appear published                                   </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 cooldown when upgrading package managers (pip and 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 use their latest versions </text><text class="breeze-ci-upgrade-r5" x="1061.4" y="483.6" textLength="122" clip-path="url(#breeze-ci-upgrade-line-19)">[default: </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                                        </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 token used to authenticate to GitHub. </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)"> Upgrade steps </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 prek autoupdate to update hook revisions </text><text class="breeze-ci-upgrade-r5" x="1293.2" y="605.6" textLength="122" clip-path="url(#breeze-ci-upgrade-line-24)">[default: </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 update-chart-dependencies to update Helm chart       </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 </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: 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 upgrade-important-versions to bump key dependency    </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 </text><text class="breeze-ci-upgrade-r5" x="854" y="727.6" textLength="451.4" clip-path="url(#breeze-ci-upgrade-line-29)">[default: 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 the floors of the curated dependencies in          </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] </text><text class="breeze-ci-upgrade-r5" x="1146.8" y="776.4" textLength="122" clip-path="url(#breeze-ci-upgrade-line-31)">[default: </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 update-uv-lock to regenerate uv.lock with latest     </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 inside Breeze CI image, then regenerate the  </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 datamodels the resolved code generator stamps its    </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 into </text><text class="breeze-ci-upgrade-r5" x="902.8" y="898.4" textLength="305" clip-path="url(#breeze-ci-upgrade-line-36)">[default: 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 K8s JSON schemas to airflow-site </text><text class="breeze-ci-upgrade-r5" x="1207.8" y="922.8" textLength="122" clip-path="url(#breeze-ci-upgrade-line-37)">[default: </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)"> Common options </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 </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 answer to questions. </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 | n | q | yes | no | 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 verbose information about performed 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 dry-run is set, commands are only printed, not 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   </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 this message and 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                               </text><text class="breeze-ci-upgrade-r1" x="707.6" y="264" textLength="732" clip-path="url(#breeze-ci-upgrade-line-10)">Commit the upgrade onto the target release branch and push  </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 there instead of opening a PR - what the release manager </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 before the test-to-stable PR, whose head is that       </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. Needs committer rights, as the release branch rules </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 only committers push without CI having passed           </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 the PR as a draft (useful for scheduled CI runs 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 human undrafts to trigger CI) </text><text class="breeze-ci-upgrade-r5" x="1098" y="410.4" textLength="231.8" clip-path="url(#breeze-ci-upgrade-line-16)">[default: 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 switch to the base branch if not already on 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 not specified, will ask)                                </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                                        </text><text class="breeze-ci-upgrade-r1" x="707.6" y="483.6" textLength="732" clip-path="url(#breeze-ci-upgrade-line-19)">Path to airflow-site checkout for publishing K8s schemas    </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: ../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                               </text><text class="breeze-ci-upgrade-r1" x="707.6" y="532.4" textLength="732" clip-path="url(#breeze-ci-upgrade-line-21)">Force syncing K8s schemas to airflow-site even if all       </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 appear published                                   </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 cooldown when upgrading package managers (pip and 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 use their latest versions </text><text class="breeze-ci-upgrade-r5" x="1061.4" y="605.6" textLength="122" clip-path="url(#breeze-ci-upgrade-line-24)">[default: </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                                        </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 token used to authenticate to GitHub. </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)"> Upgrade steps </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 prek autoupdate to update hook revisions </text><text class="breeze-ci-upgrade-r5" x="1293.2" y="727.6" textLength="122" clip-path="url(#breeze-ci-upgrade-line-29)">[default: </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 update-chart-dependencies to update Helm chart       </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 </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: 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 upgrade-important-versions to bump key dependency    </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 </text><text class="breeze-ci-upgrade-r5" x="854" y="849.6" textLength="451.4" clip-path="url(#breeze-ci-upgrade-line-34)">[default: 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 the floors of the curated dependencies in          </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] </text><text class="breeze-ci-upgrade-r5" x="1146.8" y="898.4" textLength="122" clip-path="url(#breeze-ci-upgrade-line-36)">[default: </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 the latest hardened base images (a workflow run 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) before upgrading important versions, so the 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 pin them. Needs gh credentials that may dispatch     </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 workflows </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: 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 update-uv-lock to regenerate uv.lock with latest     </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 inside Breeze CI image, then regenerate the  </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 datamodels the resolved code generator stamps its    </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 into </text><text class="breeze-ci-upgrade-r5" x="902.8" y="1118" textLength="305" clip-path="url(#breeze-ci-upgrade-line-45)">[default: 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 K8s JSON schemas to airflow-site </text><text class="breeze-ci-upgrade-r5" x="1207.8" y="1142.4" textLength="122" clip-path="url(#breeze-ci-upgrade-line-46)">[default: </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)"> Common options </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 </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 answer to questions. </text><text class="breeze-ci-upgrade-r6" x="536.8" y="1240" textLength="353.8" clip-path="url(#breeze-ci-upgrade-line-50)">(y | n | q | yes | no | 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 verbose information about performed 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 dry-run is set, commands are only printed, not 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   </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 this message and 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 @@
|
||||
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: setup 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: setup 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 | testing:providers-tests | testing:python-api-client-tests | </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 | testing:task-sdk-integration-tests | testing:task-sdk-tests | 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)">| ui | ui:check-translation-completeness | ui:compile-assets | verify | workflow-run | </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 | workflow-run:release-constraints | 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)"> Common options </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 verbose information about performed 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 dry-run is set, commands are only printed, not 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   </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 this message and 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 | workflow-run:publish-docs | workflow-run:release-constraints | </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 | 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)"> Common options </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 verbose information about performed 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 dry-run is set, commands are only printed, not 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   </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 this message and 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 | testing:providers-tests | testing:python-api-client-tests | </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 | testing:task-sdk-integration-tests | testing:task-sdk-tests | </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 | ui | ui:check-translation-completeness | ui:compile-assets | verify | </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 | workflow-run:publish-docs | workflow-run:release-constraints | </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 | workflow-run:mirror-base-images | workflow-run:publish-docs | </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 | workflow-run:sync-staging-to-main | 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 check if some images need to be regenerated. Return 0 if no need or 1 if needed. Cannot be used  </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 with </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)"> flag or </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)">.                                                              </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
|
||||
@@ -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: 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: 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 this message and 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)"> Airflow github actions workflow commands </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         </text><text class="breeze-workflow-run-r1" x="305" y="239.6" textLength="1134.6" clip-path="url(#breeze-workflow-run-line-9)">Trigger publish docs to S3 workflow                                                          </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 </text><text class="breeze-workflow-run-r1" x="305" y="264" textLength="1134.6" clip-path="url(#breeze-workflow-run-line-10)">Reset the staging branches of apache/airflow-site and apache/airflow-site-archive to main.   </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  </text><text class="breeze-workflow-run-r1" x="305" y="288.4" textLength="1134.6" clip-path="url(#breeze-workflow-run-line-11)">Trigger the workflow that resolves, publishes and tags the constraints for a release.        </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        </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 publish docs to S3 workflow                                                           </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 the staging branches of apache/airflow-site and apache/airflow-site-archive to main.    </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 </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 the workflow that resolves, publishes and tags the constraints for a release.         </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  </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 the latest hardened base images and wait until they are mirrored.                      </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       </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 the important CI environment of a branch before a release, mirroring the latest       </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 base images first. For main (before a beta) it runs the upgrade check workflow, 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 an upgrade PR, and waits for it. For a vX-Y-test branch (before its test-to-stable PR)  </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 runs the upgrade locally and pushes it to the branch, which needs committer rights.        </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: workflow-run 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 workflow-run 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 the latest hardened base images and wait until they are 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)"> Mirror flags </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 the mirror run the main upgrade check when a tag moves past </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 pin. Turn it off when the main upgrade check is what runs   </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 mirror. </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: 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)"> Common options </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 this message and 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: workflow-run 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 workflow-run 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 the important CI environment of a branch before a release, mirroring the latest hardened base images first. </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 main (before a beta) it runs the upgrade check workflow, which opens an upgrade PR, and waits for it. For a </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 branch (before its test-to-stable PR) it runs the upgrade locally and pushes it to the branch, which needs </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 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)"> Branch to upgrade </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 to upgrade. </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: 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 | 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)"> Common options </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 this message and 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},
|
||||
)
|
||||
@@ -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"
|
||||
@@ -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},
|
||||
),
|
||||
]
|
||||
@@ -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()
|
||||
@@ -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:
|
||||
|
||||
|
||||
@@ -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])
|
||||
|
||||
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||