crossplane-function-pythonic 0.6.3__py3-none-any.whl → 0.7.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- crossplane/pythonic/__about__.py +1 -3
- crossplane/pythonic/auto_ready.py +31 -22
- crossplane/pythonic/command.py +31 -24
- crossplane/pythonic/composite.py +589 -386
- crossplane/pythonic/function.py +61 -40
- crossplane/pythonic/grpc.py +4 -3
- crossplane/pythonic/main.py +7 -3
- crossplane/pythonic/packages.py +27 -19
- crossplane/pythonic/proto/v1/run_function_pb2.pyi +1121 -255
- crossplane/pythonic/proto/v1/run_function_pb2_grpc.py +1 -1
- crossplane/pythonic/proto/v1/run_function_pb2_grpc.pyi +64 -0
- crossplane/pythonic/protobuf.py +346 -196
- crossplane/pythonic/render.py +206 -81
- crossplane/pythonic/version.py +1 -1
- {crossplane_function_pythonic-0.6.3.dist-info → crossplane_function_pythonic-0.7.0.dist-info}/METADATA +4 -4
- crossplane_function_pythonic-0.7.0.dist-info/RECORD +22 -0
- {crossplane_function_pythonic-0.6.3.dist-info → crossplane_function_pythonic-0.7.0.dist-info}/WHEEL +1 -1
- crossplane/pythonic/diff.py +0 -880
- crossplane_function_pythonic-0.6.3.dist-info/RECORD +0 -22
- {crossplane_function_pythonic-0.6.3.dist-info → crossplane_function_pythonic-0.7.0.dist-info}/entry_points.txt +0 -0
crossplane/pythonic/function.py
CHANGED
|
@@ -5,26 +5,31 @@ import importlib
|
|
|
5
5
|
import inspect
|
|
6
6
|
import logging
|
|
7
7
|
import sys
|
|
8
|
+
import typing
|
|
8
9
|
|
|
9
10
|
import grpc
|
|
11
|
+
from google.protobuf.struct_pb2 import Struct
|
|
12
|
+
|
|
13
|
+
from crossplane.pythonic.composite import BaseComposite
|
|
10
14
|
|
|
11
15
|
from .. import pythonic
|
|
16
|
+
from . import protobuf
|
|
12
17
|
from .proto.v1 import run_function_pb2 as fnv1
|
|
13
18
|
from .proto.v1 import run_function_pb2_grpc as grpcv1
|
|
14
19
|
|
|
15
20
|
logger = logging.getLogger(__name__)
|
|
16
21
|
|
|
17
22
|
|
|
18
|
-
class FunctionRunner(grpcv1.
|
|
23
|
+
class FunctionRunner(grpcv1.FunctionRunnerServiceServicer):
|
|
19
24
|
"""A FunctionRunner handles gRPC RunFunctionRequests."""
|
|
20
25
|
|
|
21
|
-
def __init__(self, renderUnknowns=False, crossplane_v1=False):
|
|
26
|
+
def __init__(self, renderUnknowns: bool = False, crossplane_v1: bool = False):
|
|
22
27
|
"""Create a new FunctionRunner."""
|
|
23
|
-
self.renderUnknowns = renderUnknowns
|
|
24
|
-
self.crossplane_v1 = crossplane_v1
|
|
25
|
-
self.clazzes = {}
|
|
28
|
+
self.renderUnknowns: bool = renderUnknowns
|
|
29
|
+
self.crossplane_v1: bool = crossplane_v1
|
|
30
|
+
self.clazzes: dict[str, type[pythonic.BaseComposite]] = {}
|
|
26
31
|
|
|
27
|
-
def invalidate_module(self, module):
|
|
32
|
+
def invalidate_module(self, module: str) -> None:
|
|
28
33
|
ix = len(module)
|
|
29
34
|
while ix > 0:
|
|
30
35
|
module = module[:ix]
|
|
@@ -35,37 +40,46 @@ class FunctionRunner(grpcv1.FunctionRunnerService):
|
|
|
35
40
|
self.clazzes.clear()
|
|
36
41
|
|
|
37
42
|
async def RunFunction(
|
|
38
|
-
self,
|
|
43
|
+
self,
|
|
44
|
+
request: fnv1.RunFunctionRequest,
|
|
45
|
+
context: grpc.aio.ServicerContext | None = None,
|
|
39
46
|
) -> fnv1.RunFunctionResponse:
|
|
40
47
|
try:
|
|
41
48
|
return await self.run_function(request)
|
|
42
49
|
except Exception as e: # noqa: BLE001
|
|
43
50
|
return self.fatal(request, logger, "RunFunction", e)
|
|
44
51
|
|
|
45
|
-
async def run_function(
|
|
46
|
-
|
|
52
|
+
async def run_function(
|
|
53
|
+
self, request: fnv1.RunFunctionRequest
|
|
54
|
+
) -> fnv1.RunFunctionResponse:
|
|
55
|
+
composite: Struct = request.observed.composite.resource
|
|
47
56
|
if composite and composite.fields:
|
|
48
|
-
name = list(
|
|
49
|
-
|
|
50
|
-
|
|
57
|
+
name: list[str] = list(
|
|
58
|
+
reversed(str(composite["apiVersion"]).split("/")[0].split("."))
|
|
59
|
+
)
|
|
60
|
+
name.append(str(composite["kind"]))
|
|
61
|
+
name.append(str(typing.cast(Struct, composite["metadata"])["name"]))
|
|
51
62
|
logger = logging.getLogger(".".join(name))
|
|
52
63
|
if "inlined" in request.input and request.input["inlined"]: # noqa: RUF019
|
|
53
|
-
if (
|
|
54
|
-
"
|
|
55
|
-
|
|
56
|
-
):
|
|
64
|
+
if "spec" not in composite or str(
|
|
65
|
+
request.input["inlined"]
|
|
66
|
+
) not in typing.cast(Struct, composite["spec"]):
|
|
57
67
|
return self.fatal(
|
|
58
68
|
request,
|
|
59
69
|
logger,
|
|
60
70
|
f"Missing inlined spec.{request.input['inlined']}",
|
|
61
71
|
)
|
|
62
|
-
composite =
|
|
72
|
+
composite: str = str(
|
|
73
|
+
typing.cast(Struct, composite["spec"])[
|
|
74
|
+
str(request.input["inlined"])
|
|
75
|
+
],
|
|
76
|
+
)
|
|
63
77
|
else:
|
|
64
78
|
if "composite" not in request.input:
|
|
65
79
|
return self.fatal(
|
|
66
80
|
request, logger, 'Missing composition input "composite"'
|
|
67
81
|
)
|
|
68
|
-
composite = request.input["composite"]
|
|
82
|
+
composite: str = str(request.input["composite"])
|
|
69
83
|
operation = False
|
|
70
84
|
else:
|
|
71
85
|
logger = logging.getLogger("operation")
|
|
@@ -73,7 +87,7 @@ class FunctionRunner(grpcv1.FunctionRunnerService):
|
|
|
73
87
|
return self.fatal(
|
|
74
88
|
request, logger, 'Missing operation input "composite"'
|
|
75
89
|
)
|
|
76
|
-
composite = request.input["composite"]
|
|
90
|
+
composite: str = str(request.input["composite"])
|
|
77
91
|
operation = True
|
|
78
92
|
|
|
79
93
|
# Ideally this is something the Function API provides
|
|
@@ -111,7 +125,7 @@ class FunctionRunner(grpcv1.FunctionRunnerService):
|
|
|
111
125
|
"Composite script does not have a BaseComposite class",
|
|
112
126
|
)
|
|
113
127
|
else:
|
|
114
|
-
composite = composite.rsplit(".", 1)
|
|
128
|
+
composite: list[str] = composite.rsplit(".", 1)
|
|
115
129
|
if len(composite) == 1:
|
|
116
130
|
return self.fatal(
|
|
117
131
|
request,
|
|
@@ -129,7 +143,7 @@ class FunctionRunner(grpcv1.FunctionRunnerService):
|
|
|
129
143
|
logger,
|
|
130
144
|
f"{composite[0]} does not define: {composite[1]}",
|
|
131
145
|
)
|
|
132
|
-
composite = ".".join(composite)
|
|
146
|
+
composite: str = ".".join(composite)
|
|
133
147
|
if not inspect.isclass(clazz):
|
|
134
148
|
return self.fatal(request, logger, f"{composite} is not a class")
|
|
135
149
|
if not issubclass(clazz, pythonic.BaseComposite):
|
|
@@ -147,7 +161,7 @@ class FunctionRunner(grpcv1.FunctionRunnerService):
|
|
|
147
161
|
logger = logging.getLogger(f"operation.{composite}")
|
|
148
162
|
|
|
149
163
|
try:
|
|
150
|
-
composite = clazz(self.crossplane_v1, request, logger)
|
|
164
|
+
composite: BaseComposite = clazz(self.crossplane_v1, request, logger)
|
|
151
165
|
except Exception as e: # noqa: BLE001
|
|
152
166
|
return self.fatal(request, logger, "Instantiate", e)
|
|
153
167
|
|
|
@@ -185,13 +199,19 @@ class FunctionRunner(grpcv1.FunctionRunnerService):
|
|
|
185
199
|
self.process_usages(composite)
|
|
186
200
|
self.process_unknowns(composite)
|
|
187
201
|
# Perform auto ready on all resources.
|
|
188
|
-
for
|
|
202
|
+
for _, resource in composite.resources:
|
|
189
203
|
resource.ready # noqa: B018
|
|
190
204
|
logger.info("Completed compose")
|
|
191
205
|
|
|
192
|
-
return composite.response._message
|
|
206
|
+
return typing.cast(fnv1.RunFunctionResponse, composite.response._message)
|
|
193
207
|
|
|
194
|
-
def fatal(
|
|
208
|
+
def fatal(
|
|
209
|
+
self,
|
|
210
|
+
request: fnv1.RunFunctionRequest,
|
|
211
|
+
logger: logging.Logger,
|
|
212
|
+
message: str,
|
|
213
|
+
exception: Exception | None = None,
|
|
214
|
+
) -> fnv1.RunFunctionResponse:
|
|
195
215
|
if exception:
|
|
196
216
|
message += " exception"
|
|
197
217
|
logger.exception(message)
|
|
@@ -213,8 +233,8 @@ class FunctionRunner(grpcv1.FunctionRunnerService):
|
|
|
213
233
|
],
|
|
214
234
|
)
|
|
215
235
|
|
|
216
|
-
def get_schemas(self, step, composite):
|
|
217
|
-
schemas = []
|
|
236
|
+
def get_schemas(self, step: protobuf.Value, composite: BaseComposite) -> list[str]:
|
|
237
|
+
schemas: list[str] = []
|
|
218
238
|
for name, schema in composite.schemas:
|
|
219
239
|
if len(schema.kind) and len(schema.apiVersion):
|
|
220
240
|
s = pythonic.Map(kind=schema.kind, apiVersion=schema.apiVersion)
|
|
@@ -223,8 +243,10 @@ class FunctionRunner(grpcv1.FunctionRunnerService):
|
|
|
223
243
|
schemas.append(name)
|
|
224
244
|
return schemas
|
|
225
245
|
|
|
226
|
-
def get_requireds(
|
|
227
|
-
|
|
246
|
+
def get_requireds(
|
|
247
|
+
self, step: protobuf.Value, composite: BaseComposite
|
|
248
|
+
) -> list[str]:
|
|
249
|
+
requireds: list[str] = []
|
|
228
250
|
for name, required in composite.requireds:
|
|
229
251
|
if len(required.kind) and len(required.apiVersion):
|
|
230
252
|
r = pythonic.Map(kind=required.kind, apiVersion=required.apiVersion)
|
|
@@ -239,7 +261,7 @@ class FunctionRunner(grpcv1.FunctionRunnerService):
|
|
|
239
261
|
requireds.append(name)
|
|
240
262
|
return requireds
|
|
241
263
|
|
|
242
|
-
def process_usages(self, composite):
|
|
264
|
+
def process_usages(self, composite: BaseComposite) -> None:
|
|
243
265
|
if self.crossplane_v1:
|
|
244
266
|
apiVersion = "apiextensions.crossplane.io/v1beta1"
|
|
245
267
|
else:
|
|
@@ -285,7 +307,7 @@ class FunctionRunner(grpcv1.FunctionRunnerService):
|
|
|
285
307
|
)
|
|
286
308
|
for name, dependencies in resources.items():
|
|
287
309
|
source = composite.resources[name]
|
|
288
|
-
name = [resource.name, str(source.kind)]
|
|
310
|
+
name: list[str] = [str(resource.name), str(source.kind)]
|
|
289
311
|
if source.metadata.namespace:
|
|
290
312
|
name.append(str(source.metadata.namespace))
|
|
291
313
|
name.append(str(source.observed.metadata.name))
|
|
@@ -322,7 +344,7 @@ class FunctionRunner(grpcv1.FunctionRunnerService):
|
|
|
322
344
|
usage.kind = "ClusterUsage"
|
|
323
345
|
for key, dependencies in requireds.items():
|
|
324
346
|
source = composite.requireds[key[0]][key[1]]
|
|
325
|
-
name = [resource.name, str(source.kind)]
|
|
347
|
+
name: list[str] = [str(resource.name), str(source.kind)]
|
|
326
348
|
if source.metadata.namespace:
|
|
327
349
|
name.append(str(source.metadata.namespace))
|
|
328
350
|
name.append(str(source.metadata.name))
|
|
@@ -358,10 +380,10 @@ class FunctionRunner(grpcv1.FunctionRunnerService):
|
|
|
358
380
|
else:
|
|
359
381
|
usage.kind = "ClusterUsage"
|
|
360
382
|
|
|
361
|
-
def process_unknowns(self, composite):
|
|
362
|
-
unknownResources = []
|
|
363
|
-
warningResources = []
|
|
364
|
-
fatalResources = []
|
|
383
|
+
def process_unknowns(self, composite: BaseComposite) -> None:
|
|
384
|
+
unknownResources: list[str] = []
|
|
385
|
+
warningResources: list[str] = []
|
|
386
|
+
fatalResources: list[str] = []
|
|
365
387
|
for name, resource in sorted(entry for entry in composite.resources):
|
|
366
388
|
unknowns = resource.desired._getUnknowns
|
|
367
389
|
if unknowns:
|
|
@@ -429,8 +451,8 @@ class FunctionRunner(grpcv1.FunctionRunnerService):
|
|
|
429
451
|
if result:
|
|
430
452
|
result(reason, message)
|
|
431
453
|
|
|
432
|
-
def trimFullName(self, name):
|
|
433
|
-
name = name.split(".")
|
|
454
|
+
def trimFullName(self, name: str) -> str:
|
|
455
|
+
name: list[str] = name.split(".")
|
|
434
456
|
for values in (
|
|
435
457
|
("request", "observed", "composite", "resource"),
|
|
436
458
|
("response", "desired", "composite", "resource"),
|
|
@@ -468,8 +490,7 @@ class FunctionRunner(grpcv1.FunctionRunnerService):
|
|
|
468
490
|
return ".".join(name)
|
|
469
491
|
|
|
470
492
|
|
|
471
|
-
def ordinal(ix):
|
|
472
|
-
ix = int(ix)
|
|
493
|
+
def ordinal(ix: int) -> str:
|
|
473
494
|
if 11 <= (ix % 100) <= 13:
|
|
474
495
|
suffix = "th"
|
|
475
496
|
else:
|
crossplane/pythonic/grpc.py
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import argparse
|
|
1
2
|
import asyncio
|
|
2
3
|
import logging
|
|
3
4
|
import os
|
|
@@ -23,7 +24,7 @@ class Command(command.Command):
|
|
|
23
24
|
help = "Run function-pythonic gRPC server"
|
|
24
25
|
|
|
25
26
|
@classmethod
|
|
26
|
-
def add_parser_arguments(cls, parser):
|
|
27
|
+
def add_parser_arguments(cls, parser: argparse.ArgumentParser) -> None:
|
|
27
28
|
cls.add_function_arguments(parser)
|
|
28
29
|
parser.add_argument(
|
|
29
30
|
"--address",
|
|
@@ -86,7 +87,7 @@ class Command(command.Command):
|
|
|
86
87
|
help="Pip install command to install additional Python packages.",
|
|
87
88
|
)
|
|
88
89
|
|
|
89
|
-
def initialize(self):
|
|
90
|
+
def initialize(self) -> None:
|
|
90
91
|
if not self.args.tls_certs_dir and not self.args.insecure:
|
|
91
92
|
print(
|
|
92
93
|
"Either --tls-certs-dir or --insecure must be specified",
|
|
@@ -114,7 +115,7 @@ class Command(command.Command):
|
|
|
114
115
|
# enables read only volumes or mismatched uid volumes
|
|
115
116
|
sys.dont_write_bytecode = True
|
|
116
117
|
|
|
117
|
-
async def run(self):
|
|
118
|
+
async def run(self) -> None:
|
|
118
119
|
grpc.aio.init_grpc_aio()
|
|
119
120
|
grpc_runner = function.FunctionRunner(
|
|
120
121
|
self.args.render_unknowns, self.args.crossplane_v1
|
crossplane/pythonic/main.py
CHANGED
|
@@ -21,12 +21,16 @@ from . import (
|
|
|
21
21
|
|
|
22
22
|
|
|
23
23
|
def main():
|
|
24
|
-
parser = argparse.ArgumentParser(
|
|
25
|
-
|
|
24
|
+
parser: argparse.ArgumentParser = argparse.ArgumentParser(
|
|
25
|
+
"Crossplane Function Pythonic"
|
|
26
|
+
)
|
|
27
|
+
subparsers: argparse._SubParsersAction[argparse.ArgumentParser] = (
|
|
28
|
+
parser.add_subparsers(title="Command", metavar="")
|
|
29
|
+
)
|
|
26
30
|
grpc.Command.create(subparsers)
|
|
27
31
|
render.Command.create(subparsers)
|
|
28
32
|
version.Command.create(subparsers)
|
|
29
|
-
args = parser.parse_args()
|
|
33
|
+
args: argparse.Namespace = parser.parse_args()
|
|
30
34
|
if not hasattr(args, "command"):
|
|
31
35
|
parser.print_help()
|
|
32
36
|
sys.exit(1)
|
crossplane/pythonic/packages.py
CHANGED
|
@@ -2,9 +2,13 @@ import base64
|
|
|
2
2
|
import logging
|
|
3
3
|
import pathlib
|
|
4
4
|
import sys
|
|
5
|
+
import typing
|
|
5
6
|
|
|
7
|
+
import grpc
|
|
6
8
|
import kopf
|
|
7
9
|
|
|
10
|
+
from . import function
|
|
11
|
+
|
|
8
12
|
GRPC_SERVER = None
|
|
9
13
|
GRPC_RUNNER = None
|
|
10
14
|
PACKAGES_DIR = None
|
|
@@ -13,15 +17,15 @@ PACKAGE_LABELS = {PACKAGE_LABEL: kopf.PRESENT}
|
|
|
13
17
|
|
|
14
18
|
|
|
15
19
|
def operator(
|
|
16
|
-
grpc_server,
|
|
17
|
-
grpc_runner,
|
|
18
|
-
packages_configmaps,
|
|
19
|
-
packages_secrets,
|
|
20
|
-
packages_namespaces,
|
|
21
|
-
packages_environmentconfigs,
|
|
22
|
-
packages_compositions,
|
|
23
|
-
packages_dir,
|
|
24
|
-
):
|
|
20
|
+
grpc_server: grpc.aio.Server,
|
|
21
|
+
grpc_runner: function.FunctionRunner,
|
|
22
|
+
packages_configmaps: bool,
|
|
23
|
+
packages_secrets: bool,
|
|
24
|
+
packages_namespaces: list[str],
|
|
25
|
+
packages_environmentconfigs: bool,
|
|
26
|
+
packages_compositions: bool,
|
|
27
|
+
packages_dir: str,
|
|
28
|
+
) -> typing.Coroutine:
|
|
25
29
|
logging.getLogger("kopf.objects").setLevel(logging.INFO)
|
|
26
30
|
global GRPC_SERVER, GRPC_RUNNER, PACKAGES_DIR
|
|
27
31
|
GRPC_SERVER = grpc_server
|
|
@@ -44,7 +48,7 @@ def operator(
|
|
|
44
48
|
)
|
|
45
49
|
|
|
46
50
|
|
|
47
|
-
def on_resource(group, version, plural):
|
|
51
|
+
def on_resource(group: str, version: str, plural: str):
|
|
48
52
|
kopf.on.create(group, version, plural, labels=PACKAGE_LABELS)(create)
|
|
49
53
|
kopf.on.resume(group, version, plural, labels=PACKAGE_LABELS)(create)
|
|
50
54
|
kopf.on.update(group, version, plural, labels=PACKAGE_LABELS)(update)
|
|
@@ -52,25 +56,26 @@ def on_resource(group, version, plural):
|
|
|
52
56
|
|
|
53
57
|
|
|
54
58
|
@kopf.on.startup()
|
|
55
|
-
async def startup(settings, **_):
|
|
59
|
+
async def startup(settings: kopf.OperatorSettings, **_: typing.Any) -> None:
|
|
56
60
|
settings.scanning.disabled = True
|
|
57
61
|
|
|
58
62
|
|
|
59
63
|
@kopf.on.cleanup()
|
|
60
|
-
async def cleanup(**_):
|
|
61
|
-
|
|
64
|
+
async def cleanup(**_: typing.Any) -> None:
|
|
65
|
+
if GRPC_SERVER:
|
|
66
|
+
await GRPC_SERVER.stop(5)
|
|
62
67
|
|
|
63
68
|
|
|
64
|
-
async def create(resource, labels, body, logger, **_):
|
|
69
|
+
async def create(resource, labels, body, logger, **_: typing.Any):
|
|
65
70
|
resource_create(resource, labels, "Created", body, logger)
|
|
66
71
|
|
|
67
72
|
|
|
68
|
-
async def update(resource, labels, body, old, logger, **_):
|
|
73
|
+
async def update(resource, labels, body, old, logger, **_: typing.Any):
|
|
69
74
|
resource_delete(resource, labels, "Removed", old, logger)
|
|
70
75
|
resource_create(resource, labels, "Added", body, logger)
|
|
71
76
|
|
|
72
77
|
|
|
73
|
-
async def delete(resource, labels, body, logger, **_):
|
|
78
|
+
async def delete(resource, labels, body, logger, **_: typing.Any):
|
|
74
79
|
resource_delete(resource, labels, "Deleted", body, logger)
|
|
75
80
|
|
|
76
81
|
|
|
@@ -141,7 +146,8 @@ def package_create(resource, action, package_dir, package, logger):
|
|
|
141
146
|
package_name.write_text(value)
|
|
142
147
|
module, name = package_file_name(package_name)
|
|
143
148
|
if module:
|
|
144
|
-
GRPC_RUNNER
|
|
149
|
+
if GRPC_RUNNER:
|
|
150
|
+
GRPC_RUNNER.invalidate_module(name)
|
|
145
151
|
logger.info(f"{action} module: {name}")
|
|
146
152
|
else:
|
|
147
153
|
logger.info(f"{action} file: {name}")
|
|
@@ -157,7 +163,8 @@ def package_delete(action, package_dir, package, logger):
|
|
|
157
163
|
package_name.unlink(missing_ok=True)
|
|
158
164
|
module, name = package_file_name(package_name)
|
|
159
165
|
if module:
|
|
160
|
-
GRPC_RUNNER
|
|
166
|
+
if GRPC_RUNNER:
|
|
167
|
+
GRPC_RUNNER.invalidate_module(name)
|
|
161
168
|
logger.info(f"{action} module: {name}")
|
|
162
169
|
else:
|
|
163
170
|
logger.info(f"{action} file: {name}")
|
|
@@ -170,7 +177,8 @@ def package_delete(action, package_dir, package, logger):
|
|
|
170
177
|
parent.rmdir()
|
|
171
178
|
module = str(parent.relative_to(PACKAGES_DIR)).replace("/", ".")
|
|
172
179
|
if module != ".":
|
|
173
|
-
GRPC_RUNNER
|
|
180
|
+
if GRPC_RUNNER:
|
|
181
|
+
GRPC_RUNNER.invalidate_module(module)
|
|
174
182
|
logger.info(f"{action} package: {module}")
|
|
175
183
|
parent = parent.parent
|
|
176
184
|
elif isinstance(value, dict):
|