This is an automated email from the ASF dual-hosted git repository.

vatsrahul1001 pushed a commit to branch v3-4-test
in repository https://gitbox.apache.org/repos/asf/airflow.git


The following commit(s) were added to refs/heads/v3-4-test by this push:
     new 295ec25fcf2 [v3-4-test] Cover YAML-generated Go Dags in the go_sdk e2e 
test (#74636) (#74824)
295ec25fcf2 is described below

commit 295ec25fcf2653a303846673becf1d41343c6fad
Author: github-actions[bot] 
<41898282+github-actions[bot]@users.noreply.github.com>
AuthorDate: Sun Oct 18 17:15:33 2026 +0530

    [v3-4-test] Cover YAML-generated Go Dags in the go_sdk e2e test (#74636) 
(#74824)
    
    * Go SDK: add an example that generates Dags from a YAML file
    
    * Cover YAML-generated Go Dags in the go_sdk e2e test
    
    ---------
    (cherry picked from commit a3c8ee41d04e1191f2a9c7394ac865d21415f039)
    
    Co-authored-by: Jason(Zhe-You) Liu 
<[email protected]>
---
 airflow-e2e-tests/docker/go.yml                    |   5 +
 .../tests/airflow_e2e_tests/conftest.py            | 159 ++++++++++++++-------
 .../tests/airflow_e2e_tests/constants.py           |   4 +
 .../airflow_e2e_tests/e2e_test_utils/clients.py    |  32 +++++
 .../go_sdk_tests/test_go_sdk_dynamic_dag.py        | 110 ++++++++++++++
 go-sdk/example/dynamic/dags.go                     |  67 +++++++++
 go-sdk/example/dynamic/dags.yaml                   |  24 ++++
 go-sdk/example/dynamic/main.go                     |  60 ++++++++
 8 files changed, 407 insertions(+), 54 deletions(-)

diff --git a/airflow-e2e-tests/docker/go.yml b/airflow-e2e-tests/docker/go.yml
index 38fde7634d2..55e4bb39f12 100644
--- a/airflow-e2e-tests/docker/go.yml
+++ b/airflow-e2e-tests/docker/go.yml
@@ -30,6 +30,11 @@ services:
     environment:
       USER: airflow
       HOME: /home/airflow
+      # The dynamic Dag test swaps a YAML file and waits for the Dag IDs to 
change. Re-parse the
+      # bundle every few seconds, and mark a Dag stale soon after it leaves 
the YAML.
+      AIRFLOW__DAG_PROCESSOR__MIN_FILE_PROCESS_INTERVAL: '5'
+      AIRFLOW__DAG_PROCESSOR__STALE_DAG_THRESHOLD: '10'
+      AIRFLOW__SCHEDULER__PARSING_CLEANUP_INTERVAL: '5'
 
   airflow-worker:
     # The bundle is built with CGO_ENABLED=0, so the SDK's user.Current() call 
at
diff --git a/airflow-e2e-tests/tests/airflow_e2e_tests/conftest.py 
b/airflow-e2e-tests/tests/airflow_e2e_tests/conftest.py
index 38ea7bd8141..e9d65166c6f 100644
--- a/airflow-e2e-tests/tests/airflow_e2e_tests/conftest.py
+++ b/airflow-e2e-tests/tests/airflow_e2e_tests/conftest.py
@@ -43,6 +43,9 @@ from airflow_e2e_tests.constants import (
     GO_SDK_BIN_PATH,
     GO_SDK_BUNDLE_NAME,
     GO_SDK_DAGS_PATH,
+    GO_SDK_DYNAMIC_BUNDLE_NAME,
+    GO_SDK_DYNAMIC_BUNDLE_PKG,
+    GO_SDK_DYNAMIC_YAML_PATH,
     GO_SDK_EXAMPLE_BUNDLE_PKG,
     GO_SDK_NATIVE_BUNDLE_NAME,
     GO_SDK_NATIVE_BUNDLE_PKG,
@@ -567,13 +570,12 @@ def _setup_java_sdk_integration(dot_env_file, tmp_dir):
     _write_state_store_retention_config(tmp_dir)
 
 
-def _run_go_sdk_pack(package, output_path, *, capture_output=False, 
native=False):
-    """Run ``go tool airflow-go-pack`` natively or inside the pinned Go 
toolchain container.
+def _run_go(args, *, capture_output=False, native=False):
+    """Run ``go <args>`` in the go-sdk module, on the host or inside the 
pinned Go toolchain container.
 
-    ``go tool airflow-go-pack`` builds the bundle *package*, reads its
-    --airflow-metadata, and appends the source + airflow-metadata.yaml + the
-    AFBNDL01 trailer, writing a single self-contained executable bundle.
-    CGO_ENABLED=0 yields a fully static binary that runs on the stock worker.
+    A ``Path`` in *args* is a host path; in the container it is mapped to 
where the repo is mounted.
+    Other strings, such as ``./example/dynamic``, are passed as they are and 
resolve against go-sdk.
+    CGO_ENABLED=0 yields fully static binaries that run on the stock worker.
 
     In ``native`` mode (used in CI, where the host already has a Go toolchain 
plus
     restored module/build caches via ``actions/setup-go``) it invokes the host 
``go``
@@ -592,64 +594,102 @@ def _run_go_sdk_pack(package, output_path, *, 
capture_output=False, native=False
       go.yml so the packed binary runs the same way at execution time).
     """
     if native:
-        cwd = GO_SDK_ROOT_PATH
-        env = {**os.environ, "CGO_ENABLED": "0"}
-        argv = [
-            "go",
-            "tool",
-            "airflow-go-pack",
-            "--output",
-            str(output_path),
-            package,
-        ]
-    else:
-        cwd = None
-        env = None
-        # Mount the repo so the whole go-sdk module (go.mod, tool directive,
-        # example sources) is visible to `go tool`.
-        container_go_sdk_dir = 
f"/repo/{GO_SDK_ROOT_PATH.relative_to(AIRFLOW_ROOT_PATH)}"
-        container_bin_dir = 
f"/repo/{GO_SDK_BIN_PATH.relative_to(AIRFLOW_ROOT_PATH)}"
-        argv = [
-            "docker",
-            "run",
-            "--rm",
-            "--user",
-            f"{os.getuid()}:{os.getgid()}",
-            "-e",
-            f"HOME={container_bin_dir}/.home",
-            "-e",
-            "USER=airflow",
-            "-e",
-            "CGO_ENABLED=0",
-            "-v",
-            f"{AIRFLOW_ROOT_PATH}:/repo",
-            "-w",
-            container_go_sdk_dir,
-            GO_BUILDER_IMAGE,
-            "go",
-            "tool",
-            "airflow-go-pack",
-            "--output",
-            f"{container_bin_dir}/{output_path.name}",
-            package,
-        ]
-    return subprocess.run(argv, cwd=cwd, env=env, check=True, 
capture_output=capture_output, text=True)
+        argv = ["go", *(str(arg) for arg in args)]
+        return subprocess.run(
+            argv,
+            cwd=GO_SDK_ROOT_PATH,
+            env={**os.environ, "CGO_ENABLED": "0"},
+            check=True,
+            capture_output=capture_output,
+            text=True,
+        )
 
+    # Mount the repo so the whole go-sdk module (go.mod, tool directive,
+    # example sources) is visible to `go`.
+    container_bin_dir = _get_container_repo_path(GO_SDK_BIN_PATH)
+    argv = [
+        "docker",
+        "run",
+        "--rm",
+        "--user",
+        f"{os.getuid()}:{os.getgid()}",
+        "-e",
+        f"HOME={container_bin_dir}/.home",
+        "-e",
+        "USER=airflow",
+        "-e",
+        "CGO_ENABLED=0",
+        "-v",
+        f"{AIRFLOW_ROOT_PATH}:/repo",
+        "-w",
+        _get_container_repo_path(GO_SDK_ROOT_PATH),
+        GO_BUILDER_IMAGE,
+        "go",
+        *(_get_container_repo_path(arg) if isinstance(arg, Path) else str(arg) 
for arg in args),
+    ]
+    return subprocess.run(argv, check=True, capture_output=capture_output, 
text=True)
 
-def _pack_go_sdk_bundle(package, name, *, native=False):
-    """Build a Go SDK bundle, capturing output so a failure prints the build 
log."""
-    output_path = GO_SDK_BIN_PATH / name
+
+def _get_container_repo_path(host_path):
+    """Map a host path under the repo to where ``_run_go`` mounts the repo in 
the container."""
+    return f"/repo/{host_path.relative_to(AIRFLOW_ROOT_PATH)}"
+
+
+def _run_go_logged(description, args, *, native=False):
+    """Run ``go <args>`` with ``_run_go``, printing its output, or the output 
of a failure."""
     mode_label = "host toolchain" if native else GO_BUILDER_IMAGE
-    console.print(f"[yellow]Building Go SDK bundle {package} 
({mode_label})...")
+    console.print(f"[yellow]{description} ({mode_label})...")
     try:
-        completed = _run_go_sdk_pack(package, output_path, 
capture_output=True, native=native)
+        completed = _run_go(args, capture_output=True, native=native)
     except subprocess.CalledProcessError as e:
-        console.print(f"[red]Go SDK bundle {package} build failed:")
+        console.print(f"[red]{description} failed:")
         console.print(e.stdout, e.stderr, sep="\n", markup=False, 
soft_wrap=True)
         raise
     console.print(completed.stdout, completed.stderr, sep="\n", markup=False, 
soft_wrap=True)
 
 
+def _pack_go_sdk_bundle(package, name, *, native=False):
+    """Build a Go SDK *package* into a self-contained bundle with ``go tool 
airflow-go-pack``."""
+    _run_go_logged(
+        f"Building Go SDK bundle {package}",
+        ["tool", "airflow-go-pack", "--output", GO_SDK_BIN_PATH / name, 
package],
+        native=native,
+    )
+
+
+def _pack_go_sdk_dynamic_bundle(*, native=False):
+    """Build and pack the dynamic example bundle, which generates its Dags 
from a dags.yaml.
+
+    The packer reads the Dag IDs by running the binary, and the binary reads 
dags.yaml next to
+    itself. So build the binary, put dags.yaml beside it, then pack that 
binary in place with
+    ``--executable``. The packer needs ``--source`` there, as it cannot find 
the entrypoint of a
+    pre-built binary.
+    """
+    bin_dir = GO_SDK_BIN_PATH / "dynamic"
+    bin_dir.mkdir(parents=True, exist_ok=True)
+    raw_binary = bin_dir / f"{GO_SDK_DYNAMIC_BUNDLE_NAME}.raw"
+    _run_go_logged(
+        f"Building Go SDK binary {GO_SDK_DYNAMIC_BUNDLE_PKG}",
+        ["build", "-o", raw_binary, GO_SDK_DYNAMIC_BUNDLE_PKG],
+        native=native,
+    )
+    copyfile(GO_SDK_DYNAMIC_YAML_PATH, bin_dir / "dags.yaml")
+    _run_go_logged(
+        f"Packing Go SDK bundle {GO_SDK_DYNAMIC_BUNDLE_PKG}",
+        [
+            "tool",
+            "airflow-go-pack",
+            "--executable",
+            raw_binary,
+            "--source",
+            f"{GO_SDK_DYNAMIC_BUNDLE_PKG}/main.go",
+            "--output",
+            GO_SDK_BIN_PATH / GO_SDK_DYNAMIC_BUNDLE_NAME,
+        ],
+        native=native,
+    )
+
+
 def _setup_go_sdk_integration(dot_env_file, tmp_dir):
     """Set up the go_sdk E2E test mode.
 
@@ -662,12 +702,16 @@ def _setup_go_sdk_integration(dot_env_file, tmp_dir):
     processor runs it to parse its Dags. Its tasks run on the ``golang`` queue
     through the same coordinator.
 
+    The dynamic example goes into the Dags folder as well, with the 
``dags.yaml`` it
+    generates its Dags from next to it, so a test can swap the YAML to change 
the Dags.
+
     The packed bundle is a statically linked native executable (built with
     ``CGO_ENABLED=0``), so the stock Airflow worker image can exec it directly
     without a Go toolchain or any extra runtime installed -- see ``go.yml``.
     """
     _pack_go_sdk_bundle(GO_SDK_EXAMPLE_BUNDLE_PKG, GO_SDK_BUNDLE_NAME, 
native=LANG_SDK_NATIVE_TOOLCHAIN)
     _pack_go_sdk_bundle(GO_SDK_NATIVE_BUNDLE_PKG, GO_SDK_NATIVE_BUNDLE_NAME, 
native=LANG_SDK_NATIVE_TOOLCHAIN)
+    _pack_go_sdk_dynamic_bundle(native=LANG_SDK_NATIVE_TOOLCHAIN)
 
     # Copy the compose override into the temp directory.
     copyfile(GO_COMPOSE_PATH, tmp_dir / "go.yml")
@@ -691,6 +735,13 @@ def _setup_go_sdk_integration(dot_env_file, tmp_dir):
     copyfile(GO_SDK_BIN_PATH / GO_SDK_NATIVE_BUNDLE_NAME, packed_native_bundle)
     os.chmod(packed_native_bundle, 0o755)
 
+    dynamic_dags_dir = tmp_dir / "dags" / "go-dynamic"
+    dynamic_dags_dir.mkdir()
+    packed_dynamic_bundle = dynamic_dags_dir / GO_SDK_DYNAMIC_BUNDLE_NAME
+    copyfile(GO_SDK_BIN_PATH / GO_SDK_DYNAMIC_BUNDLE_NAME, 
packed_dynamic_bundle)
+    os.chmod(packed_dynamic_bundle, 0o755)
+    copyfile(GO_SDK_DYNAMIC_YAML_PATH, dynamic_dags_dir / "dags.yaml")
+
     # Copy the Go SDK example stub Dag so Airflow can discover and serialize 
it.
     copyfile(GO_SDK_DAGS_PATH / "go_examples.py", tmp_dir / "dags" / 
"go_examples.py")
 
diff --git a/airflow-e2e-tests/tests/airflow_e2e_tests/constants.py 
b/airflow-e2e-tests/tests/airflow_e2e_tests/constants.py
index 7fd1323cc3f..40de0c37ffc 100644
--- a/airflow-e2e-tests/tests/airflow_e2e_tests/constants.py
+++ b/airflow-e2e-tests/tests/airflow_e2e_tests/constants.py
@@ -103,6 +103,10 @@ GO_SDK_BUNDLE_NAME = "example_dags"
 # The native-Dag example: Dags declared in Go and parsed from the binary by 
the Dag processor.
 GO_SDK_NATIVE_BUNDLE_PKG = "./example/native"
 GO_SDK_NATIVE_BUNDLE_NAME = "go_native_dags"
+# The dynamic example: Dags generated from a dags.yaml shipped next to the 
packed binary.
+GO_SDK_DYNAMIC_BUNDLE_PKG = "./example/dynamic"
+GO_SDK_DYNAMIC_BUNDLE_NAME = "go_dynamic_dags"
+GO_SDK_DYNAMIC_YAML_PATH = GO_SDK_ROOT_PATH / "example" / "dynamic" / 
"dags.yaml"
 # Where airflow-go-pack writes the packed bundle inside the repo (go-sdk/bin 
is gitignored).
 GO_SDK_BIN_PATH = GO_SDK_ROOT_PATH / "bin"
 GO_COMPOSE_PATH = AIRFLOW_ROOT_PATH / "airflow-e2e-tests" / "docker" / "go.yml"
diff --git 
a/airflow-e2e-tests/tests/airflow_e2e_tests/e2e_test_utils/clients.py 
b/airflow-e2e-tests/tests/airflow_e2e_tests/e2e_test_utils/clients.py
index 1f1d1ad435a..821765ad212 100644
--- a/airflow-e2e-tests/tests/airflow_e2e_tests/e2e_test_utils/clients.py
+++ b/airflow-e2e-tests/tests/airflow_e2e_tests/e2e_test_utils/clients.py
@@ -125,6 +125,38 @@ class AirflowClient:
             time.sleep(check_interval)
         raise TimeoutError(f"Dag {dag_id} was not registered within 
{timeout}s. Last error: {last_error}")
 
+    def list_dag_ids(self, tags: list[str]) -> set[str]:
+        """List the IDs of the live Dags with any of *tags*; the API leaves 
out stale Dags by default."""
+        dag_ids: set[str] = set()
+        limit = 100
+        offset = 0
+        while True:
+            resp = self._make_request(
+                method="GET", endpoint="dags", params={"tags": tags, "limit": 
limit, "offset": offset}
+            )
+            dag_ids |= {dag["dag_id"] for dag in resp["dags"]}
+            offset += len(resp["dags"])
+            if not resp["dags"] or offset >= resp["total_entries"]:
+                return dag_ids
+
+    def wait_for_dag_ids(
+        self, expected: set[str], tags: list[str], timeout: int = 120, 
check_interval: int = 3
+    ):
+        """Poll until the live Dags with *tags* are exactly *expected*.
+
+        Raises ``TimeoutError`` naming the expected and the last seen Dag IDs 
after *timeout* seconds.
+        """
+        start = time.monotonic()
+        seen: set[str] = set()
+        while time.monotonic() - start < timeout:
+            seen = self.list_dag_ids(tags)
+            if seen == expected:
+                return
+            time.sleep(check_interval)
+        raise TimeoutError(
+            f"Dag IDs did not become {sorted(expected)} within {timeout}s. 
Last seen: {sorted(seen)}"
+        )
+
     def un_pause_dag(self, dag_id: str):
         self.wait_for_dag(dag_id)
         return self._make_request(
diff --git 
a/airflow-e2e-tests/tests/airflow_e2e_tests/go_sdk_tests/test_go_sdk_dynamic_dag.py
 
b/airflow-e2e-tests/tests/airflow_e2e_tests/go_sdk_tests/test_go_sdk_dynamic_dag.py
new file mode 100644
index 00000000000..ca9c3e07269
--- /dev/null
+++ 
b/airflow-e2e-tests/tests/airflow_e2e_tests/go_sdk_tests/test_go_sdk_dynamic_dag.py
@@ -0,0 +1,110 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under the License.
+"""E2E test for the Go SDK Dags that ``go-sdk/example/dynamic`` generates from 
a YAML file.
+
+Run with::
+
+    E2E_TEST_MODE=go_sdk uv run --project airflow-e2e-tests pytest \\
+        tests/airflow_e2e_tests/go_sdk_tests/test_go_sdk_dynamic_dag.py -xvs
+
+``conftest._setup_go_sdk_integration`` puts the packed binary and its 
``dags.yaml`` into the Dags
+folder. The binary generates one Dag for each entry of the YAML, so the test 
swaps only the YAML,
+with no rebuild, and checks that the live Dag IDs follow it and that each live 
Dag runs.
+"""
+
+from __future__ import annotations
+
+import os
+from datetime import UTC, datetime
+from pathlib import Path
+
+import yaml
+
+from airflow_e2e_tests.e2e_test_utils.clients import AirflowClient
+
+# The Dag processor execs the binary to parse it, so the Dags appear some time 
after the stack is up.
+_PARSE_TIMEOUT = 600
+# After that, the Dag processor re-parses every few seconds (see go.yml), and 
a Dag that left the
+# YAML goes stale a few seconds after that.
+_RELOAD_TIMEOUT = 180
+_GO_TASK_TIMEOUT = 600
+
+_TAGS = ["go_dynamic"]
+
+
+def _write_yaml_atomically(path: Path, greetings: dict[str, str]) -> None:
+    """Replace the YAML in one step, so the Dag processor never reads half of 
it."""
+    entries = [{"dag_id": dag_id, "greeting": greeting} for dag_id, greeting 
in greetings.items()]
+    tmp_path = path.with_name(f"{path.name}.tmp")
+    tmp_path.write_text(yaml.safe_dump({"dags": entries}))
+    os.replace(tmp_path, path)
+
+
+def _swap_yaml_and_check(
+    client: AirflowClient, yaml_path: Path, greetings: dict[str, str], 
previous: set[str], timeout: int
+) -> set[str]:
+    """Swap in a YAML of *greetings*, then check the Dags, a run of each, and 
the Dags it dropped."""
+    _write_yaml_atomically(yaml_path, greetings)
+    client.wait_for_dag_ids(set(greetings), tags=_TAGS, timeout=timeout)
+
+    run_ids = {
+        dag_id: client.trigger_dag(dag_id, json={"logical_date": 
datetime.now(UTC).isoformat()})["dag_run_id"]
+        for dag_id in greetings
+    }
+    for dag_id, run_id in run_ids.items():
+        state = client.wait_for_dag_run(dag_id=dag_id, run_id=run_id, 
timeout=_GO_TASK_TIMEOUT)
+        assert state == "success", f"{dag_id} run {run_id} ended {state!r}"
+        xcom = client.get_xcom_value(dag_id=dag_id, task_id="greet", 
run_id=run_id, key="return_value")
+        assert xcom["value"] == greetings[dag_id], f"{dag_id} returned 
{xcom['value']!r}"
+
+    for dag_id in previous - set(greetings):
+        assert client.get_dag(dag_id)["is_stale"], f"{dag_id} left the YAML 
but is not stale"
+    return set(greetings)
+
+
+def test_swapping_the_yaml_changes_the_live_dags(airflow_dags_path: Path):
+    client = AirflowClient()
+    yaml_path = airflow_dags_path / "go-dynamic" / "dags.yaml"
+
+    live = _swap_yaml_and_check(
+        client,
+        yaml_path,
+        {"go_dynamic_a": "hello from a", "go_dynamic_b": "hello from b", 
"go_dynamic_c": "hello from c"},
+        previous=set(),
+        timeout=_PARSE_TIMEOUT,
+    )
+    live = _swap_yaml_and_check(
+        client,
+        yaml_path,
+        {
+            "go_dynamic_a": "hello from a",
+            "go_dynamic_b": "hello from b",
+            "go_dynamic_c": "hello from c",
+            "go_dynamic_d": "hello from d",
+            "go_dynamic_e": "hello from e",
+        },
+        previous=live,
+        timeout=_RELOAD_TIMEOUT,
+    )
+    # a keeps its Dag ID but gets a new greeting, so its run shows the Dag was 
rebuilt from the new YAML.
+    _swap_yaml_and_check(
+        client,
+        yaml_path,
+        {"go_dynamic_a": "hello again from a", "go_dynamic_f": "hello from f"},
+        previous=live,
+        timeout=_RELOAD_TIMEOUT,
+    )
diff --git a/go-sdk/example/dynamic/dags.go b/go-sdk/example/dynamic/dags.go
new file mode 100644
index 00000000000..c83fe06e722
--- /dev/null
+++ b/go-sdk/example/dynamic/dags.go
@@ -0,0 +1,67 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+package main
+
+import (
+       "fmt"
+       "os"
+
+       "gopkg.in/yaml.v3"
+
+       "github.com/apache/airflow/go-sdk/airflow"
+)
+
+type dagsFile struct {
+       Dags []dagEntry `yaml:"dags"`
+}
+
+type dagEntry struct {
+       DagID    string `yaml:"dag_id"`
+       Greeting string `yaml:"greeting"`
+}
+
+// loadDags builds one Dag for each entry of the YAML file at path.
+func loadDags(path string) ([]airflow.Registerable, error) {
+       data, err := os.ReadFile(path)
+       if err != nil {
+               return nil, err
+       }
+       var file dagsFile
+       if err := yaml.Unmarshal(data, &file); err != nil {
+               return nil, fmt.Errorf("parsing %s: %w", path, err)
+       }
+
+       dags := make([]airflow.Registerable, 0, len(file.Dags))
+       for _, entry := range file.Dags {
+               dag := airflow.Dag(entry.DagID, airflow.DagSpec{
+                       Queue: "golang",
+                       Tags:  []string{"go", "go_dynamic"},
+               })
+               dag.Task(
+                       greet,
+                       airflow.TaskSpec{TaskID: "greet"},
+                       airflow.Inputs(airflow.Literal(entry.Greeting)),
+               )
+               dags = append(dags, dag)
+       }
+       return dags, nil
+}
+
+func greet(_ airflow.Context, greeting string) (string, error) {
+       return greeting, nil
+}
diff --git a/go-sdk/example/dynamic/dags.yaml b/go-sdk/example/dynamic/dags.yaml
new file mode 100644
index 00000000000..5c59cb8d49a
--- /dev/null
+++ b/go-sdk/example/dynamic/dags.yaml
@@ -0,0 +1,24 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under the License.
+---
+dags:
+  - dag_id: go_dynamic_a
+    greeting: hello from a
+  - dag_id: go_dynamic_b
+    greeting: hello from b
+  - dag_id: go_dynamic_c
+    greeting: hello from c
diff --git a/go-sdk/example/dynamic/main.go b/go-sdk/example/dynamic/main.go
new file mode 100644
index 00000000000..8287de26c0e
--- /dev/null
+++ b/go-sdk/example/dynamic/main.go
@@ -0,0 +1,60 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+// Package main is an example of a Dag definition that generates its Dags from 
a dags.yaml file
+// next to the packed binary.
+//
+// The packer reads the Dag IDs by running the binary, so dags.yaml must sit 
next to the binary
+// when it packs. Build the binary, put dags.yaml next to it, and pack it in 
place with
+// --executable, which also needs --source:
+//
+//     go build -o ./bin/dynamic/go_dynamic_dags.raw ./example/dynamic
+//     cp ./example/dynamic/dags.yaml ./bin/dynamic/dags.yaml
+//     go tool airflow-go-pack --executable ./bin/dynamic/go_dynamic_dags.raw \
+//       --source ./example/dynamic/main.go --output ./bin/go_dynamic_dags
+//
+// Put the packed bundle and dags.yaml together in a Dag bundle. Swapping only 
dags.yaml there
+// changes the Dags with no rebuild. Without a dags.yaml next to it, the 
bundle registers no Dags.
+package main
+
+import (
+       "errors"
+       "io/fs"
+       "log"
+       "os"
+       "path/filepath"
+
+       "github.com/apache/airflow/go-sdk/airflow"
+)
+
+func main() {
+       exe, err := os.Executable()
+       if err != nil {
+               log.Fatal(err)
+       }
+       dags, err := loadDags(filepath.Join(filepath.Dir(exe), "dags.yaml"))
+       if err != nil && !errors.Is(err, fs.ErrNotExist) {
+               log.Fatal(err)
+       }
+
+       bundle := airflow.Bundle()
+       bundle.Register(dags...)
+
+       if err := bundle.Serve(); err != nil {
+               log.Fatal(err)
+       }
+}

Reply via email to