Skip to content

Commit 368c991

Browse files
committed
function - c.d.
1 parent a5c6fbe commit 368c991

9 files changed

Lines changed: 144 additions & 76 deletions

File tree

example/README.md

Lines changed: 16 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -5,21 +5,27 @@ with these example manifests.
55

66
```shell
77
# Run the function locally
8-
$ hatch run development
8+
hatch run development
99
```
1010

1111
```shell
1212
# Then, in another terminal, call it with these example manifests
13-
$ crossplane beta render xr.yaml composition.yaml functions.yaml -r
13+
crossplane render xr.yaml composition.yaml functions.yaml \
14+
--required-resources secret.yaml --extra-resources environment.yaml -r
15+
```
16+
17+
```
1418
---
15-
apiVersion: example.crossplane.io/v1
16-
kind: XR
19+
apiVersion: eos.netclab.dev/v1alpha1
20+
kind: EosCommand
1721
metadata:
18-
name: example-xr
22+
name: eoscommand-1
23+
spec:
24+
endpoint: ceos01.default.svc.cluster.local
25+
cmds:
26+
ip prefix-list PL-Loopback0:
27+
seq 10 permit 10.0.0.1/32 eq 32: {}
28+
seq 20 permit 10.0.0.2/32 eq 32: {}
1929
---
20-
apiVersion: render.crossplane.io/v1beta1
21-
kind: Result
22-
message: I was run with input "Hello world"!
23-
severity: SEVERITY_NORMAL
24-
step: run-the-template
30+
<...>
2531
```

example/composition.yaml

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,25 @@ spec:
88
kind: EosCommand
99
mode: Pipeline
1010
pipeline:
11-
- step: create-requests
11+
- step: environmentConfigs
12+
functionRef:
13+
name: crossplane-contrib-function-environment-configs
14+
input:
15+
apiVersion: environmentconfigs.fn.crossplane.io/v1beta1
16+
kind: Input
17+
spec:
18+
environmentConfigs:
19+
- type: Reference
20+
ref:
21+
name: endpoint-protocols
22+
23+
- step: requests
1224
functionRef:
1325
name: function-eapi
26+
requirements:
27+
requiredResources:
28+
- apiVersion: v1
29+
kind: Secret
30+
name: eos-creds
31+
namespace: crossplane-system
32+
requirementName: eos-creds

example/environment.yaml

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
apiVersion: apiextensions.crossplane.io/v1beta1
2+
kind: EnvironmentConfig
3+
metadata:
4+
name: endpoint-protocols
5+
data:
6+
restconf:
7+
scheme: https
8+
port: 6020
9+
jsonrpc:
10+
scheme: http
11+
port: 6021

example/functions.yaml

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,13 @@ metadata:
66
annotations:
77
# This tells crossplane beta render to connect to the function locally.
88
render.crossplane.io/runtime: Development
9+
# spec:
10+
# # This is ignored when using the Development runtime.
11+
# package: function-
12+
---
13+
apiVersion: pkg.crossplane.io/v1
14+
kind: Function
15+
metadata:
16+
name: crossplane-contrib-function-environment-configs
917
spec:
10-
# This is ignored when using the Development runtime.
11-
package: function-
18+
package: xpkg.upbound.io/crossplane-contrib/function-environment-configs:v0.4.0

example/secret.yaml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
apiVersion: v1
2+
kind: Secret
3+
metadata:
4+
name: eos-creds
5+
namespace: crossplane-system
6+
type: Opaque
7+
data:
8+
basicAuth: YXJpc3RhOmFyaXN0YQ==

example/xr.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,3 +7,4 @@ spec:
77
cmds:
88
ip prefix-list PL-Loopback0:
99
seq 10 permit 10.0.0.1/32 eq 32: {}
10+
seq 20 permit 10.0.0.2/32 eq 32: {}

function/fn.py

Lines changed: 69 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,12 @@
11
"""A Crossplane composition function."""
22

3+
import hashlib
4+
35
import grpc
4-
from crossplane.function import logging, resource, response
6+
from crossplane.function import logging, request, resource, response
57
from crossplane.function.proto.v1 import run_function_pb2 as fnv1
68
from crossplane.function.proto.v1 import run_function_pb2_grpc as grpcv1
9+
from jsonrpcclient import request_json
710

811

