This is an automated email from the ASF dual-hosted git repository.
jason810496 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 2e2132c3af5 Build lang-SDK k8s test artifacts from the branch when
targeting main (#71527)
2e2132c3af5 is described below
commit 2e2132c3af5baecb201a13add600d20b584ef9f6
Author: Jason(Zhe-You) Liu <[email protected]>
AuthorDate: Fri Aug 14 12:31:32 2026 +0800
Build lang-SDK k8s test artifacts from the branch when targeting main
(#71527)
* Build lang-SDK k8s test artifacts from the branch when targeting main
The k8s lang-SDK test compiled go_example/java_example -- harness fixtures
that
track the checked-out branch -- against upstream main's SDK
unconditionally, so
a PR that changes the Go/Java SDK was never exercised by the test that is
meant
to cover it, and any SDK rename failed the build outright.
Building from upstream main exists for release/backport branches, which may
lack
go-sdk/java-sdk or carry a branch-cut-frozen copy. That reason only applies
when
the run targets such a branch, so scope it to that case.
* Address lang-SDK k8s target-branch review feedback
Separates the upstream-fetch ref from the local-sources sentinel so
the two independent meanings don't share one constant, fixes the
"falls back to this checkout's branch" test case to assert against
AIRFLOW_BRANCH instead of a hardcoded "main" (which fails on backport
branches like v3-3-test), and stops claiming DEFAULT_BRANCH is set by
CI for this host-side lookup since nothing currently wires it in.
---
.../airflow_breeze/commands/kubernetes_commands.py | 71 ++++++++++++++++------
.../tests/test_kubernetes_lang_sdk_commands.py | 70 ++++++++++++++++++---
kubernetes-tests/lang_sdk/README.md | 38 +++++++-----
3 files changed, 139 insertions(+), 40 deletions(-)
diff --git a/dev/breeze/src/airflow_breeze/commands/kubernetes_commands.py
b/dev/breeze/src/airflow_breeze/commands/kubernetes_commands.py
index c064e7400b3..47d1cf3710a 100644
--- a/dev/breeze/src/airflow_breeze/commands/kubernetes_commands.py
+++ b/dev/breeze/src/airflow_breeze/commands/kubernetes_commands.py
@@ -32,6 +32,7 @@ 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,
@@ -2509,10 +2510,41 @@ LANG_SDK_AWS_CONN_URI = (
"aws://test:test@/?region_name=us-east-1&"
"endpoint_url=http%3A%2F%2Flocalstack.airflow.svc.cluster.local%3A4566"
)
-# The Go/Java SDKs are always built from upstream main so branches with stale
or missing
-# go-sdk/java-sdk copies still test current SDK sources. See
kubernetes-tests/lang_sdk/README.md.
+# 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.
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.
+ """
+ target = _lang_sdk_target_branch()
+ if target == LANG_SDK_LOCAL_SOURCE_BRANCH:
+ get_console(output=output).print(
+ f"[info]Run targets {target}: building the lang-SDK Go/Java
artifacts from this branch"
+ )
+ return AIRFLOW_ROOT_PATH / "go-sdk", AIRFLOW_ROOT_PATH / "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}"
+ )
+ return _lang_sdk_fetch_upstream_sdk_sources(staging, output)
def _lang_sdk_fetch_upstream_sdk_sources(staging: Path, output: Output | None)
-> tuple[Path, Path]:
@@ -2582,7 +2614,7 @@ def _lang_sdk_fetch_upstream_sdk_sources(staging: Path,
output: Output | None) -
def _lang_sdk_build_go_bundle(
- staging: Path, upstream_go_sdk: Path, output: Output | None, *, native:
bool = False
+ staging: Path, go_sdk_source: Path, output: Output | None, *, native: bool
= False
) -> None:
"""Build the Go bundle into ``staging/go-artifacts`` and copy the result
into the staging dir.
@@ -2592,10 +2624,10 @@ def _lang_sdk_build_go_bundle(
skipping the container image pull and reusing the runner's module/build
cache.
go_example's go.mod ``replace``s go-sdk by relative path, so the build
runs in a scratch
- workspace mirroring the repo layout with ``upstream_go_sdk`` at
``<workspace>/go-sdk``,
- letting the unmodified directive resolve against the upstream copy. The
scratch go_example is
- re-tidied before packing so its go.sum reconciles to that upstream go-sdk
(which may differ from
- the in-repo go-sdk its committed go.sum was tidied against).
+ 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.
"""
go_dir = staging / "go-artifacts"
go_dir.mkdir(parents=True, exist_ok=True)
@@ -2604,10 +2636,10 @@ def _lang_sdk_build_go_bundle(
example_path = workspace / example_rel
example_path.parent.mkdir(parents=True, exist_ok=True)
shutil.copytree(LANG_SDK_GO_EXAMPLE_PATH, example_path,
ignore=shutil.ignore_patterns(".home"))
- # In dry-run the fetch/extract commands are skipped, so the upstream copy
and build outputs
+ # In dry-run the fetch/extract commands are skipped, so the extracted copy
and build outputs
# never materialize -- skip the filesystem work that depends on them.
if not get_dry_run():
- shutil.copytree(upstream_go_sdk, workspace / "go-sdk")
+ shutil.copytree(go_sdk_source, workspace / "go-sdk")
output_bin = example_path / "bin" / LANG_SDK_GO_BUNDLE_NAME
output_bin.parent.mkdir(parents=True, exist_ok=True)
@@ -2677,7 +2709,7 @@ def _lang_sdk_build_go_bundle(
def _lang_sdk_build_java_jar(
- staging: Path, upstream_java_sdk: Path, output: Output | None, *, native:
bool = False
+ staging: Path, java_sdk_source: Path, output: Output | None, *, native:
bool = False
) -> None:
"""Publish the Java SDK to mavenLocal then build the java_example jar into
``staging/java-artifacts``.
@@ -2688,9 +2720,10 @@ def _lang_sdk_build_java_jar(
cache. ``java_example`` resolves the SDK from ``mavenLocal()``, so the SDK
is published first, then
the bundle is built with java-sdk's gradle wrapper pointed at the example
project (``-p``).
- Both gradle invocations run against ``upstream_java_sdk`` rather than the
local ``java-sdk/``;
- only ``-p`` stays pointed at the local ``java_example``, which is
test-harness code that keeps
- tracking the checked-out branch.
+ 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.
"""
java_dir = staging / "java-artifacts"
java_dir.mkdir(parents=True, exist_ok=True)
@@ -2701,14 +2734,14 @@ def _lang_sdk_build_java_jar(
)
run_command(
["./gradlew", "publishToMavenLocal", "-PskipSigning=true",
"--no-daemon", "--console=plain"],
- cwd=upstream_java_sdk,
+ cwd=java_sdk_source,
output=output,
check=True,
)
get_console(output=output).print("[info]Building Java jar with the
host Gradle toolchain")
run_command(
["./gradlew", "-p", str(LANG_SDK_JAVA_EXAMPLE_PATH), "bundle",
"--no-daemon", "--console=plain"],
- cwd=upstream_java_sdk,
+ cwd=java_sdk_source,
output=output,
check=True,
)
@@ -2738,7 +2771,7 @@ def _lang_sdk_build_java_jar(
"-v",
f"{AIRFLOW_ROOT_PATH}:/repo",
"-v",
- f"{upstream_java_sdk}:/repo/java-sdk",
+ f"{java_sdk_source}:/repo/java-sdk",
]
get_console(output=output).print("[info]Publishing Java SDK artifacts
to local Maven repository")
run_command(
@@ -3059,15 +3092,15 @@ def _setup_lang_sdk_test(
native = os.environ.get("LANG_SDK_NATIVE_TOOLCHAIN", "").lower() == "true"
with tempfile.TemporaryDirectory(prefix="lang_sdk_artifacts_") as tmp:
staging = Path(tmp)
- upstream_go_sdk, upstream_java_sdk =
_lang_sdk_fetch_upstream_sdk_sources(staging, output)
+ go_sdk_source, java_sdk_source =
_lang_sdk_resolve_sdk_sources(staging, output)
steps: list[tuple[str, Callable[[Output | None], Any]]] = [
(
"Build Go bundle",
- lambda o: _lang_sdk_build_go_bundle(staging, upstream_go_sdk,
o, native=native),
+ lambda o: _lang_sdk_build_go_bundle(staging, go_sdk_source, o,
native=native),
),
(
"Build Java jar",
- lambda o: _lang_sdk_build_java_jar(staging, upstream_java_sdk,
o, native=native),
+ lambda o: _lang_sdk_build_java_jar(staging, java_sdk_source,
o, native=native),
),
("Deploy localstack", lambda o:
_lang_sdk_deploy_localstack(python, kubernetes_version, o)),
]
diff --git a/dev/breeze/tests/test_kubernetes_lang_sdk_commands.py
b/dev/breeze/tests/test_kubernetes_lang_sdk_commands.py
index 31332c24d16..0f12758e249 100644
--- a/dev/breeze/tests/test_kubernetes_lang_sdk_commands.py
+++ b/dev/breeze/tests/test_kubernetes_lang_sdk_commands.py
@@ -20,11 +20,14 @@ 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
@@ -325,8 +328,8 @@ class TestSetupLangSdkTestNativeSelection:
monkeypatch.setenv("LANG_SDK_NATIVE_TOOLCHAIN", env_value)
captured: dict[str, bool] = {}
- fake_go_sdk = tmp_path / "upstream_go_sdk"
- fake_java_sdk = tmp_path / "upstream_java_sdk"
+ fake_go_sdk = tmp_path / "resolved_go_sdk"
+ fake_java_sdk = tmp_path / "resolved_java_sdk"
def fake_parallel(steps, output):
for _title, thunk in steps:
@@ -335,21 +338,21 @@ class TestSetupLangSdkTestNativeSelection:
monkeypatch.setattr(kubernetes_commands, "_run_lang_sdk_parallel",
fake_parallel)
monkeypatch.setattr(
kubernetes_commands,
- "_lang_sdk_fetch_upstream_sdk_sources",
+ "_lang_sdk_resolve_sdk_sources",
lambda staging, output: (fake_go_sdk, fake_java_sdk),
)
monkeypatch.setattr(
kubernetes_commands,
"_lang_sdk_build_go_bundle",
- lambda staging, upstream_go_sdk, output, *, native:
captured.update(
- go=native, go_sdk=upstream_go_sdk
+ lambda staging, go_sdk_source, output, *, native: captured.update(
+ go=native, go_sdk=go_sdk_source
),
)
monkeypatch.setattr(
kubernetes_commands,
"_lang_sdk_build_java_jar",
- lambda staging, upstream_java_sdk, output, *, native:
captured.update(
- java=native, java_sdk=upstream_java_sdk
+ lambda staging, java_sdk_source, output, *, native:
captured.update(
+ java=native, java_sdk=java_sdk_source
),
)
for name in (
@@ -374,3 +377,56 @@ class TestSetupLangSdkTestNativeSelection:
"go_sdk": fake_go_sdk,
"java_sdk": fake_java_sdk,
}
+
+
+class TestLangSdkTargetBranch:
+ @pytest.mark.parametrize(
+ ("env", "expected"),
+ [
+ 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(
+ {"GITHUB_BASE_REF": "main", "DEFAULT_BRANCH": "v3-3-test"},
+ "main",
+ id="pr-target-wins-over-default-branch",
+ ),
+ pytest.param({}, AIRFLOW_BRANCH,
id="falls-back-to-this-checkouts-branch"),
+ ],
+ )
+ def test_resolves_target_branch(self, env, expected, 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")
+
+ 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"
+ mock_fetch.assert_not_called()
+
+ @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")
+ mock_fetch.return_value = (tmp_path / "go-sdk", tmp_path / "java-sdk")
+
+ go_sdk, java_sdk = _lang_sdk_resolve_sdk_sources(tmp_path, None)
+
+ mock_fetch.assert_called_once_with(tmp_path, None)
+ assert (go_sdk, java_sdk) == (tmp_path / "go-sdk", tmp_path /
"java-sdk")
diff --git a/kubernetes-tests/lang_sdk/README.md
b/kubernetes-tests/lang_sdk/README.md
index 52e081c197f..570ec2b67f0 100644
--- a/kubernetes-tests/lang_sdk/README.md
+++ b/kubernetes-tests/lang_sdk/README.md
@@ -63,22 +63,32 @@ coordinator scans.
The Go binary, Java jar, and stub Dag share one object store (localstack) but
live in
**separate buckets** (`go-artifacts`, `java-artifacts`, `dags`).
-## SDK sources always come from upstream main
+## 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`:
+
+| Target branch | 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.
-`go-sdk/` and `java-sdk/` are young, fast-moving directories that a
release/backport branch may lack
-entirely or carry a stale, branch-cut-frozen copy of. So regardless of which
branch/ref this repo is
-checked out at, `breeze k8s setup-lang-sdk-test` (and `run-complete-tests
--lang-sdk-test`) always
-builds the Go bundle and Java jar from upstream `main`'s `go-sdk`/`java-sdk` —
fetched fresh via
-`_lang_sdk_fetch_upstream_sdk_sources()` in `kubernetes_commands.py`, never
from whatever's on disk.
Everything else — `airflow-core/`, `task-sdk/`, the deployed Airflow image,
and this directory's own
-`go_example`/`java_example` harness fixtures — still comes from the
checked-out branch as before, so a
-backport of a core/task-sdk fix to a release-test branch keeps testing against
current SDK code.
-
-Because the branch's `go_example` and upstream main's `go-sdk` 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), the Go bundle build re-runs `go mod tidy` in its scratch workspace
before packing, so the
-build 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.
+`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