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 c22cc6d345f Pin lang-SDK k8s test's Go/Java SDKs to upstream main 
(#69545)
c22cc6d345f is described below

commit c22cc6d345f3abe06d61cfc95fe851fbab8bd568
Author: Jason(Zhe-You) Liu <[email protected]>
AuthorDate: Wed Jul 15 11:43:46 2026 +0900

    Pin lang-SDK k8s test's Go/Java SDKs to upstream main (#69545)
    
    * Pin lang-SDK k8s test's Go/Java SDKs to upstream main
    
    Release/backport branches often lack go-sdk/java-sdk entirely or carry a
    stale, branch-cut-frozen copy, so backporting a core/task-sdk fix to such
    a branch would otherwise run the lang-SDK coordinator test against a
    missing or outdated SDK snapshot. The Go bundle and Java jar builds now
    always fetch go-sdk/java-sdk from upstream main regardless of the checked
    out branch, while airflow-core/task-sdk and the test's own harness
    fixtures keep tracking whatever branch is checked out.
    
    Fix lang-SDK Java build missing task-sdk sibling in native mode
    
    java-sdk's sdk/build.gradle.kts reads a sibling ../task-sdk/.../schema.json
    at build time. The upstream-main extraction only contains go-sdk/java-sdk,
    so building from it directly (native/CI toolchain mode) failed looking for
    a task-sdk directory that doesn't exist. Symlink the real, local task-sdk
    alongside the extraction so the reference resolves, without pulling
    task-sdk itself from upstream.
    
    Restore lang-SDK k8s test's gradle wrapper jar dropped by export-ignore
    
    java-sdk/.gitattributes marks gradle/wrapper/gradle-wrapper.jar 
export-ignore
    so ASF source-release tarballs stay free of compiled binaries (LEGAL-570).
    git archive, used to pull go-sdk/java-sdk out of upstream main for this 
test,
    respects that attribute and silently drops the jar, leaving the extracted
    java-sdk's ./gradlew unable to find itself and failing every build.
    
    Shorten gradle-wrapper.jar restoration comments
    
    * Restore gradle wrapper scripts dropped by export-ignore in lang-SDK k8s 
test
    
    Upstream main's java-sdk/.gitattributes export-ignores gradlew and
    gradlew.bat (ASF LEGAL-570) in addition to the wrapper jar, so the
    git-archive extraction of upstream sources no longer contains the
    wrapper script the Java build invokes, failing CI with
    FileNotFoundError: './gradlew'. Restore all three wrapper files from
    the pinned commit instead of only the jar.
    
    * Fix breeze k8s setup-lang-sdk-test crash with --dry-run
    
    In dry-run mode run_command skips execution and returns stdout="" (a
    str), so every filesystem step consuming a command's output crashed:
    write_bytes on the gradle wrapper restore raised TypeError, the Go
    bundle copytree/copy raised FileNotFoundError, the Java jar glob hit
    sys.exit(1), and the artifact upload raised StopIteration. Guard those
    steps so --dry-run prints the full provisioning command sequence and
    completes cleanly.
---
 .../airflow_breeze/commands/kubernetes_commands.py | 158 +++++++++++--
 .../tests/test_kubernetes_lang_sdk_commands.py     | 244 +++++++++++++++++++--
 kubernetes-tests/lang_sdk/README.md                |  11 +
 3 files changed, 379 insertions(+), 34 deletions(-)

diff --git a/dev/breeze/src/airflow_breeze/commands/kubernetes_commands.py 
b/dev/breeze/src/airflow_breeze/commands/kubernetes_commands.py
index 7b36aebe00b..ac5bbbfad04 100644
--- a/dev/breeze/src/airflow_breeze/commands/kubernetes_commands.py
+++ b/dev/breeze/src/airflow_breeze/commands/kubernetes_commands.py
@@ -100,6 +100,7 @@ from airflow_breeze.utils.run_utils import (
     check_if_image_exists,
     run_command,
 )
+from airflow_breeze.utils.shared_options import get_dry_run
 
 KUBERNETES_PYTEST_ARGS = [
     "--strict-markers",
@@ -2497,6 +2498,7 @@ LANG_SDK_JAVA_EXAMPLE_PATH = LANG_SDK_PATH / 
"java_example"
 LANG_SDK_GO_BUILDER_IMAGE = os.environ.get("GO_BUILDER_IMAGE", 
"golang:1.25-alpine")
 LANG_SDK_JAVA_BUILDER_IMAGE = "eclipse-temurin:17-jdk"
 LANG_SDK_MAVEN_CACHE_PATH = AIRFLOW_ROOT_PATH / "files" / "m2"
+LANG_SDK_GRADLE_CACHE_PATH = AIRFLOW_ROOT_PATH / "files" / "gradle"
 # The Java queue needs a JRE the JavaCoordinator can exec; the Go queue runs on
 # the plain prod image. Building the Java worker image as a separate tag (prod 
+
 # JRE, see Dockerfile.java) lets each coordinator route its queue to a distinct
@@ -2507,19 +2509,105 @@ 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.
+LANG_SDK_UPSTREAM_GIT_URL = "https://github.com/apache/airflow.git";
+LANG_SDK_UPSTREAM_REF = "main"
 
 
-def _lang_sdk_build_go_bundle(staging: Path, output: Output | None, *, native: 
bool = False) -> None:
+def _lang_sdk_fetch_upstream_sdk_sources(staging: Path, output: Output | None) 
-> tuple[Path, Path]:
+    """Extract go-sdk/ and java-sdk/ from upstream main into a throwaway 
staging dir.
+
+    Prefers the ``upstream`` remote when configured, falling back to the 
canonical GitHub URL
+    (CI has no ``upstream`` and ``origin`` may be a fork). Shallow fetch + 
``git archive`` never
+    touch the working tree or index.
+
+    The real, local task-sdk is symlinked alongside the extraction because 
java-sdk's
+    ``sdk/build.gradle.kts`` reads a sibling ``../task-sdk/.../schema.json``. 
The gradle wrapper
+    scripts and jar are ``export-ignore`` (ASF LEGAL-570) so ``git archive`` 
drops them;
+    ``git show`` restores them, re-marking ``gradlew`` executable.
+    """
+    remotes = run_command(
+        ["git", "remote"], cwd=AIRFLOW_ROOT_PATH, output=output, 
capture_output=True, text=True, check=True
+    ).stdout.split()
+    fetch_source = "upstream" if "upstream" in remotes else 
LANG_SDK_UPSTREAM_GIT_URL
+    get_console(output=output).print(
+        f"[info]Fetching {LANG_SDK_UPSTREAM_REF} from {fetch_source} for the 
lang-SDK Go/Java sources"
+    )
+    run_command(
+        ["git", "fetch", "--depth=1", fetch_source, LANG_SDK_UPSTREAM_REF],
+        cwd=AIRFLOW_ROOT_PATH,
+        output=output,
+        check=True,
+    )
+    sha = run_command(
+        ["git", "rev-parse", "FETCH_HEAD"],
+        cwd=AIRFLOW_ROOT_PATH,
+        output=output,
+        capture_output=True,
+        text=True,
+        check=True,
+    ).stdout.strip()
+    get_console(output=output).print(f"[info]lang-SDK Go/Java sources pinned 
to upstream main @ {sha}")
+    extracted = staging / "upstream_lang_sdk_sources"
+    extracted.mkdir(parents=True, exist_ok=True)
+    archive_path = staging / "upstream_lang_sdk_sources.tar"
+    run_command(
+        ["git", "archive", "--format=tar", f"--output={archive_path}", sha, 
"--", "go-sdk", "java-sdk"],
+        cwd=AIRFLOW_ROOT_PATH,
+        output=output,
+        check=True,
+    )
+    run_command(["tar", "-xf", str(archive_path), "-C", str(extracted)], 
output=output, check=True)
+    for rel_path, mode in (
+        ("java-sdk/gradlew", 0o755),
+        ("java-sdk/gradlew.bat", 0o644),
+        ("java-sdk/gradle/wrapper/gradle-wrapper.jar", 0o644),
+    ):
+        restored = extracted / rel_path
+        restored.parent.mkdir(parents=True, exist_ok=True)
+        restored.write_bytes(
+            run_command(
+                ["git", "show", f"{sha}:{rel_path}"],
+                cwd=AIRFLOW_ROOT_PATH,
+                output=output,
+                capture_output=True,
+                check=True,
+            ).stdout
+            or b""
+        )
+        restored.chmod(mode)
+    (extracted / "task-sdk").symlink_to(AIRFLOW_ROOT_PATH / "task-sdk")
+    return extracted / "go-sdk", extracted / "java-sdk"
+
+
+def _lang_sdk_build_go_bundle(
+    staging: Path, upstream_go_sdk: Path, output: Output | None, *, native: 
bool = False
+) -> None:
     """Build the Go bundle into ``staging/go-artifacts`` and copy the result 
into the staging dir.
 
     By default the build runs in an ephemeral Go toolchain container so the 
host needs no Go install,
     writing its caches into a gitignored dir under the repo. In ``native`` 
mode (used in CI, where the
     host already has a cached Go toolchain via ``actions/setup-go``) it 
invokes the host ``go`` directly,
     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.
     """
     go_dir = staging / "go-artifacts"
     go_dir.mkdir(parents=True, exist_ok=True)
-    output_bin = LANG_SDK_GO_EXAMPLE_PATH / "bin" / LANG_SDK_GO_BUNDLE_NAME
+    example_rel = LANG_SDK_GO_EXAMPLE_PATH.relative_to(AIRFLOW_ROOT_PATH)
+    workspace = staging / "go_workspace"
+    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
+    # never materialize -- skip the filesystem work that depends on them.
+    if not get_dry_run():
+        shutil.copytree(upstream_go_sdk, workspace / "go-sdk")
+    output_bin = example_path / "bin" / LANG_SDK_GO_BUNDLE_NAME
+    output_bin.parent.mkdir(parents=True, exist_ok=True)
 
     # CGO_ENABLED=0 yields a fully static binary that runs on the stock 
worker. The package built is
     # the current dir (".") because go_example is its own module.
@@ -2527,17 +2615,18 @@ def _lang_sdk_build_go_bundle(staging: Path, output: 
Output | None, *, native: b
         get_console(output=output).print("[info]Building Go bundle with the 
host Go toolchain")
         run_command(
             ["go", "tool", "airflow-go-pack", "--output", str(output_bin), 
"."],
-            cwd=LANG_SDK_GO_EXAMPLE_PATH,
+            cwd=example_path,
             env={**os.environ, "CGO_ENABLED": "0"},
             output=output,
             check=True,
         )
     else:
         uid_gid = f"{os.getuid()}:{os.getgid()}"
-        go_example_ctr = 
f"/repo/{LANG_SDK_GO_EXAMPLE_PATH.relative_to(AIRFLOW_ROOT_PATH).as_posix()}"
+        go_example_ctr = f"/repo/{example_rel.as_posix()}"
         # USER/HOME must be set because the SDK calls user.Current() at init; 
with cgo disabled Go's
-        # pure-Go resolver reads those env vars and panics if either is empty. 
HOME points at a
-        # writable, gitignored dir under go_example so the caches persist 
between runs on a dev host.
+        # pure-Go resolver reads those env vars and panics if either is empty. 
HOME is mounted from
+        # the real go_example's gitignored cache dir so the caches persist 
across scratch workspaces.
+        (LANG_SDK_GO_EXAMPLE_PATH / ".home").mkdir(parents=True, exist_ok=True)
         get_console(output=output).print(f"[info]Building Go bundle in 
{LANG_SDK_GO_BUILDER_IMAGE}")
         run_command(
             [
@@ -2553,7 +2642,9 @@ def _lang_sdk_build_go_bundle(staging: Path, output: 
Output | None, *, native: b
                 "-e",
                 "CGO_ENABLED=0",
                 "-v",
-                f"{AIRFLOW_ROOT_PATH}:/repo",
+                f"{workspace}:/repo",
+                "-v",
+                f"{LANG_SDK_GO_EXAMPLE_PATH / '.home'}:{go_example_ctr}/.home",
                 "-w",
                 go_example_ctr,
                 LANG_SDK_GO_BUILDER_IMAGE,
@@ -2567,10 +2658,13 @@ def _lang_sdk_build_go_bundle(staging: Path, output: 
Output | None, *, native: b
             output=output,
             check=True,
         )
-    shutil.copy(output_bin, go_dir / LANG_SDK_GO_BUNDLE_NAME)
+    if not get_dry_run():
+        shutil.copy(output_bin, go_dir / LANG_SDK_GO_BUNDLE_NAME)
 
 
-def _lang_sdk_build_java_jar(staging: Path, output: Output | None, *, native: 
bool = False) -> None:
+def _lang_sdk_build_java_jar(
+    staging: Path, upstream_java_sdk: Path, output: Output | None, *, native: 
bool = False
+) -> None:
     """Publish the Java SDK to mavenLocal then build the java_example jar into 
``staging/java-artifacts``.
 
     By default the build runs in an ephemeral JDK container so the host needs 
no JDK, persisting the
@@ -2579,10 +2673,13 @@ def _lang_sdk_build_java_jar(staging: Path, output: 
Output | None, *, native: bo
     host ``./gradlew`` directly, skipping the container image pull and reusing 
the runner's ``~/.gradle``
     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.
     """
     java_dir = staging / "java-artifacts"
     java_dir.mkdir(parents=True, exist_ok=True)
-    java_sdk_path = AIRFLOW_ROOT_PATH / "java-sdk"
 
     if native:
         get_console(output=output).print(
@@ -2590,14 +2687,14 @@ def _lang_sdk_build_java_jar(staging: Path, output: 
Output | None, *, native: bo
         )
         run_command(
             ["./gradlew", "publishToMavenLocal", "-PskipSigning=true", 
"--no-daemon", "--console=plain"],
-            cwd=java_sdk_path,
+            cwd=upstream_java_sdk,
             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=java_sdk_path,
+            cwd=upstream_java_sdk,
             output=output,
             check=True,
         )
@@ -2605,9 +2702,11 @@ def _lang_sdk_build_java_jar(staging: Path, output: 
Output | None, *, native: bo
         uid_gid = f"{os.getuid()}:{os.getgid()}"
         java_example_ctr = 
f"/repo/{LANG_SDK_JAVA_EXAMPLE_PATH.relative_to(AIRFLOW_ROOT_PATH).as_posix()}"
         # --user keeps build outputs owned by the host user; HOME is set 
explicitly because that UID has
-        # no /etc/passwd entry; GRADLE_USER_HOME and the mounted ~/.m2 persist 
the Gradle distribution and
-        # dependency caches between runs on a dev host.
+        # no /etc/passwd entry. GRADLE_USER_HOME and the mounted ~/.m2 persist 
the Gradle distribution
+        # and dependency caches between runs -- outside /repo/java-sdk, which 
is remounted to the
+        # (per-run) upstream copy below.
         LANG_SDK_MAVEN_CACHE_PATH.mkdir(parents=True, exist_ok=True)
+        LANG_SDK_GRADLE_CACHE_PATH.mkdir(parents=True, exist_ok=True)
         java_docker_prefix = [
             "docker",
             "run",
@@ -2615,13 +2714,17 @@ def _lang_sdk_build_java_jar(staging: Path, output: 
Output | None, *, native: bo
             "--user",
             uid_gid,
             "-e",
-            "GRADLE_USER_HOME=/repo/java-sdk/.gradle",
+            "GRADLE_USER_HOME=/workspace-home/.gradle",
             "-e",
             "HOME=/workspace-home",
             "-v",
             f"{LANG_SDK_MAVEN_CACHE_PATH}:/workspace-home/.m2",
             "-v",
+            f"{LANG_SDK_GRADLE_CACHE_PATH}:/workspace-home/.gradle",
+            "-v",
             f"{AIRFLOW_ROOT_PATH}:/repo",
+            "-v",
+            f"{upstream_java_sdk}:/repo/java-sdk",
         ]
         get_console(output=output).print("[info]Publishing Java SDK artifacts 
to local Maven repository")
         run_command(
@@ -2656,6 +2759,8 @@ def _lang_sdk_build_java_jar(staging: Path, output: 
Output | None, *, native: bo
             output=output,
             check=True,
         )
+    if get_dry_run():
+        return
     jars = list((LANG_SDK_JAVA_EXAMPLE_PATH / "build" / 
"bundle").glob("*.jar"))
     if not jars:
         get_console(output=output).print("[error]No jar produced by the Java 
bundle build")
@@ -2716,7 +2821,11 @@ def _lang_sdk_upload_artifacts(
     ).stdout.strip()
 
     go_bundle = staging / "go-artifacts" / "lang_sdk_combined"
-    java_jar = next((staging / "java-artifacts").glob("*.jar"))
+    if get_dry_run():
+        # The dry-run build steps produce no jar; use the placeholder name so 
the commands still print.
+        java_jar = staging / "java-artifacts" / "app.jar"
+    else:
+        java_jar = next((staging / "java-artifacts").glob("*.jar"))
     stub_dag = LANG_SDK_PATH / "dags" / "lang_sdk_combined.py"
 
     for src, dest in (
@@ -2921,9 +3030,9 @@ def _setup_lang_sdk_test(
 ) -> None:
     """Provision the lang-SDK coordinator env on an already-deployed 
KubernetesExecutor cluster.
 
-    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.
+    Fetches go-sdk/java-sdk from upstream main, 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.
     """
     go_image = go_image or 
f"{BuildProdParams(python=python).airflow_image_kubernetes}:latest"
     build_java_image = java_image is None
@@ -2936,9 +3045,16 @@ 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)
         steps: list[tuple[str, Callable[[Output | None], Any]]] = [
-            ("Build Go bundle", lambda o: _lang_sdk_build_go_bundle(staging, 
o, native=native)),
-            ("Build Java jar", lambda o: _lang_sdk_build_java_jar(staging, o, 
native=native)),
+            (
+                "Build Go bundle",
+                lambda o: _lang_sdk_build_go_bundle(staging, upstream_go_sdk, 
o, native=native),
+            ),
+            (
+                "Build Java jar",
+                lambda o: _lang_sdk_build_java_jar(staging, upstream_java_sdk, 
o, native=native),
+            ),
             ("Deploy localstack", lambda o: 
_lang_sdk_deploy_localstack(python, kubernetes_version, o)),
         ]
         if build_java_image:
diff --git a/dev/breeze/tests/test_kubernetes_lang_sdk_commands.py 
b/dev/breeze/tests/test_kubernetes_lang_sdk_commands.py
index 651c0a17dc5..ab5eb9946ea 100644
--- a/dev/breeze/tests/test_kubernetes_lang_sdk_commands.py
+++ b/dev/breeze/tests/test_kubernetes_lang_sdk_commands.py
@@ -24,7 +24,15 @@ 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_upload_artifacts,
 )
+from airflow_breeze.utils import shared_options
+
+
[email protected]
+def dry_run(monkeypatch):
+    monkeypatch.setattr(shared_options._SharedOptions, "dry_run_value", True)
 
 
 @pytest.fixture
@@ -38,11 +46,21 @@ def go_example(tmp_path, monkeypatch):
     return go_dir
 
 
[email protected]
+def upstream_go_sdk(tmp_path):
+    """A fake extracted upstream-main go-sdk copy, distinguishable from 
anything under go_example."""
+    sdk_dir = tmp_path / "upstream_go_sdk"
+    sdk_dir.mkdir()
+    (sdk_dir / "marker.go").write_text("upstream")
+    return sdk_dir
+
+
 @pytest.fixture
 def java_example(tmp_path, monkeypatch):
     """Point the java_example dir at a tmp path (under a tmp repo root) with a 
pre-built bundle jar."""
     monkeypatch.setattr(kubernetes_commands, "AIRFLOW_ROOT_PATH", tmp_path)
     monkeypatch.setattr(kubernetes_commands, "LANG_SDK_MAVEN_CACHE_PATH", 
tmp_path / "m2")
+    monkeypatch.setattr(kubernetes_commands, "LANG_SDK_GRADLE_CACHE_PATH", 
tmp_path / "gradle")
     java_dir = tmp_path / "java_example"
     (java_dir / "build" / "bundle").mkdir(parents=True)
     (java_dir / "build" / "bundle" / "app.jar").write_text("jar")
@@ -50,31 +68,52 @@ def java_example(tmp_path, monkeypatch):
     return java_dir
 
 
[email protected]
+def upstream_java_sdk(tmp_path):
+    """A fake extracted upstream-main java-sdk copy, distinguishable from the 
real java-sdk/."""
+    sdk_dir = tmp_path / "upstream_java_sdk"
+    sdk_dir.mkdir()
+    (sdk_dir / "marker.gradle").write_text("upstream")
+    return sdk_dir
+
+
 class TestLangSdkBuildGoBundle:
     @mock.patch.object(kubernetes_commands, "run_command")
-    def test_native_uses_host_go_toolchain(self, mock_run, tmp_path, 
go_example):
-        _lang_sdk_build_go_bundle(tmp_path, None, native=True)
+    def test_native_uses_host_go_toolchain(self, mock_run, tmp_path, 
go_example, upstream_go_sdk):
+        _lang_sdk_build_go_bundle(tmp_path, upstream_go_sdk, None, native=True)
 
         cmd = mock_run.call_args.args[0]
         assert cmd[:3] == ["go", "tool", "airflow-go-pack"]
         assert "docker" not in cmd
-        assert mock_run.call_args.kwargs["cwd"] == go_example
+        workspace_example = mock_run.call_args.kwargs["cwd"]
+        # The build runs against a scratch workspace copy, not the real 
go_example dir.
+        assert workspace_example != go_example
+        assert workspace_example.name == go_example.name
         assert mock_run.call_args.kwargs["env"]["CGO_ENABLED"] == "0"
+        # The workspace mirrors the repo layout, with go-sdk swapped for the 
upstream copy.
+        assert (workspace_example.parent / "go-sdk" / "marker.go").read_text() 
== "upstream"
         assert (tmp_path / "go-artifacts" / 
kubernetes_commands.LANG_SDK_GO_BUNDLE_NAME).exists()
 
     @mock.patch.object(kubernetes_commands, "run_command")
-    def test_container_mode_runs_in_docker(self, mock_run, tmp_path, 
go_example):
-        _lang_sdk_build_go_bundle(tmp_path, None, native=False)
+    def test_container_mode_runs_in_docker(self, mock_run, tmp_path, 
go_example, upstream_go_sdk):
+        _lang_sdk_build_go_bundle(tmp_path, upstream_go_sdk, None, 
native=False)
 
         cmd = mock_run.call_args.args[0]
         assert cmd[0] == "docker"
         assert kubernetes_commands.LANG_SDK_GO_BUILDER_IMAGE in cmd
+        # The workspace, not AIRFLOW_ROOT_PATH, is mounted at /repo; the 
persistent HOME cache
+        # dir comes from the real go_example.
+        mounts = [cmd[i + 1] for i, arg in enumerate(cmd) if arg == "-v"]
+        repo_mount = next(m for m in mounts if m.endswith(":/repo"))
+        assert repo_mount.split(":")[0] != str(go_example.parent)
+        home_mount = next(m for m in mounts if m.endswith("/.home"))
+        assert home_mount.startswith(str(go_example / ".home"))
 
 
 class TestLangSdkBuildJavaJar:
     @mock.patch.object(kubernetes_commands, "run_command")
-    def test_native_uses_host_gradle_toolchain(self, mock_run, tmp_path, 
java_example):
-        _lang_sdk_build_java_jar(tmp_path, None, native=True)
+    def test_native_uses_host_gradle_toolchain(self, mock_run, tmp_path, 
java_example, upstream_java_sdk):
+        _lang_sdk_build_java_jar(tmp_path, upstream_java_sdk, None, 
native=True)
 
         publish_cmd, bundle_cmd = (call.args[0] for call in 
mock_run.call_args_list)
         assert publish_cmd == [
@@ -92,14 +131,177 @@ class TestLangSdkBuildJavaJar:
             "--no-daemon",
             "--console=plain",
         ]
+        # gradlew runs from the upstream copy; only -p stays pointed at the 
local java_example.
+        assert all(call.kwargs["cwd"] == upstream_java_sdk for call in 
mock_run.call_args_list)
         assert all("docker" not in call.args[0] for call in 
mock_run.call_args_list)
         assert (tmp_path / "java-artifacts" / "app.jar").exists()
 
     @mock.patch.object(kubernetes_commands, "run_command")
-    def test_container_mode_runs_in_docker(self, mock_run, tmp_path, 
java_example):
-        _lang_sdk_build_java_jar(tmp_path, None, native=False)
+    def test_container_mode_runs_in_docker(self, mock_run, tmp_path, 
java_example, upstream_java_sdk):
+        _lang_sdk_build_java_jar(tmp_path, upstream_java_sdk, None, 
native=False)
 
         assert all(call.args[0][0] == "docker" for call in 
mock_run.call_args_list)
+        cmd = mock_run.call_args_list[0].args[0]
+        mounts = [cmd[i + 1] for i, arg in enumerate(cmd) if arg == "-v"]
+        # java-sdk is remounted to the upstream copy; GRADLE_USER_HOME lives 
outside it.
+        assert f"{upstream_java_sdk}:/repo/java-sdk" in mounts
+        assert "GRADLE_USER_HOME=/workspace-home/.gradle" in cmd
+        assert any(mount.endswith(":/workspace-home/.gradle") for mount in 
mounts)
+
+
+class TestLangSdkFetchUpstreamSdkSources:
+    @mock.patch.object(kubernetes_commands, "run_command")
+    def test_prefers_upstream_remote_when_present(self, mock_run, tmp_path):
+        mock_run.side_effect = [
+            mock.Mock(stdout="origin\nupstream\n"),
+            mock.Mock(),  # git fetch
+            mock.Mock(stdout="deadbeef\n"),  # git rev-parse
+            mock.Mock(),  # git archive
+            mock.Mock(),  # tar -xf
+            mock.Mock(stdout=b""),  # git show gradlew
+            mock.Mock(stdout=b""),  # git show gradlew.bat
+            mock.Mock(stdout=b""),  # git show gradle-wrapper.jar
+        ]
+
+        _lang_sdk_fetch_upstream_sdk_sources(tmp_path, None)
+
+        fetch_cmd = mock_run.call_args_list[1].args[0]
+        assert fetch_cmd == ["git", "fetch", "--depth=1", "upstream", "main"]
+
+    @mock.patch.object(kubernetes_commands, "run_command")
+    def test_falls_back_to_canonical_url_when_no_upstream_remote(self, 
mock_run, tmp_path):
+        mock_run.side_effect = [
+            mock.Mock(stdout="origin\n"),
+            mock.Mock(),  # git fetch
+            mock.Mock(stdout="deadbeef\n"),  # git rev-parse
+            mock.Mock(),  # git archive
+            mock.Mock(),  # tar -xf
+            mock.Mock(stdout=b""),  # git show gradlew
+            mock.Mock(stdout=b""),  # git show gradlew.bat
+            mock.Mock(stdout=b""),  # git show gradle-wrapper.jar
+        ]
+
+        _lang_sdk_fetch_upstream_sdk_sources(tmp_path, None)
+
+        fetch_cmd = mock_run.call_args_list[1].args[0]
+        assert fetch_cmd == [
+            "git",
+            "fetch",
+            "--depth=1",
+            "https://github.com/apache/airflow.git";,
+            "main",
+        ]
+
+    @mock.patch.object(kubernetes_commands, "run_command")
+    def test_returns_extracted_go_sdk_and_java_sdk_paths(self, mock_run, 
tmp_path):
+        mock_run.side_effect = [
+            mock.Mock(stdout="upstream\n"),
+            mock.Mock(),
+            mock.Mock(stdout="deadbeef\n"),
+            mock.Mock(),
+            mock.Mock(),
+            mock.Mock(stdout=b""),
+            mock.Mock(stdout=b""),
+            mock.Mock(stdout=b""),
+        ]
+
+        go_sdk, java_sdk = _lang_sdk_fetch_upstream_sdk_sources(tmp_path, None)
+
+        extracted = tmp_path / "upstream_lang_sdk_sources"
+        assert go_sdk == extracted / "go-sdk"
+        assert java_sdk == extracted / "java-sdk"
+        archive_cmd = mock_run.call_args_list[3].args[0]
+        assert archive_cmd[:2] == ["git", "archive"]
+        assert archive_cmd[-2:] == ["go-sdk", "java-sdk"]
+        assert "deadbeef" in archive_cmd
+
+    @mock.patch.object(kubernetes_commands, "run_command")
+    def test_symlinks_real_task_sdk_alongside_the_extraction(self, mock_run, 
tmp_path, monkeypatch):
+        # java-sdk's build reads a sibling ../task-sdk/.../schema.json; 
without the symlink a
+        # native-mode build from the extraction fails.
+        repo_root = tmp_path / "repo"
+        (repo_root / "task-sdk").mkdir(parents=True)
+        monkeypatch.setattr(kubernetes_commands, "AIRFLOW_ROOT_PATH", 
repo_root)
+        staging = tmp_path / "staging"
+        staging.mkdir()
+        mock_run.side_effect = [
+            mock.Mock(stdout="upstream\n"),
+            mock.Mock(),
+            mock.Mock(stdout="deadbeef\n"),
+            mock.Mock(),
+            mock.Mock(),
+            mock.Mock(stdout=b""),
+            mock.Mock(stdout=b""),
+            mock.Mock(stdout=b""),
+        ]
+
+        _lang_sdk_fetch_upstream_sdk_sources(staging, None)
+
+        task_sdk_link = staging / "upstream_lang_sdk_sources" / "task-sdk"
+        assert task_sdk_link.is_symlink()
+        assert task_sdk_link.resolve() == (repo_root / "task-sdk").resolve()
+
+    @mock.patch.object(kubernetes_commands, "run_command")
+    def test_restores_gradle_wrapper_files_dropped_by_export_ignore(self, 
mock_run, tmp_path):
+        # export-ignore (ASF LEGAL-570) drops gradlew, gradlew.bat, and the 
wrapper jar from
+        # `git archive`; the build invokes ./gradlew from the extraction, so 
all must be restored.
+        mock_run.side_effect = [
+            mock.Mock(stdout="upstream\n"),
+            mock.Mock(),
+            mock.Mock(stdout="deadbeef\n"),
+            mock.Mock(),
+            mock.Mock(),
+            mock.Mock(stdout=b"fake-gradlew"),
+            mock.Mock(stdout=b"fake-gradlew-bat"),
+            mock.Mock(stdout=b"fake-jar-bytes"),
+        ]
+
+        _, java_sdk = _lang_sdk_fetch_upstream_sdk_sources(tmp_path, None)
+
+        gradlew = java_sdk / "gradlew"
+        assert gradlew.read_bytes() == b"fake-gradlew"
+        assert gradlew.stat().st_mode & 0o111, "gradlew must be executable"
+        assert (java_sdk / "gradlew.bat").read_bytes() == b"fake-gradlew-bat"
+        assert (java_sdk / "gradle" / "wrapper" / 
"gradle-wrapper.jar").read_bytes() == b"fake-jar-bytes"
+        show_cmds = [call.args[0] for call in mock_run.call_args_list[5:8]]
+        assert show_cmds == [
+            ["git", "show", "deadbeef:java-sdk/gradlew"],
+            ["git", "show", "deadbeef:java-sdk/gradlew.bat"],
+            ["git", "show", 
"deadbeef:java-sdk/gradle/wrapper/gradle-wrapper.jar"],
+        ]
+
+
+class TestLangSdkDryRun:
+    """Dry-run skips the commands, so the filesystem work depending on their 
outputs must not run."""
+
+    def test_fetch_upstream_sdk_sources_does_not_crash_on_str_stdout(self, 
dry_run, tmp_path, monkeypatch):
+        monkeypatch.setattr(kubernetes_commands, "AIRFLOW_ROOT_PATH", tmp_path 
/ "repo")
+
+        _, java_sdk = _lang_sdk_fetch_upstream_sdk_sources(tmp_path, None)
+
+        # Regression: dry-run run_command returns stdout="" (str) and 
write_bytes("") raised TypeError.
+        assert (java_sdk / "gradlew").read_bytes() == b""
+
+    def test_build_go_bundle_skips_copies_of_never_built_artifacts(self, 
dry_run, tmp_path, go_example):
+        _lang_sdk_build_go_bundle(tmp_path, tmp_path / 
"missing_upstream_go_sdk", None, native=True)
+
+        assert not (tmp_path / "go-artifacts" / 
kubernetes_commands.LANG_SDK_GO_BUNDLE_NAME).exists()
+
+    def test_build_java_jar_skips_jar_copy(self, dry_run, tmp_path, 
java_example, upstream_java_sdk):
+        (java_example / "build" / "bundle" / "app.jar").unlink()
+
+        _lang_sdk_build_java_jar(tmp_path, upstream_java_sdk, None, 
native=True)
+
+        assert not (tmp_path / "java-artifacts" / "app.jar").exists()
+
+    @mock.patch.object(kubernetes_commands, "run_command_with_k8s_env")
+    def test_upload_artifacts_uses_placeholder_for_never_built_jar(self, 
mock_run, dry_run, tmp_path):
+        mock_run.return_value = mock.Mock(stdout="")
+
+        _lang_sdk_upload_artifacts(tmp_path, "3.10", "v1.35.0", None)
+
+        cp_sources = [call.args[0][2] for call in mock_run.call_args_list if 
call.args[0][1] == "cp"]
+        assert str(tmp_path / "java-artifacts" / "app.jar") in cp_sources
 
 
 class TestSetupLangSdkTestNativeSelection:
@@ -107,28 +309,39 @@ class TestSetupLangSdkTestNativeSelection:
         ("env_value", "expected_native"),
         [("true", True), ("True", True), ("false", False), ("", False), (None, 
False)],
     )
-    def test_native_flag_is_read_from_env(self, monkeypatch, env_value, 
expected_native):
+    def test_native_flag_is_read_from_env(self, monkeypatch, tmp_path, 
env_value, expected_native):
         if env_value is None:
             monkeypatch.delenv("LANG_SDK_NATIVE_TOOLCHAIN", raising=False)
         else:
             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"
 
         def fake_parallel(steps, output):
             for _title, thunk in steps:
                 thunk(None)
 
         monkeypatch.setattr(kubernetes_commands, "_run_lang_sdk_parallel", 
fake_parallel)
+        monkeypatch.setattr(
+            kubernetes_commands,
+            "_lang_sdk_fetch_upstream_sdk_sources",
+            lambda staging, output: (fake_go_sdk, fake_java_sdk),
+        )
         monkeypatch.setattr(
             kubernetes_commands,
             "_lang_sdk_build_go_bundle",
-            lambda staging, output, *, native: captured.update(go=native),
+            lambda staging, upstream_go_sdk, output, *, native: 
captured.update(
+                go=native, go_sdk=upstream_go_sdk
+            ),
         )
         monkeypatch.setattr(
             kubernetes_commands,
             "_lang_sdk_build_java_jar",
-            lambda staging, output, *, native: captured.update(java=native),
+            lambda staging, upstream_java_sdk, output, *, native: 
captured.update(
+                java=native, java_sdk=upstream_java_sdk
+            ),
         )
         for name in (
             "_lang_sdk_deploy_localstack",
@@ -146,4 +359,9 @@ class TestSetupLangSdkTestNativeSelection:
 
         kubernetes_commands._setup_lang_sdk_test(python="3.10", 
kubernetes_version="v1.35.0")
 
-        assert captured == {"go": expected_native, "java": expected_native}
+        assert captured == {
+            "go": expected_native,
+            "java": expected_native,
+            "go_sdk": fake_go_sdk,
+            "java_sdk": fake_java_sdk,
+        }
diff --git a/kubernetes-tests/lang_sdk/README.md 
b/kubernetes-tests/lang_sdk/README.md
index 55e0d697935..f4840623c33 100644
--- a/kubernetes-tests/lang_sdk/README.md
+++ b/kubernetes-tests/lang_sdk/README.md
@@ -63,6 +63,17 @@ 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
+
+`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.
+
 ## Running it
 
 The artifacts, localstack, config, and Helm release are provisioned by a 
single breeze


Reply via email to