crossplane-function-pythonic 0.0.10__py3-none-any.whl → 0.1.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/__init__.py +1 -4
- crossplane/pythonic/composite.py +68 -18
- crossplane/pythonic/function.py +176 -88
- crossplane/pythonic/main.py +6 -1
- crossplane/pythonic/packages.py +61 -70
- crossplane/pythonic/protobuf.py +701 -354
- {crossplane_function_pythonic-0.0.10.dist-info → crossplane_function_pythonic-0.1.0.dist-info}/METADATA +24 -2
- crossplane_function_pythonic-0.1.0.dist-info/RECORD +11 -0
- crossplane_function_pythonic-0.0.10.dist-info/RECORD +0 -11
- {crossplane_function_pythonic-0.0.10.dist-info → crossplane_function_pythonic-0.1.0.dist-info}/WHEEL +0 -0
- {crossplane_function_pythonic-0.0.10.dist-info → crossplane_function_pythonic-0.1.0.dist-info}/entry_points.txt +0 -0
- {crossplane_function_pythonic-0.0.10.dist-info → crossplane_function_pythonic-0.1.0.dist-info}/licenses/LICENSE +0 -0
crossplane/pythonic/__init__.py
CHANGED
@@ -1,10 +1,7 @@
|
|
1
1
|
|
2
|
-
import base64
|
3
2
|
|
4
3
|
from .composite import BaseComposite
|
5
|
-
from .protobuf import append, Map, List, Unknown, Yaml, Json
|
6
|
-
B64Encode = lambda s: base64.b64encode(s.encode('utf-8')).decode('utf-8')
|
7
|
-
B64Decode = lambda s: base64.b64decode(s.encode('utf-8')).decode('utf-8')
|
4
|
+
from .protobuf import append, Map, List, Unknown, Yaml, Json, B64Encode, B64Decode
|
8
5
|
|
9
6
|
__all__ = [
|
10
7
|
'BaseComposite',
|
crossplane/pythonic/composite.py
CHANGED
@@ -1,5 +1,6 @@
|
|
1
1
|
|
2
2
|
import datetime
|
3
|
+
from google.protobuf.duration_pb2 import Duration
|
3
4
|
from crossplane.function.proto.v1 import run_function_pb2 as fnv1
|
4
5
|
|
5
6
|
from . import protobuf
|
@@ -9,8 +10,18 @@ _notset = object()
|
|
9
10
|
|
10
11
|
|
11
12
|
class BaseComposite:
|
12
|
-
def __init__(self, request,
|
13
|
+
def __init__(self, request, logger):
|
13
14
|
self.request = protobuf.Message(None, 'request', request.DESCRIPTOR, request, 'Function Request')
|
15
|
+
response = fnv1.RunFunctionResponse(
|
16
|
+
meta=fnv1.ResponseMeta(
|
17
|
+
tag=request.meta.tag,
|
18
|
+
ttl=Duration(
|
19
|
+
seconds=60,
|
20
|
+
),
|
21
|
+
),
|
22
|
+
desired=request.desired,
|
23
|
+
context=request.context,
|
24
|
+
)
|
14
25
|
self.response = protobuf.Message(None, 'response', response.DESCRIPTOR, response)
|
15
26
|
self.logger = logger
|
16
27
|
self.credentials = Credentials(self.request)
|
@@ -20,6 +31,7 @@ class BaseComposite:
|
|
20
31
|
self.resources = Resources(self)
|
21
32
|
self.unknownsFatal = True
|
22
33
|
self.autoReady = True
|
34
|
+
self.usages = False
|
23
35
|
|
24
36
|
observed = self.request.observed.composite
|
25
37
|
desired = self.response.desired.composite
|
@@ -36,11 +48,23 @@ class BaseComposite:
|
|
36
48
|
|
37
49
|
@property
|
38
50
|
def ttl(self):
|
39
|
-
|
51
|
+
if self.response.meta.ttl.nanos:
|
52
|
+
return float(self.response.meta.ttl.seconds) + (float(self.response.meta.ttl.nanos) / 1000000000.0)
|
53
|
+
return int(self.response.meta.ttl.seconds)
|
40
54
|
|
41
55
|
@ttl.setter
|
42
56
|
def ttl(self, ttl):
|
43
|
-
|
57
|
+
if isinstance(ttl, int):
|
58
|
+
self.response.meta.ttl.seconds = ttl
|
59
|
+
self.response.meta.ttl.nanos = 0
|
60
|
+
elif isinstance(ttl, float):
|
61
|
+
self.response.meta.ttl.seconds = int(ttl)
|
62
|
+
if ttl.is_integer():
|
63
|
+
self.response.meta.ttl.nanos = 0
|
64
|
+
else:
|
65
|
+
self.response.meta.ttl.nanos = int((ttl - int(self.response.meta.ttl.seconds)) * 1000000000)
|
66
|
+
else:
|
67
|
+
raise ValueError('ttl must be an int or float')
|
44
68
|
|
45
69
|
@property
|
46
70
|
def ready(self):
|
@@ -55,7 +79,7 @@ class BaseComposite:
|
|
55
79
|
def ready(self, ready):
|
56
80
|
if ready:
|
57
81
|
ready = fnv1.Ready.READY_TRUE
|
58
|
-
elif ready == None or (isinstance(ready, protobuf.
|
82
|
+
elif ready == None or (isinstance(ready, protobuf.Value) and ready._isUnknown):
|
59
83
|
ready = fnv1.Ready.READY_UNSPECIFIED
|
60
84
|
else:
|
61
85
|
ready = fnv1.Ready.READY_FALSE
|
@@ -73,22 +97,46 @@ class Credentials:
|
|
73
97
|
return self[key]
|
74
98
|
|
75
99
|
def __getitem__(self, key):
|
76
|
-
return self._request.credentials[key]
|
100
|
+
return Credential(self._request.credentials[key])
|
77
101
|
|
78
102
|
def __bool__(self):
|
79
|
-
return bool(_request.credentials)
|
103
|
+
return bool(self._request.credentials)
|
80
104
|
|
81
105
|
def __len__(self):
|
82
106
|
return len(self._request.credentials)
|
83
107
|
|
84
108
|
def __contains__(self, key):
|
85
|
-
return key in _request.credentials
|
109
|
+
return key in self._request.credentials
|
86
110
|
|
87
111
|
def __iter__(self):
|
88
112
|
for key, resource in self._request.credentials:
|
89
113
|
yield key, self[key]
|
90
114
|
|
91
115
|
|
116
|
+
class Credential:
|
117
|
+
def __init__(self, credential):
|
118
|
+
self.__dict__['_credential'] = credential
|
119
|
+
|
120
|
+
def __getattr__(self, key):
|
121
|
+
return self[key]
|
122
|
+
|
123
|
+
def __getitem__(self, key):
|
124
|
+
return self._credential.credential_data.data[key]
|
125
|
+
|
126
|
+
def __bool__(self):
|
127
|
+
return bool(self._credential.credential_data.data)
|
128
|
+
|
129
|
+
def __len__(self):
|
130
|
+
return len(self._credential.credential_data.data)
|
131
|
+
|
132
|
+
def __contains__(self, key):
|
133
|
+
return key in self._credential.credential_data.data
|
134
|
+
|
135
|
+
def __iter__(self):
|
136
|
+
for key, resource in self._credential.credential_data.data:
|
137
|
+
yield key, self[key]
|
138
|
+
|
139
|
+
|
92
140
|
class Resources:
|
93
141
|
def __init__(self, composite):
|
94
142
|
self.__dict__['_composite'] = composite
|
@@ -137,6 +185,7 @@ class Resource:
|
|
137
185
|
self.connection = Connection(observed)
|
138
186
|
self.unknownsFatal = None
|
139
187
|
self.autoReady = None
|
188
|
+
self.usages = None
|
140
189
|
|
141
190
|
def __call__(self, apiVersion=_notset, kind=_notset, namespace=_notset, name=_notset):
|
142
191
|
self.desired()
|
@@ -152,7 +201,7 @@ class Resource:
|
|
152
201
|
|
153
202
|
@property
|
154
203
|
def apiVersion(self):
|
155
|
-
return self.
|
204
|
+
return self.desired.apiVersion
|
156
205
|
|
157
206
|
@apiVersion.setter
|
158
207
|
def apiVersion(self, apiVersion):
|
@@ -160,7 +209,7 @@ class Resource:
|
|
160
209
|
|
161
210
|
@property
|
162
211
|
def kind(self):
|
163
|
-
return self.
|
212
|
+
return self.desired.kind
|
164
213
|
|
165
214
|
@kind.setter
|
166
215
|
def kind(self, kind):
|
@@ -218,7 +267,7 @@ class Resource:
|
|
218
267
|
def ready(self, ready):
|
219
268
|
if ready:
|
220
269
|
ready = fnv1.Ready.READY_TRUE
|
221
|
-
elif ready == None or (isinstance(ready, protobuf.
|
270
|
+
elif ready == None or (isinstance(ready, protobuf.Value) and ready._isUnknown):
|
222
271
|
ready = fnv1.Ready.READY_UNSPECIFIED
|
223
272
|
else:
|
224
273
|
ready = fnv1.Ready.READY_FALSE
|
@@ -329,8 +378,8 @@ class RequiredResources:
|
|
329
378
|
elif isinstance(entry, (list, tuple)):
|
330
379
|
self._selector.match_labels.labels[entry[0]] = entry[1]
|
331
380
|
|
332
|
-
def __getitem__(self,
|
333
|
-
return RequiredResource(self.name, self._resources.items[
|
381
|
+
def __getitem__(self, ix):
|
382
|
+
return RequiredResource(self.name, ix, self._resources.items[ix])
|
334
383
|
|
335
384
|
def __bool__(self):
|
336
385
|
return bool(self._resources.items)
|
@@ -344,8 +393,9 @@ class RequiredResources:
|
|
344
393
|
|
345
394
|
|
346
395
|
class RequiredResource:
|
347
|
-
def __init__(self, name, resource):
|
396
|
+
def __init__(self, name, ix, resource):
|
348
397
|
self.name = name
|
398
|
+
self.ix = ix
|
349
399
|
self.observed = resource.resource
|
350
400
|
self.apiVersion = self.observed.apiVersion
|
351
401
|
self.kind = self.observed.kind
|
@@ -440,7 +490,7 @@ class Condition(protobuf.ProtobufValue):
|
|
440
490
|
condition.status = fnv1.Status.STATUS_CONDITION_TRUE
|
441
491
|
elif status == None:
|
442
492
|
condition.status = fnv1.Status.STATUS_CONDITION_UNKNOWN
|
443
|
-
elif isinstance(status, protobuf.
|
493
|
+
elif isinstance(status, protobuf.Value) and status._isUnknown:
|
444
494
|
condition.status = fnv1.Status.STATUS_CONDITION_UNSPECIFIED
|
445
495
|
else:
|
446
496
|
condition.status = fnv1.Status.STATUS_CONDITION_FALSE
|
@@ -474,7 +524,7 @@ class Condition(protobuf.ProtobufValue):
|
|
474
524
|
if observed.type == self.type:
|
475
525
|
time = observed.lastTransitionTime
|
476
526
|
if time:
|
477
|
-
return datetime.datetime.fromisoformat(time)
|
527
|
+
return datetime.datetime.fromisoformat(str(time))
|
478
528
|
return None
|
479
529
|
|
480
530
|
@property
|
@@ -487,7 +537,7 @@ class Condition(protobuf.ProtobufValue):
|
|
487
537
|
condition = self._find_condition(True)
|
488
538
|
if claim:
|
489
539
|
condition.target = fnv1.Target.TARGET_COMPOSITE_AND_CLAIM
|
490
|
-
elif claim == None or (isinstance(claim, protobuf.
|
540
|
+
elif claim == None or (isinstance(claim, protobuf.Value) and claim._isUnknown):
|
491
541
|
condition.target = fnv1.Target.TARGET_UNSPECIFIED
|
492
542
|
else:
|
493
543
|
condition.target = fnv1.Target.TARGET_COMPOSITE
|
@@ -587,7 +637,7 @@ class Events:
|
|
587
637
|
def __getitem__(self, key):
|
588
638
|
if key >= len(self._results):
|
589
639
|
return Event()
|
590
|
-
return Event(self._results[
|
640
|
+
return Event(self._results[key])
|
591
641
|
|
592
642
|
def __iter__(self):
|
593
643
|
for ix in range(len(self._results)):
|
@@ -664,7 +714,7 @@ class Event:
|
|
664
714
|
if bool(self):
|
665
715
|
if claim:
|
666
716
|
self._result.target = fnv1.Target.TARGET_COMPOSITE_AND_CLAIM
|
667
|
-
elif claim == None or (isinstance(claim, protobuf.
|
717
|
+
elif claim == None or (isinstance(claim, protobuf.Value) and claim._isUnknown):
|
668
718
|
self._result.target = fnv1.Target.TARGET_UNSPECIFIED
|
669
719
|
else:
|
670
720
|
self._result.target = fnv1.Target.TARGET_COMPOSITE
|
crossplane/pythonic/function.py
CHANGED
@@ -1,38 +1,26 @@
|
|
1
1
|
"""A Crossplane composition function."""
|
2
2
|
|
3
3
|
import asyncio
|
4
|
-
import base64
|
5
|
-
import builtins
|
6
4
|
import importlib
|
7
5
|
import inspect
|
8
6
|
import logging
|
9
7
|
import sys
|
10
8
|
|
11
9
|
import grpc
|
12
|
-
import crossplane.function.response
|
13
10
|
from crossplane.function.proto.v1 import run_function_pb2 as fnv1
|
14
11
|
from crossplane.function.proto.v1 import run_function_pb2_grpc as grpcv1
|
15
12
|
from .. import pythonic
|
16
13
|
|
17
|
-
builtins.BaseComposite = pythonic.BaseComposite
|
18
|
-
builtins.append = pythonic.append
|
19
|
-
builtins.Map = pythonic.Map
|
20
|
-
builtins.List = pythonic.List
|
21
|
-
builtins.Unknown = pythonic.Unknown
|
22
|
-
builtins.Yaml = pythonic.Yaml
|
23
|
-
builtins.Json = pythonic.Json
|
24
|
-
builtins.B64Encode = pythonic.B64Encode
|
25
|
-
builtins.B64Decode = pythonic.B64Decode
|
26
|
-
|
27
14
|
logger = logging.getLogger(__name__)
|
28
15
|
|
29
16
|
|
30
17
|
class FunctionRunner(grpcv1.FunctionRunnerService):
|
31
18
|
"""A FunctionRunner handles gRPC RunFunctionRequests."""
|
32
19
|
|
33
|
-
def __init__(self, debug=False):
|
20
|
+
def __init__(self, debug=False, renderUnknowns=False):
|
34
21
|
"""Create a new FunctionRunner."""
|
35
22
|
self.debug = debug
|
23
|
+
self.renderUnknowns = renderUnknowns
|
36
24
|
self.clazzes = {}
|
37
25
|
|
38
26
|
def invalidate_module(self, module):
|
@@ -46,9 +34,8 @@ class FunctionRunner(grpcv1.FunctionRunnerService):
|
|
46
34
|
) -> fnv1.RunFunctionResponse:
|
47
35
|
try:
|
48
36
|
return await self.run_function(request)
|
49
|
-
except:
|
50
|
-
|
51
|
-
raise
|
37
|
+
except Exception as e:
|
38
|
+
return self.fatal(request, logger, 'RunFunction', e)
|
52
39
|
|
53
40
|
async def run_function(self, request):
|
54
41
|
composite = request.observed.composite.resource
|
@@ -56,27 +43,22 @@ class FunctionRunner(grpcv1.FunctionRunnerService):
|
|
56
43
|
name.append(composite['kind'])
|
57
44
|
name.append(composite['metadata']['name'])
|
58
45
|
logger = logging.getLogger('.'.join(name))
|
59
|
-
if 'iteration' in request.context:
|
60
|
-
request.context['iteration'] = request.context['iteration'] + 1
|
61
|
-
else:
|
62
|
-
request.context['iteration'] = 1
|
63
|
-
logger.debug(f"Starting compose, {ordinal(request.context['iteration'])} pass")
|
64
|
-
|
65
|
-
response = crossplane.function.response.to(request)
|
66
46
|
|
67
47
|
if composite['apiVersion'] == 'pythonic.fortra.com/v1alpha1' and composite['kind'] == 'Composite':
|
68
|
-
if 'composite' not in composite['spec']:
|
69
|
-
|
70
|
-
crossplane.function.response.fatal(response, 'Missing spec "composite"')
|
71
|
-
return response
|
48
|
+
if 'spec' not in composite or 'composite' not in composite['spec']:
|
49
|
+
return self.fatal(request, logger, 'Missing spec "composite"')
|
72
50
|
composite = composite['spec']['composite']
|
73
51
|
else:
|
74
52
|
if 'composite' not in request.input:
|
75
|
-
|
76
|
-
crossplane.function.response.fatal(response, 'Missing input "composite"')
|
77
|
-
return response
|
53
|
+
return self.fatal(request, logger, 'Missing input "composite"')
|
78
54
|
composite = request.input['composite']
|
79
55
|
|
56
|
+
# Ideally this is something the Function API provides
|
57
|
+
if 'step' in request.input:
|
58
|
+
step = request.input['step']
|
59
|
+
else:
|
60
|
+
step = str(hash(composite))
|
61
|
+
|
80
62
|
clazz = self.clazzes.get(composite)
|
81
63
|
if not clazz:
|
82
64
|
if '\n' in composite:
|
@@ -84,82 +66,174 @@ class FunctionRunner(grpcv1.FunctionRunnerService):
|
|
84
66
|
try:
|
85
67
|
exec(composite, module.__dict__)
|
86
68
|
except Exception as e:
|
87
|
-
|
88
|
-
crossplane.function.response.fatal(response, f"Exec exception: {e}")
|
89
|
-
return response
|
69
|
+
return self.fatal(request, logger, 'Exec', e)
|
90
70
|
for field in dir(module):
|
91
71
|
value = getattr(module, field)
|
92
|
-
if inspect.isclass(value) and issubclass(value, BaseComposite) and value != BaseComposite:
|
72
|
+
if inspect.isclass(value) and issubclass(value, pythonic.BaseComposite) and value != pythonic.BaseComposite:
|
93
73
|
if clazz:
|
94
|
-
|
95
|
-
crossplane.function.response.fatal(response, 'Composite script has multiple BaseComposite classes')
|
96
|
-
return response
|
74
|
+
return self.fatal(request, logger, 'Composite script has multiple BaseComposite classes')
|
97
75
|
clazz = value
|
98
76
|
if not clazz:
|
99
|
-
|
100
|
-
crossplane.function.response.fatal(response, 'Composite script does have have a BaseComposite class')
|
101
|
-
return response
|
77
|
+
return self.fatal(request, logger, 'Composite script does not have a BaseComposite class')
|
102
78
|
else:
|
103
79
|
composite = composite.rsplit('.', 1)
|
104
80
|
if len(composite) == 1:
|
105
|
-
|
106
|
-
crossplane.function.response.fatal(response, f"Composite class name does not include module: {composite[0]}")
|
107
|
-
return response
|
81
|
+
return self.fatal(request, logger, f"Composite class name does not include module: {composite[0]}")
|
108
82
|
try:
|
109
83
|
module = importlib.import_module(composite[0])
|
110
84
|
except Exception as e:
|
111
|
-
|
112
|
-
crossplane.function.response.fatal(response, f"Import module exception: {e}")
|
113
|
-
return response
|
85
|
+
return self.fatal(request, logger, 'Import module', e)
|
114
86
|
clazz = getattr(module, composite[1], None)
|
115
87
|
if not clazz:
|
116
|
-
|
117
|
-
crossplane.function.response.fatal(response, f"{composite[0]} did not define: {composite[1]}")
|
118
|
-
return response
|
88
|
+
return self.fatal(request, logger, f"{composite[0]} does not define: {composite[1]}")
|
119
89
|
composite = '.'.join(composite)
|
120
90
|
if not inspect.isclass(clazz):
|
121
|
-
|
122
|
-
|
123
|
-
return
|
124
|
-
if not issubclass(clazz, BaseComposite):
|
125
|
-
logger.error(f"{composite} is not a subclass of BaseComposite")
|
126
|
-
crossplane.function.response.fatal(response, f"{composite} is not a subclass of BaseComposite")
|
127
|
-
return response
|
91
|
+
return self.fatal(request, logger, f"{composite} is not a class")
|
92
|
+
if not issubclass(clazz, pythonic.BaseComposite):
|
93
|
+
return self.fatal(request, logger, f"{composite} is not a subclass of BaseComposite")
|
128
94
|
self.clazzes[composite] = clazz
|
129
95
|
|
130
96
|
try:
|
131
|
-
composite = clazz(request,
|
97
|
+
composite = clazz(request, logger)
|
132
98
|
except Exception as e:
|
133
|
-
|
134
|
-
|
135
|
-
|
99
|
+
return self.fatal(request, logger, 'Instantiate', e)
|
100
|
+
|
101
|
+
step = composite.context._pythonic[step]
|
102
|
+
iteration = int(step.iteration) + 1
|
103
|
+
step.iteration = iteration
|
104
|
+
composite.context.iteration = iteration
|
105
|
+
logger.debug(f"Starting compose, {ordinal(len(composite.context._pythonic))} step, {ordinal(iteration)} pass")
|
136
106
|
|
137
107
|
try:
|
138
108
|
result = composite.compose()
|
139
109
|
if asyncio.iscoroutine(result):
|
140
110
|
await result
|
141
111
|
except Exception as e:
|
142
|
-
|
143
|
-
|
144
|
-
|
112
|
+
return self.fatal(request, logger, 'Compose', e)
|
113
|
+
|
114
|
+
if requireds := self.get_requireds(step, composite):
|
115
|
+
logger.info(f"Requireds requested: {','.join(requireds)}")
|
116
|
+
else:
|
117
|
+
self.process_usages(composite)
|
118
|
+
self.process_unknowns(composite)
|
119
|
+
self.process_auto_readies(composite)
|
120
|
+
logger.info('Completed compose')
|
145
121
|
|
146
|
-
|
122
|
+
return composite.response._message
|
123
|
+
|
124
|
+
def fatal(self, request, logger, message, exception=None):
|
125
|
+
if exception:
|
126
|
+
message += ' exceptiion'
|
127
|
+
logger.exception(message)
|
128
|
+
m = str(exception)
|
129
|
+
if not m:
|
130
|
+
m = exception.__class__.__name__
|
131
|
+
message += ': ' + m
|
132
|
+
else:
|
133
|
+
logger.error(message)
|
134
|
+
return fnv1.RunFunctionResponse(
|
135
|
+
meta=fnv1.ResponseMeta(
|
136
|
+
tag=request.meta.tag,
|
137
|
+
),
|
138
|
+
results=[
|
139
|
+
fnv1.Result(
|
140
|
+
severity=fnv1.SEVERITY_FATAL,
|
141
|
+
message=message,
|
142
|
+
)
|
143
|
+
]
|
144
|
+
)
|
145
|
+
|
146
|
+
def get_requireds(self, step, composite):
|
147
|
+
requireds = []
|
147
148
|
for name, required in composite.requireds:
|
148
149
|
if required.apiVersion and required.kind:
|
149
|
-
r = Map(apiVersion=required.apiVersion, kind=required.kind)
|
150
|
+
r = pythonic.Map(apiVersion=required.apiVersion, kind=required.kind)
|
150
151
|
if required.namespace:
|
151
152
|
r.namespace = required.namespace
|
152
153
|
if required.matchName:
|
153
154
|
r.matchName = required.matchName
|
154
155
|
for key, value in required.matchLabels:
|
155
156
|
r.matchLabels[key] = value
|
156
|
-
if r !=
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
157
|
+
if r != step.requireds[name]:
|
158
|
+
step.requireds[name] = r
|
159
|
+
requireds.append(name)
|
160
|
+
return requireds
|
161
|
+
|
162
|
+
def process_usages(self, composite):
|
163
|
+
for _, resource in sorted(entry for entry in composite.resources):
|
164
|
+
dependencies = resource.desired._getDependencies
|
165
|
+
if dependencies:
|
166
|
+
if self.debug:
|
167
|
+
for destination, source in sorted(dependencies.items()):
|
168
|
+
destination = self.trimFullName(destination)
|
169
|
+
source = self.trimFullName(source)
|
170
|
+
composite.logger.debug(f"Dependency: {destination} = {source}")
|
171
|
+
if resource.usages or (resource.usages is None and composite.usages):
|
172
|
+
resources = {}
|
173
|
+
requireds = {}
|
174
|
+
for destination, source in sorted(dependencies.items()):
|
175
|
+
name = source.split('.')
|
176
|
+
if (len(name) > 5 and
|
177
|
+
name[0] == 'request' and
|
178
|
+
name[1] == 'observed' and
|
179
|
+
name[2] == 'resources' and
|
180
|
+
name[4] == 'resource'
|
181
|
+
):
|
182
|
+
if name[3] not in resources:
|
183
|
+
resources[name[3]] = []
|
184
|
+
resources[name[3]].append(f"{'.'.join(destination.split('.')[5:])} = {'.'.join(name[5:])}")
|
185
|
+
elif (len(name) > 5 and
|
186
|
+
name[0] == 'request' and
|
187
|
+
name[1] == 'extra_resources' and
|
188
|
+
name[3].startswith('items[') and name[3][-1] == ']' and
|
189
|
+
name[4] == 'resource'
|
190
|
+
):
|
191
|
+
key = (name[2], int(name[3][6:-1]))
|
192
|
+
if key not in requireds:
|
193
|
+
requireds[key] = []
|
194
|
+
requireds[key].append(f"{'.'.join(destination.split('.')[5:])} = {'.'.join(name[5:])}")
|
195
|
+
for name, dependencies in resources.items():
|
196
|
+
source = composite.resources[name]
|
197
|
+
name = [resource.name, str(source.kind)]
|
198
|
+
if source.metadata.namespace:
|
199
|
+
name.append(str(source.metadata.namespace))
|
200
|
+
name.append(str(source.observed.metadata.name))
|
201
|
+
usage = composite.resources['_'.join(name)]('apiextensions.crossplane.io/v1beta1', 'Usage')
|
202
|
+
#usage = composite.resources['_'.join(name)]('protection.crossplane.io/v1beta1', 'Usage')
|
203
|
+
if resource.metadata.namespace:
|
204
|
+
usage.metadata.namespace = resource.metadata.namespace
|
205
|
+
usage.spec.reason = '\n'.join(dependencies)
|
206
|
+
usage.spec.replayDeletion = True
|
207
|
+
usage.spec.by.apiVersion = resource.apiVersion
|
208
|
+
usage.spec.by.kind = resource.kind
|
209
|
+
usage.spec.by.resourceRef.name = resource.observed.metadata.name
|
210
|
+
usage.spec.of.apiVersion = source.apiVersion
|
211
|
+
usage.spec.of.kind = source.kind
|
212
|
+
if source.metadata.namespace:
|
213
|
+
usage.spec.of.resourceRef.namespace = source.metadata.namespace
|
214
|
+
usage.spec.of.resourceRef.name = source.observed.metadata.name
|
215
|
+
for key, dependencies in requireds.items():
|
216
|
+
source = composite.requireds[key[0]][key[1]]
|
217
|
+
name = [resource.name, str(source.kind)]
|
218
|
+
if source.metadata.namespace:
|
219
|
+
name.append(str(source.metadata.namespace))
|
220
|
+
name.append(str(source.metadata.name))
|
221
|
+
usage = composite.resources['_'.join(name)]('apiextensions.crossplane.io/v1beta1', 'Usage')
|
222
|
+
#usage = composite.resources['_'.join(name)]('protection.crossplane.io/v1beta1', 'Usage')
|
223
|
+
if resource.metadata.namespace:
|
224
|
+
usage.metadata.namespace = resource.metadata.namespace
|
225
|
+
usage.spec.reason = '\n'.join(dependencies)
|
226
|
+
usage.spec.replayDeletion = True
|
227
|
+
usage.spec.by.apiVersion = resource.apiVersion
|
228
|
+
usage.spec.by.kind = resource.kind
|
229
|
+
usage.spec.by.resourceRef.name = resource.observed.metadata.name
|
230
|
+
usage.spec.of.apiVersion = source.apiVersion
|
231
|
+
usage.spec.of.kind = source.kind
|
232
|
+
if source.metadata.namespace:
|
233
|
+
usage.spec.of.resourceRef.namespace = source.metadata.namespace
|
234
|
+
usage.spec.of.resourceRef.name = source.observed.metadata.name
|
162
235
|
|
236
|
+
def process_unknowns(self, composite):
|
163
237
|
unknownResources = []
|
164
238
|
warningResources = []
|
165
239
|
fatalResources = []
|
@@ -180,30 +254,32 @@ class FunctionRunner(grpcv1.FunctionRunnerService):
|
|
180
254
|
destination = self.trimFullName(destination)
|
181
255
|
source = self.trimFullName(source)
|
182
256
|
if fatal:
|
183
|
-
logger.error(f'Observed unknown: {destination} = {source}')
|
257
|
+
composite.logger.error(f'Observed unknown: {destination} = {source}')
|
184
258
|
elif warning:
|
185
|
-
logger.warning(f'Observed unknown: {destination} = {source}')
|
259
|
+
composite.logger.warning(f'Observed unknown: {destination} = {source}')
|
186
260
|
else:
|
187
|
-
logger.debug(f'Desired unknown: {destination} = {source}')
|
261
|
+
composite.logger.debug(f'Desired unknown: {destination} = {source}')
|
188
262
|
if resource.observed:
|
189
263
|
resource.desired._patchUnknowns(resource.observed)
|
264
|
+
elif self.renderUnknowns:
|
265
|
+
resource.desired._renderUnknowns(self.trimFullName)
|
190
266
|
else:
|
191
267
|
del composite.resources[name]
|
192
268
|
|
193
269
|
if fatalResources:
|
194
|
-
level = logger.error
|
270
|
+
level = composite.logger.error
|
195
271
|
reason = 'FatalUnknowns'
|
196
272
|
message = f"Observed resources with unknowns: {','.join(fatalResources)}"
|
197
273
|
status = False
|
198
274
|
event = composite.events.fatal
|
199
275
|
elif warningResources:
|
200
|
-
level = logger.warning
|
276
|
+
level = composite.logger.warning
|
201
277
|
reason = 'ObservedUnknowns'
|
202
278
|
message = f"Observed resources with unknowns: {','.join(warningResources)}"
|
203
279
|
status = False
|
204
280
|
event = composite.events.warning
|
205
281
|
elif unknownResources:
|
206
|
-
level = logger.info
|
282
|
+
level = composite.logger.info
|
207
283
|
reason = 'DesiredUnknowns'
|
208
284
|
message = f"Desired resources with unknowns: {','.join(unknownResources)}"
|
209
285
|
status = False
|
@@ -220,26 +296,30 @@ class FunctionRunner(grpcv1.FunctionRunnerService):
|
|
220
296
|
if event:
|
221
297
|
event(reason, message)
|
222
298
|
|
299
|
+
def process_auto_readies(self, composite):
|
223
300
|
for name, resource in composite.resources:
|
224
301
|
if resource.autoReady or (resource.autoReady is None and composite.autoReady):
|
225
302
|
if resource.ready is None:
|
226
303
|
if resource.conditions.Ready.status:
|
227
304
|
resource.ready = True
|
228
305
|
|
229
|
-
logger.info('Completed compose')
|
230
|
-
return response
|
231
|
-
|
232
306
|
def trimFullName(self, name):
|
233
307
|
name = name.split('.')
|
234
308
|
for values in (
|
309
|
+
('request', 'observed', 'composite', 'resource'),
|
235
310
|
('request', 'observed', 'resources', None, 'resource'),
|
236
|
-
('request', 'extra_resources', None, 'items', 'resource'),
|
311
|
+
('request', 'extra_resources', None, 'items', None, 'resource'),
|
237
312
|
('response', 'desired', 'resources', None, 'resource'),
|
238
313
|
):
|
239
|
-
if len(values)
|
240
|
-
|
241
|
-
|
242
|
-
|
314
|
+
if len(values) < len(name):
|
315
|
+
ix = 0
|
316
|
+
for iv, value in enumerate(values):
|
317
|
+
if value:
|
318
|
+
if value != name[ix]:
|
319
|
+
if not name[ix].startswith(f"{values[iv]}[") or iv+1 >= len(values) or values[iv+1]:
|
320
|
+
break
|
321
|
+
continue
|
322
|
+
ix += 1
|
243
323
|
else:
|
244
324
|
ix = 0
|
245
325
|
for value in values:
|
@@ -251,7 +331,6 @@ class FunctionRunner(grpcv1.FunctionRunnerService):
|
|
251
331
|
del name[ix]
|
252
332
|
else:
|
253
333
|
name[ix] = name[ix][len(value):]
|
254
|
-
ix += 1
|
255
334
|
else:
|
256
335
|
ix += 1
|
257
336
|
break
|
@@ -268,4 +347,13 @@ def ordinal(ix):
|
|
268
347
|
|
269
348
|
|
270
349
|
class Module:
|
271
|
-
|
350
|
+
def __init__(self):
|
351
|
+
self.BaseComposite = pythonic.BaseComposite
|
352
|
+
self.append = pythonic.append
|
353
|
+
self.Map = pythonic.Map
|
354
|
+
self.List = pythonic.List
|
355
|
+
self.Unknown = pythonic.Unknown
|
356
|
+
self.Yaml = pythonic.Yaml
|
357
|
+
self.Json = pythonic.Json
|
358
|
+
self.B64Encode = pythonic.B64Encode
|
359
|
+
self.B64Decode = pythonic.B64Decode
|
crossplane/pythonic/main.py
CHANGED
@@ -92,6 +92,11 @@ class Main:
|
|
92
92
|
action='store_true',
|
93
93
|
help='Allow oversized protobuf messages'
|
94
94
|
)
|
95
|
+
parser.add_argument(
|
96
|
+
'--render-unknowns',
|
97
|
+
action='store_true',
|
98
|
+
help='Render resources with unknowns, useful during local develomment'
|
99
|
+
)
|
95
100
|
args = parser.parse_args()
|
96
101
|
if not args.tls_certs_dir and not args.insecure:
|
97
102
|
print('Either --tls-certs-dir or --insecure must be specified', file=sys.stderr)
|
@@ -117,7 +122,7 @@ class Main:
|
|
117
122
|
api_implementation._c_module.SetAllowOversizeProtos(True)
|
118
123
|
|
119
124
|
grpc.aio.init_grpc_aio()
|
120
|
-
grpc_runner = function.FunctionRunner(args.debug)
|
125
|
+
grpc_runner = function.FunctionRunner(args.debug, args.render_unknowns)
|
121
126
|
grpc_server = grpc.aio.server()
|
122
127
|
grpcv1.add_FunctionRunnerServiceServicer_to_server(grpc_runner, grpc_server)
|
123
128
|
if args.insecure:
|