This is an automated email from the ASF dual-hosted git repository.
shahar1 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 7c7157b124e Stop breeze k8s commands from silently rewriting uv.lock
(#75763)
7c7157b124e is described below
commit 7c7157b124e5f90e32086137c1a76f1d3e498c97
Author: Shahar Epstein <[email protected]>
AuthorDate: Tue Oct 27 16:53:49 2026 +0200
Stop breeze k8s commands from silently rewriting uv.lock (#75763)
A v3-4-test backport bumped a workspace member's version without updating
uv.lock, and the `update-uv-lock` static check still passed. The chart's
prek hooks run `breeze k8s setup-env`, which ran a plain `uv sync` in
kubernetes-tests. That project is a uv workspace member, so the sync
re-resolved and rewrote the root uv.lock before the root hook checked it.
Nested prek configs do not inherit the root UV_FROZEN=1, and prek does not
report files a nested-project hook modifies outside its own project, so
the rewrite went unnoticed and the stale lock was merged. It then broke
every "Generate constraints" job on the branch.
The `uv run pytest` calls of `breeze k8s tests` and the overlay tests had
the same effect on contributors' checkouts.
CI now also fails when the static checks leave tracked files modified, so
any other hook writing outside its project is caught.
---
.github/workflows/basic-tests.yml | 11 +++++++++++
.github/workflows/ci-image-checks.yml | 11 +++++++++++
.../airflow_breeze/commands/kubernetes_commands.py | 2 +-
.../commands/kubernetes_kustomize_commands.py | 2 +-
.../src/airflow_breeze/utils/kubernetes_utils.py | 3 ++-
dev/breeze/tests/test_kubernetes_commands.py | 17 +++++++++++++++++
.../tests/test_kubernetes_kustomize_commands.py | 20 ++++++++++++++++++++
dev/breeze/tests/test_kubernetes_utils.py | 7 +++++++
8 files changed, 70 insertions(+), 3 deletions(-)
diff --git a/.github/workflows/basic-tests.yml
b/.github/workflows/basic-tests.yml
index 944315d8e83..9d3a9046e56 100644
--- a/.github/workflows/basic-tests.yml
+++ b/.github/workflows/basic-tests.yml
@@ -332,6 +332,17 @@ jobs:
GITHUB_TOKEN: ${{ github.token }}
SKIP: ${{ inputs.skip-prek-hooks }}
COLUMNS: "202"
+ - name: "Fail if static checks modified tracked files"
+ # prek does not report files that a nested-project hook modifies
outside its own project,
+ # so check the tree independently.
+ shell: bash
+ run: |
+ if ! git diff --quiet; then
+ echo "::error::Static checks passed but modified tracked files.
Commit the changes below."
+ git diff --stat
+ git diff | head -n 500 || true
+ exit 1
+ fi
test-git-clone-on-windows:
timeout-minutes: 5
diff --git a/.github/workflows/ci-image-checks.yml
b/.github/workflows/ci-image-checks.yml
index cca6c1826b4..ce2e8c41b33 100644
--- a/.github/workflows/ci-image-checks.yml
+++ b/.github/workflows/ci-image-checks.yml
@@ -162,6 +162,17 @@ jobs:
PR_NUMBER: ${{ inputs.newsfragment-pr-number }}
DEFAULT_BRANCH: ${{ inputs.branch }}
RUFF_FORMAT: "github"
+ - name: "Fail if static checks modified tracked files"
+ # prek does not report files that a nested-project hook modifies
outside its own project
+ # (e.g. `breeze k8s setup-env` relocking uv.lock), so check the tree
independently.
+ shell: bash
+ run: |
+ if ! git diff --quiet; then
+ echo "::error::Static checks passed but modified tracked files.
Commit the changes below."
+ git diff --stat
+ git diff | head -n 500 || true
+ exit 1
+ fi
- name: "Show prek log on failure"
run: cat ~/.cache/prek/prek.log || true
if: failure()
diff --git a/dev/breeze/src/airflow_breeze/commands/kubernetes_commands.py
b/dev/breeze/src/airflow_breeze/commands/kubernetes_commands.py
index 4434c1422e6..e59f0aca69c 100644
--- a/dev/breeze/src/airflow_breeze/commands/kubernetes_commands.py
+++ b/dev/breeze/src/airflow_breeze/commands/kubernetes_commands.py
@@ -2035,7 +2035,7 @@ def _run_tests(
f"[info]You can deploy airflow with {executor} by
running:[/]\nbreeze k8s configure-cluster\nbreeze k8s deploy-airflow
--multi-namespace-mode --executor {executor}"
)
return 1, f"Tests {kubectl_cluster_name}"
- pytest_cmd = ["uv", "run", "pytest"]
+ pytest_cmd = ["uv", "run", "--frozen", "pytest"]
the_tests: list[str] = ["tests"]
ordered_unique_args = dict.fromkeys(chain(pytest_cmd, the_tests,
test_args))
command_to_run = " ".join(quote(arg) for arg in ordered_unique_args)
diff --git
a/dev/breeze/src/airflow_breeze/commands/kubernetes_kustomize_commands.py
b/dev/breeze/src/airflow_breeze/commands/kubernetes_kustomize_commands.py
index d7aa3b24862..fe1da3f0689 100644
--- a/dev/breeze/src/airflow_breeze/commands/kubernetes_kustomize_commands.py
+++ b/dev/breeze/src/airflow_breeze/commands/kubernetes_kustomize_commands.py
@@ -488,7 +488,7 @@ def _run_overlay_pytest(
env["OVERLAY_UNDER_TEST"] = overlay_name
env["OVERLAY_NAMESPACE"] = namespace
env["OVERLAY_RELEASE_NAME"] = release_name
- pytest_cmd = ["uv", "run", "pytest",
str(test_file.relative_to(CHART_PATH)), "-xvs"]
+ pytest_cmd = ["uv", "run", "--frozen", "pytest",
str(test_file.relative_to(CHART_PATH)), "-xvs"]
console_print(f"[info]Running behavioural tests: {' '.join(pytest_cmd)}")
result = run_command(pytest_cmd, env=env, check=False,
cwd=CHART_PATH.as_posix())
return result.returncode
diff --git a/dev/breeze/src/airflow_breeze/utils/kubernetes_utils.py
b/dev/breeze/src/airflow_breeze/utils/kubernetes_utils.py
index d6e9c343b9b..55b1a9195bd 100644
--- a/dev/breeze/src/airflow_breeze/utils/kubernetes_utils.py
+++ b/dev/breeze/src/airflow_breeze/utils/kubernetes_utils.py
@@ -291,7 +291,8 @@ def make_sure_kubernetes_tools_are_installed():
def sync_virtualenv(force_venv_setup: bool) -> RunCommandResult:
- uv_sync_cmd = ["uv", "sync"]
+ # kubernetes-tests is a workspace member: a plain `uv sync` would
re-resolve and rewrite the root uv.lock.
+ uv_sync_cmd = ["uv", "sync", "--frozen"]
if force_venv_setup:
uv_sync_cmd.append("--reinstall")
if os.environ.get("CI", "false") == "true":
diff --git a/dev/breeze/tests/test_kubernetes_commands.py
b/dev/breeze/tests/test_kubernetes_commands.py
index 142e389bf41..a40e15660b8 100644
--- a/dev/breeze/tests/test_kubernetes_commands.py
+++ b/dev/breeze/tests/test_kubernetes_commands.py
@@ -124,3 +124,20 @@ def
test_commands_pass_the_github_repository_from_the_environment(
assert result.exit_code == 0, result.output
mock_helper.assert_called_once()
assert mock_helper.call_args.kwargs["github_repository"] == FORK_REPOSITORY
+
+
[email protected](kubernetes_commands, "run_command", autospec=True)
[email protected](kubernetes_commands, "get_kubectl_cluster_name",
autospec=True, return_value="airflow")
[email protected](kubernetes_commands, "get_k8s_env", autospec=True,
return_value={"SHELL": "/bin/bash"})
+def test_run_tests_does_not_relock(mock_get_k8s_env, mock_get_cluster_name,
mock_run_command):
+ mock_run_command.return_value = mock.Mock(returncode=0)
+
+ kubernetes_commands._run_tests(
+ python="3.11",
+ kubernetes_version="v1.31.12",
+ output=None,
+ executor="LocalExecutor",
+ test_args=[],
+ )
+
+ assert mock_run_command.call_args.args[0][-1].startswith("uv run --frozen
pytest ")
diff --git a/dev/breeze/tests/test_kubernetes_kustomize_commands.py
b/dev/breeze/tests/test_kubernetes_kustomize_commands.py
index 0c6d48deea3..e58ac56b1fb 100644
--- a/dev/breeze/tests/test_kubernetes_kustomize_commands.py
+++ b/dev/breeze/tests/test_kubernetes_kustomize_commands.py
@@ -16,8 +16,11 @@
# under the License.
from __future__ import annotations
+from unittest import mock
+
import pytest
+from airflow_breeze.commands import kubernetes_kustomize_commands
from airflow_breeze.commands.kubernetes_kustomize_commands import (
ALLOWED_OVERLAY_IMAGES,
_discover_overlay_images,
@@ -76,3 +79,20 @@ class TestFindDisallowedOverlayImages:
)
def test_flags_images_not_on_allow_list(self, images, expected):
assert _find_disallowed_overlay_images(images) == expected
+
+
[email protected](kubernetes_kustomize_commands, "run_command", autospec=True)
[email protected](kubernetes_kustomize_commands, "get_k8s_env",
autospec=True, return_value={})
+def test_run_overlay_pytest_does_not_relock(mock_get_k8s_env,
mock_run_command):
+ mock_run_command.return_value = mock.Mock(returncode=0)
+
+ kubernetes_kustomize_commands._run_overlay_pytest(
+ overlay_name="kerberos",
+ namespace="airflow",
+ release_name="airflow",
+ python="3.11",
+ kubernetes_version="v1.31.12",
+ executor="LocalExecutor",
+ )
+
+ assert mock_run_command.call_args.args[0][:4] == ["uv", "run", "--frozen",
"pytest"]
diff --git a/dev/breeze/tests/test_kubernetes_utils.py
b/dev/breeze/tests/test_kubernetes_utils.py
index 88dee2d715f..d0ce39f84d2 100644
--- a/dev/breeze/tests/test_kubernetes_utils.py
+++ b/dev/breeze/tests/test_kubernetes_utils.py
@@ -27,6 +27,7 @@ from airflow_breeze.utils.kubernetes_utils import (
SCRIPTS_CI_KUBERNETES_PATH,
_render_kind_cluster_config,
get_kubernetes_port_numbers,
+ sync_virtualenv,
)
# Rendered config of a cluster created before the switch to single-node
clusters —
@@ -84,3 +85,9 @@ def
test_get_kubernetes_port_numbers_raises_when_forwarded_port_mapping_is_missi
def test_nodeport_service_exposes_forwarded_node_port():
node_port_service = yaml.safe_load((SCRIPTS_CI_KUBERNETES_PATH /
"nodeport.yaml").read_text())
assert [port["nodePort"] for port in node_port_service["spec"]["ports"]]
== [FORWARDED_NODE_PORT]
+
+
+@patch("airflow_breeze.utils.kubernetes_utils.run_command", autospec=True)
+def test_sync_virtualenv_does_not_relock(mock_run_command):
+ sync_virtualenv(force_venv_setup=False)
+ assert mock_run_command.call_args.args[0][:3] == ["uv", "sync", "--frozen"]