This is an automated email from the ASF dual-hosted git repository.
potiuk pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/airflow.git
The following commit(s) were added to refs/heads/main by this push:
new fdddc181370 Vendor K8s JSON schemas for helm tests and add
multi-version validation (#62820)
fdddc181370 is described below
commit fdddc181370a0a1c85080fe1eac1d094189cb0db
Author: Jarek Potiuk <[email protected]>
AuthorDate: Wed Mar 4 21:51:02 2026 +0100
Vendor K8s JSON schemas for helm tests and add multi-version validation
(#62820)
Co-authored-by: Claude Opus 4.6 <[email protected]>
---
.github/actions/breeze/action.yml | 2 +-
.github/actions/install-prek/action.yml | 2 +-
.github/workflows/basic-tests.yml | 2 +-
.github/workflows/ci-amd-arm.yml | 4 +-
.github/workflows/helm-tests.yml | 17 +-
.github/workflows/release_dockerhub_image.yml | 2 +-
.pre-commit-config.yaml | 8 +
AGENTS.md | 3 +
Dockerfile | 2 +-
Dockerfile.ci | 2 +-
dev/breeze/doc/ci/02_images.md | 2 +-
dev/breeze/doc/images/output_ci_upgrade.svg | 84 +++++-
dev/breeze/doc/images/output_ci_upgrade.txt | 2 +-
.../doc/images/output_testing_helm-tests.svg | 52 ++--
.../doc/images/output_testing_helm-tests.txt | 2 +-
.../src/airflow_breeze/commands/ci_commands.py | 189 +++++++++++++-
.../airflow_breeze/commands/ci_commands_config.py | 14 +-
.../commands/release_management_commands.py | 2 +-
.../airflow_breeze/commands/testing_commands.py | 14 +
.../commands/testing_commands_config.py | 1 +
dev/breeze/src/airflow_breeze/global_constants.py | 2 +-
.../src/airflow_breeze/utils/selective_checks.py | 10 +
dev/breeze/tests/test_selective_checks.py | 61 +++++
dev/breeze/uv.lock | 54 ++--
.../tests/chart_utils/helm_template_generator.py | 110 +++-----
pyproject.toml | 2 +-
scripts/ci/prek/check_k8s_schemas_published.py | 83 ++++++
scripts/ci/prek/common_prek_utils.py | 20 ++
scripts/ci/prek/download_k8s_schemas.py | 281 +++++++++++++++++++++
scripts/tools/setup_breeze | 2 +-
30 files changed, 873 insertions(+), 158 deletions(-)
diff --git a/.github/actions/breeze/action.yml
b/.github/actions/breeze/action.yml
index 605e1737f31..53d94793c0d 100644
--- a/.github/actions/breeze/action.yml
+++ b/.github/actions/breeze/action.yml
@@ -24,7 +24,7 @@ inputs:
default: "3.10"
uv-version:
description: 'uv version to use'
- default: "0.10.7" # Keep this comment to allow automatic replacement of
uv version
+ default: "0.10.8" # Keep this comment to allow automatic replacement of
uv version
outputs:
host-python-version:
description: Python version used in host
diff --git a/.github/actions/install-prek/action.yml
b/.github/actions/install-prek/action.yml
index 3a1b4864b4b..00821eb44c7 100644
--- a/.github/actions/install-prek/action.yml
+++ b/.github/actions/install-prek/action.yml
@@ -24,7 +24,7 @@ inputs:
default: "3.10"
uv-version:
description: 'uv version to use'
- default: "0.10.7" # Keep this comment to allow automatic replacement of
uv version
+ default: "0.10.8" # Keep this comment to allow automatic replacement of
uv version
prek-version:
description: 'prek version to use'
default: "0.3.4" # Keep this comment to allow automatic replacement of
prek version
diff --git a/.github/workflows/basic-tests.yml
b/.github/workflows/basic-tests.yml
index 47672b895dc..d197df3e746 100644
--- a/.github/workflows/basic-tests.yml
+++ b/.github/workflows/basic-tests.yml
@@ -70,7 +70,7 @@ on: # yamllint disable-line rule:truthy
type: string
uv-version:
description: 'uv version to use'
- default: "0.10.7" # Keep this comment to allow automatic replacement
of uv version
+ default: "0.10.8" # Keep this comment to allow automatic replacement
of uv version
type: string
platform:
description: 'Platform for the build - linux/amd64 or linux/arm64'
diff --git a/.github/workflows/ci-amd-arm.yml b/.github/workflows/ci-amd-arm.yml
index ca5229a8cc1..3643392e011 100644
--- a/.github/workflows/ci-amd-arm.yml
+++ b/.github/workflows/ci-amd-arm.yml
@@ -40,7 +40,7 @@ env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GITHUB_USERNAME: ${{ github.actor }}
SLACK_BOT_TOKEN: ${{ secrets.SLACK_BOT_TOKEN }}
- UV_VERSION: "0.10.7" # Keep this comment to allow automatic replacement of
uv version
+ UV_VERSION: "0.10.8" # Keep this comment to allow automatic replacement of
uv version
VERBOSE: "true"
concurrency:
@@ -81,6 +81,7 @@ jobs:
full-tests-needed: ${{ steps.selective-checks.outputs.full-tests-needed
}}
has-migrations: ${{ steps.selective-checks.outputs.has-migrations }}
helm-test-packages: ${{
steps.selective-checks.outputs.helm-test-packages }}
+ helm-test-kubernetes-versions: ${{
steps.selective-checks.outputs.helm-test-kubernetes-versions }}
include-success-outputs: ${{
steps.selective-checks.outputs.include-success-outputs }}
individual-providers-test-types-list-as-strings-in-json: >-
${{
steps.selective-checks.outputs.individual-providers-test-types-list-as-strings-in-json
}}
@@ -392,6 +393,7 @@ jobs:
runners: ${{ needs.build-info.outputs.runner-type }}
platform: ${{ needs.build-info.outputs.platform }}
helm-test-packages: ${{ needs.build-info.outputs.helm-test-packages }}
+ helm-test-kubernetes-versions: ${{
needs.build-info.outputs.helm-test-kubernetes-versions }}
default-python-version: "${{
needs.build-info.outputs.default-python-version }}"
use-uv: ${{ needs.build-info.outputs.use-uv }}
if: >
diff --git a/.github/workflows/helm-tests.yml b/.github/workflows/helm-tests.yml
index 89bd7c7c0bb..14d5fe6f944 100644
--- a/.github/workflows/helm-tests.yml
+++ b/.github/workflows/helm-tests.yml
@@ -32,6 +32,10 @@ on: # yamllint disable-line rule:truthy
description: "Stringified JSON array of helm test packages to test"
required: true
type: string
+ helm-test-kubernetes-versions:
+ description: "Stringified JSON array of Kubernetes versions to
validate against"
+ required: true
+ type: string
default-python-version:
description: "Which version of python should be used by default"
required: true
@@ -45,13 +49,16 @@ permissions:
jobs:
tests-helm:
timeout-minutes: 80
- name: "Unit tests Helm: ${{ matrix.helm-test-package }}"
+ name: >-
+ Unit tests Helm: ${{ matrix.helm-test-package }}
+ (K8S ${{ matrix.kubernetes-version }})
runs-on: ${{ fromJSON(inputs.runners) }}
strategy:
fail-fast: false
max-parallel: 20
matrix:
helm-test-package: ${{ fromJSON(inputs.helm-test-packages) }}
+ kubernetes-version: ${{ fromJSON(inputs.helm-test-kubernetes-versions)
}}
env:
# Always use default Python version of CI image for preparing packages
PYTHON_MAJOR_MINOR_VERSION: "${{ inputs.default-python-version }}"
@@ -79,10 +86,14 @@ jobs:
python: "${{ inputs.default-python-version }}"
use-uv: ${{ inputs.use-uv }}
make-mnt-writeable-and-cleanup: true
- - name: "Helm Unit Tests: ${{ matrix.helm-test-package }}"
+ - name: "Helm Unit Tests: ${{ matrix.helm-test-package }} (K8S ${{
matrix.kubernetes-version }})"
env:
HELM_TEST_PACKAGE: "${{ matrix.helm-test-package }}"
- run: breeze testing helm-tests --test-type "${HELM_TEST_PACKAGE}"
+ HELM_TEST_KUBERNETES_VERSION: "${{ matrix.kubernetes-version }}"
+ run: >
+ breeze testing helm-tests
+ --test-type "${HELM_TEST_PACKAGE}"
+ --kubernetes-version "${HELM_TEST_KUBERNETES_VERSION}"
tests-helm-release:
timeout-minutes: 80
diff --git a/.github/workflows/release_dockerhub_image.yml
b/.github/workflows/release_dockerhub_image.yml
index 3d6ab532387..7fac56a7d19 100644
--- a/.github/workflows/release_dockerhub_image.yml
+++ b/.github/workflows/release_dockerhub_image.yml
@@ -58,7 +58,7 @@ jobs:
AIRFLOW_VERSION: ${{ github.event.inputs.airflowVersion }}
AMD_ONLY: ${{ github.event.inputs.amdOnly }}
LIMIT_PYTHON_VERSIONS: ${{ github.event.inputs.limitPythonVersions }}
- UV_VERSION: "0.10.7" # Keep this comment to allow automatic replacement
of uv version
+ UV_VERSION: "0.10.8" # Keep this comment to allow automatic replacement
of uv version
if: contains(fromJSON('[
"ashb",
"bugraoz93",
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index 694f2706173..aca03e5f2cb 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -658,6 +658,7 @@ repos:
^scripts/ci/docker-compose/integration-keycloak\.yml$|
^scripts/ci/docker-compose/keycloak/keycloak-entrypoint\.sh$|
^scripts/ci/prek/upgrade_important_versions.py$|
+ ^scripts/ci/prek/download_k8s_schemas\.py$|
^scripts/ci/prek/vendor_k8s_json_schema\.py$
- id: check-template-context-variable-in-sync
name: Sync template context variable refs
@@ -953,6 +954,13 @@ repos:
- "B101,B301,B324,B403,B404,B603"
- "--severity-level"
- "high" # TODO: remove this line when we fix all the issues
+ - id: check-k8s-schemas-published
+ name: Check K8s schemas are published on airflow.apache.org
+ entry: ./scripts/ci/prek/check_k8s_schemas_published.py
+ language: python
+ pass_filenames: false
+ files: ^dev/breeze/src/airflow_breeze/global_constants\.py$
+ require_serial: true
## ADD MOST PREK HOOK ABOVE THAT LINE
# The below prek hooks are those requiring CI image to be built
- id: mypy-dev
diff --git a/AGENTS.md b/AGENTS.md
index 22724b78dad..8427eacf54c 100644
--- a/AGENTS.md
+++ b/AGENTS.md
@@ -26,6 +26,9 @@
`v3-1-test` when creating a PR for the 3.1 branch.
- **Build docs:** `breeze build-docs`
+- **Run Helm chart tests:** `breeze testing helm-tests --use-xdist`
+- **Run Helm tests with specific K8s version:** `breeze testing helm-tests
--use-xdist --kubernetes-version 1.35.0`
+- **Run specific Helm test type:** `breeze testing helm-tests --use-xdist
--test-type <type>` (types: `airflow_aux`, `airflow_core`, `apiserver`,
`dagprocessor`, `other`, `redis`, `security`, `statsd`, `webserver`)
SQLite is the default backend. Use `--backend postgres` or `--backend mysql`
for integration tests that need those databases. If Docker networking fails,
run `docker network prune`.
diff --git a/Dockerfile b/Dockerfile
index b04509c0353..faeef2b3b54 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -73,7 +73,7 @@ ARG PYTHON_LTO="true"
# Also use `force pip` label on your PR to swap all places we use `uv` to `pip`
ARG AIRFLOW_PIP_VERSION=26.0.1
# ARG AIRFLOW_PIP_VERSION="git+https://github.com/pypa/pip.git@main"
-ARG AIRFLOW_UV_VERSION=0.10.7
+ARG AIRFLOW_UV_VERSION=0.10.8
ARG AIRFLOW_USE_UV="false"
ARG AIRFLOW_IMAGE_REPOSITORY="https://github.com/apache/airflow"
ARG
AIRFLOW_IMAGE_README_URL="https://raw.githubusercontent.com/apache/airflow/main/docs/docker-stack/README.md"
diff --git a/Dockerfile.ci b/Dockerfile.ci
index e641015d57a..3919a04c5d5 100644
--- a/Dockerfile.ci
+++ b/Dockerfile.ci
@@ -1733,7 +1733,7 @@ COPY --from=scripts common.sh install_packaging_tools.sh
install_additional_depe
# Also use `force pip` label on your PR to swap all places we use `uv` to `pip`
ARG AIRFLOW_PIP_VERSION=26.0.1
# ARG AIRFLOW_PIP_VERSION="git+https://github.com/pypa/pip.git@main"
-ARG AIRFLOW_UV_VERSION=0.10.7
+ARG AIRFLOW_UV_VERSION=0.10.8
ARG AIRFLOW_PREK_VERSION="0.3.4"
# UV_LINK_MODE=copy is needed since we are using cache mounted from the host
diff --git a/dev/breeze/doc/ci/02_images.md b/dev/breeze/doc/ci/02_images.md
index 4b184d6fbed..ac9772f4a0f 100644
--- a/dev/breeze/doc/ci/02_images.md
+++ b/dev/breeze/doc/ci/02_images.md
@@ -443,7 +443,7 @@ can be used for CI images:
| `ADDITIONAL_DEV_APT_DEPS` | | Additional
apt dev dependencies installed in the first part of the image
|
| `ADDITIONAL_DEV_APT_ENV` | | Additional
env variables defined when installing dev deps
|
| `AIRFLOW_PIP_VERSION` | `26.0.1` | `pip`
version used.
|
-| `AIRFLOW_UV_VERSION` | `0.10.7` | `uv`
version used.
|
+| `AIRFLOW_UV_VERSION` | `0.10.8` | `uv`
version used.
|
| `AIRFLOW_PREK_VERSION` | `0.3.4` | `prek`
version used.
|
| `AIRFLOW_USE_UV` | `true` | Whether to
use UV for installation.
|
| `PIP_PROGRESS_BAR` | `on` | Progress
bar for PIP installation
|
diff --git a/dev/breeze/doc/images/output_ci_upgrade.svg
b/dev/breeze/doc/images/output_ci_upgrade.svg
index 0287487009f..2fee555288b 100644
--- a/dev/breeze/doc/images/output_ci_upgrade.svg
+++ b/dev/breeze/doc/images/output_ci_upgrade.svg
@@ -1,4 +1,4 @@
-<svg class="rich-terminal" viewBox="0 0 1482 513.5999999999999"
xmlns="http://www.w3.org/2000/svg">
+<svg class="rich-terminal" viewBox="0 0 1482 904.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="462.59999999999997" />
+ <rect x="0" y="0" width="1463.0" height="853.0" />
</clipPath>
<clipPath id="breeze-ci-upgrade-line-0">
<rect x="0" y="1.5" width="1464" height="24.65"/>
@@ -99,9 +99,57 @@
<clipPath id="breeze-ci-upgrade-line-17">
<rect x="0" y="416.3" width="1464" height="24.65"/>
</clipPath>
+<clipPath id="breeze-ci-upgrade-line-18">
+ <rect x="0" y="440.7" width="1464" height="24.65"/>
+ </clipPath>
+<clipPath id="breeze-ci-upgrade-line-19">
+ <rect x="0" y="465.1" width="1464" height="24.65"/>
+ </clipPath>
+<clipPath id="breeze-ci-upgrade-line-20">
+ <rect x="0" y="489.5" width="1464" height="24.65"/>
+ </clipPath>
+<clipPath id="breeze-ci-upgrade-line-21">
+ <rect x="0" y="513.9" width="1464" height="24.65"/>
+ </clipPath>
+<clipPath id="breeze-ci-upgrade-line-22">
+ <rect x="0" y="538.3" width="1464" height="24.65"/>
+ </clipPath>
+<clipPath id="breeze-ci-upgrade-line-23">
+ <rect x="0" y="562.7" width="1464" height="24.65"/>
+ </clipPath>
+<clipPath id="breeze-ci-upgrade-line-24">
+ <rect x="0" y="587.1" width="1464" height="24.65"/>
+ </clipPath>
+<clipPath id="breeze-ci-upgrade-line-25">
+ <rect x="0" y="611.5" width="1464" height="24.65"/>
+ </clipPath>
+<clipPath id="breeze-ci-upgrade-line-26">
+ <rect x="0" y="635.9" width="1464" height="24.65"/>
+ </clipPath>
+<clipPath id="breeze-ci-upgrade-line-27">
+ <rect x="0" y="660.3" width="1464" height="24.65"/>
+ </clipPath>
+<clipPath id="breeze-ci-upgrade-line-28">
+ <rect x="0" y="684.7" width="1464" height="24.65"/>
+ </clipPath>
+<clipPath id="breeze-ci-upgrade-line-29">
+ <rect x="0" y="709.1" width="1464" height="24.65"/>
+ </clipPath>
+<clipPath id="breeze-ci-upgrade-line-30">
+ <rect x="0" y="733.5" width="1464" height="24.65"/>
+ </clipPath>
+<clipPath id="breeze-ci-upgrade-line-31">
+ <rect x="0" y="757.9" width="1464" height="24.65"/>
+ </clipPath>
+<clipPath id="breeze-ci-upgrade-line-32">
+ <rect x="0" y="782.3" width="1464" height="24.65"/>
+ </clipPath>
+<clipPath id="breeze-ci-upgrade-line-33">
+ <rect x="0" y="806.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="511.6" 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="902" 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"/>
@@ -123,13 +171,29 @@
</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="488" y="239.6" textLength="951.6"
clip-path="url(#breeze-ci-upgrade-line-9)">ask)                                         
[...]
</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="195.2"
clip-path="url(#breeze-ci-upgrade-line-10)">--switch-to-base</text><text
class="breeze-ci-upgrade-r1" x="219.6" y="264" textLength="12.2"
clip-path="url(#breeze-ci-upgrade-line-10)">/</text><text
class="breeze-ci-upgrade-r4" x="231.8" y="264" textLength="231.8"
clip-path="url(#breeze-ci-upgrade [...]
</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="488" y="288.4" textLength="951.6"
clip-path="url(#breeze-ci-upgrade-line-11)">specified, will ask)                                    &#
[...]
-</text><text class="breeze-ci-upgrade-r5" x="0" y="312.8" textLength="1464"
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="24.4"
clip-path="url(#breeze-ci-upgrade-line-13)">╭─</text><text
class="breeze-ci-upgrade-r5" x="24.4" y="337.2" textLength="195.2"
clip-path="url(#breeze-ci-upgrade-line-13)"> Common options </text><text
class="breeze-ci-upgrade-r5" x="219.6" y="337.2" textLength="1220"
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="109.8"
clip-path="url(#breeze-ci-upgrade-line-14)">--answer </text><text
class="breeze-ci-upgrade-r7" x="158.6" y="361.6" textLength="24.4"
clip-path="url(#breeze-ci-upgrade-line-14)">-a</text><text
class="breeze-ci-upgrade-r1" x="207.4" y="361.6" textLength="329.4"
clip-path="url(#breeze-ci- [...]
-</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="109.8"
clip-path="url(#breeze-ci-upgrade-line-15)">--verbose</text><text
class="breeze-ci-upgrade-r7" x="158.6" y="386" textLength="24.4"
clip-path="url(#breeze-ci-upgrade-line-15)">-v</text><text
class="breeze-ci-upgrade-r1" x="207.4" y="386" textLength="585.6"
clip-path="url(#breeze-ci-upgrade-line- [...]
-</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="109.8"
clip-path="url(#breeze-ci-upgrade-line-16)">--dry-run</text><text
class="breeze-ci-upgrade-r7" x="158.6" y="410.4" textLength="24.4"
clip-path="url(#breeze-ci-upgrade-line-16)">-D</text><text
class="breeze-ci-upgrade-r1" x="207.4" y="410.4" textLength="719.8"
clip-path="url(#breeze-ci-upgra [...]
-</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="109.8"
clip-path="url(#breeze-ci-upgrade-line-17)">--help   </text><text
class="breeze-ci-upgrade-r7" x="158.6" y="434.8" textLength="24.4"
clip-path="url(#breeze-ci-upgrade-line-17)">-h</text><text
class="breeze-ci-upgrade-r1" x="207.4" y="434.8" textLength="329.4"
clip-path="url(# [...]
-</text><text class="breeze-ci-upgrade-r5" x="0" y="459.2" textLength="1464"
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="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="439.2"
clip-path="url(#breeze-ci-upgrade-line-12)">--airflow-site                      </text><text
class="breeze-ci-upgrade-r1" x="488" y="312.8" textLength="695.4"
clip-path="url(#breeze [...]
+</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-r5" x="488" y="337.2" textLength="195.2"
clip-path="url(#breeze-ci-upgrade-line-13)">../airflow-site]</text><text
class="breeze-ci-upgrade-r6" x="695.4" y="337.2" textLength="134.2"
clip-path="url(#breeze-ci-upgrade-line-13)">(DIRECTORY)</text><text
class="breeze-ci-upgrade-r5" x="1451.8" y="337.2" textLength="12.2"
clip-path="url( [...]
+</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="439.2"
clip-path="url(#breeze-ci-upgrade-line-14)">--force-k8s-schema-sync             </text><text
class="breeze-ci-upgrade-r1" x="488" y="361.6" textLength="951.6"
clip-path="url(#breeze-ci-upgrade-line-14)">Force syncing  [...]
+</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-r1" x="488" y="386" textLength="951.6"
clip-path="url(#breeze-ci-upgrade-line-15)">published                                        
[...]
+</text><text class="breeze-ci-upgrade-r5" x="0" y="410.4" textLength="1464"
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="24.4"
clip-path="url(#breeze-ci-upgrade-line-17)">╭─</text><text
class="breeze-ci-upgrade-r5" x="24.4" y="434.8" textLength="183"
clip-path="url(#breeze-ci-upgrade-line-17)"> Upgrade steps </text><text
class="breeze-ci-upgrade-r5" x="207.4" y="434.8" textLength="1232.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="146.4"
clip-path="url(#breeze-ci-upgrade-line-18)">--autoupdate</text><text
class="breeze-ci-upgrade-r1" x="170.8" y="459.2" textLength="12.2"
clip-path="url(#breeze-ci-upgrade-line-18)">/</text><text
class="breeze-ci-upgrade-r4" x="183" y="459.2" textLength="183"
clip-path="url(#breeze-ci-upgrade [...]
+</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-r5" x="744.2" y="483.6" textLength="134.2"
clip-path="url(#breeze-ci-upgrade-line-19)">autoupdate]</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-upgr [...]
+</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-r4" x="24.4" y="508" textLength="170.8"
clip-path="url(#breeze-ci-upgrade-line-20)">--pin-versions</text><text
class="breeze-ci-upgrade-r1" x="195.2" y="508" textLength="12.2"
clip-path="url(#breeze-ci-upgrade-line-20)">/</text><text
class="breeze-ci-upgrade-r4" x="207.4" y="508" textLength="207.4"
clip-path="url(#breeze-ci-upgrade-l [...]
+</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-r5" x="744.2" y="532.4" textLength="158.6"
clip-path="url(#breeze-ci-upgrade-line-21)">pin-versions]</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-up [...]
+</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-r4" x="24.4" y="556.8" textLength="329.4"
clip-path="url(#breeze-ci-upgrade-line-22)">--update-chart-dependencies</text><text
class="breeze-ci-upgrade-r1" x="353.8" y="556.8" textLength="12.2"
clip-path="url(#breeze-ci-upgrade-line-22)">/</text><text
class="breeze-ci-upgrade-r4" x="366" y="556.8" textLength="353.8"
clip-path="url(# [...]
+</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="12.2"
clip-path="url(#breeze-ci-upgrade-line-23)">s</text><text
class="breeze-ci-upgrade-r1" x="744.2" y="581.2" textLength="158.6"
clip-path="url(#breeze-ci-upgrade-line-23)">dependencies </text><text
class="breeze-ci-upgrade-r5" x="902.8" y="581.2" textLength="439.2"
clip-path="url(#breeze- [...]
+</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="341.6"
clip-path="url(#breeze-ci-upgrade-line-24)">--upgrade-important-versions</text><text
class="breeze-ci-upgrade-r1" x="366" y="605.6" textLength="12.2"
clip-path="url(#breeze-ci-upgrade-line-24)">/</text><text
class="breeze-ci-upgrade-r4" x="378.2" y="605.6" textLength="341.6"
clip-path="url( [...]
+</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-r4" x="24.4" y="630" textLength="36.6"
clip-path="url(#breeze-ci-upgrade-line-25)">ons</text><text
class="breeze-ci-upgrade-r1" x="744.2" y="630" textLength="109.8"
clip-path="url(#breeze-ci-upgrade-line-25)">versions </text><text
class="breeze-ci-upgrade-r5" x="854" y="630" textLength="451.4"
clip-path="url(#breeze-ci-upgrade-l [...]
+</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="207.4"
clip-path="url(#breeze-ci-upgrade-line-26)">--k8s-schema-sync</text><text
class="breeze-ci-upgrade-r1" x="231.8" y="654.4" textLength="12.2"
clip-path="url(#breeze-ci-upgrade-line-26)">/</text><text
class="breeze-ci-upgrade-r4" x="244" y="654.4" textLength="244"
clip-path="url(#breeze-ci-up [...]
+</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-r5" x="744.2" y="678.8" textLength="195.2"
clip-path="url(#breeze-ci-upgrade-line-27)">k8s-schema-sync]</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 [...]
+</text><text class="breeze-ci-upgrade-r5" x="0" y="703.2" textLength="1464"
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="24.4"
clip-path="url(#breeze-ci-upgrade-line-29)">╭─</text><text
class="breeze-ci-upgrade-r5" x="24.4" y="727.6" textLength="195.2"
clip-path="url(#breeze-ci-upgrade-line-29)"> Common options </text><text
class="breeze-ci-upgrade-r5" x="219.6" y="727.6" textLength="1220"
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="109.8"
clip-path="url(#breeze-ci-upgrade-line-30)">--answer </text><text
class="breeze-ci-upgrade-r7" x="158.6" y="752" textLength="24.4"
clip-path="url(#breeze-ci-upgrade-line-30)">-a</text><text
class="breeze-ci-upgrade-r1" x="207.4" y="752" textLength="329.4"
clip-path="url(#breeze-ci-upgrade- [...]
+</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="109.8"
clip-path="url(#breeze-ci-upgrade-line-31)">--verbose</text><text
class="breeze-ci-upgrade-r7" x="158.6" y="776.4" textLength="24.4"
clip-path="url(#breeze-ci-upgrade-line-31)">-v</text><text
class="breeze-ci-upgrade-r1" x="207.4" y="776.4" textLength="585.6"
clip-path="url(#breeze-ci-upgra [...]
+</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="109.8"
clip-path="url(#breeze-ci-upgrade-line-32)">--dry-run</text><text
class="breeze-ci-upgrade-r7" x="158.6" y="800.8" textLength="24.4"
clip-path="url(#breeze-ci-upgrade-line-32)">-D</text><text
class="breeze-ci-upgrade-r1" x="207.4" y="800.8" textLength="719.8"
clip-path="url(#breeze-ci-upgra [...]
+</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="109.8"
clip-path="url(#breeze-ci-upgrade-line-33)">--help   </text><text
class="breeze-ci-upgrade-r7" x="158.6" y="825.2" textLength="24.4"
clip-path="url(#breeze-ci-upgrade-line-33)">-h</text><text
class="breeze-ci-upgrade-r1" x="207.4" y="825.2" textLength="329.4"
clip-path="url(# [...]
+</text><text class="breeze-ci-upgrade-r5" x="0" y="849.6" textLength="1464"
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>
</g>
</g>
diff --git a/dev/breeze/doc/images/output_ci_upgrade.txt
b/dev/breeze/doc/images/output_ci_upgrade.txt
index 890eea2fe0f..d825b5556de 100644
--- a/dev/breeze/doc/images/output_ci_upgrade.txt
+++ b/dev/breeze/doc/images/output_ci_upgrade.txt
@@ -1 +1 @@
-96a3cebcb6753bff242b0d524d734d00
+e8f12d48c804aac757f9a853dba754f7
diff --git a/dev/breeze/doc/images/output_testing_helm-tests.svg
b/dev/breeze/doc/images/output_testing_helm-tests.svg
index de020c2ec91..b7e38b97f53 100644
--- a/dev/breeze/doc/images/output_testing_helm-tests.svg
+++ b/dev/breeze/doc/images/output_testing_helm-tests.svg
@@ -1,4 +1,4 @@
-<svg class="rich-terminal" viewBox="0 0 1482 660.0"
xmlns="http://www.w3.org/2000/svg">
+<svg class="rich-terminal" viewBox="0 0 1482 708.8"
xmlns="http://www.w3.org/2000/svg">
<!-- Generated with Rich https://www.textualize.io -->
<style>
@@ -43,7 +43,7 @@
<defs>
<clipPath id="breeze-testing-helm-tests-clip-terminal">
- <rect x="0" y="0" width="1463.0" height="609.0" />
+ <rect x="0" y="0" width="1463.0" height="657.8" />
</clipPath>
<clipPath id="breeze-testing-helm-tests-line-0">
<rect x="0" y="1.5" width="1464" height="24.65"/>
@@ -117,9 +117,15 @@
<clipPath id="breeze-testing-helm-tests-line-23">
<rect x="0" y="562.7" width="1464" height="24.65"/>
</clipPath>
+<clipPath id="breeze-testing-helm-tests-line-24">
+ <rect x="0" y="587.1" width="1464" height="24.65"/>
+ </clipPath>
+<clipPath id="breeze-testing-helm-tests-line-25">
+ <rect x="0" y="611.5" 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="658" rx="8"/><text
class="breeze-testing-helm-tests-title" fill="#c5c8c6" text-anchor="middle"
x="740" y="27">Command: testing helm-tests</text>
+ <rect fill="#292929" stroke="rgba(255,255,255,0.35)" stroke-width="1"
x="1" y="1" width="1480" height="706.8" rx="8"/><text
class="breeze-testing-helm-tests-title" fill="#c5c8c6" text-anchor="middle"
x="740" y="27">Command: testing helm-tests</text>
<g transform="translate(26,22)">
<circle cx="0" cy="0" r="7" fill="#ff5f57"/>
<circle cx="22" cy="0" r="7" fill="#febc2e"/>
@@ -135,25 +141,27 @@
</text><text class="breeze-testing-helm-tests-r1" x="12.2" y="93.2"
textLength="256.2"
clip-path="url(#breeze-testing-helm-tests-line-3)">Run Helm chart tests.</text><text
class="breeze-testing-helm-tests-r1" x="1464" y="93.2" textLength="12.2"
clip-path="url(#breeze-testing-helm-tests-line-3)">
</text><text class="breeze-testing-helm-tests-r1" x="1464" y="117.6"
textLength="12.2" clip-path="url(#breeze-testing-helm-tests-line-4)">
</text><text class="breeze-testing-helm-tests-r5" x="0" y="142"
textLength="24.4"
clip-path="url(#breeze-testing-helm-tests-line-5)">╭─</text><text
class="breeze-testing-helm-tests-r5" x="24.4" y="142" textLength="378.2"
clip-path="url(#breeze-testing-helm-tests-line-5)"> Flags for helms-tests command </text><text
class="breeze-testing-helm-tests-r5" x="402.6" y="142" textLength="1037"
clip-path="url(#breeze-testing-helm-tests-line-5)">───────────────────────────
[...]
-</text><text class="breeze-testing-helm-tests-r5" x="0" y="166.4"
textLength="12.2"
clip-path="url(#breeze-testing-helm-tests-line-6)">│</text><text
class="breeze-testing-helm-tests-r4" x="24.4" y="166.4" textLength="170.8"
clip-path="url(#breeze-testing-helm-tests-line-6)">--test-type   </text><text
class="breeze-testing-helm-tests-r1" x="219.6" y="166.4" textLength="317.2"
clip-path="url(#breeze-testing-helm-tests-line-6)">Type of helm tests to r
[...]
-</text><text class="breeze-testing-helm-tests-r5" x="0" y="190.8"
textLength="12.2"
clip-path="url(#breeze-testing-helm-tests-line-7)">│</text><text
class="breeze-testing-helm-tests-r6" x="219.6" y="190.8" textLength="744.2"
clip-path="url(#breeze-testing-helm-tests-line-7)">dagprocessor | other | redis | security | statsd | webserver)</text><text
class="breeze-testing-helm-tests-r5" x="1451.8" y="190.8" textLength="12.2"
clip-path="url(# [...]
-</text><text class="breeze-testing-helm-tests-r5" x="0" y="215.2"
textLength="12.2"
clip-path="url(#breeze-testing-helm-tests-line-8)">│</text><text
class="breeze-testing-helm-tests-r4" x="24.4" y="215.2" textLength="170.8"
clip-path="url(#breeze-testing-helm-tests-line-8)">--test-timeout</text><text
class="breeze-testing-helm-tests-r1" x="219.6" y="215.2" textLength="1220"
clip-path="url(#breeze-testing-helm-tests-line-8)">Test timeout in seconds. Set the p
[...]
-</text><text class="breeze-testing-helm-tests-r5" x="0" y="239.6"
textLength="12.2"
clip-path="url(#breeze-testing-helm-tests-line-9)">│</text><text
class="breeze-testing-helm-tests-r5" x="219.6" y="239.6" textLength="158.6"
clip-path="url(#breeze-testing-helm-tests-line-9)">[default: 60]</text><text
class="breeze-testing-helm-tests-r6" x="390.4" y="239.6" textLength="244"
clip-path="url(#breeze-testing-helm-tests-line-9)">(INTEGER RANGE x>=0)</text><text
class="breeze- [...]
-</text><text class="breeze-testing-helm-tests-r5" x="0" y="264"
textLength="12.2"
clip-path="url(#breeze-testing-helm-tests-line-10)">│</text><text
class="breeze-testing-helm-tests-r4" x="24.4" y="264" textLength="170.8"
clip-path="url(#breeze-testing-helm-tests-line-10)">--use-xdist   </text><text
class="breeze-testing-helm-tests-r1" x="219.6" y="264" textLength="329.4"
clip-path="url(#breeze-testing-helm-tests-line-10)">Use xdist plugin for pytest</te
[...]
-</text><text class="breeze-testing-helm-tests-r5" x="0" y="288.4"
textLength="12.2"
clip-path="url(#breeze-testing-helm-tests-line-11)">│</text><text
class="breeze-testing-helm-tests-r4" x="24.4" y="288.4" textLength="170.8"
clip-path="url(#breeze-testing-helm-tests-line-11)">--parallelism </text><text
class="breeze-testing-helm-tests-r1" x="219.6" y="288.4" textLength="927.2"
clip-path="url(#breeze-testing-helm-tests-line-11)">Maximum number of processes to 
[...]
-</text><text class="breeze-testing-helm-tests-r5" x="0" y="312.8"
textLength="12.2"
clip-path="url(#breeze-testing-helm-tests-line-12)">│</text><text
class="breeze-testing-helm-tests-r6" x="219.6" y="312.8" textLength="170.8"
clip-path="url(#breeze-testing-helm-tests-line-12)">RANGE 1<=x<=8)</text><text
class="breeze-testing-helm-tests-r5" x="1451.8" y="312.8" textLength="12.2"
clip-path="url(#breeze-testing-helm-tests-line-12)">│</text><text
class="breeze-testing-helm-tests-r [...]
-</text><text class="breeze-testing-helm-tests-r5" x="0" y="337.2"
textLength="1464"
clip-path="url(#breeze-testing-helm-tests-line-13)">╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯</text><text
class="breeze-testing-helm-tests-r1" x="1464" y="337.2" textLength="12.2"
clip-path="url(#breeze-testing-helm-tests-line-13)">
-</text><text class="breeze-testing-helm-tests-r5" x="0" y="361.6"
textLength="24.4"
clip-path="url(#breeze-testing-helm-tests-line-14)">╭─</text><text
class="breeze-testing-helm-tests-r5" x="24.4" y="361.6" textLength="451.4"
clip-path="url(#breeze-testing-helm-tests-line-14)"> Advanced flag for helm-test command </text><text
class="breeze-testing-helm-tests-r5" x="475.8" y="361.6" textLength="963.8"
clip-path="url(#breeze-testing-helm-tests-line-14)">────── [...]
-</text><text class="breeze-testing-helm-tests-r5" x="0" y="386"
textLength="12.2"
clip-path="url(#breeze-testing-helm-tests-line-15)">│</text><text
class="breeze-testing-helm-tests-r4" x="24.4" y="386" textLength="231.8"
clip-path="url(#breeze-testing-helm-tests-line-15)">--github-repository</text><text
class="breeze-testing-helm-tests-r7" x="280.6" y="386" textLength="24.4"
clip-path="url(#breeze-testing-helm-tests-line-15)">-g</text><text
class="breeze-testing-helm-tests-r1" x="329.4" [...]
-</text><text class="breeze-testing-helm-tests-r5" x="0" y="410.4"
textLength="12.2"
clip-path="url(#breeze-testing-helm-tests-line-16)">│</text><text
class="breeze-testing-helm-tests-r4" x="24.4" y="410.4" textLength="231.8"
clip-path="url(#breeze-testing-helm-tests-line-16)">--mount-sources    </text><text
class="breeze-testing-helm-tests-r1" x="329.4" y="410.4" textLength="1110.2"
clip-path="url(#breeze-testing-helm-tests-line-16)">Choose scope of loc
[...]
-</text><text class="breeze-testing-helm-tests-r5" x="0" y="434.8"
textLength="12.2"
clip-path="url(#breeze-testing-helm-tests-line-17)">│</text><text
class="breeze-testing-helm-tests-r1" x="329.4" y="434.8" textLength="134.2"
clip-path="url(#breeze-testing-helm-tests-line-17)">selected). </text><text
class="breeze-testing-helm-tests-r5" x="463.6" y="434.8" textLength="231.8"
clip-path="url(#breeze-testing-helm-tests-line-17)">[default: selected]</text><text
class="breeze-testin [...]
-</text><text class="breeze-testing-helm-tests-r5" x="0" y="459.2"
textLength="12.2"
clip-path="url(#breeze-testing-helm-tests-line-18)">│</text><text
class="breeze-testing-helm-tests-r6" x="329.4" y="459.2" textLength="244"
clip-path="url(#breeze-testing-helm-tests-line-18)">providers-and-tests)</text><text
class="breeze-testing-helm-tests-r5" x="1451.8" y="459.2" textLength="12.2"
clip-path="url(#breeze-testing-helm-tests-line-18)">│</text><text
class="breeze-testing-helm-tests-r1" x="1 [...]
-</text><text class="breeze-testing-helm-tests-r5" x="0" y="483.6"
textLength="1464"
clip-path="url(#breeze-testing-helm-tests-line-19)">╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯</text><text
class="breeze-testing-helm-tests-r1" x="1464" y="483.6" textLength="12.2"
clip-path="url(#breeze-testing-helm-tests-line-19)">
-</text><text class="breeze-testing-helm-tests-r5" x="0" y="508"
textLength="24.4"
clip-path="url(#breeze-testing-helm-tests-line-20)">╭─</text><text
class="breeze-testing-helm-tests-r5" x="24.4" y="508" textLength="195.2"
clip-path="url(#breeze-testing-helm-tests-line-20)"> Common options </text><text
class="breeze-testing-helm-tests-r5" x="219.6" y="508" textLength="1220"
clip-path="url(#breeze-testing-helm-tests-line-20)">─────────────────────────────────────────────────
[...]
-</text><text class="breeze-testing-helm-tests-r5" x="0" y="532.4"
textLength="12.2"
clip-path="url(#breeze-testing-helm-tests-line-21)">│</text><text
class="breeze-testing-helm-tests-r4" x="24.4" y="532.4" textLength="109.8"
clip-path="url(#breeze-testing-helm-tests-line-21)">--verbose</text><text
class="breeze-testing-helm-tests-r7" x="158.6" y="532.4" textLength="24.4"
clip-path="url(#breeze-testing-helm-tests-line-21)">-v</text><text
class="breeze-testing-helm-tests-r1" x="207.4" y="5 [...]
-</text><text class="breeze-testing-helm-tests-r5" x="0" y="556.8"
textLength="12.2"
clip-path="url(#breeze-testing-helm-tests-line-22)">│</text><text
class="breeze-testing-helm-tests-r4" x="24.4" y="556.8" textLength="109.8"
clip-path="url(#breeze-testing-helm-tests-line-22)">--dry-run</text><text
class="breeze-testing-helm-tests-r7" x="158.6" y="556.8" textLength="24.4"
clip-path="url(#breeze-testing-helm-tests-line-22)">-D</text><text
class="breeze-testing-helm-tests-r1" x="207.4" y="5 [...]
-</text><text class="breeze-testing-helm-tests-r5" x="0" y="581.2"
textLength="12.2"
clip-path="url(#breeze-testing-helm-tests-line-23)">│</text><text
class="breeze-testing-helm-tests-r4" x="24.4" y="581.2" textLength="109.8"
clip-path="url(#breeze-testing-helm-tests-line-23)">--help   </text><text
class="breeze-testing-helm-tests-r7" x="158.6" y="581.2" textLength="24.4"
clip-path="url(#breeze-testing-helm-tests-line-23)">-h</text><text
class="breeze-testing-helm-tests-r1" [...]
-</text><text class="breeze-testing-helm-tests-r5" x="0" y="605.6"
textLength="1464"
clip-path="url(#breeze-testing-helm-tests-line-24)">╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯</text><text
class="breeze-testing-helm-tests-r1" x="1464" y="605.6" textLength="12.2"
clip-path="url(#breeze-testing-helm-tests-line-24)">
+</text><text class="breeze-testing-helm-tests-r5" x="0" y="166.4"
textLength="12.2"
clip-path="url(#breeze-testing-helm-tests-line-6)">│</text><text
class="breeze-testing-helm-tests-r4" x="24.4" y="166.4" textLength="244"
clip-path="url(#breeze-testing-helm-tests-line-6)">--test-type         </text><text
class="breeze-testing-helm-tests-r1" x="292.8" y="166.4" textLength="317.2"
clip-path="url(#breeze-testing-helm-tests-line-6)">Type of&# [...]
+</text><text class="breeze-testing-helm-tests-r5" x="0" y="190.8"
textLength="12.2"
clip-path="url(#breeze-testing-helm-tests-line-7)">│</text><text
class="breeze-testing-helm-tests-r6" x="292.8" y="190.8" textLength="744.2"
clip-path="url(#breeze-testing-helm-tests-line-7)">dagprocessor | other | redis | security | statsd | webserver)</text><text
class="breeze-testing-helm-tests-r5" x="1451.8" y="190.8" textLength="12.2"
clip-path="url(# [...]
+</text><text class="breeze-testing-helm-tests-r5" x="0" y="215.2"
textLength="12.2"
clip-path="url(#breeze-testing-helm-tests-line-8)">│</text><text
class="breeze-testing-helm-tests-r4" x="24.4" y="215.2" textLength="244"
clip-path="url(#breeze-testing-helm-tests-line-8)">--test-timeout      </text><text
class="breeze-testing-helm-tests-r1" x="292.8" y="215.2" textLength="1146.8"
clip-path="url(#breeze-testing-helm-tests-line-8)">Test timeout in
[...]
+</text><text class="breeze-testing-helm-tests-r5" x="0" y="239.6"
textLength="12.2"
clip-path="url(#breeze-testing-helm-tests-line-9)">│</text><text
class="breeze-testing-helm-tests-r5" x="292.8" y="239.6" textLength="158.6"
clip-path="url(#breeze-testing-helm-tests-line-9)">[default: 60]</text><text
class="breeze-testing-helm-tests-r6" x="463.6" y="239.6" textLength="244"
clip-path="url(#breeze-testing-helm-tests-line-9)">(INTEGER RANGE x>=0)</text><text
class="breeze- [...]
+</text><text class="breeze-testing-helm-tests-r5" x="0" y="264"
textLength="12.2"
clip-path="url(#breeze-testing-helm-tests-line-10)">│</text><text
class="breeze-testing-helm-tests-r4" x="24.4" y="264" textLength="244"
clip-path="url(#breeze-testing-helm-tests-line-10)">--kubernetes-version</text><text
class="breeze-testing-helm-tests-r1" x="292.8" y="264" textLength="658.8"
clip-path="url(#breeze-testing-helm-tests-line-10)">Kubernetes version to validate helm t
[...]
+</text><text class="breeze-testing-helm-tests-r5" x="0" y="288.4"
textLength="12.2"
clip-path="url(#breeze-testing-helm-tests-line-11)">│</text><text
class="breeze-testing-helm-tests-r6" x="292.8" y="288.4" textLength="414.8"
clip-path="url(#breeze-testing-helm-tests-line-11)">1.32.8 | 1.33.4 | 1.34.0 | 1.35.0)</text><text
class="breeze-testing-helm-tests-r5" x="1451.8" y="288.4" textLength="12.2"
clip-path="url(#breeze-testing-helm-tests-line-11)">│</text>< [...]
+</text><text class="breeze-testing-helm-tests-r5" x="0" y="312.8"
textLength="12.2"
clip-path="url(#breeze-testing-helm-tests-line-12)">│</text><text
class="breeze-testing-helm-tests-r4" x="24.4" y="312.8" textLength="244"
clip-path="url(#breeze-testing-helm-tests-line-12)">--use-xdist         </text><text
class="breeze-testing-helm-tests-r1" x="292.8" y="312.8" textLength="329.4"
clip-path="url(#breeze-testing-helm-tests-line-12)">Use xd [...]
+</text><text class="breeze-testing-helm-tests-r5" x="0" y="337.2"
textLength="12.2"
clip-path="url(#breeze-testing-helm-tests-line-13)">│</text><text
class="breeze-testing-helm-tests-r4" x="24.4" y="337.2" textLength="244"
clip-path="url(#breeze-testing-helm-tests-line-13)">--parallelism       </text><text
class="breeze-testing-helm-tests-r1" x="292.8" y="337.2" textLength="927.2"
clip-path="url(#breeze-testing-helm-tests-line-13)">Maximum number&# [...]
+</text><text class="breeze-testing-helm-tests-r5" x="0" y="361.6"
textLength="12.2"
clip-path="url(#breeze-testing-helm-tests-line-14)">│</text><text
class="breeze-testing-helm-tests-r6" x="292.8" y="361.6" textLength="280.6"
clip-path="url(#breeze-testing-helm-tests-line-14)">(INTEGER RANGE 1<=x<=8)</text><text
class="breeze-testing-helm-tests-r5" x="1451.8" y="361.6" textLength="12.2"
clip-path="url(#breeze-testing-helm-tests-line-14)">│</text><text
class="breeze-testin [...]
+</text><text class="breeze-testing-helm-tests-r5" x="0" y="386"
textLength="1464"
clip-path="url(#breeze-testing-helm-tests-line-15)">╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯</text><text
class="breeze-testing-helm-tests-r1" x="1464" y="386" textLength="12.2"
clip-path="url(#breeze-testing-helm-tests-line-15)">
+</text><text class="breeze-testing-helm-tests-r5" x="0" y="410.4"
textLength="24.4"
clip-path="url(#breeze-testing-helm-tests-line-16)">╭─</text><text
class="breeze-testing-helm-tests-r5" x="24.4" y="410.4" textLength="451.4"
clip-path="url(#breeze-testing-helm-tests-line-16)"> Advanced flag for helm-test command </text><text
class="breeze-testing-helm-tests-r5" x="475.8" y="410.4" textLength="963.8"
clip-path="url(#breeze-testing-helm-tests-line-16)">────── [...]
+</text><text class="breeze-testing-helm-tests-r5" x="0" y="434.8"
textLength="12.2"
clip-path="url(#breeze-testing-helm-tests-line-17)">│</text><text
class="breeze-testing-helm-tests-r4" x="24.4" y="434.8" textLength="231.8"
clip-path="url(#breeze-testing-helm-tests-line-17)">--github-repository</text><text
class="breeze-testing-helm-tests-r7" x="280.6" y="434.8" textLength="24.4"
clip-path="url(#breeze-testing-helm-tests-line-17)">-g</text><text
class="breeze-testing-helm-tests-r1" x="3 [...]
+</text><text class="breeze-testing-helm-tests-r5" x="0" y="459.2"
textLength="12.2"
clip-path="url(#breeze-testing-helm-tests-line-18)">│</text><text
class="breeze-testing-helm-tests-r4" x="24.4" y="459.2" textLength="231.8"
clip-path="url(#breeze-testing-helm-tests-line-18)">--mount-sources    </text><text
class="breeze-testing-helm-tests-r1" x="329.4" y="459.2" textLength="1110.2"
clip-path="url(#breeze-testing-helm-tests-line-18)">Choose scope of loc
[...]
+</text><text class="breeze-testing-helm-tests-r5" x="0" y="483.6"
textLength="12.2"
clip-path="url(#breeze-testing-helm-tests-line-19)">│</text><text
class="breeze-testing-helm-tests-r1" x="329.4" y="483.6" textLength="134.2"
clip-path="url(#breeze-testing-helm-tests-line-19)">selected). </text><text
class="breeze-testing-helm-tests-r5" x="463.6" y="483.6" textLength="231.8"
clip-path="url(#breeze-testing-helm-tests-line-19)">[default: selected]</text><text
class="breeze-testin [...]
+</text><text class="breeze-testing-helm-tests-r5" x="0" y="508"
textLength="12.2"
clip-path="url(#breeze-testing-helm-tests-line-20)">│</text><text
class="breeze-testing-helm-tests-r6" x="329.4" y="508" textLength="244"
clip-path="url(#breeze-testing-helm-tests-line-20)">providers-and-tests)</text><text
class="breeze-testing-helm-tests-r5" x="1451.8" y="508" textLength="12.2"
clip-path="url(#breeze-testing-helm-tests-line-20)">│</text><text
class="breeze-testing-helm-tests-r1" x="1464" y [...]
+</text><text class="breeze-testing-helm-tests-r5" x="0" y="532.4"
textLength="1464"
clip-path="url(#breeze-testing-helm-tests-line-21)">╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯</text><text
class="breeze-testing-helm-tests-r1" x="1464" y="532.4" textLength="12.2"
clip-path="url(#breeze-testing-helm-tests-line-21)">
+</text><text class="breeze-testing-helm-tests-r5" x="0" y="556.8"
textLength="24.4"
clip-path="url(#breeze-testing-helm-tests-line-22)">╭─</text><text
class="breeze-testing-helm-tests-r5" x="24.4" y="556.8" textLength="195.2"
clip-path="url(#breeze-testing-helm-tests-line-22)"> Common options </text><text
class="breeze-testing-helm-tests-r5" x="219.6" y="556.8" textLength="1220"
clip-path="url(#breeze-testing-helm-tests-line-22)">───────────────────────────────────────────
[...]
+</text><text class="breeze-testing-helm-tests-r5" x="0" y="581.2"
textLength="12.2"
clip-path="url(#breeze-testing-helm-tests-line-23)">│</text><text
class="breeze-testing-helm-tests-r4" x="24.4" y="581.2" textLength="109.8"
clip-path="url(#breeze-testing-helm-tests-line-23)">--verbose</text><text
class="breeze-testing-helm-tests-r7" x="158.6" y="581.2" textLength="24.4"
clip-path="url(#breeze-testing-helm-tests-line-23)">-v</text><text
class="breeze-testing-helm-tests-r1" x="207.4" y="5 [...]
+</text><text class="breeze-testing-helm-tests-r5" x="0" y="605.6"
textLength="12.2"
clip-path="url(#breeze-testing-helm-tests-line-24)">│</text><text
class="breeze-testing-helm-tests-r4" x="24.4" y="605.6" textLength="109.8"
clip-path="url(#breeze-testing-helm-tests-line-24)">--dry-run</text><text
class="breeze-testing-helm-tests-r7" x="158.6" y="605.6" textLength="24.4"
clip-path="url(#breeze-testing-helm-tests-line-24)">-D</text><text
class="breeze-testing-helm-tests-r1" x="207.4" y="6 [...]
+</text><text class="breeze-testing-helm-tests-r5" x="0" y="630"
textLength="12.2"
clip-path="url(#breeze-testing-helm-tests-line-25)">│</text><text
class="breeze-testing-helm-tests-r4" x="24.4" y="630" textLength="109.8"
clip-path="url(#breeze-testing-helm-tests-line-25)">--help   </text><text
class="breeze-testing-helm-tests-r7" x="158.6" y="630" textLength="24.4"
clip-path="url(#breeze-testing-helm-tests-line-25)">-h</text><text
class="breeze-testing-helm-tests-r1" x="20 [...]
+</text><text class="breeze-testing-helm-tests-r5" x="0" y="654.4"
textLength="1464"
clip-path="url(#breeze-testing-helm-tests-line-26)">╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯</text><text
class="breeze-testing-helm-tests-r1" x="1464" y="654.4" textLength="12.2"
clip-path="url(#breeze-testing-helm-tests-line-26)">
</text>
</g>
</g>
diff --git a/dev/breeze/doc/images/output_testing_helm-tests.txt
b/dev/breeze/doc/images/output_testing_helm-tests.txt
index d57dd55b5c1..c816fbb2bb8 100644
--- a/dev/breeze/doc/images/output_testing_helm-tests.txt
+++ b/dev/breeze/doc/images/output_testing_helm-tests.txt
@@ -1 +1 @@
-769d6c7a6082bff2444e0a347a83d01c
+2a8a6d65107b81afe9bc7ffbec9dfb2e
diff --git a/dev/breeze/src/airflow_breeze/commands/ci_commands.py
b/dev/breeze/src/airflow_breeze/commands/ci_commands.py
index 95dfea5b107..fb375d60630 100644
--- a/dev/breeze/src/airflow_breeze/commands/ci_commands.py
+++ b/dev/breeze/src/airflow_breeze/commands/ci_commands.py
@@ -431,6 +431,102 @@ def get_workflow_info(github_context: str,
github_context_input: StringIO):
wi.print_all_ga_outputs()
+def _check_k8s_schema_published(version: str) -> bool:
+ """Check if K8s schemas for a given version are published on
airflow.apache.org."""
+ from urllib.error import HTTPError, URLError
+ from urllib.request import Request, urlopen
+
+ url =
f"https://airflow.apache.org/k8s-schemas/v{version}-standalone-strict/configmap-v1.json"
+ req = Request(url, method="HEAD")
+ try:
+ resp = urlopen(req, timeout=15)
+ return resp.status == 200
+ except (HTTPError, URLError):
+ return False
+
+
+def _sync_k8s_schemas_to_airflow_site(airflow_site: Path, force: bool,
command_env: dict[str, str]) -> None:
+ """Sync K8s schemas to airflow-site directory if needed."""
+ from airflow_breeze.global_constants import ALLOWED_KUBERNETES_VERSIONS
+
+ versions = [v.lstrip("v") for v in ALLOWED_KUBERNETES_VERSIONS]
+ missing: list[str] = []
+ for version in versions:
+ if not _check_k8s_schema_published(version):
+ missing.append(version)
+
+ if not missing and not force:
+ get_console().print("[success]All K8s schema versions are already
published. Skipping sync.[/]")
+ return
+
+ if missing:
+ get_console().print(
+ f"[warning]K8s schemas missing for versions: {', '.join(f'v{v}'
for v in missing)}[/]"
+ )
+ else:
+ get_console().print("[info]Force sync requested.[/]")
+
+ if not airflow_site.is_dir():
+ get_console().print(
+ f"[error]airflow-site directory not found at {airflow_site}. "
+ "Use --airflow-site to specify the path to the airflow-site
checkout.[/]"
+ )
+ return
+
+ # Verify this is the airflow-site repo by checking git remote
+ remote_result = run_command(
+ ["git", "-C", str(airflow_site), "remote", "-v"],
+ capture_output=True,
+ text=True,
+ check=False,
+ )
+ if remote_result.returncode != 0 or "airflow-site" not in
remote_result.stdout:
+ get_console().print(
+ f"[error]{airflow_site} does not appear to be a clone of the
airflow-site repository.[/]"
+ )
+ return
+
+ static_dir = airflow_site / "landing-pages" / "site" / "static"
+ if not static_dir.is_dir():
+ get_console().print(
+ f"[error]Expected directory structure not found: {static_dir}\n"
+ "The airflow-site checkout should contain
landing-pages/site/static/.[/]"
+ )
+ return
+
+ output_dir = static_dir / "k8s-schemas"
+
+ # Filter out versions already present in the local airflow-site checkout
+ versions_to_download = missing if (missing and not force) else versions
+ versions_to_download = [
+ v
+ for v in versions_to_download
+ if not (output_dir / f"v{v}-standalone-strict").is_dir()
+ or not any((output_dir / f"v{v}-standalone-strict").iterdir())
+ ]
+
+ if not versions_to_download:
+ get_console().print(
+ "[success]All required K8s schema versions already exist in
airflow-site. Skipping download.[/]"
+ )
+ return
+
+ get_console().print(
+ f"[info]Downloading K8s schemas for versions "
+ f"{', '.join(f'v{v}' for v in versions_to_download)} to
{output_dir}...[/]"
+ )
+ cmd = [
+ "uv",
+ "run",
+ str(AIRFLOW_ROOT_PATH / "scripts" / "ci" / "prek" /
"download_k8s_schemas.py"),
+ "--output-dir",
+ str(output_dir),
+ "--versions",
+ *versions_to_download,
+ ]
+ run_command(cmd, check=False, env=command_env)
+
+
@ci_group.command(
name="upgrade",
help="Perform important upgrade steps of the CI environment. And create a
PR",
@@ -453,10 +549,64 @@ def get_workflow_info(github_context: str,
github_context_input: StringIO):
help="Automatically switch to the base branch if not already on it (if not
specified, will ask)",
is_flag=True,
)
[email protected](
+ "--airflow-site",
+ default="../airflow-site",
+ show_default=True,
+ type=click.Path(file_okay=False, dir_okay=True, resolve_path=True,
path_type=Path),
+ help="Path to airflow-site checkout for publishing K8s schemas",
+)
[email protected](
+ "--force-k8s-schema-sync",
+ is_flag=True,
+ default=False,
+ help="Force syncing K8s schemas to airflow-site even if all versions
appear published",
+)
[email protected](
+ "--autoupdate/--no-autoupdate",
+ default=True,
+ show_default=True,
+ help="Run prek autoupdate to update hook revisions",
+)
[email protected](
+ "--pin-versions/--no-pin-versions",
+ default=True,
+ show_default=True,
+ help="Run pin-versions to pin CI dependency versions",
+)
[email protected](
+ "--update-chart-dependencies/--no-update-chart-dependencies",
+ default=True,
+ show_default=True,
+ help="Run update-chart-dependencies to update Helm chart dependencies",
+)
[email protected](
+ "--upgrade-important-versions/--no-upgrade-important-versions",
+ default=True,
+ show_default=True,
+ help="Run upgrade-important-versions to bump key dependency versions",
+)
[email protected](
+ "--k8s-schema-sync/--no-k8s-schema-sync",
+ default=True,
+ show_default=True,
+ help="Sync K8s JSON schemas to airflow-site",
+)
@option_answer
@option_verbose
@option_dry_run
-def upgrade(target_branch: str, create_pr: bool | None, switch_to_base: bool |
None):
+def upgrade(
+ target_branch: str,
+ create_pr: bool | None,
+ switch_to_base: bool | None,
+ airflow_site: Path,
+ force_k8s_schema_sync: bool,
+ autoupdate: bool,
+ pin_versions: bool,
+ update_chart_dependencies: bool,
+ upgrade_important_versions: bool,
+ k8s_schema_sync: bool,
+):
# Validate target_branch pattern
target_branch_pattern = re.compile(r"^(main|v\d+-\d+-test)$")
if not target_branch_pattern.match(target_branch):
@@ -634,16 +784,37 @@ def upgrade(target_branch: str, create_pr: bool | None,
switch_to_base: bool | N
)
# Define all upgrade commands to run (all run with check=False to continue
on errors)
- upgrade_commands = [
- "prek autoupdate --cooldown-days 4 --freeze",
- "prek --all-files --verbose --hook-stage manual pin-versions",
- "prek --all-files --show-diff-on-failure --color always --verbose
--hook-stage manual update-chart-dependencies",
- "prek --all-files --show-diff-on-failure --color always --verbose
--hook-stage manual upgrade-important-versions",
+ upgrade_commands: list[tuple[str, str]] = [
+ ("autoupdate", "prek autoupdate --cooldown-days 4 --freeze"),
+ ("pin-versions", "prek --all-files --verbose --hook-stage manual
pin-versions"),
+ (
+ "update-chart-dependencies",
+ "prek --all-files --show-diff-on-failure --color always --verbose
--hook-stage manual update-chart-dependencies",
+ ),
+ (
+ "upgrade-important-versions",
+ "prek --all-files --show-diff-on-failure --color always --verbose
--hook-stage manual upgrade-important-versions",
+ ),
]
+ step_enabled = {
+ "autoupdate": autoupdate,
+ "pin-versions": pin_versions,
+ "update-chart-dependencies": update_chart_dependencies,
+ "upgrade-important-versions": upgrade_important_versions,
+ }
+
+ # Execute enabled upgrade commands with the environment containing GitHub
token
+ for step_name, command in upgrade_commands:
+ if step_enabled[step_name]:
+ run_command(command.split(), check=False, env=command_env)
+ else:
+ get_console().print(f"[info]Skipping {step_name} (disabled).[/]")
- # Execute all upgrade commands with the environment containing GitHub token
- for command in upgrade_commands:
- run_command(command.split(), check=False, env=command_env)
+ # Sync K8s schemas to airflow-site
+ if k8s_schema_sync:
+ _sync_k8s_schemas_to_airflow_site(airflow_site, force_k8s_schema_sync,
command_env)
+ else:
+ get_console().print("[info]Skipping K8s schema sync (disabled).[/]")
res = run_command(["git", "diff", "--exit-code"], check=False)
if res.returncode == 0:
diff --git a/dev/breeze/src/airflow_breeze/commands/ci_commands_config.py
b/dev/breeze/src/airflow_breeze/commands/ci_commands_config.py
index 2df31059727..e3bc9b89d4b 100644
--- a/dev/breeze/src/airflow_breeze/commands/ci_commands_config.py
+++ b/dev/breeze/src/airflow_breeze/commands/ci_commands_config.py
@@ -75,8 +75,20 @@ CI_PARAMETERS: dict[str, list[dict[str, str | list[str]]]] =
{
"--target-branch",
"--create-pr",
"--switch-to-base",
+ "--airflow-site",
+ "--force-k8s-schema-sync",
],
- }
+ },
+ {
+ "name": "Upgrade steps",
+ "options": [
+ "--autoupdate",
+ "--pin-versions",
+ "--update-chart-dependencies",
+ "--upgrade-important-versions",
+ "--k8s-schema-sync",
+ ],
+ },
],
"breeze ci set-milestone": [
{
diff --git
a/dev/breeze/src/airflow_breeze/commands/release_management_commands.py
b/dev/breeze/src/airflow_breeze/commands/release_management_commands.py
index cc30b9c97e3..253cf846003 100644
--- a/dev/breeze/src/airflow_breeze/commands/release_management_commands.py
+++ b/dev/breeze/src/airflow_breeze/commands/release_management_commands.py
@@ -260,7 +260,7 @@ class VersionedFile(NamedTuple):
AIRFLOW_PIP_VERSION = "26.0.1"
-AIRFLOW_UV_VERSION = "0.10.7"
+AIRFLOW_UV_VERSION = "0.10.8"
AIRFLOW_USE_UV = False
GITPYTHON_VERSION = "3.1.46"
RICH_VERSION = "14.3.3"
diff --git a/dev/breeze/src/airflow_breeze/commands/testing_commands.py
b/dev/breeze/src/airflow_breeze/commands/testing_commands.py
index c8cad6db6c3..55f1b0c49be 100644
--- a/dev/breeze/src/airflow_breeze/commands/testing_commands.py
+++ b/dev/breeze/src/airflow_breeze/commands/testing_commands.py
@@ -86,6 +86,7 @@ from airflow_breeze.commands.release_management_commands
import option_distribut
from airflow_breeze.global_constants import (
ALL_TEST_SUITES,
ALL_TEST_TYPE,
+ ALLOWED_KUBERNETES_VERSIONS,
ALLOWED_TEST_TYPE_CHOICES,
GroupOfTests,
all_selective_core_test_types,
@@ -572,6 +573,16 @@ option_test_type_helm = click.option(
show_default=True,
type=BetterChoice(ALLOWED_TEST_TYPE_CHOICES[GroupOfTests.HELM]),
)
+# Strip "v" prefix from ALLOWED_KUBERNETES_VERSIONS for helm tests (schemas
use bare versions)
+_HELM_K8S_VERSIONS = [v.lstrip("v") for v in ALLOWED_KUBERNETES_VERSIONS]
+option_helm_kubernetes_version = click.option(
+ "--kubernetes-version",
+ help="Kubernetes version to validate helm templates against",
+ default=_HELM_K8S_VERSIONS[0],
+ envvar="HELM_TEST_KUBERNETES_VERSION",
+ show_default=True,
+ type=BetterChoice(_HELM_K8S_VERSIONS),
+)
option_test_type_task_sdk_group = click.option(
"--test-type",
help="Type of test to run for task SDK",
@@ -1243,6 +1254,7 @@ def system_tests(
@option_test_timeout
@option_parallelism
@option_test_type_helm
+@option_helm_kubernetes_version
@option_use_xdist
@option_verbose
@option_dry_run
@@ -1253,6 +1265,7 @@ def helm_tests(
github_repository: str,
test_timeout: int,
test_type: str,
+ kubernetes_version: str,
parallelism: int,
use_xdist: bool,
):
@@ -1263,6 +1276,7 @@ def helm_tests(
test_type=test_type,
)
env = shell_params.env_variables_for_docker_commands
+ env["HELM_TEST_KUBERNETES_VERSION"] = kubernetes_version
perform_environment_checks()
fix_ownership_using_docker()
cleanup_python_generated_files()
diff --git a/dev/breeze/src/airflow_breeze/commands/testing_commands_config.py
b/dev/breeze/src/airflow_breeze/commands/testing_commands_config.py
index 68a7150f7f3..ca3b0c11f61 100644
--- a/dev/breeze/src/airflow_breeze/commands/testing_commands_config.py
+++ b/dev/breeze/src/airflow_breeze/commands/testing_commands_config.py
@@ -265,6 +265,7 @@ TESTING_PARAMETERS: dict[str, list[dict[str, str |
list[str]]]] = {
"options": [
"--test-type",
"--test-timeout",
+ "--kubernetes-version",
"--use-xdist",
"--parallelism",
],
diff --git a/dev/breeze/src/airflow_breeze/global_constants.py
b/dev/breeze/src/airflow_breeze/global_constants.py
index 390af7fcd77..386928e5265 100644
--- a/dev/breeze/src/airflow_breeze/global_constants.py
+++ b/dev/breeze/src/airflow_breeze/global_constants.py
@@ -219,7 +219,7 @@ if MYSQL_INNOVATION_RELEASE:
ALLOWED_INSTALL_MYSQL_CLIENT_TYPES = ["mariadb"]
PIP_VERSION = "26.0.1"
-UV_VERSION = "0.10.7"
+UV_VERSION = "0.10.8"
# packages that providers docs
REGULAR_DOC_PACKAGES = [
diff --git a/dev/breeze/src/airflow_breeze/utils/selective_checks.py
b/dev/breeze/src/airflow_breeze/utils/selective_checks.py
index 8470b1e0247..4a349c459ac 100644
--- a/dev/breeze/src/airflow_breeze/utils/selective_checks.py
+++ b/dev/breeze/src/airflow_breeze/utils/selective_checks.py
@@ -1432,6 +1432,16 @@ class SelectiveChecks:
def helm_test_packages(self) -> str:
return json.dumps(all_helm_test_packages())
+ @cached_property
+ def helm_test_kubernetes_versions(self) -> str:
+ default = CURRENT_KUBERNETES_VERSIONS[0]
+ if self.all_versions:
+ last = CURRENT_KUBERNETES_VERSIONS[-1]
+ versions = [default] if default == last else [default, last]
+ else:
+ versions = [default]
+ return json.dumps([v.lstrip("v") for v in versions])
+
@cached_property
def selected_providers_list_as_string(self) -> str | None:
if self._default_branch != "main":
diff --git a/dev/breeze/tests/test_selective_checks.py
b/dev/breeze/tests/test_selective_checks.py
index 3ac9f26f1cf..054c8346a8e 100644
--- a/dev/breeze/tests/test_selective_checks.py
+++ b/dev/breeze/tests/test_selective_checks.py
@@ -54,6 +54,15 @@ ALL_KUBERNETES_VERSIONS_AS_LIST = "[" + ", ".join([f"'{v}'"
for v in ALLOWED_KUB
ALL_PYTHON_VERSIONS_AS_STRING = " ".join(ALLOWED_PYTHON_MAJOR_MINOR_VERSIONS)
ALL_PYTHON_VERSIONS_AS_LIST = "[" + ", ".join([f"'{v}'" for v in
ALLOWED_PYTHON_MAJOR_MINOR_VERSIONS]) + "]"
+DEFAULT_HELM_K8S_VERSION = ALLOWED_KUBERNETES_VERSIONS[0].lstrip("v")
+LAST_HELM_K8S_VERSION = ALLOWED_KUBERNETES_VERSIONS[-1].lstrip("v")
+DEFAULT_HELM_K8S_VERSIONS_JSON = json.dumps([DEFAULT_HELM_K8S_VERSION])
+ALL_HELM_K8S_VERSIONS_JSON = json.dumps(
+ [DEFAULT_HELM_K8S_VERSION]
+ if DEFAULT_HELM_K8S_VERSION == LAST_HELM_K8S_VERSION
+ else [DEFAULT_HELM_K8S_VERSION, LAST_HELM_K8S_VERSION]
+)
+
PYTHON_K8S_COMBO_LENGTH = max(len(ALLOWED_PYTHON_MAJOR_MINOR_VERSIONS),
len(ALLOWED_KUBERNETES_VERSIONS))
PYTHON_VERSIONS_MAX = (ALLOWED_PYTHON_MAJOR_MINOR_VERSIONS *
2)[:PYTHON_K8S_COMBO_LENGTH]
KUBERNETES_VERSIONS_MAX = (ALLOWED_KUBERNETES_VERSIONS *
2)[:PYTHON_K8S_COMBO_LENGTH]
@@ -3399,3 +3408,55 @@ dependencies = [
# Should pass with the skip label
result = selective_checks.common_compat_changed_without_next_version
assert result is True
+
+
[email protected](
+ ("files", "pr_labels", "expected_outputs"),
+ [
+ pytest.param(
+ ("helm-tests/tests/helm_tests/random_helm_test.py",),
+ (),
+ {
+ "helm-test-kubernetes-versions":
DEFAULT_HELM_K8S_VERSIONS_JSON,
+ },
+ id="Default K8s version when no all-versions label",
+ ),
+ pytest.param(
+ ("helm-tests/tests/helm_tests/random_helm_test.py",),
+ ("all versions",),
+ {
+ "helm-test-kubernetes-versions": ALL_HELM_K8S_VERSIONS_JSON,
+ },
+ id="First and last K8s versions when all-versions label is set",
+ ),
+ pytest.param(
+ ("INTHEWILD.md",),
+ ("full tests needed", "all versions"),
+ {
+ "helm-test-kubernetes-versions": ALL_HELM_K8S_VERSIONS_JSON,
+ "all-versions": "true",
+ },
+ id="First and last K8s versions when full tests needed with all
versions",
+ ),
+ pytest.param(
+ ("INTHEWILD.md",),
+ ("full tests needed",),
+ {
+ "helm-test-kubernetes-versions":
DEFAULT_HELM_K8S_VERSIONS_JSON,
+ "all-versions": "false",
+ },
+ id="Default K8s version when full tests needed but no all-versions
label",
+ ),
+ ],
+)
+def test_helm_test_kubernetes_versions(
+ files: tuple[str, ...], pr_labels: tuple[str, ...], expected_outputs:
dict[str, str]
+):
+ stderr = SelectiveChecks(
+ files=files,
+ commit_ref=NEUTRAL_COMMIT,
+ github_event=GithubEvents.PULL_REQUEST,
+ pr_labels=pr_labels,
+ default_branch="main",
+ )
+ assert_outputs_are_printed(expected_outputs, str(stderr))
diff --git a/dev/breeze/uv.lock b/dev/breeze/uv.lock
index 3bc2641740b..99f8b4ef812 100644
--- a/dev/breeze/uv.lock
+++ b/dev/breeze/uv.lock
@@ -260,30 +260,30 @@ wheels = [
[[package]]
name = "boto3"
-version = "1.42.59"
+version = "1.42.60"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "botocore" },
{ name = "jmespath" },
{ name = "s3transfer" },
]
-sdist = { url =
"https://files.pythonhosted.org/packages/b0/4e/499cb52aaee9468c346bcc1158965e24e72b4e2a20052725b680e0ac949b/boto3-1.42.59.tar.gz",
hash =
"sha256:6c4a14a4eb37b58a9048901bdeefbe1c529638b73e8f55413319a25f010ca211", size
= 112725, upload-time = "2026-02-27T20:25:33.228Z" }
+sdist = { url =
"https://files.pythonhosted.org/packages/b8/e0/071e00265d3d8127b28c27ba3918ba283f49b39943864a389ac3f5096ef3/boto3-1.42.60.tar.gz",
hash =
"sha256:3d549d15c821dcc871a0821319049e7d493ae3317121eb01e4b1f5230c19d5d4", size
= 112786, upload-time = "2026-03-03T21:21:07.199Z" }
wheels = [
- { url =
"https://files.pythonhosted.org/packages/17/c0/22d868b9408dc5a33935a72896ec8d638b2766c459668d1b37c3e5ac2066/boto3-1.42.59-py3-none-any.whl",
hash =
"sha256:7a66e3e8e2087ea4403e135e9de592e6d63fc9a91080d8dac415bb74df873a72", size
= 140557, upload-time = "2026-02-27T20:25:31.774Z" },
+ { url =
"https://files.pythonhosted.org/packages/ed/30/156ff2b5afb7dd03383b5f97d0e32535e9c0e783917380c476fe2fbc1874/boto3-1.42.60-py3-none-any.whl",
hash =
"sha256:c0cc3d93cd76c99461f6e109e04bb020defe3ffcd04c6163c72836dff5591614", size
= 140554, upload-time = "2026-03-03T21:21:05.194Z" },
]
[[package]]
name = "botocore"
-version = "1.42.59"
+version = "1.42.60"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "jmespath" },
{ name = "python-dateutil" },
{ name = "urllib3" },
]
-sdist = { url =
"https://files.pythonhosted.org/packages/45/ae/50fb33bdf1911c216d50f98d989dd032a506f054cf829ebd737c6fa7e3e6/botocore-1.42.59.tar.gz",
hash =
"sha256:5314f19e1da8fc0ebc41bdb8bbe17c9a7397d87f4d887076ac8bdef972a34138", size
= 14950271, upload-time = "2026-02-27T20:25:20.614Z" }
+sdist = { url =
"https://files.pythonhosted.org/packages/82/d7/bfe8413cc7dc167e04ca9c68ea136251307960f662ec5889512615565b25/botocore-1.42.60.tar.gz",
hash =
"sha256:de9278810fb2e92a9ffe3dc8ffa68f1066e6d2caf19da9460760743b39ca5215", size
= 14950855, upload-time = "2026-03-03T21:20:51.529Z" }
wheels = [
- { url =
"https://files.pythonhosted.org/packages/59/df/9d52819e0d804ead073d53ab1823bc0f0cb172a250fba31107b0b43fbb04/botocore-1.42.59-py3-none-any.whl",
hash =
"sha256:d2f2ff7ecc31e86ef46b5daee112cfbca052c13801285fb23af909f7bff5b657", size
= 14619293, upload-time = "2026-02-27T20:25:17.455Z" },
+ { url =
"https://files.pythonhosted.org/packages/8d/63/5cf970a00e9ddcbb9e65ecc79276717a9555a77d3d0571bd962676e19c3b/botocore-1.42.60-py3-none-any.whl",
hash =
"sha256:d8b4aab06cc134e21d294c068cb94e0eeb59bacd27c836fb6b882b61433df2f4", size
= 14621726, upload-time = "2026-03-03T21:20:47.935Z" },
]
[[package]]
@@ -2072,27 +2072,27 @@ wheels = [
[[package]]
name = "uv"
-version = "0.10.7"
-source = { registry = "https://pypi.org/simple" }
-sdist = { url =
"https://files.pythonhosted.org/packages/7c/ec/b324a43b55fe59577505478a396cb1d2758487a2e2270c81ccfa4ac6c96d/uv-0.10.7.tar.gz",
hash =
"sha256:7c3b0133c2d6bd725d5a35ec5e109ebf0d75389943abe826f3d9ea6d6667a375", size
= 3922193, upload-time = "2026-02-27T12:33:58.525Z" }
-wheels = [
- { url =
"https://files.pythonhosted.org/packages/f3/1b/decff24553325561850d70b75c737076e6fcbcfbf233011a27a33f06e4d9/uv-0.10.7-py3-none-linux_armv6l.whl",
hash =
"sha256:6a0af6c7a90fd2053edfa2c8ee719078ea906a2d9f4798d3fb3c03378726209a", size
= 22497542, upload-time = "2026-02-27T12:33:39.425Z" },
- { url =
"https://files.pythonhosted.org/packages/fc/b5/51152c87921bc2576fecb982df4a02ac9cfd7fc934e28114a1232b99eed4/uv-0.10.7-py3-none-macosx_10_12_x86_64.whl",
hash =
"sha256:3b7db0cab77232a7c8856062904fc3b9db22383f1dec7e97a9588fb6c8470f6a", size
= 21558860, upload-time = "2026-02-27T12:34:03.362Z" },
- { url =
"https://files.pythonhosted.org/packages/5e/15/8365dc2ded350a4ee5fcbbf9b15195cb2b45855114f2a154b5effb6fa791/uv-0.10.7-py3-none-macosx_11_0_arm64.whl",
hash =
"sha256:d872d2ff9c9dfba989b5f05f599715bc0f19b94cd0dbf8ae4ad22f8879a66c8c", size
= 20212775, upload-time = "2026-02-27T12:33:55.365Z" },
- { url =
"https://files.pythonhosted.org/packages/53/a0/ccf25e897f3907b5a6fd899007ff9a80b5bbf151b3a75a375881005611fd/uv-0.10.7-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.musllinux_1_1_aarch64.whl",
hash =
"sha256:d9b40d03693efda80a41e5d18ac997efdf1094b27fb75471c1a8f51a9ebeffb3", size
= 22015584, upload-time = "2026-02-27T12:33:47.374Z" },
- { url =
"https://files.pythonhosted.org/packages/fa/3a/5099747954e7774768572d30917bb6bda6b8d465d7a3c49c9bbf7af2a812/uv-0.10.7-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.musllinux_1_1_armv7l.whl",
hash =
"sha256:e74fe4df9cf31fe84f20b84a0054874635077d31ce20e7de35ff0dd64d498d7b", size
= 22100376, upload-time = "2026-02-27T12:34:06.169Z" },
- { url =
"https://files.pythonhosted.org/packages/0c/1a/75897fd966b871803cf78019fa31757ced0d54af5ffd7f57bce8b01d64f3/uv-0.10.7-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl",
hash =
"sha256:9c76659fc8bb618dd35cd83b2f479c6f880555a16630a454a251045c4c118ea4", size
= 22105202, upload-time = "2026-02-27T12:34:16.972Z" },
- { url =
"https://files.pythonhosted.org/packages/b5/1e/0b8caedd66ca911533e18fd051da79a213c792404138812c66043d529b9e/uv-0.10.7-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl",
hash =
"sha256:1d160cceb9468024ca40dc57a180289dfd2024d98e42f2284b9ec44355723b0a", size
= 23335601, upload-time = "2026-02-27T12:34:11.161Z" },
- { url =
"https://files.pythonhosted.org/packages/69/94/b741af277e39a92e0da07fe48c338eee1429c2607e7a192e41345208bb24/uv-0.10.7-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl",
hash =
"sha256:c775975d891cb60cf10f00953e61e643fcb9a9139e94c9ef5c805fe36e90477f", size
= 24152851, upload-time = "2026-02-27T12:33:33.904Z" },
- { url =
"https://files.pythonhosted.org/packages/27/b2/da351ccd02f0fb1aec5f992b886bea1374cce44276a78904348e2669dd78/uv-0.10.7-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl",
hash =
"sha256:a709e75583231cc1f39567fb3d8d9b4077ff94a64046eb242726300144ed1a4a", size
= 23276444, upload-time = "2026-02-27T12:33:36.891Z" },
- { url =
"https://files.pythonhosted.org/packages/71/a9/2735cc9dc39457c9cf64d1ce2ba5a9a8ecbb103d0fb64b052bf33ba3d669/uv-0.10.7-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl",
hash =
"sha256:89de2504407dcf04aece914c6ca3b9d8e60cf9ff39a13031c1df1f7c040cea81", size
= 23218464, upload-time = "2026-02-27T12:34:00.904Z" },
- { url =
"https://files.pythonhosted.org/packages/20/5f/5f204e9c3f04f5fc844d2f98d80a7de64b6b304af869644ab478d909f6ff/uv-0.10.7-py3-none-manylinux_2_28_aarch64.whl",
hash =
"sha256:9945de1d11c4a5ad77e9c4f36f8b5f9e7c9c3c32999b8bc0e7e579145c3b641c", size
= 22092562, upload-time = "2026-02-27T12:34:14.155Z" },
- { url =
"https://files.pythonhosted.org/packages/dd/a4/16bebf106e3289a29cc1e1482d551c49bd220983e9b4bc5960142389ad3f/uv-0.10.7-py3-none-manylinux_2_31_riscv64.whl",
hash =
"sha256:dbe43527f478e2ffa420516aa465f82057763936bbea56f814fd054a9b7f961f", size
= 22851312, upload-time = "2026-02-27T12:34:08.651Z" },
- { url =
"https://files.pythonhosted.org/packages/d1/7a/953b1da589225d98ca8668412f665c3192f6deed2a0f4bb782b0df18f611/uv-0.10.7-py3-none-musllinux_1_1_i686.whl",
hash =
"sha256:c0783f327631141501bdc5f31dd2b4c748df7e7f5dc5cdbfc0fbb82da86cc9ca", size
= 22543775, upload-time = "2026-02-27T12:33:30.935Z" },
- { url =
"https://files.pythonhosted.org/packages/8b/67/e133afdabf76e43989448be1c2ef607f13afc32aa1ee9f6897115dec8417/uv-0.10.7-py3-none-musllinux_1_1_x86_64.whl",
hash =
"sha256:eba438899010522812d3497af586e6eedc94fa2b0ced028f51812f0c10aafb30", size
= 23431187, upload-time = "2026-02-27T12:33:42.131Z" },
- { url =
"https://files.pythonhosted.org/packages/ba/40/6ffb58ec88a33d6cbe9a606966f9558807f37a50f7be7dc756824df2d04c/uv-0.10.7-py3-none-win32.whl",
hash =
"sha256:b56d1818aafb2701d92e94f552126fe71d30a13f28712d99345ef5cafc53d874", size
= 21524397, upload-time = "2026-02-27T12:33:44.579Z" },
- { url =
"https://files.pythonhosted.org/packages/e3/1f/74f4d625db838f716a555908d41777b6357bacc141ddef117a01855e5ef9/uv-0.10.7-py3-none-win_amd64.whl",
hash =
"sha256:ad0d0ddd9f5407ad8699e3b20fe6c18406cd606336743e246b16914801cfd8b0", size
= 23999929, upload-time = "2026-02-27T12:33:49.839Z" },
- { url =
"https://files.pythonhosted.org/packages/48/4e/20cbfbcb1a0f48c5c1ca94f6baa0fa00754aafda365da9160c15e3b9c277/uv-0.10.7-py3-none-win_arm64.whl",
hash =
"sha256:edf732de80c1a9701180ef8c7a2fa926a995712e4a34ae8c025e090f797c2e0b", size
= 22353084, upload-time = "2026-02-27T12:33:52.792Z" },
+version = "0.10.8"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url =
"https://files.pythonhosted.org/packages/a3/e7/600a90d4662dbd8414c1f6b709c8c79075d37d2044f72b94acbfaf29baad/uv-0.10.8.tar.gz",
hash =
"sha256:4b23242b5224c7eaea481ce6c6dbc210f0eafb447cf60211633980947cd23de4", size
= 3936600, upload-time = "2026-03-03T21:35:22.386Z" }
+wheels = [
+ { url =
"https://files.pythonhosted.org/packages/a6/6c/8ef256575242d5f3869c5a445ffd4363b91a89acb34a3e043bec2ad5a1be/uv-0.10.8-py3-none-linux_armv6l.whl",
hash =
"sha256:d214c82c7c14dd23f9aeb609d03070b8ea2b2f0cf249c9321cbbb5375a17e5df", size
= 22461003, upload-time = "2026-03-03T21:35:20.093Z" },
+ { url =
"https://files.pythonhosted.org/packages/c9/fb/fd0656a92e6b9c4f92ddba7dcd76bd87469be500755125e06fea853dc212/uv-0.10.8-py3-none-macosx_10_12_x86_64.whl",
hash =
"sha256:d1315c3901c5859aec2c5b4a17da4c5410d17f6890890f9f1a31f25aa0fa9ace", size
= 21549446, upload-time = "2026-03-03T21:35:58.203Z" },
+ { url =
"https://files.pythonhosted.org/packages/64/b9/1a4105df3afe7af99791f5b00fb037d85b2e3aaa1227e95878538d51ecf3/uv-0.10.8-py3-none-macosx_11_0_arm64.whl",
hash =
"sha256:a253e5d2cae9e02654de31918b610dfc8f1f16a33f34046603757820bc45ee1b", size
= 20222180, upload-time = "2026-03-03T21:35:46.984Z" },
+ { url =
"https://files.pythonhosted.org/packages/c5/72/6e98e0f8b3fe80cb881c36492dca6d932fbb05f956dfdccbdb8ebe4ceff4/uv-0.10.8-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.musllinux_1_1_aarch64.whl",
hash =
"sha256:57a24e15fd9dd4a36bcec2ccbe4b26d2a172c109e954a8940f5e8a8b965dae74", size
= 22064813, upload-time = "2026-03-03T21:35:17.108Z" },
+ { url =
"https://files.pythonhosted.org/packages/71/b6/737da8577f4b1799f7024f6cd98fffcac77076a1b078b277cffc84946e96/uv-0.10.8-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.musllinux_1_1_armv7l.whl",
hash =
"sha256:675dc659195f9b9811ef5534eb3f16459fc88e109aefacbc91c07751b5b9715a", size
= 22064861, upload-time = "2026-03-03T21:35:25.067Z" },
+ { url =
"https://files.pythonhosted.org/packages/7e/21/464ee3cd81f44345953cb26dd49870811f7647f3074f7651775cadb2158b/uv-0.10.8-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl",
hash =
"sha256:18d2968b0a50111c2fc6b782f7c63ded4f461c44efab537f552cf565f9aaae25", size
= 22054515, upload-time = "2026-03-03T21:35:44.572Z" },
+ { url =
"https://files.pythonhosted.org/packages/11/2c/1c592d7b843ffa999502116b0dc573732b40cb37061a4acc741dcdb181da/uv-0.10.8-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl",
hash =
"sha256:8ed3c7ebb6f757cddedb56dec3d7c745e5ea7310b11e12ae1c28f1e8172e7bbf", size
= 23433992, upload-time = "2026-03-03T21:35:36.886Z" },
+ { url =
"https://files.pythonhosted.org/packages/f1/e2/2b716f0613746138294598668bbe65295a8da3d8fa104a756dec6284bf3c/uv-0.10.8-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl",
hash =
"sha256:ffaf115501e33be0d4f13cb5b7c2b46b031d4c679a6109e24a7edfb719c44c6c", size
= 24257250, upload-time = "2026-03-03T21:35:49.954Z" },
+ { url =
"https://files.pythonhosted.org/packages/3e/4d/0165e82cd1117cd6f8a7d9a2122c23cc091f7cf738aa4a2a54579420a08f/uv-0.10.8-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl",
hash =
"sha256:0209ee8cb573e113ff4a760360f28448f9ebcdcf9c91ca49e872821de5d2d054", size
= 23338918, upload-time = "2026-03-03T21:35:33.795Z" },
+ { url =
"https://files.pythonhosted.org/packages/20/74/652129a25145732482bb0020602507f52d9a5ca0e1a40ddd6deb27402333/uv-0.10.8-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl",
hash =
"sha256:11dc790f732dc5fee61f0f6bd998fc2e9c200df1082245604ac091c32c23a523", size
= 23259370, upload-time = "2026-03-03T21:35:39.478Z" },
+ { url =
"https://files.pythonhosted.org/packages/19/c5/6e5923d6c9e3b50dc8542647bea692b7c227a9489f59ddff4fdfb20d8459/uv-0.10.8-py3-none-manylinux_2_28_aarch64.whl",
hash =
"sha256:e26f8c35684face38db814d452dd1a2181152dbf7f7b2de1f547e6ba0c378d67", size
= 22174747, upload-time = "2026-03-03T21:35:42.081Z" },
+ { url =
"https://files.pythonhosted.org/packages/92/cd/eee9e1883888327d07f51e7595ed5952e0bca2dc79d1c03b8a6e4309553e/uv-0.10.8-py3-none-manylinux_2_31_riscv64.whl",
hash =
"sha256:385add107d40c43dc00ca8c1a21ecf43101f846f8339eb7026bf6c9f6df7760d", size
= 22893359, upload-time = "2026-03-03T21:35:30.802Z" },
+ { url =
"https://files.pythonhosted.org/packages/bf/36/407a22917e55ce5cc2e7af956e3b9d91648a96558858acef84e3c50d5ca8/uv-0.10.8-py3-none-musllinux_1_1_i686.whl",
hash =
"sha256:24e8eb28c4f05acb38e60fefe2a2b15f4283a3849ce580bf2a62aca0a13123b3", size
= 22637451, upload-time = "2026-03-03T21:35:55.677Z" },
+ { url =
"https://files.pythonhosted.org/packages/21/d5/dabef9914e1ff27ad95e4b1daf59cd97c80e26a44c04c2870bcca7c83fc0/uv-0.10.8-py3-none-musllinux_1_1_x86_64.whl",
hash =
"sha256:73a8c1a1fceac73cd983dcc0a64f4f94f5fd1e5428681a5a76132574264504fb", size
= 23480991, upload-time = "2026-03-03T21:35:52.809Z" },
+ { url =
"https://files.pythonhosted.org/packages/2f/c0/1a4a45a9246f087e9446d0d804a436f6ee0befeaef731b04d1b2802d9d8f/uv-0.10.8-py3-none-win32.whl",
hash =
"sha256:9f344fdb34938ce35e9211a1b866adfa0c7f043967652ed1431917514aeec062", size
= 21579030, upload-time = "2026-03-03T21:35:28.176Z" },
+ { url =
"https://files.pythonhosted.org/packages/a4/2b/b29510efa1e6f409db105dbdafbd942ca3a2b638bef682ff2e5b9f6e4021/uv-0.10.8-py3-none-win_amd64.whl",
hash =
"sha256:1e63015284ed28c2112717256c328513215fb966a57c5870788eac2e8f949f28", size
= 23944828, upload-time = "2026-03-03T21:36:00.763Z" },
+ { url =
"https://files.pythonhosted.org/packages/3f/9e/b5a11b0523171c0103c4fed54da76685a765ad4d3215e8220facfd24aed9/uv-0.10.8-py3-none-win_arm64.whl",
hash =
"sha256:a80284f46b6f2e0b3d03eb7c2d43e17139a4ec313e8b9f56a71efafc996804cb", size
= 22322224, upload-time = "2026-03-03T21:35:14.148Z" },
]
[[package]]
diff --git a/helm-tests/tests/chart_utils/helm_template_generator.py
b/helm-tests/tests/chart_utils/helm_template_generator.py
index f8ea40e2b8d..f4f0d0576e4 100644
--- a/helm-tests/tests/chart_utils/helm_template_generator.py
+++ b/helm-tests/tests/chart_utils/helm_template_generator.py
@@ -16,31 +16,47 @@
# under the License.
from __future__ import annotations
+import ast
import json
import os
import subprocess
-from datetime import datetime, timezone
from functools import cache
from pathlib import Path
from tempfile import NamedTemporaryFile
from typing import Any
+from urllib.error import HTTPError
+from urllib.request import urlopen
import jmespath
import jsonschema
-import requests
import yaml
from kubernetes.client.api_client import ApiClient
-from requests import Response
-from rich.console import Console
api_client = ApiClient()
-CHART_DIR = Path(__file__).resolve().parents[3] / "chart"
+AIRFLOW_ROOT = Path(__file__).resolve().parents[3]
+CHART_DIR = AIRFLOW_ROOT / "chart"
-DEFAULT_KUBERNETES_VERSION = "1.30.13"
-BASE_URL_SPEC = (
- f"https://api.github.com/repos/yannh/kubernetes-json-schema/contents/"
- f"v{DEFAULT_KUBERNETES_VERSION}-standalone-strict"
+SCHEMA_URL_TEMPLATE = (
+
"https://airflow.apache.org/k8s-schemas/v{kubernetes_version}-standalone-strict/{filename}"
+)
+
+
+def _read_default_kubernetes_version() -> str:
+ """Read the first ALLOWED_KUBERNETES_VERSIONS entry from
global_constants.py."""
+ gc_path = AIRFLOW_ROOT / "dev" / "breeze" / "src" / "airflow_breeze" /
"global_constants.py"
+ tree = ast.parse(gc_path.read_text())
+ for node in tree.body:
+ if isinstance(node, ast.Assign):
+ for target in node.targets:
+ if isinstance(target, ast.Name) and target.id ==
"ALLOWED_KUBERNETES_VERSIONS":
+ versions: list[str] = ast.literal_eval(node.value)
+ return versions[0].lstrip("v")
+ raise RuntimeError("ALLOWED_KUBERNETES_VERSIONS not found in
global_constants.py")
+
+
+DEFAULT_KUBERNETES_VERSION = os.environ.get(
+ "HELM_TEST_KUBERNETES_VERSION", _read_default_kubernetes_version()
)
MY_DIR = Path(__file__).parent.resolve()
@@ -51,50 +67,6 @@ crd_lookup = {
}
-GITHUB_TOKEN = os.environ.get("GITHUB_TOKEN", "")
-
-console = Console(width=400, color_system="standard")
-
-
-def log_github_rate_limit_error(response: Response) -> None:
- """
- Logs info about GitHub rate limit errors (primary or secondary).
- """
- if response.status_code not in (403, 429):
- return
-
- remaining = response.headers.get("x-rateLimit-remaining")
- reset = response.headers.get("x-rateLimit-reset")
- retry_after = response.headers.get("retry-after")
-
- try:
- message = response.json().get("message", "")
- except Exception:
- message = response.text or ""
-
- remaining_int = int(remaining) if remaining and remaining.isdigit() else
None
-
- if reset and reset.isdigit():
- reset_dt = datetime.fromtimestamp(int(reset), tz=timezone.utc)
- reset_time = reset_dt.strftime("%Y-%m-%d %H:%M:%S UTC")
- else:
- reset_time = "unknown"
-
- if remaining_int == 0:
- print(f"Primary rate limit exceeded. No requests remaining. Reset at
{reset_time}.")
- return
-
- # Message for secondary looks like: "You have exceeded a secondary rate
limit"
- if "secondary rate limit" in message.lower():
- if retry_after and retry_after.isdigit():
- print(f"Secondary rate limit exceeded. Retry after {retry_after}
seconds.")
- else:
- print(f"Secondary rate limit exceeded. Please wait until
{reset_time} or at least 60 seconds.")
- return
-
- print(f"Rate limit error. Status: {response.status_code}, Message:
{message}")
-
-
@cache
def get_schema_k8s(api_version, kind, kubernetes_version):
api_version = api_version.lower()
@@ -103,27 +75,21 @@ def get_schema_k8s(api_version, kind, kubernetes_version):
if "/" in api_version:
ext, _, api_version = api_version.partition("/")
ext = ext.split(".")[0]
- url = f"{BASE_URL_SPEC}/{kind}-{ext}-{api_version}.json"
+ filename = f"{kind}-{ext}-{api_version}.json"
else:
- url = f"{BASE_URL_SPEC}/{kind}-{api_version}.json"
+ filename = f"{kind}-{api_version}.json"
- headers = {
- "Accept": "application/vnd.github.v3.raw",
- }
- if GITHUB_TOKEN:
- headers["Authorization"] = f"Bearer {GITHUB_TOKEN}"
- headers["X-GitHub-Api-Version"] = "2022-11-28"
- else:
- console.print("[bright_blue] No GITHUB_TOKEN found. Using
unauthenticated requests.")
-
- response = requests.get(url, headers=headers)
- log_github_rate_limit_error(response)
- response.raise_for_status()
- schema = json.loads(
- response.text.replace(
- "kubernetesjsonschema.dev",
"raw.githubusercontent.com/yannh/kubernetes-json-schema/master"
- )
- )
+ url = SCHEMA_URL_TEMPLATE.format(kubernetes_version=kubernetes_version,
filename=filename)
+ try:
+ resp = urlopen(url, timeout=30)
+ schema = json.loads(resp.read())
+ except HTTPError as e:
+ if e.code == 404:
+ raise FileNotFoundError(
+ f"K8s JSON schema not found at {url}\n"
+ f"Ensure schemas for K8s v{kubernetes_version} are published
to airflow-site."
+ ) from e
+ raise
return schema
diff --git a/pyproject.toml b/pyproject.toml
index e633cba7f44..c4c320ba023 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -538,7 +538,7 @@ packages = []
"apache-airflow-providers-amazon[s3fs]",
]
"uv" = [
- "uv>=0.10.7",
+ "uv>=0.10.8",
]
[project.urls]
diff --git a/scripts/ci/prek/check_k8s_schemas_published.py
b/scripts/ci/prek/check_k8s_schemas_published.py
new file mode 100755
index 00000000000..53648fb4aa5
--- /dev/null
+++ b/scripts/ci/prek/check_k8s_schemas_published.py
@@ -0,0 +1,83 @@
+#!/usr/bin/env python
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+"""Pre-commit hook that verifies K8s JSON schemas are published on
airflow.apache.org.
+
+Triggered when ``global_constants.py`` changes. For each version in
+``ALLOWED_KUBERNETES_VERSIONS``, sends a HEAD request to
+``https://airflow.apache.org/k8s-schemas/v{version}-standalone-strict/configmap-v1.json``.
+If any version returns non-200 the hook fails with instructions.
+"""
+
+from __future__ import annotations
+
+import sys
+from pathlib import Path
+from urllib.error import HTTPError, URLError
+from urllib.request import Request, urlopen
+
+sys.path.insert(0, str(Path(__file__).parent.resolve()))
+from common_prek_utils import console, read_allowed_kubernetes_versions
+
+PROBE_URL_TEMPLATE =
"https://airflow.apache.org/k8s-schemas/v{version}-standalone-strict/configmap-v1.json"
+
+
+def _print(msg: str) -> None:
+ if console:
+ console.print(msg)
+ else:
+ print(msg, file=sys.stderr)
+
+
+def main() -> int:
+ versions = read_allowed_kubernetes_versions()
+ missing: list[str] = []
+
+ for version in versions:
+ url = PROBE_URL_TEMPLATE.format(version=version)
+ req = Request(url, method="HEAD")
+ try:
+ resp = urlopen(req, timeout=15)
+ if resp.status == 200:
+ _print(f" v{version}: published")
+ else:
+ _print(f" v{version}: HTTP {resp.status}")
+ missing.append(version)
+ except HTTPError as e:
+ _print(f" v{version}: HTTP {e.code}")
+ missing.append(version)
+ except URLError as e:
+ _print(f" v{version}: {e.reason}")
+ missing.append(version)
+
+ if missing:
+ _print(
+ "\nK8s schemas are NOT published for the following versions:\n"
+ + "\n".join(f" - v{v}" for v in missing)
+ + "\n\nTo fix this:\n"
+ " 1. Check out the airflow-site repository.\n"
+ " 2. Run: breeze ci upgrade --airflow-site
<path-to-airflow-site>\n"
+ " 3. Commit and push changes in both repos.\n"
+ )
+ return 1
+
+ _print("All K8s schema versions are published.")
+ return 0
+
+
+if __name__ == "__main__":
+ raise SystemExit(main())
diff --git a/scripts/ci/prek/common_prek_utils.py
b/scripts/ci/prek/common_prek_utils.py
index c26c4888c6e..5a9024e00ac 100644
--- a/scripts/ci/prek/common_prek_utils.py
+++ b/scripts/ci/prek/common_prek_utils.py
@@ -113,6 +113,26 @@ def read_airflow_version() -> str:
raise RuntimeError("Couldn't find __version__ in AST")
+GLOBAL_CONSTANTS_PATH = (
+ AIRFLOW_ROOT_PATH / "dev" / "breeze" / "src" / "airflow_breeze" /
"global_constants.py"
+)
+
+
+def read_allowed_kubernetes_versions() -> list[str]:
+ """Parse ALLOWED_KUBERNETES_VERSIONS from global_constants.py (single
source of truth).
+
+ Returns versions without the ``v`` prefix, e.g. ``["1.30.13", "1.31.12",
...]``.
+ """
+ tree = ast.parse(GLOBAL_CONSTANTS_PATH.read_text())
+ for node in tree.body:
+ if isinstance(node, ast.Assign):
+ for target in node.targets:
+ if isinstance(target, ast.Name) and target.id ==
"ALLOWED_KUBERNETES_VERSIONS":
+ versions: list[str] = ast.literal_eval(node.value)
+ return [v.lstrip("v") for v in versions]
+ raise RuntimeError("ALLOWED_KUBERNETES_VERSIONS not found in
global_constants.py")
+
+
def pre_process_files(files: list[str]) -> list[str]:
"""Pre-process files passed to mypy.
diff --git a/scripts/ci/prek/download_k8s_schemas.py
b/scripts/ci/prek/download_k8s_schemas.py
new file mode 100755
index 00000000000..60069ba9e65
--- /dev/null
+++ b/scripts/ci/prek/download_k8s_schemas.py
@@ -0,0 +1,281 @@
+#!/usr/bin/env python
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+# /// script
+# requires-python = ">=3.10,<3.11"
+# dependencies = [
+# "pyyaml>=6.0.3",
+# "requests>=2.31.0",
+# "rich>=13.6.0",
+# ]
+# ///
+"""Download K8s JSON schemas used by helm chart tests.
+
+Runs ``helm template`` with multiple value combinations to discover all
+rendered (apiVersion, kind) pairs, then downloads the matching
+standalone-strict JSON schemas from the yannh/kubernetes-json-schema
+GitHub repository for every supported Kubernetes version and stores them
+in a target directory (typically airflow-site/k8s-schemas for publishing
+to https://airflow.apache.org/k8s-schemas/).
+"""
+
+from __future__ import annotations
+
+import argparse
+import json
+import subprocess
+import sys
+from pathlib import Path
+from tempfile import NamedTemporaryFile
+
+import requests
+import yaml
+
+sys.path.insert(0, str(Path(__file__).parent.resolve()))
+from common_prek_utils import AIRFLOW_ROOT_PATH, console,
read_allowed_kubernetes_versions
+
+KUBERNETES_VERSIONS = read_allowed_kubernetes_versions()
+DEFAULT_KUBERNETES_VERSION = KUBERNETES_VERSIONS[0]
+CHART_DIR = AIRFLOW_ROOT_PATH / "chart"
+BASE_URL_TEMPLATE = (
+
"https://api.github.com/repos/yannh/kubernetes-json-schema/contents/v{version}-standalone-strict"
+)
+
+# Value combinations that exercise conditional templates (executors, KEDA,
+# flower, persistence, pgbouncer, HPA, ingress, network policies,
+# priority classes, CronJobs, PDBs, ClusterRoles, etc.)
+VALUE_SETS: list[dict] = [
+ {},
+ {"executor": "CeleryExecutor", "flower": {"enabled": True}},
+ {"executor": "CeleryExecutor"},
+ {"executor": "KubernetesExecutor"},
+ {"executor": "CeleryKubernetesExecutor"},
+ {"executor": "LocalExecutor"},
+ {"executor": "LocalKubernetesExecutor"},
+ {
+ "executor": "CeleryExecutor",
+ "workers": {"keda": {"enabled": True}},
+ },
+ {"pgbouncer": {"enabled": True}},
+ {
+ "dags": {"persistence": {"enabled": True}},
+ "logs": {"persistence": {"enabled": True}},
+ },
+ {"redis": {"enabled": True}},
+ {"statsd": {"enabled": True}},
+ {
+ "webserver": {"defaultUser": {"enabled": True}},
+ "cleanup": {"enabled": True},
+ "databaseCleanup": {"enabled": True},
+ },
+ {
+ "ingress": {"web": {"enabled": True}, "flower": {"enabled": True}},
+ "networkPolicies": {"enabled": True},
+ "flower": {"enabled": True},
+ "executor": "CeleryExecutor",
+ },
+ {
+ "workers": {"hpa": {"enabled": True}},
+ "webserver": {
+ "hpa": {"enabled": True},
+ "podDisruptionBudget": {"enabled": True},
+ },
+ "scheduler": {"podDisruptionBudget": {"enabled": True}},
+ },
+ {
+ "multiNamespaceMode": True,
+ "limits": [{"type": "Container", "max": {"cpu": "2"}}],
+ "quotas": {"pods": "10"},
+ },
+ {
+ "priorityClasses": [
+ {"name": "high", "preemptionPolicy": "PreemptLowerPriority",
"value": 1000},
+ ],
+ },
+]
+
+# Additional (apiVersion, kind) pairs that are needed but not produced by
+# ``helm template`` on the main chart (e.g. the pod-template-file is only
+# copied into templates/ during tests).
+EXTRA_PAIRS: set[tuple[str, str]] = {
+ ("v1", "Pod"),
+}
+
+
+def schema_filename(api_version: str, kind: str) -> str:
+ """Compute the schema filename using the same logic as
``get_schema_k8s``."""
+ api_version = api_version.lower()
+ kind = kind.lower()
+ if "/" in api_version:
+ ext, _, ver = api_version.partition("/")
+ ext = ext.split(".")[0]
+ return f"{kind}-{ext}-{ver}.json"
+ return f"{kind}-{api_version}.json"
+
+
+def discover_pairs() -> set[tuple[str, str]]:
+ """Run helm template with each value set and collect (apiVersion, kind)
pairs."""
+ pairs: set[tuple[str, str]] = set()
+ for values in VALUE_SETS:
+ with NamedTemporaryFile(mode="w", suffix=".yaml") as tmp:
+ yaml.dump(values, tmp)
+ tmp.flush()
+ result = subprocess.run(
+ [
+ "helm",
+ "template",
+ "release-name",
+ str(CHART_DIR),
+ "--values",
+ tmp.name,
+ "--kube-version",
+ DEFAULT_KUBERNETES_VERSION,
+ ],
+ capture_output=True,
+ check=False,
+ )
+ if result.returncode != 0:
+ console.print(
+ f"[yellow]helm template failed for values {values}:
{result.stderr.decode()[:200]}[/]"
+ )
+ continue
+ for obj in yaml.safe_load_all(result.stdout):
+ if obj and "apiVersion" in obj and "kind" in obj:
+ pairs.add((obj["apiVersion"], obj["kind"]))
+ pairs.update(EXTRA_PAIRS)
+ return pairs
+
+
+def download_schema(base_url: str, filename: str, token: str | None, retries:
int = 3) -> str | None:
+ """Download a single schema file from GitHub. Returns content or None."""
+ import time
+
+ url = f"{base_url}/{filename}"
+ headers = {"Accept": "application/vnd.github.v3.raw"}
+ if token:
+ headers["Authorization"] = f"Bearer {token}"
+ headers["X-GitHub-Api-Version"] = "2022-11-28"
+ for attempt in range(retries):
+ resp = requests.get(url, headers=headers)
+ if resp.status_code == 404:
+ console.print(f"[yellow] Schema not found (404): {filename}[/]")
+ return None
+ if resp.status_code >= 500 and attempt < retries - 1:
+ wait = 2**attempt
+ console.print(f"[yellow] Server error {resp.status_code},
retrying in {wait}s...[/]")
+ time.sleep(wait)
+ continue
+ resp.raise_for_status()
+ # Replace references to kubernetesjsonschema.dev with the raw GitHub
URL
+ return resp.text.replace(
+ "kubernetesjsonschema.dev",
+ "raw.githubusercontent.com/yannh/kubernetes-json-schema/master",
+ )
+ return None
+
+
+def download_schemas_for_version(
+ version: str,
+ filenames: dict[str, tuple[str, str]],
+ token: str | None,
+ output_dir: Path,
+) -> tuple[int, int]:
+ """Download all schema files for a given K8s version. Returns (downloaded,
skipped)."""
+ schema_dir = output_dir / f"v{version}-standalone-strict"
+ base_url = BASE_URL_TEMPLATE.format(version=version)
+ console.print(f"[bold] Downloading {len(filenames)} schemas for
v{version} to {schema_dir}[/]")
+ schema_dir.mkdir(parents=True, exist_ok=True)
+
+ downloaded = 0
+ skipped = 0
+ for fname in sorted(filenames):
+ api_version, kind = filenames[fname]
+ target = schema_dir / fname
+ console.print(f" {api_version}/{kind} -> {fname}")
+ content = download_schema(base_url, fname, token)
+ if content is None:
+ skipped += 1
+ continue
+ # Validate it's proper JSON
+ json.loads(content)
+ target.write_text(content)
+ downloaded += 1
+
+ return downloaded, skipped
+
+
+def main() -> None:
+ import os
+
+ parser = argparse.ArgumentParser(description="Download K8s JSON schemas
for helm chart tests.")
+ parser.add_argument(
+ "--output-dir",
+ type=Path,
+ required=True,
+ help="Directory to write schemas to (e.g. airflow-site/k8s-schemas).",
+ )
+ parser.add_argument(
+ "--versions",
+ nargs="+",
+ default=None,
+ help="Specific K8s versions to download (default: all
ALLOWED_KUBERNETES_VERSIONS).",
+ )
+ args = parser.parse_args()
+
+ output_dir: Path = args.output_dir
+ versions: list[str] = args.versions if args.versions else
KUBERNETES_VERSIONS
+
+ token = os.environ.get("GITHUB_TOKEN")
+ if not token:
+ # Try gh CLI
+ try:
+ result = subprocess.run(["gh", "auth", "token"],
capture_output=True, text=True, check=False)
+ if result.returncode == 0 and result.stdout.strip():
+ token = result.stdout.strip()
+ except FileNotFoundError:
+ pass
+
+ if token:
+ console.print("[green]Using GitHub token for authenticated
requests.[/]")
+ else:
+ console.print("[yellow]No GitHub token found. Using unauthenticated
requests (60 req/hr limit).[/]")
+
+ console.print("[bold]Discovering (apiVersion, kind) pairs via helm
template...[/]")
+ pairs = discover_pairs()
+ console.print(f"[green]Found {len(pairs)} unique (apiVersion, kind)
pairs.[/]")
+
+ # Compute filenames
+ filenames: dict[str, tuple[str, str]] = {}
+ for api_version, kind in sorted(pairs):
+ fname = schema_filename(api_version, kind)
+ filenames[fname] = (api_version, kind)
+
+ total_downloaded = 0
+ total_skipped = 0
+ for version in versions:
+ downloaded, skipped = download_schemas_for_version(version, filenames,
token, output_dir)
+ total_downloaded += downloaded
+ total_skipped += skipped
+
+ console.print(
+ f"[green]Done. Downloaded {total_downloaded} schemas across "
+ f"{len(versions)} versions, skipped {total_skipped}.[/]"
+ )
+
+
+if __name__ == "__main__":
+ main()
diff --git a/scripts/tools/setup_breeze b/scripts/tools/setup_breeze
index a277a9c2d6c..7f2d54ccea7 100755
--- a/scripts/tools/setup_breeze
+++ b/scripts/tools/setup_breeze
@@ -27,7 +27,7 @@ COLOR_YELLOW=$'\e[33m'
COLOR_BLUE=$'\e[34m'
COLOR_RESET=$'\e[0m'
-UV_VERSION="0.10.7"
+UV_VERSION="0.10.8"
function manual_instructions() {
echo