crossplane-function-pythonic 0.6.2__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 +6 -2
- crossplane/pythonic/__init__.py +21 -13
- crossplane/pythonic/__main__.py +1 -0
- crossplane/pythonic/auto_ready.py +118 -61
- crossplane/pythonic/command.py +69 -58
- crossplane/pythonic/composite.py +656 -428
- crossplane/pythonic/function.py +233 -130
- crossplane/pythonic/grpc.py +89 -65
- crossplane/pythonic/main.py +18 -5
- crossplane/pythonic/packages.py +82 -51
- crossplane/pythonic/proto/v1/run_function_pb2.py +3 -3
- crossplane/pythonic/proto/v1/run_function_pb2.pyi +1121 -255
- crossplane/pythonic/proto/v1/run_function_pb2_grpc.py +4 -4
- crossplane/pythonic/proto/v1/run_function_pb2_grpc.pyi +64 -0
- crossplane/pythonic/protobuf.py +589 -372
- crossplane/pythonic/render.py +568 -235
- crossplane/pythonic/version.py +3 -4
- {crossplane_function_pythonic-0.6.2.dist-info → crossplane_function_pythonic-0.7.0.dist-info}/METADATA +23 -26
- crossplane_function_pythonic-0.7.0.dist-info/RECORD +22 -0
- {crossplane_function_pythonic-0.6.2.dist-info → crossplane_function_pythonic-0.7.0.dist-info}/WHEEL +1 -1
- {crossplane_function_pythonic-0.6.2.dist-info → crossplane_function_pythonic-0.7.0.dist-info}/entry_points.txt +1 -0
- crossplane_function_pythonic-0.6.2.dist-info/RECORD +0 -22
- crossplane_function_pythonic-0.6.2.dist-info/licenses/LICENSE +0 -201
crossplane/pythonic/function.py
CHANGED
|
@@ -5,115 +5,165 @@ 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
|
|
14
|
+
|
|
15
|
+
from .. import pythonic
|
|
16
|
+
from . import protobuf
|
|
10
17
|
from .proto.v1 import run_function_pb2 as fnv1
|
|
11
18
|
from .proto.v1 import run_function_pb2_grpc as grpcv1
|
|
12
|
-
from .. import pythonic
|
|
13
19
|
|
|
14
20
|
logger = logging.getLogger(__name__)
|
|
15
21
|
|
|
16
22
|
|
|
17
|
-
class FunctionRunner(grpcv1.
|
|
23
|
+
class FunctionRunner(grpcv1.FunctionRunnerServiceServicer):
|
|
18
24
|
"""A FunctionRunner handles gRPC RunFunctionRequests."""
|
|
19
25
|
|
|
20
|
-
def __init__(self, renderUnknowns=False, crossplane_v1=False):
|
|
26
|
+
def __init__(self, renderUnknowns: bool = False, crossplane_v1: bool = False):
|
|
21
27
|
"""Create a new FunctionRunner."""
|
|
22
|
-
self.renderUnknowns = renderUnknowns
|
|
23
|
-
self.crossplane_v1 = crossplane_v1
|
|
24
|
-
self.clazzes = {}
|
|
28
|
+
self.renderUnknowns: bool = renderUnknowns
|
|
29
|
+
self.crossplane_v1: bool = crossplane_v1
|
|
30
|
+
self.clazzes: dict[str, type[pythonic.BaseComposite]] = {}
|
|
25
31
|
|
|
26
|
-
def invalidate_module(self, module):
|
|
32
|
+
def invalidate_module(self, module: str) -> None:
|
|
27
33
|
ix = len(module)
|
|
28
34
|
while ix > 0:
|
|
29
35
|
module = module[:ix]
|
|
30
36
|
if module in sys.modules:
|
|
31
37
|
del sys.modules[module]
|
|
32
|
-
ix = module.rfind(
|
|
38
|
+
ix = module.rfind(".")
|
|
33
39
|
importlib.invalidate_caches()
|
|
34
40
|
self.clazzes.clear()
|
|
35
41
|
|
|
36
42
|
async def RunFunction(
|
|
37
|
-
self,
|
|
43
|
+
self,
|
|
44
|
+
request: fnv1.RunFunctionRequest,
|
|
45
|
+
context: grpc.aio.ServicerContext | None = None,
|
|
38
46
|
) -> fnv1.RunFunctionResponse:
|
|
39
47
|
try:
|
|
40
48
|
return await self.run_function(request)
|
|
41
|
-
except Exception as e:
|
|
42
|
-
return self.fatal(request, logger,
|
|
49
|
+
except Exception as e: # noqa: BLE001
|
|
50
|
+
return self.fatal(request, logger, "RunFunction", e)
|
|
43
51
|
|
|
44
|
-
async def run_function(
|
|
45
|
-
|
|
52
|
+
async def run_function(
|
|
53
|
+
self, request: fnv1.RunFunctionRequest
|
|
54
|
+
) -> fnv1.RunFunctionResponse:
|
|
55
|
+
composite: Struct = request.observed.composite.resource
|
|
46
56
|
if composite and composite.fields:
|
|
47
|
-
name = list(
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
composite
|
|
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"]))
|
|
62
|
+
logger = logging.getLogger(".".join(name))
|
|
63
|
+
if "inlined" in request.input and request.input["inlined"]: # noqa: RUF019
|
|
64
|
+
if "spec" not in composite or str(
|
|
65
|
+
request.input["inlined"]
|
|
66
|
+
) not in typing.cast(Struct, composite["spec"]):
|
|
67
|
+
return self.fatal(
|
|
68
|
+
request,
|
|
69
|
+
logger,
|
|
70
|
+
f"Missing inlined spec.{request.input['inlined']}",
|
|
71
|
+
)
|
|
72
|
+
composite: str = str(
|
|
73
|
+
typing.cast(Struct, composite["spec"])[
|
|
74
|
+
str(request.input["inlined"])
|
|
75
|
+
],
|
|
76
|
+
)
|
|
55
77
|
else:
|
|
56
|
-
if
|
|
57
|
-
return self.fatal(
|
|
58
|
-
|
|
78
|
+
if "composite" not in request.input:
|
|
79
|
+
return self.fatal(
|
|
80
|
+
request, logger, 'Missing composition input "composite"'
|
|
81
|
+
)
|
|
82
|
+
composite: str = str(request.input["composite"])
|
|
59
83
|
operation = False
|
|
60
84
|
else:
|
|
61
|
-
logger = logging.getLogger(
|
|
62
|
-
if
|
|
63
|
-
return self.fatal(
|
|
64
|
-
|
|
85
|
+
logger = logging.getLogger("operation")
|
|
86
|
+
if "composite" not in request.input:
|
|
87
|
+
return self.fatal(
|
|
88
|
+
request, logger, 'Missing operation input "composite"'
|
|
89
|
+
)
|
|
90
|
+
composite: str = str(request.input["composite"])
|
|
65
91
|
operation = True
|
|
66
92
|
|
|
67
93
|
# Ideally this is something the Function API provides
|
|
68
|
-
if
|
|
69
|
-
step = request.input[
|
|
94
|
+
if "step" in request.input:
|
|
95
|
+
step = request.input["step"]
|
|
70
96
|
else:
|
|
71
97
|
step = str(hash(composite))
|
|
72
98
|
|
|
73
99
|
clazz = self.clazzes.get(composite)
|
|
74
100
|
if not clazz:
|
|
75
|
-
if
|
|
101
|
+
if "\n" in composite:
|
|
76
102
|
module = Module()
|
|
77
103
|
try:
|
|
78
|
-
exec(composite, module.__dict__)
|
|
79
|
-
except Exception as e:
|
|
80
|
-
return self.fatal(request, logger,
|
|
104
|
+
exec(composite, module.__dict__) # noqa: S102
|
|
105
|
+
except Exception as e: # noqa: BLE001
|
|
106
|
+
return self.fatal(request, logger, "Exec", e)
|
|
81
107
|
for field in dir(module):
|
|
82
108
|
value = getattr(module, field)
|
|
83
|
-
if
|
|
109
|
+
if (
|
|
110
|
+
inspect.isclass(value)
|
|
111
|
+
and issubclass(value, pythonic.BaseComposite)
|
|
112
|
+
and value != pythonic.BaseComposite
|
|
113
|
+
):
|
|
84
114
|
if clazz:
|
|
85
|
-
return self.fatal(
|
|
115
|
+
return self.fatal(
|
|
116
|
+
request,
|
|
117
|
+
logger,
|
|
118
|
+
"Composite script has multiple BaseComposite classes",
|
|
119
|
+
)
|
|
86
120
|
clazz = value
|
|
87
121
|
if not clazz:
|
|
88
|
-
return self.fatal(
|
|
122
|
+
return self.fatal(
|
|
123
|
+
request,
|
|
124
|
+
logger,
|
|
125
|
+
"Composite script does not have a BaseComposite class",
|
|
126
|
+
)
|
|
89
127
|
else:
|
|
90
|
-
composite = composite.rsplit(
|
|
128
|
+
composite: list[str] = composite.rsplit(".", 1)
|
|
91
129
|
if len(composite) == 1:
|
|
92
|
-
return self.fatal(
|
|
130
|
+
return self.fatal(
|
|
131
|
+
request,
|
|
132
|
+
logger,
|
|
133
|
+
f"Composite class name does not include module: {composite[0]}",
|
|
134
|
+
)
|
|
93
135
|
try:
|
|
94
136
|
module = importlib.import_module(composite[0])
|
|
95
|
-
except Exception as e:
|
|
96
|
-
return self.fatal(request, logger,
|
|
137
|
+
except Exception as e: # noqa: BLE001
|
|
138
|
+
return self.fatal(request, logger, "Import module", e)
|
|
97
139
|
clazz = getattr(module, composite[1], None)
|
|
98
140
|
if not clazz:
|
|
99
|
-
return self.fatal(
|
|
100
|
-
|
|
141
|
+
return self.fatal(
|
|
142
|
+
request,
|
|
143
|
+
logger,
|
|
144
|
+
f"{composite[0]} does not define: {composite[1]}",
|
|
145
|
+
)
|
|
146
|
+
composite: str = ".".join(composite)
|
|
101
147
|
if not inspect.isclass(clazz):
|
|
102
148
|
return self.fatal(request, logger, f"{composite} is not a class")
|
|
103
149
|
if not issubclass(clazz, pythonic.BaseComposite):
|
|
104
|
-
return self.fatal(
|
|
150
|
+
return self.fatal(
|
|
151
|
+
request,
|
|
152
|
+
logger,
|
|
153
|
+
f"{composite} is not a subclass of BaseComposite",
|
|
154
|
+
)
|
|
105
155
|
self.clazzes[composite] = clazz
|
|
106
156
|
|
|
107
157
|
if operation:
|
|
108
|
-
if
|
|
158
|
+
if "\n" in composite:
|
|
109
159
|
logger = logging.getLogger(f"operation.{clazz.__name__}")
|
|
110
160
|
else:
|
|
111
161
|
logger = logging.getLogger(f"operation.{composite}")
|
|
112
162
|
|
|
113
163
|
try:
|
|
114
|
-
composite = clazz(self.crossplane_v1, request, logger)
|
|
115
|
-
except Exception as e:
|
|
116
|
-
return self.fatal(request, logger,
|
|
164
|
+
composite: BaseComposite = clazz(self.crossplane_v1, request, logger)
|
|
165
|
+
except Exception as e: # noqa: BLE001
|
|
166
|
+
return self.fatal(request, logger, "Instantiate", e)
|
|
117
167
|
|
|
118
168
|
step = composite.context._pythonic[step]
|
|
119
169
|
iteration = int(step.iteration) + 1
|
|
@@ -121,18 +171,22 @@ class FunctionRunner(grpcv1.FunctionRunnerService):
|
|
|
121
171
|
composite.context.iteration = iteration
|
|
122
172
|
if iteration == 1 and not logger.isEnabledFor(logging.DEBUG):
|
|
123
173
|
if len(composite.context._pythonic) == 1:
|
|
124
|
-
logger.info(
|
|
174
|
+
logger.info("Starting compose")
|
|
125
175
|
else:
|
|
126
|
-
logger.info(
|
|
176
|
+
logger.info(
|
|
177
|
+
f"Starting compose, {ordinal(len(composite.context._pythonic))} step"
|
|
178
|
+
)
|
|
127
179
|
else:
|
|
128
|
-
logger.debug(
|
|
180
|
+
logger.debug(
|
|
181
|
+
f"Starting compose, {ordinal(len(composite.context._pythonic))} step, {ordinal(iteration)} pass"
|
|
182
|
+
)
|
|
129
183
|
|
|
130
184
|
try:
|
|
131
185
|
result = composite.compose()
|
|
132
186
|
if asyncio.iscoroutine(result):
|
|
133
187
|
await result
|
|
134
|
-
except Exception as e:
|
|
135
|
-
return self.fatal(request, logger,
|
|
188
|
+
except Exception as e: # noqa: BLE001
|
|
189
|
+
return self.fatal(request, logger, "Compose", e)
|
|
136
190
|
|
|
137
191
|
schemas = self.get_schemas(step, composite)
|
|
138
192
|
requireds = self.get_requireds(step, composite)
|
|
@@ -145,20 +199,26 @@ class FunctionRunner(grpcv1.FunctionRunnerService):
|
|
|
145
199
|
self.process_usages(composite)
|
|
146
200
|
self.process_unknowns(composite)
|
|
147
201
|
# Perform auto ready on all resources.
|
|
148
|
-
for
|
|
149
|
-
resource.ready
|
|
150
|
-
logger.info(
|
|
202
|
+
for _, resource in composite.resources:
|
|
203
|
+
resource.ready # noqa: B018
|
|
204
|
+
logger.info("Completed compose")
|
|
151
205
|
|
|
152
|
-
return composite.response._message
|
|
206
|
+
return typing.cast(fnv1.RunFunctionResponse, composite.response._message)
|
|
153
207
|
|
|
154
|
-
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:
|
|
155
215
|
if exception:
|
|
156
|
-
message +=
|
|
216
|
+
message += " exception"
|
|
157
217
|
logger.exception(message)
|
|
158
218
|
m = str(exception)
|
|
159
219
|
if not m:
|
|
160
220
|
m = exception.__class__.__name__
|
|
161
|
-
message +=
|
|
221
|
+
message += ": " + m
|
|
162
222
|
else:
|
|
163
223
|
logger.error(message)
|
|
164
224
|
return fnv1.RunFunctionResponse(
|
|
@@ -170,11 +230,11 @@ class FunctionRunner(grpcv1.FunctionRunnerService):
|
|
|
170
230
|
severity=fnv1.SEVERITY_FATAL,
|
|
171
231
|
message=message,
|
|
172
232
|
)
|
|
173
|
-
]
|
|
233
|
+
],
|
|
174
234
|
)
|
|
175
235
|
|
|
176
|
-
def get_schemas(self, step, composite):
|
|
177
|
-
schemas = []
|
|
236
|
+
def get_schemas(self, step: protobuf.Value, composite: BaseComposite) -> list[str]:
|
|
237
|
+
schemas: list[str] = []
|
|
178
238
|
for name, schema in composite.schemas:
|
|
179
239
|
if len(schema.kind) and len(schema.apiVersion):
|
|
180
240
|
s = pythonic.Map(kind=schema.kind, apiVersion=schema.apiVersion)
|
|
@@ -183,8 +243,10 @@ class FunctionRunner(grpcv1.FunctionRunnerService):
|
|
|
183
243
|
schemas.append(name)
|
|
184
244
|
return schemas
|
|
185
245
|
|
|
186
|
-
def get_requireds(
|
|
187
|
-
|
|
246
|
+
def get_requireds(
|
|
247
|
+
self, step: protobuf.Value, composite: BaseComposite
|
|
248
|
+
) -> list[str]:
|
|
249
|
+
requireds: list[str] = []
|
|
188
250
|
for name, required in composite.requireds:
|
|
189
251
|
if len(required.kind) and len(required.apiVersion):
|
|
190
252
|
r = pythonic.Map(kind=required.kind, apiVersion=required.apiVersion)
|
|
@@ -199,11 +261,11 @@ class FunctionRunner(grpcv1.FunctionRunnerService):
|
|
|
199
261
|
requireds.append(name)
|
|
200
262
|
return requireds
|
|
201
263
|
|
|
202
|
-
def process_usages(self, composite):
|
|
264
|
+
def process_usages(self, composite: BaseComposite) -> None:
|
|
203
265
|
if self.crossplane_v1:
|
|
204
|
-
apiVersion =
|
|
266
|
+
apiVersion = "apiextensions.crossplane.io/v1beta1"
|
|
205
267
|
else:
|
|
206
|
-
apiVersion =
|
|
268
|
+
apiVersion = "protection.crossplane.io/v1beta1"
|
|
207
269
|
for _, resource in sorted(entry for entry in composite.resources):
|
|
208
270
|
dependencies = resource.desired._getDependencies
|
|
209
271
|
if dependencies:
|
|
@@ -216,34 +278,41 @@ class FunctionRunner(grpcv1.FunctionRunnerService):
|
|
|
216
278
|
resources = {}
|
|
217
279
|
requireds = {}
|
|
218
280
|
for destination, source in sorted(dependencies.items()):
|
|
219
|
-
name = source.split(
|
|
220
|
-
if (
|
|
221
|
-
name
|
|
222
|
-
name[
|
|
223
|
-
name[
|
|
224
|
-
name[
|
|
281
|
+
name = source.split(".")
|
|
282
|
+
if (
|
|
283
|
+
len(name) > 5
|
|
284
|
+
and name[0] in ("request", "response")
|
|
285
|
+
and name[1] in ("observed", "desired")
|
|
286
|
+
and name[2] == "resources"
|
|
287
|
+
and name[4] == "resource"
|
|
225
288
|
):
|
|
226
289
|
if name[3] not in resources:
|
|
227
290
|
resources[name[3]] = []
|
|
228
|
-
resources[name[3]].append(
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
name
|
|
233
|
-
name[
|
|
291
|
+
resources[name[3]].append(
|
|
292
|
+
f"{'.'.join(destination.split('.')[5:])} = {'.'.join(name[5:])}"
|
|
293
|
+
)
|
|
294
|
+
elif (
|
|
295
|
+
len(name) > 5
|
|
296
|
+
and name[0] == "request"
|
|
297
|
+
and name[1] in ("required_resources", "extra_resources")
|
|
298
|
+
and name[3].startswith("items[")
|
|
299
|
+
and name[3][-1] == "]"
|
|
300
|
+
and name[4] == "resource"
|
|
234
301
|
):
|
|
235
302
|
key = (name[2], int(name[3][6:-1]))
|
|
236
303
|
if key not in requireds:
|
|
237
304
|
requireds[key] = []
|
|
238
|
-
requireds[key].append(
|
|
305
|
+
requireds[key].append(
|
|
306
|
+
f"{'.'.join(destination.split('.')[5:])} = {'.'.join(name[5:])}"
|
|
307
|
+
)
|
|
239
308
|
for name, dependencies in resources.items():
|
|
240
309
|
source = composite.resources[name]
|
|
241
|
-
name = [resource.name, str(source.kind)]
|
|
310
|
+
name: list[str] = [str(resource.name), str(source.kind)]
|
|
242
311
|
if source.metadata.namespace:
|
|
243
312
|
name.append(str(source.metadata.namespace))
|
|
244
313
|
name.append(str(source.observed.metadata.name))
|
|
245
|
-
usage = composite.resources[
|
|
246
|
-
usage.spec.reason =
|
|
314
|
+
usage = composite.resources["_".join(name)](apiVersion, "Usage")
|
|
315
|
+
usage.spec.reason = "\n".join(dependencies)
|
|
247
316
|
usage.spec.replayDeletion = True
|
|
248
317
|
usage.spec.by.apiVersion = resource.apiVersion
|
|
249
318
|
usage.spec.by.kind = resource.kind
|
|
@@ -253,22 +322,34 @@ class FunctionRunner(grpcv1.FunctionRunnerService):
|
|
|
253
322
|
usage.spec.of.resourceRef.name = source.observed.metadata.name
|
|
254
323
|
if not self.crossplane_v1:
|
|
255
324
|
if composite.metadata.namespace:
|
|
256
|
-
if
|
|
257
|
-
|
|
325
|
+
if (
|
|
326
|
+
source.metadata.namespace
|
|
327
|
+
and source.metadata.namespace
|
|
328
|
+
!= composite.metadata.namespace
|
|
329
|
+
):
|
|
330
|
+
usage.spec.of.resourceRef.namespace = (
|
|
331
|
+
source.metadata.namespace
|
|
332
|
+
)
|
|
258
333
|
elif resource.metadata.namespace:
|
|
259
334
|
usage.metadata.namespace = resource.metadata.namespace
|
|
260
|
-
if
|
|
261
|
-
|
|
335
|
+
if (
|
|
336
|
+
source.metadata.namespace
|
|
337
|
+
and source.metadata.namespace
|
|
338
|
+
!= resource.metadata.namespace
|
|
339
|
+
):
|
|
340
|
+
usage.spec.of.resourceRef.namespace = (
|
|
341
|
+
source.metadata.namespace
|
|
342
|
+
)
|
|
262
343
|
else:
|
|
263
|
-
usage.kind =
|
|
344
|
+
usage.kind = "ClusterUsage"
|
|
264
345
|
for key, dependencies in requireds.items():
|
|
265
346
|
source = composite.requireds[key[0]][key[1]]
|
|
266
|
-
name = [resource.name, str(source.kind)]
|
|
347
|
+
name: list[str] = [str(resource.name), str(source.kind)]
|
|
267
348
|
if source.metadata.namespace:
|
|
268
349
|
name.append(str(source.metadata.namespace))
|
|
269
350
|
name.append(str(source.metadata.name))
|
|
270
|
-
usage = composite.resources[
|
|
271
|
-
usage.spec.reason =
|
|
351
|
+
usage = composite.resources["_".join(name)](apiVersion, "Usage")
|
|
352
|
+
usage.spec.reason = "\n".join(dependencies)
|
|
272
353
|
usage.spec.replayDeletion = True
|
|
273
354
|
usage.spec.by.apiVersion = resource.apiVersion
|
|
274
355
|
usage.spec.by.kind = resource.kind
|
|
@@ -278,19 +359,31 @@ class FunctionRunner(grpcv1.FunctionRunnerService):
|
|
|
278
359
|
usage.spec.of.resourceRef.name = source.observed.metadata.name
|
|
279
360
|
if not self.crossplane_v1:
|
|
280
361
|
if composite.metadata.namespace:
|
|
281
|
-
if
|
|
282
|
-
|
|
362
|
+
if (
|
|
363
|
+
source.metadata.namespace
|
|
364
|
+
and source.metadata.namespace
|
|
365
|
+
!= composite.metadata.namespace
|
|
366
|
+
):
|
|
367
|
+
usage.spec.of.resourceRef.namespace = (
|
|
368
|
+
source.metadata.namespace
|
|
369
|
+
)
|
|
283
370
|
elif resource.metadata.namespace:
|
|
284
371
|
usage.metadata.namespace = resource.metadata.namespace
|
|
285
|
-
if
|
|
286
|
-
|
|
372
|
+
if (
|
|
373
|
+
source.metadata.namespace
|
|
374
|
+
and source.metadata.namespace
|
|
375
|
+
!= resource.metadata.namespace
|
|
376
|
+
):
|
|
377
|
+
usage.spec.of.resourceRef.namespace = (
|
|
378
|
+
source.metadata.namespace
|
|
379
|
+
)
|
|
287
380
|
else:
|
|
288
|
-
usage.kind =
|
|
381
|
+
usage.kind = "ClusterUsage"
|
|
289
382
|
|
|
290
|
-
def process_unknowns(self, composite):
|
|
291
|
-
unknownResources = []
|
|
292
|
-
warningResources = []
|
|
293
|
-
fatalResources = []
|
|
383
|
+
def process_unknowns(self, composite: BaseComposite) -> None:
|
|
384
|
+
unknownResources: list[str] = []
|
|
385
|
+
warningResources: list[str] = []
|
|
386
|
+
fatalResources: list[str] = []
|
|
294
387
|
for name, resource in sorted(entry for entry in composite.resources):
|
|
295
388
|
unknowns = resource.desired._getUnknowns
|
|
296
389
|
if unknowns:
|
|
@@ -300,7 +393,9 @@ class FunctionRunner(grpcv1.FunctionRunnerService):
|
|
|
300
393
|
if resource.observed:
|
|
301
394
|
warningResources.append(name)
|
|
302
395
|
warning = True
|
|
303
|
-
if resource.unknownsFatal or (
|
|
396
|
+
if resource.unknownsFatal or (
|
|
397
|
+
resource.unknownsFatal is None and composite.unknownsFatal
|
|
398
|
+
):
|
|
304
399
|
fatalResources.append(name)
|
|
305
400
|
fatal = True
|
|
306
401
|
if composite.logger.isEnabledFor(logging.DEBUG):
|
|
@@ -308,11 +403,17 @@ class FunctionRunner(grpcv1.FunctionRunnerService):
|
|
|
308
403
|
destination = self.trimFullName(destination)
|
|
309
404
|
source = self.trimFullName(source)
|
|
310
405
|
if fatal:
|
|
311
|
-
composite.logger.error(
|
|
406
|
+
composite.logger.error(
|
|
407
|
+
f"Observed unknown: {destination} = {source}"
|
|
408
|
+
)
|
|
312
409
|
elif warning:
|
|
313
|
-
composite.logger.warning(
|
|
410
|
+
composite.logger.warning(
|
|
411
|
+
f"Observed unknown: {destination} = {source}"
|
|
412
|
+
)
|
|
314
413
|
else:
|
|
315
|
-
composite.logger.debug(
|
|
414
|
+
composite.logger.debug(
|
|
415
|
+
f"Desired unknown: {destination} = {source}"
|
|
416
|
+
)
|
|
316
417
|
if resource.observed:
|
|
317
418
|
resource.desired._patchUnknowns(resource.observed)
|
|
318
419
|
elif self.renderUnknowns:
|
|
@@ -322,26 +423,26 @@ class FunctionRunner(grpcv1.FunctionRunnerService):
|
|
|
322
423
|
|
|
323
424
|
if fatalResources:
|
|
324
425
|
level = composite.logger.error
|
|
325
|
-
reason =
|
|
426
|
+
reason = "FatalUnknowns"
|
|
326
427
|
message = f"Observed resources with unknowns: {','.join(fatalResources)}"
|
|
327
428
|
status = False
|
|
328
429
|
result = composite.results.fatal
|
|
329
430
|
elif warningResources:
|
|
330
431
|
level = composite.logger.warning
|
|
331
|
-
reason =
|
|
432
|
+
reason = "ObservedUnknowns"
|
|
332
433
|
message = f"Observed resources with unknowns: {','.join(warningResources)}"
|
|
333
434
|
status = False
|
|
334
435
|
result = composite.results.warning
|
|
335
436
|
elif unknownResources:
|
|
336
437
|
level = composite.logger.info
|
|
337
|
-
reason =
|
|
438
|
+
reason = "DesiredUnknowns"
|
|
338
439
|
message = f"Desired resources with unknowns: {','.join(unknownResources)}"
|
|
339
440
|
status = False
|
|
340
441
|
result = composite.results.info
|
|
341
442
|
else:
|
|
342
443
|
level = None
|
|
343
|
-
reason =
|
|
344
|
-
message =
|
|
444
|
+
reason = "AllComposed"
|
|
445
|
+
message = "All resources are composed"
|
|
345
446
|
status = True
|
|
346
447
|
result = None
|
|
347
448
|
if not composite.logger.isEnabledFor(logging.DEBUG) and level:
|
|
@@ -350,24 +451,27 @@ class FunctionRunner(grpcv1.FunctionRunnerService):
|
|
|
350
451
|
if result:
|
|
351
452
|
result(reason, message)
|
|
352
453
|
|
|
353
|
-
def trimFullName(self, name):
|
|
354
|
-
name = name.split(
|
|
454
|
+
def trimFullName(self, name: str) -> str:
|
|
455
|
+
name: list[str] = name.split(".")
|
|
355
456
|
for values in (
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
457
|
+
("request", "observed", "composite", "resource"),
|
|
458
|
+
("response", "desired", "composite", "resource"),
|
|
459
|
+
("request", "observed", "resources", None, "resource"),
|
|
460
|
+
("response", "desired", "resources", None, "resource"),
|
|
461
|
+
("request", "required_resources", None, "items", None, "resource"),
|
|
462
|
+
("request", "extra_resources", None, "items", None, "resource"),
|
|
362
463
|
):
|
|
363
464
|
if len(values) < len(name):
|
|
364
465
|
ix = 0
|
|
365
466
|
for iv, value in enumerate(values):
|
|
366
|
-
if value:
|
|
367
|
-
if
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
467
|
+
if value and value != name[ix]:
|
|
468
|
+
if (
|
|
469
|
+
not name[ix].startswith(f"{value}[")
|
|
470
|
+
or iv + 1 >= len(values)
|
|
471
|
+
or values[iv + 1]
|
|
472
|
+
):
|
|
473
|
+
break
|
|
474
|
+
continue
|
|
371
475
|
ix += 1
|
|
372
476
|
else:
|
|
373
477
|
ix = 0
|
|
@@ -376,22 +480,21 @@ class FunctionRunner(grpcv1.FunctionRunnerService):
|
|
|
376
480
|
if value == name[ix]:
|
|
377
481
|
del name[ix]
|
|
378
482
|
elif ix:
|
|
379
|
-
name[ix-1] += name[ix][len(value):]
|
|
483
|
+
name[ix - 1] += name[ix][len(value) :]
|
|
380
484
|
del name[ix]
|
|
381
485
|
else:
|
|
382
|
-
name[ix] = name[ix][len(value):]
|
|
486
|
+
name[ix] = name[ix][len(value) :]
|
|
383
487
|
else:
|
|
384
488
|
ix += 1
|
|
385
489
|
break
|
|
386
|
-
return
|
|
490
|
+
return ".".join(name)
|
|
387
491
|
|
|
388
492
|
|
|
389
|
-
def ordinal(ix):
|
|
390
|
-
ix = int(ix)
|
|
493
|
+
def ordinal(ix: int) -> str:
|
|
391
494
|
if 11 <= (ix % 100) <= 13:
|
|
392
|
-
suffix =
|
|
495
|
+
suffix = "th"
|
|
393
496
|
else:
|
|
394
|
-
suffix = [
|
|
497
|
+
suffix = ["th", "st", "nd", "rd", "th"][min(ix % 10, 4)]
|
|
395
498
|
return str(ix) + suffix
|
|
396
499
|
|
|
397
500
|
|