Skip to content

Commit e57eab3

Browse files
committed
tests, license, ...
1 parent 5a4fe35 commit e57eab3

7 files changed

Lines changed: 187 additions & 106 deletions

File tree

LICENSE

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -186,7 +186,7 @@
186186
same "printed page" as the copyright notice for easier
187187
identification within third-party archives.
188188

189-
Copyright [yyyy] [name of copyright owner]
189+
Copyright 2025-present Michal Bakalarski and Netclab Contributors
190190

191191
Licensed under the Apache License, Version 2.0 (the "License");
192192
you may not use this file except in compliance with the License.

NOTICE

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
function-eapi
2+
3+
Copyright 2025–present Michal Bakalarski and Netclab Contributors
4+
5+
This product includes software derived from
6+
Crossplane function-template-python
7+
https://github.com/crossplane/function-template-python
8+
9+
Licensed under the Apache License, Version 2.0.

README.md

Lines changed: 20 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,46 +1,37 @@
1-
# function-template-python
1+
# eAPI Function
22

3-
[![CI](https://github.com/crossplane/function-template-python/actions/workflows/ci.yml/badge.svg)](https://github.com/crossplane/function-template-go/actions/workflows/ci.yml)
3+
This Crossplane composition function generates resources for interacting with Arista EOS devices via JSON-RPC.<br>
4+
It is intended for use in Crossplane Configuration packages to manage device state automatically.
45

5-
A template for writing a [composition function][functions] in [Python][python].
6-
7-
To learn how to use this template:
8-
9-
* [Follow the guide to writing a composition function in Python][function guide]
10-
* [Learn about how composition functions work][functions]
11-
* [Read the function-sdk-python package documentation][package docs]
12-
13-
If you just want to jump in and get started:
14-
15-
1. Replace `function-template-python` with your function's name in
16-
`package/crossplane.yaml`.
17-
1. Add your logic to `RunFunction` in `function/fn.py`
18-
1. Add tests for your logic in `test/test_fn.py`
19-
1. Update this file, `README.md`, to be about your function!
20-
21-
This template uses [Python][python], [Docker][docker], and the [Crossplane
22-
CLI][cli] to build functions.
236

247
```shell
25-
# Run the code in development mode, for crossplane beta render
8+
# Run the code in development mode, for crossplane render
269
hatch run development
10+
```
2711

12+
```shell
2813
# Lint and format the code - see pyproject.toml
2914
hatch fmt
15+
```
3016

17+
```shell
3118
# Run unit tests - see tests/test_fn.py
3219
hatch test
20+
```
3321

22+
```shell
3423
# Build the function's runtime image - see Dockerfile
35-
$ docker build . --tag=runtime
24+
docker build . --tag=runtime
25+
```
3626

27+
```shell
3728
# Build a function package - see package/crossplane.yaml
38-
$ crossplane xpkg build -f package --embed-runtime-image=runtime
29+
crossplane xpkg build -f package --embed-runtime-image=runtime
3930
```
4031

41-
[functions]: https://docs.crossplane.io/latest/concepts/composition-functions
42-
[function guide]: https://docs.crossplane.io/knowledge-base/guides/write-a-composition-function-in-python
43-
[package docs]: https://crossplane.github.io/function-sdk-python
44-
[python]: https://python.org
45-
[docker]: https://www.docker.com
46-
[cli]: https://docs.crossplane.io/latest/cli
32+
## License
33+
34+
This project is licensed under the Apache License 2.0.
35+
36+
This function was originally created using the
37+
Crossplane function-template-python project.

example/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# Example manifests
22

3-
You can run your function locally and test it using `crossplane beta render`
3+
You can run your function locally and test it using `crossplane render`
44
with these example manifests.
55

66
```shell

function/fn.py

Lines changed: 24 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,16 @@
11
"""A Crossplane composition function."""
22

33
import hashlib
4+
import json
45

56
import grpc
67
from crossplane.function import logging, request, resource, response
78
from crossplane.function.proto.v1 import run_function_pb2 as fnv1
89
from crossplane.function.proto.v1 import run_function_pb2_grpc as grpcv1
910
from jsonrpcclient import request_json
1011

12+
JSONRPC_BASE = {"version": 1, "format": "json"}
13+
1114

1215
class FunctionRunner(grpcv1.FunctionRunnerService):
1316
"""A FunctionRunner handles gRPC RunFunctionRequests."""
@@ -47,29 +50,29 @@ async def RunFunction(
4750
}
4851

4952
jsonrpc_observe_params = {
50-
"version": 1,
51-
"format": "json",
53+
**JSONRPC_BASE,
5254
"cmds": ["enable", "show running-config"],
5355
}
5456
jsonrpc_observe = request_json("runCmds", params=jsonrpc_observe_params)
5557

56-
command_paths = walk_cmds(cmds)
58+
command_paths = sorted(walk_cmds(cmds))
5759
log.info("Generated command paths", count=len(command_paths))
5860

5961
for path in command_paths:
6062
name = name_prefix + "-" + name_from_path(path)
6163

64+
path_log = log.bind(resource=name, path=" | ".join(path))
65+
path_log.debug("Creating resource")
66+
6267
jsonrpc_create_params = {
63-
"version": 1,
64-
"format": "json",
68+
**JSONRPC_BASE,
6569
"cmds": ["enable", "configure", *path],
6670
}
6771
jsonrpc_create = request_json("runCmds", params=jsonrpc_create_params)
6872

6973
remove_path = [*path[:-1], f"no {path[-1]}"]
7074
jsonrpc_remove_params = {
71-
"version": 1,
72-
"format": "json",
75+
**JSONRPC_BASE,
7376
"cmds": ["enable", "configure", *remove_path],
7477
}
7578
jsonrpc_remove = request_json("runCmds", params=jsonrpc_remove_params)
@@ -94,19 +97,25 @@ async def RunFunction(
9497
return rsp
9598

9699

100+
def jq_path(cmds: list[str]) -> str:
101+
"""Create middle part of jq logic expression."""
102+
return "".join([f".cmds[{json.dumps(c)}]" for c in cmds])
103+
104+
97105
def create_jq_logic_expressions(path: list[str]) -> tuple[str, str]:
98-
"""Create jq logic exp for Request."""
99-
logic = ".response.body.error == null and ("
100-
for cmd in path[:-1]:
101-
logic = logic + f'.response.body.result[1].cmds."{cmd}".cmds'
102-
logic1 = logic + f' | has("{path[-1]}")' + ")"
103-
logic2 = logic + f' | has("{path[-1]}")' + " | not)"
106+
"""Compose jq logic expressions."""
107+
base = ".response.body"
108+
tree = f"{base}.result[1]{jq_path(path[:-1])}.cmds"
109+
check = f"has({json.dumps(path[-1])})"
110+
111+
expected = f"{base}.error == null and ({tree} | {check})"
112+
removed = f"{base}.error == null and ({tree} | {check} | not)"
104113

105-
return logic1, logic2
114+
return expected, removed
106115

107116

108117
def walk_cmds(cmds: dict, path: list[str] | None = None) -> list[list[str]]:
109-
"""walk_cmds function."""
118+
"""Make command paths from nested command tree."""
110119
if path is None:
111120
path = []
112121

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ readme = "README.md"
99
requires-python = ">=3.11,<3.12"
1010
license = "Apache-2.0"
1111
keywords = []
12-
authors = [{ name = "Netclab Contributors" }]
12+
authors = [{ name = "Michal Bakalarski" }]
1313
classifiers = [
1414
"Development Status :: 3 - Alpha",
1515
"Programming Language :: Python",

0 commit comments

Comments
 (0)