912
class FunctionRunner(grpcv1.FunctionRunnerService):
@@ -27,37 +30,55 @@ async def RunFunction(
2730
endpoint = composite["spec"]["endpoint"]
2831
cmds = composite["spec"]["cmds"]
2932

33+
environment = resource.struct_to_dict(
34+
req.context["apiextensions.crossplane.io/environment"]
35+
)
36+
port, scheme, insecure_skip_tls_verify = get_envs(environment)
37+
38+
secret = request.get_required_resource(req, "eos-creds")
39+
basic_auth = secret.get("data", {}).get("basicAuth", "") if secret else ""
40+
41+
jsonrpc_cfg = {
42+
"endpoint": endpoint,
43+
"port": port,
44+
"scheme": scheme,
45+
"basicAuth": basic_auth,
46+
"insecureSkipTLSVerify": insecure_skip_tls_verify,
47+
}
48+
3049
command_paths = walk_cmds(cmds)
3150
log.info("Generated command paths", count=len(command_paths))
3251

3352
for path in command_paths:
3453
name = name_prefix + "-" + name_from_path(path)
3554

55+
jsonrpc_params = {
56+
"version": 1,
57+
"format": "json",
58+
"cmds": ["enable", "configure", *path],
59+
}
60+
jsonrpc = request_json("runCmds", params=jsonrpc_params)
61+
62+
resource_data = construct_resource_request(jsonrpc, jsonrpc_cfg)
63+
3664
resource.update(
3765
rsp.desired.resources[name],
38-
{
39-
"apiVersion": "http.crossplane.io/v1alpha2",
40-
"kind": "Request",
41-
"spec": {
42-
"endpoint": endpoint,
43-
"command": path,
44-
},
45-
},
66+
resource_data,
4667
)
4768

4869
return rsp
4970

5071

5172
def walk_cmds(cmds: dict, path: list[str] | None = None) -> list[list[str]]:
73+
"""walk_cmds function."""
5274
if path is None:
5375
path = []
5476

5577
results: list[list[str]] = []
5678

57-
# NOTE: keys are sorted to ensure deterministic reconciliation
5879
for cmd in sorted(cmds.keys()):
5980
subtree = cmds[cmd]
60-
next_path = path + [cmd]
81+
next_path = [*path, cmd]
6182

6283
if subtree == {}:
6384
# Leaf → emit array
@@ -69,8 +90,42 @@ def walk_cmds(cmds: dict, path: list[str] | None = None) -> list[list[str]]:
6990
return results
7091

7192

72-
import hashlib
73-
7493
def name_from_path(path: list[str]) -> str:
94+
"""name_from_path function."""
7595
joined = "|".join(path)
76-
return hashlib.sha1(joined.encode()).hexdigest()[:10]
96+
return hashlib.sha1(joined.encode(), usedforsecurity=False).hexdigest()[:10]
97+
98+
99+
def get_envs(environment: dict) -> tuple[int, str, bool]:
100+
"""Extract jsonrpc configuration from the environment."""
101+
if not environment:
102+
return 0, "", True
103+
104+
protocols_settings = environment.get("jsonrpc", {})
105+
106+
port = int(protocols_settings.get("port", 0))
107+
scheme = protocols_settings.get("scheme", "")
108+
insecure_skip_tls_verify = bool(protocols_settings.get("skipTlsVerify", True))
109+
110+
return port, scheme, insecure_skip_tls_verify
111+
112+
113+
def construct_resource_request(jsonrpc: str, config: dict) -> dict:
114+
"""Construct the resource request for the given data."""
115+
return {
116+
"apiVersion": "http.crossplane.io/v1alpha2",
117+
"kind": "Request",
118+
"spec": {
119+
"forProvider": {
120+
"insecureSkipTLSVerify": config["insecureSkipTLSVerify"],
121+
"headers": {
122+
"Accept": ["application/json"],
123+
"Authorization": [f"Basic {config['basicAuth']}"],
124+
},
125+
"payload": {
126+
"baseUrl": f"{config['scheme']}://{config['endpoint']}:{config['port']}",
127+
"body": jsonrpc,
128+
},
129+
},
130+
},
131+
}

package/input/template.fn.crossplane.io_inputs.yaml

Lines changed: 0 additions & 41 deletions
This file was deleted.

pyproject.toml

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,31 +3,33 @@ requires = ["hatchling"]
33
build-backend = "hatchling.build"
44

55
[project]
6-
name = "function"
6+
name = "function-eapi"
77
description = 'A composition function'
88
readme = "README.md"
99
requires-python = ">=3.11,<3.12"
1010
license = "Apache-2.0"
1111
keywords = []
12-
authors = [{ name = "Crossplane Maintainers", email = "info@crossplane.io" }]
12+
authors = [{ name = "Netclab Contributors" }]
1313
classifiers = [
14-
"Development Status :: 4 - Beta",
14+
"Development Status :: 3 - Alpha",
1515
"Programming Language :: Python",
1616
"Programming Language :: Python :: 3.11",
1717
]
1818

1919
dependencies = [
20-
"crossplane-function-sdk-python==0.8.0",
20+
"jsonrpcclient==4.0.3",
21+
"crossplane-function-sdk-python==0.9.0",
2122
"click==8.3.1",
22-
"grpcio==1.73.1",
23+
"grpcio==1.74.0",
24+
"protobuf==6.31.1"
2325
]
2426

2527
dynamic = ["version"]
2628

2729
[project.urls]
28-
Documentation = "https://github.com/crossplane/function-template-python#readme"
29-
Issues = "https://github.com/crossplane/function-template-python/issues"
30-
Source = "https://github.com/crossplane/function-template-python"
30+
Documentation = "https://github.com/mbakalarski/function-eapi#readme"
31+
Issues = "https://github.com/mbakalarski/function-eapi/issues"
32+
Source = "https://github.com/mbakalarski/function-eapi"
3133

3234
[project.scripts]
3335
function = "function.main:cli"

0 commit comments

Comments
 (0)