This is an automated email from the ASF dual-hosted git repository. potiuk pushed a commit to branch ci-sync-v3-4-test in repository https://gitbox.apache.org/repos/asf/airflow.git
commit 3121a96581e869e738b5675cd40a485d516f0f29 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. (cherry picked from commit 7c7157b124e5f90e32086137c1a76f1d3e498c97) --- .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 772eab0ee04..c980fcd1d13 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 1a50e2f627a..971d7d0df58 100644 --- a/dev/breeze/tests/test_kubernetes_commands.py +++ b/dev/breeze/tests/test_kubernetes_commands.py @@ -125,3 +125,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"]
