This is an automated email from the ASF dual-hosted git repository.
potiuk pushed a commit to branch v3-3-test
in repository https://gitbox.apache.org/repos/asf/airflow.git
The following commit(s) were added to refs/heads/v3-3-test by this push:
new 9c18c7be40b Fix lang-SDK k8s test on release branches by using their
own SDKs (#71640)
9c18c7be40b is described below
commit 9c18c7be40b9886baea44055c7bc10f18845efb4
Author: Jarek Potiuk <[email protected]>
AuthorDate: Fri Aug 14 23:23:31 2026 +0200
Fix lang-SDK k8s test on release branches by using their own SDKs (#71640)
The Go/Java SDK sources were always fetched from upstream main, on the
assumption that a release branch either lacks them or carries a stale
copy. That copy is not stale, it is the matching one: a packed bundle
declares a dated supervisor_schema_version and the task-SDK supervisor
refuses a bundle whose version it does not know, so upstream main's SDK
cannot run against a release branch's Airflow at all.
On v3-3-test every run of the test therefore failed with "cannot find
executable bundle with usable supervisor_schema_version" as soon as main
moved to a newer schema version than the branch.
---
.../airflow_breeze/commands/kubernetes_commands.py | 52 ++++++++---------
.../tests/test_kubernetes_lang_sdk_commands.py | 65 +++++++++++-----------
kubernetes-tests/lang_sdk/README.md | 39 ++++++-------
3 files changed, 77 insertions(+), 79 deletions(-)
diff --git a/dev/breeze/src/airflow_breeze/commands/kubernetes_commands.py
b/dev/breeze/src/airflow_breeze/commands/kubernetes_commands.py
index 84fac9d60f4..ac6ae2752a0 100644
--- a/dev/breeze/src/airflow_breeze/commands/kubernetes_commands.py
+++ b/dev/breeze/src/airflow_breeze/commands/kubernetes_commands.py
@@ -32,7 +32,6 @@ from typing import Any
import click
import yaml
-from airflow_breeze.branch_defaults import AIRFLOW_BRANCH
from airflow_breeze.commands.common_options import (
option_answer,
option_debug_resources,
@@ -2502,39 +2501,34 @@ LANG_SDK_AWS_CONN_URI = (
"aws://test:test@/?region_name=us-east-1&"
"endpoint_url=http%3A%2F%2Flocalstack.airflow.svc.cluster.local%3A4566"
)
-# Runs targeting a branch other than main build the Go/Java SDKs from upstream
main, so
-# release/backport branches with stale or missing go-sdk/java-sdk copies still
test current SDK
-# sources. See kubernetes-tests/lang_sdk/README.md.
+# Only a checkout that carries no go-sdk/java-sdk of its own falls back to
these.
+# See kubernetes-tests/lang_sdk/README.md.
LANG_SDK_UPSTREAM_GIT_URL = "https://github.com/apache/airflow.git"
LANG_SDK_UPSTREAM_REF = "main"
-# The target branch for which the lang-SDK artifacts are built from this
checkout's own SDK
-# sources rather than fetched from upstream. Distinct from
LANG_SDK_UPSTREAM_REF (the ref fetched
-# from upstream) even though both are "main" today -- one names a git ref, the
other a sentinel.
-LANG_SDK_LOCAL_SOURCE_BRANCH = "main"
-
-
-def _lang_sdk_target_branch() -> str:
- """Branch this run targets: GITHUB_BASE_REF or DEFAULT_BRANCH if set, else
this checkout's."""
- return os.environ.get("GITHUB_BASE_REF") or
os.environ.get("DEFAULT_BRANCH") or AIRFLOW_BRANCH
def _lang_sdk_resolve_sdk_sources(staging: Path, output: Output | None) ->
tuple[Path, Path]:
"""Resolve the go-sdk/java-sdk trees the lang-SDK artifacts are built from.
- A run targeting main builds the checked-out branch's own SDK sources, so a
PR's SDK changes
- are what the k8s test exercises -- without this,
``java_example``/``go_example`` (harness code
- that tracks the branch) is compiled against a different SDK than the
branch it belongs to, and
- any SDK rename breaks the build. Runs targeting anything else fall back to
upstream main.
+ The checkout's own sources win whenever it has them, because they are the
ones that pair with
+ the Airflow this test deploys: a packed bundle declares a dated
``supervisor_schema_version``
+ and the task-SDK supervisor rejects a bundle whose version it does not
know, so a release
+ branch's Airflow cannot run an SDK built from a later main. Building the
checkout's copy is
+ also what makes the k8s test exercise a PR's SDK changes --
``go_example``/``java_example`` are
+ harness fixtures that track the checked-out branch, so compiling them
against a different SDK
+ breaks on any SDK rename.
+
+ Only a branch cut before ``go-sdk``/``java-sdk`` existed falls back to
upstream main.
"""
- target = _lang_sdk_target_branch()
- if target == LANG_SDK_LOCAL_SOURCE_BRANCH:
+ go_sdk, java_sdk = AIRFLOW_ROOT_PATH / "go-sdk", AIRFLOW_ROOT_PATH /
"java-sdk"
+ if go_sdk.is_dir() and java_sdk.is_dir():
get_console(output=output).print(
- f"[info]Run targets {target}: building the lang-SDK Go/Java
artifacts from this branch"
+ "[info]Building the lang-SDK Go/Java artifacts from this
checkout's own go-sdk/java-sdk"
)
- return AIRFLOW_ROOT_PATH / "go-sdk", AIRFLOW_ROOT_PATH / "java-sdk"
+ return go_sdk, java_sdk
get_console(output=output).print(
- f"[info]Run targets {target}, not {LANG_SDK_LOCAL_SOURCE_BRANCH}:
building the lang-SDK Go/Java "
- f"artifacts from upstream {LANG_SDK_UPSTREAM_REF}"
+ f"[info]This checkout has no go-sdk/java-sdk: building the lang-SDK
Go/Java artifacts from "
+ f"upstream {LANG_SDK_UPSTREAM_REF}"
)
return _lang_sdk_fetch_upstream_sdk_sources(staging, output)
@@ -2619,7 +2613,7 @@ def _lang_sdk_build_go_bundle(
workspace mirroring the repo layout with ``go_sdk_source`` at
``<workspace>/go-sdk``, letting
the unmodified directive resolve against it. The scratch go_example is
re-tidied before packing
so its go.sum reconciles to that go-sdk, which differs from the in-repo
one its committed go.sum
- was tidied against whenever the source is upstream main.
+ was tidied against whenever the source is the upstream-main fallback.
"""
go_dir = staging / "go-artifacts"
go_dir.mkdir(parents=True, exist_ok=True)
@@ -2639,8 +2633,8 @@ def _lang_sdk_build_go_bundle(
# the current dir (".") because go_example is its own module.
#
# go_example's go.sum is tidied against the in-repo go-sdk, but the bundle
is built against the
- # upstream-main go-sdk copied in above. When a branch changes go-sdk's
dependency graph those two
- # go-sdks differ, and Go refuses to build on the resulting go.sum drift.
Re-tidy the scratch copy
+ # go-sdk copied in above, which is the upstream-main fallback on a
checkout that has none of its
+ # own. Those two go-sdks differ, and Go refuses to build on the go.sum
drift. Re-tidy the scratch copy
# first so the build reconciles to whichever go-sdk it is actually
compiled against; the committed
# go.sum is untouched and stays guarded by the check-go-example-mod-tidy
prek hook.
if native:
@@ -2714,8 +2708,8 @@ def _lang_sdk_build_java_jar(
Both gradle invocations run against ``java_sdk_source``; only ``-p`` stays
pointed at the local
``java_example``, which is test-harness code that keeps tracking the
checked-out branch. The two
- therefore only agree when the source is the local ``java-sdk/`` -- on a
run targeting a non-main
- branch the example is compiled against upstream main's SDK, so it must
stay compatible with it.
+ therefore only agree when the source is the local ``java-sdk/`` -- on the
upstream-main fallback
+ the example is compiled against upstream's SDK, so it must stay compatible
with it.
"""
java_dir = staging / "java-artifacts"
java_dir.mkdir(parents=True, exist_ok=True)
@@ -3069,7 +3063,7 @@ def _setup_lang_sdk_test(
) -> None:
"""Provision the lang-SDK coordinator env on an already-deployed
KubernetesExecutor cluster.
- Fetches go-sdk/java-sdk from upstream main, then builds the Go/Java
artifacts, the Java worker
+ Resolves the go-sdk/java-sdk sources, then builds the Go/Java artifacts,
the Java worker
image and deploys localstack in parallel, then serially uploads the
artifacts, applies the
config + secret, and helm-upgrades Airflow with the lang-SDK values.
"""
diff --git a/dev/breeze/tests/test_kubernetes_lang_sdk_commands.py
b/dev/breeze/tests/test_kubernetes_lang_sdk_commands.py
index 0f12758e249..a94976a44e7 100644
--- a/dev/breeze/tests/test_kubernetes_lang_sdk_commands.py
+++ b/dev/breeze/tests/test_kubernetes_lang_sdk_commands.py
@@ -20,14 +20,12 @@ from unittest import mock
import pytest
-from airflow_breeze.branch_defaults import AIRFLOW_BRANCH
from airflow_breeze.commands import kubernetes_commands
from airflow_breeze.commands.kubernetes_commands import (
_lang_sdk_build_go_bundle,
_lang_sdk_build_java_jar,
_lang_sdk_fetch_upstream_sdk_sources,
_lang_sdk_resolve_sdk_sources,
- _lang_sdk_target_branch,
_lang_sdk_upload_artifacts,
)
from airflow_breeze.utils import shared_options
@@ -379,51 +377,56 @@ class TestSetupLangSdkTestNativeSelection:
}
-class TestLangSdkTargetBranch:
+class TestLangSdkResolveSdkSources:
+ @pytest.fixture
+ def repo_root(self, tmp_path, monkeypatch):
+ root = tmp_path / "repo"
+ root.mkdir()
+ monkeypatch.setattr(kubernetes_commands, "AIRFLOW_ROOT_PATH", root)
+ return root
+
@pytest.mark.parametrize(
- ("env", "expected"),
+ "env",
[
- pytest.param({"GITHUB_BASE_REF": "main"}, "main",
id="pr-targeting-main"),
- pytest.param({"GITHUB_BASE_REF": "v3-3-test"}, "v3-3-test",
id="pr-targeting-release"),
- pytest.param({"DEFAULT_BRANCH": "v3-3-test"}, "v3-3-test",
id="ci-default-branch"),
+ pytest.param({}, id="no-branch-env"),
+ # A release-branch run must build the branch's own SDKs: upstream
main's speak a later
+ # supervisor_schema_version than that branch's task-SDK supervisor
knows.
pytest.param(
- {"GITHUB_BASE_REF": "main", "DEFAULT_BRANCH": "v3-3-test"},
- "main",
- id="pr-target-wins-over-default-branch",
+ {"GITHUB_BASE_REF": "v3-3-test", "DEFAULT_BRANCH":
"v3-3-test"},
+ id="release-branch-env",
),
- pytest.param({}, AIRFLOW_BRANCH,
id="falls-back-to-this-checkouts-branch"),
],
)
- def test_resolves_target_branch(self, env, expected, monkeypatch):
+ @mock.patch.object(kubernetes_commands,
"_lang_sdk_fetch_upstream_sdk_sources")
+ def test_checkout_with_both_sdks_builds_from_them(
+ self, mock_fetch, env, repo_root, tmp_path, monkeypatch
+ ):
monkeypatch.delenv("GITHUB_BASE_REF", raising=False)
monkeypatch.delenv("DEFAULT_BRANCH", raising=False)
for key, value in env.items():
monkeypatch.setenv(key, value)
-
- assert _lang_sdk_target_branch() == expected
-
- def test_empty_env_var_does_not_mask_the_fallback(self, monkeypatch):
- """GitHub sets GITHUB_BASE_REF to an empty string on non-PR events
(push, schedule)."""
- monkeypatch.setenv("GITHUB_BASE_REF", "")
- monkeypatch.setenv("DEFAULT_BRANCH", "v3-3-test")
-
- assert _lang_sdk_target_branch() == "v3-3-test"
-
-
-class TestLangSdkResolveSdkSources:
- @mock.patch.object(kubernetes_commands,
"_lang_sdk_fetch_upstream_sdk_sources")
- def test_targeting_main_uses_the_checked_out_branch(self, mock_fetch,
tmp_path, monkeypatch):
- monkeypatch.setenv("GITHUB_BASE_REF", "main")
+ (repo_root / "go-sdk").mkdir()
+ (repo_root / "java-sdk").mkdir()
go_sdk, java_sdk = _lang_sdk_resolve_sdk_sources(tmp_path, None)
- assert go_sdk == kubernetes_commands.AIRFLOW_ROOT_PATH / "go-sdk"
- assert java_sdk == kubernetes_commands.AIRFLOW_ROOT_PATH / "java-sdk"
+ assert (go_sdk, java_sdk) == (repo_root / "go-sdk", repo_root /
"java-sdk")
mock_fetch.assert_not_called()
+ @pytest.mark.parametrize(
+ "present",
+ [
+ pytest.param((), id="neither-sdk"),
+ pytest.param(("go-sdk",), id="only-go-sdk"),
+ pytest.param(("java-sdk",), id="only-java-sdk"),
+ ],
+ )
@mock.patch.object(kubernetes_commands,
"_lang_sdk_fetch_upstream_sdk_sources")
- def test_targeting_another_branch_falls_back_to_upstream_main(self,
mock_fetch, tmp_path, monkeypatch):
- monkeypatch.setenv("GITHUB_BASE_REF", "v3-3-test")
+ def test_checkout_without_both_sdks_falls_back_to_upstream_main(
+ self, mock_fetch, present, repo_root, tmp_path
+ ):
+ for name in present:
+ (repo_root / name).mkdir()
mock_fetch.return_value = (tmp_path / "go-sdk", tmp_path / "java-sdk")
go_sdk, java_sdk = _lang_sdk_resolve_sdk_sources(tmp_path, None)
diff --git a/kubernetes-tests/lang_sdk/README.md
b/kubernetes-tests/lang_sdk/README.md
index 570ec2b67f0..93583ecbd8b 100644
--- a/kubernetes-tests/lang_sdk/README.md
+++ b/kubernetes-tests/lang_sdk/README.md
@@ -65,31 +65,32 @@ The Go binary, Java jar, and stub Dag share one object
store (localstack) but li
## Which SDK sources get built
-`go-sdk/` and `java-sdk/` are only developed on `main`; a release/backport
branch may lack them
-entirely or carry a stale, branch-cut-frozen copy. So `breeze k8s
setup-lang-sdk-test` (and
-`run-complete-tests --lang-sdk-test`) picks the sources from the branch the
run **targets**, resolved
-by `_lang_sdk_resolve_sdk_sources()` in `kubernetes_commands.py`:
+`breeze k8s setup-lang-sdk-test` (and `run-complete-tests --lang-sdk-test`)
resolves them in
+`_lang_sdk_resolve_sdk_sources()` in `kubernetes_commands.py`:
-| Target branch | Go/Java SDK sources |
+| Checkout | Go/Java SDK sources |
| --- | --- |
-| `main` | the checked-out branch's own `go-sdk/` and `java-sdk/` |
-| anything else (`v3-*-test`, …) | upstream `main`, fetched fresh via
`_lang_sdk_fetch_upstream_sdk_sources()` |
-
-The target is `GITHUB_BASE_REF` for a PR, then `DEFAULT_BRANCH` if set,
falling back to this
-checkout's own `AIRFLOW_BRANCH`. Building a main-targeting PR's own SDK is
what makes the k8s test
-exercise that PR: `go_example`/`java_example` are harness fixtures that track
the checked-out branch,
-so compiling them against a *different* SDK means any SDK rename in the PR
fails to build. A
-backport to a release-test branch still gets current SDK code, as before.
+| has `go-sdk/` and `java-sdk/` | its own copies |
+| has neither (or only one) | upstream `main`, fetched fresh via
`_lang_sdk_fetch_upstream_sdk_sources()` |
+
+The checkout's own copies win because they are the ones that pair with the
Airflow this test
+deploys. A packed bundle declares a dated `supervisor_schema_version` and the
task-SDK supervisor
+rejects a bundle whose version it does not know, so a release branch's Airflow
cannot run an SDK
+built from a later `main` — the Go task fails with `cannot find executable
bundle with usable
+supervisor_schema_version`. Building the checkout's own SDK is also what makes
the k8s test exercise
+a PR's SDK changes: `go_example`/`java_example` are harness fixtures that
track the checked-out
+branch, so compiling them against a *different* SDK means any SDK rename in
the PR fails to build.
+
+The upstream-`main` fallback is only for a branch cut before
`go-sdk`/`java-sdk` existed. When it
+kicks in, that copy and the branch's `go_example` can diverge (upstream may
change go-sdk's
+dependency graph while `go_example`'s committed `go.sum` is tidied against the
in-repo go-sdk), so
+the Go bundle build re-runs `go mod tidy` in its scratch workspace before
packing and reconciles to
+whichever `go-sdk` it is compiled against. The committed `go_example` `go.sum`
is untouched and
+stays guarded by the `check-go-example-mod-tidy` prek hook.
Everything else — `airflow-core/`, `task-sdk/`, the deployed Airflow image,
and this directory's own
`go_example`/`java_example` fixtures — always comes from the checked-out
branch.
-When the sources do come from upstream main, that copy and the branch's
`go_example` can diverge (a
-branch may change go-sdk's dependency graph while `go_example`'s committed
`go.sum` is tidied against
-the in-repo go-sdk), so the Go bundle build re-runs `go mod tidy` in its
scratch workspace before
-packing and reconciles to whichever `go-sdk` it is compiled against. The
committed `go_example`
-`go.sum` is untouched and stays guarded by the `check-go-example-mod-tidy`
prek hook.
-
## Running it
The artifacts, localstack, config, and Helm release are provisioned by a
single breeze