From de500db33aec43ca15c77779254b2fe9d071e7c1 Mon Sep 17 00:00:00 2001 From: mbakalarski <64490638+mbakalarski@users.noreply.github.com> Date: Mon, 2 Feb 2026 14:03:01 +0000 Subject: [PATCH 1/3] CliConfig XR --- README.md | 5 ++--- example/README.md | 2 +- example/composition.yaml | 4 ++-- example/xr1.yaml | 6 +++--- example/xr2.yaml | 6 +++--- function/fn.py | 36 +++++++++++++++++++++--------------- tests/test_fn.py | 18 ++++++++++++------ 7 files changed, 44 insertions(+), 33 deletions(-) diff --git a/README.md b/README.md index 08685e0..b0eb80a 100644 --- a/README.md +++ b/README.md @@ -31,7 +31,6 @@ crossplane xpkg build -f package --embed-runtime-image=runtime ## License -This project is licensed under the Apache License 2.0. +Copyright 2026-present Michal Bakalarski and Netclab Contributors -This function was originally created using the -Crossplane function-template-python project. +The project is published under [Apache 2.0 License](LICENSE) diff --git a/example/README.md b/example/README.md index 62f2848..178814e 100644 --- a/example/README.md +++ b/example/README.md @@ -10,6 +10,6 @@ hatch run development ```shell # Then, in another terminal, call it with these example manifests -crossplane render xr.yaml composition.yaml functions.yaml \ +crossplane render xr1.yaml composition.yaml functions.yaml \ --required-resources secret.yaml --extra-resources environment.yaml -r ``` diff --git a/example/composition.yaml b/example/composition.yaml index 0a5747e..35deaf3 100644 --- a/example/composition.yaml +++ b/example/composition.yaml @@ -4,8 +4,8 @@ metadata: name: eoscommands spec: compositeTypeRef: - apiVersion: eos.netclab.dev/v1alpha1 - kind: EosCommand + apiVersion: netclab.dev/v1alpha1 + kind: CliConfig mode: Pipeline pipeline: - step: environmentConfigs diff --git a/example/xr1.yaml b/example/xr1.yaml index 0fafa69..79bb8dc 100644 --- a/example/xr1.yaml +++ b/example/xr1.yaml @@ -1,7 +1,7 @@ -apiVersion: eos.netclab.dev/v1alpha1 -kind: EosCommand +apiVersion: netclab.dev/v1alpha1 +kind: CliConfig metadata: - name: eoscommand-2 + name: eoscommand-1 spec: endpoint: ceos01.default.svc.cluster.local removeContainer: false diff --git a/example/xr2.yaml b/example/xr2.yaml index 46e40f0..4b2d4c7 100644 --- a/example/xr2.yaml +++ b/example/xr2.yaml @@ -1,7 +1,7 @@ -apiVersion: eos.netclab.dev/v1alpha1 -kind: EosCommand +apiVersion: netclab.dev/v1alpha1 +kind: CliConfig metadata: - name: eoscommand-1 + name: eoscommand-2 spec: endpoint: ceos01.default.svc.cluster.local removeContainer: true diff --git a/function/fn.py b/function/fn.py index 6b151a1..f1366ba 100644 --- a/function/fn.py +++ b/function/fn.py @@ -43,12 +43,11 @@ async def RunFunction( rsp = response.to(req) - composite = resource.struct_to_dict(req.observed.composite.resource) - name_prefix = composite.get("metadata").get("name") - composite_spec: dict = composite.get("spec") - fqdn = composite_spec.get("endpoint") - cmds = composite_spec.get("cmds") - remove_container = composite_spec.get("removeContainer") + observed_xr = resource.struct_to_dict(req.observed.composite.resource) + observed_xr_name = observed_xr.get("metadata").get("name") + fqdn = observed_xr["spec"].get("endpoint") + cmds = observed_xr["spec"].get("cmds") + remove_container = observed_xr["spec"].get("removeContainer") environment = resource.struct_to_dict( req.context["apiextensions.crossplane.io/environment"] @@ -77,7 +76,7 @@ async def RunFunction( log.info("Generated command paths", count=len(command_paths)) for path in command_paths: - name = name_prefix + "-" + name_from_path(path) + name = name_based_on_path(observed_xr_name, path) path_log = log.bind(resource=name, path=" | ".join(path)) path_log.debug("Creating resource") @@ -108,7 +107,7 @@ async def RunFunction( "isRemovedCheck": removed_logic, } - resource_data = construct_resource_request(jsonrpc_ops, jsonrpc_cfg) + resource_data = construct_request_resource(name, jsonrpc_ops, jsonrpc_cfg) resource.update( rsp.desired.resources[name], @@ -118,6 +117,16 @@ async def RunFunction( return rsp +def name_based_on_path(observed: str, path: list[str]) -> str: + """name_based_on_path function.""" + joined = "|".join(path) + hashed_path = hashlib.sha256( + joined.encode("utf-8"), usedforsecurity=False + ).hexdigest() + full = f"{observed}-{hashed_path}" + return full[:253].rstrip("-") + + def toggle_no(cmd: str) -> str: """To clarify intent of build_remove_path().""" return cmd.removeprefix("no ") if cmd.startswith("no ") else f"no {cmd}" @@ -180,12 +189,6 @@ def walk_cmds(cmds: dict, path: list[str] | None = None) -> list[list[str]]: return results -def name_from_path(path: list[str]) -> str: - """name_from_path function.""" - joined = "|".join(path) - return hashlib.sha1(joined.encode(), usedforsecurity=False).hexdigest()[:10] - - def get_envs(environment: dict) -> tuple[int, str, bool]: """Extract jsonrpc configuration from the environment.""" if not environment: @@ -200,11 +203,14 @@ def get_envs(environment: dict) -> tuple[int, str, bool]: return port, scheme, insecure_skip_tls_verify -def construct_resource_request(ops: dict, config: dict) -> dict: +def construct_request_resource(name: str, ops: dict, config: dict) -> dict: """Construct the resource request for the given data.""" return { "apiVersion": "http.crossplane.io/v1alpha2", "kind": "Request", + "metadata": { + "name": name, + }, "spec": { "forProvider": { "insecureSkipTLSVerify": config["insecureSkipTLSVerify"], diff --git a/tests/test_fn.py b/tests/test_fn.py index 7a7918e..841e417 100644 --- a/tests/test_fn.py +++ b/tests/test_fn.py @@ -29,15 +29,15 @@ def setUp(self) -> None: logging.configure(level=logging.Level.DISABLED) async def test_run_function_generates_request(self) -> None: - """Generates a valid HTTP Request from an EosCommand.""" + """Generates a valid HTTP Request from an CliConfig.""" # ---------------------------- # Inputs # ---------------------------- composite = { - "apiVersion": "eos.netclab.dev/v1alpha1", - "kind": "EosCommand", + "apiVersion": "netclab.dev/v1alpha1", + "kind": "CliConfig", "metadata": {"name": "eoscommand-1"}, "spec": { "endpoint": "ceos01.default.svc.cluster.local", @@ -99,15 +99,21 @@ async def test_run_function_generates_request(self) -> None: # Resource existence # ---------------------------- + request_name = "eoscommand-1-5cc898bff667884f5d8706ea7c4543e267a567c90662a48ac7efb4964dca9015" + + self.assertGreater( + len(resp.desired.resources), 0, "No desired resources generated" + ) + self.assertIn( - "eoscommand-1-b1ed383535", + request_name, resp.desired.resources, "Expected Request resource not found", ) - resource_msg = resp.desired.resources["eoscommand-1-b1ed383535"].resource + request_resource = resp.desired.resources[request_name].resource - result = MessageToDict(resource_msg) + result = MessageToDict(request_resource) # ---------------------------- # Top-level assertions From 0b75cf0637e1e8cc29c39a736c079f323793dbf0 Mon Sep 17 00:00:00 2001 From: mbakalarski <64490638+mbakalarski@users.noreply.github.com> Date: Mon, 2 Feb 2026 14:10:12 +0000 Subject: [PATCH 2/3] fix: fmt in tests/test_fn.py --- tests/test_fn.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_fn.py b/tests/test_fn.py index 841e417..c2016ba 100644 --- a/tests/test_fn.py +++ b/tests/test_fn.py @@ -99,7 +99,7 @@ async def test_run_function_generates_request(self) -> None: # Resource existence # ---------------------------- - request_name = "eoscommand-1-5cc898bff667884f5d8706ea7c4543e267a567c90662a48ac7efb4964dca9015" + request_name = "eoscommand-1-5cc898bff667884f5d8706ea7c4543e267a567c90662a48ac7efb4964dca9015" # noqa E501 self.assertGreater( len(resp.desired.resources), 0, "No desired resources generated" From 896e53e2b0568f4117a091f00bab60700bcffc33 Mon Sep 17 00:00:00 2001 From: mbakalarski <64490638+mbakalarski@users.noreply.github.com> Date: Mon, 2 Feb 2026 14:11:53 +0000 Subject: [PATCH 3/3] gh templates removed --- .github/ISSUE_TEMPLATE/bug_report.md | 40 ----------------------- .github/ISSUE_TEMPLATE/feature_request.md | 24 -------------- 2 files changed, 64 deletions(-) delete mode 100644 .github/ISSUE_TEMPLATE/bug_report.md delete mode 100644 .github/ISSUE_TEMPLATE/feature_request.md diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md deleted file mode 100644 index d834757..0000000 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ /dev/null @@ -1,40 +0,0 @@ ---- -name: Bug Report -about: Help us diagnose and fix bugs in this Function -labels: bug ---- - - -### What happened? - - - -### How can we reproduce it? - - -### What environment did it happen in? -Function version: - - diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md deleted file mode 100644 index a9ddcf0..0000000 --- a/.github/ISSUE_TEMPLATE/feature_request.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -name: Feature Request -about: Help us make this Function more useful -labels: enhancement ---- - - -### What problem are you facing? - - -### How could this Function help solve your problem? -