crossplane-function-pythonic 0.0.11__py3-none-any.whl → 0.1.1__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 +16 -13
- crossplane/pythonic/function.py +121 -38
- crossplane/pythonic/protobuf.py +647 -324
- {crossplane_function_pythonic-0.0.11.dist-info → crossplane_function_pythonic-0.1.1.dist-info}/METADATA +26 -4
- crossplane_function_pythonic-0.1.1.dist-info/RECORD +11 -0
- crossplane_function_pythonic-0.0.11.dist-info/RECORD +0 -11
- {crossplane_function_pythonic-0.0.11.dist-info → crossplane_function_pythonic-0.1.1.dist-info}/WHEEL +0 -0
- {crossplane_function_pythonic-0.0.11.dist-info → crossplane_function_pythonic-0.1.1.dist-info}/entry_points.txt +0 -0
- {crossplane_function_pythonic-0.0.11.dist-info → crossplane_function_pythonic-0.1.1.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
@@ -31,6 +31,7 @@ class BaseComposite:
|
|
31
31
|
self.resources = Resources(self)
|
32
32
|
self.unknownsFatal = True
|
33
33
|
self.autoReady = True
|
34
|
+
self.usages = False
|
34
35
|
|
35
36
|
observed = self.request.observed.composite
|
36
37
|
desired = self.response.desired.composite
|
@@ -49,7 +50,7 @@ class BaseComposite:
|
|
49
50
|
def ttl(self):
|
50
51
|
if self.response.meta.ttl.nanos:
|
51
52
|
return float(self.response.meta.ttl.seconds) + (float(self.response.meta.ttl.nanos) / 1000000000.0)
|
52
|
-
return self.response.meta.ttl.seconds
|
53
|
+
return int(self.response.meta.ttl.seconds)
|
53
54
|
|
54
55
|
@ttl.setter
|
55
56
|
def ttl(self, ttl):
|
@@ -61,7 +62,7 @@ class BaseComposite:
|
|
61
62
|
if ttl.is_integer():
|
62
63
|
self.response.meta.ttl.nanos = 0
|
63
64
|
else:
|
64
|
-
self.response.meta.ttl.nanos = int((ttl -
|
65
|
+
self.response.meta.ttl.nanos = int((ttl - int(self.response.meta.ttl.seconds)) * 1000000000)
|
65
66
|
else:
|
66
67
|
raise ValueError('ttl must be an int or float')
|
67
68
|
|
@@ -78,7 +79,7 @@ class BaseComposite:
|
|
78
79
|
def ready(self, ready):
|
79
80
|
if ready:
|
80
81
|
ready = fnv1.Ready.READY_TRUE
|
81
|
-
elif ready == None or (isinstance(ready, protobuf.
|
82
|
+
elif ready == None or (isinstance(ready, protobuf.Value) and ready._isUnknown):
|
82
83
|
ready = fnv1.Ready.READY_UNSPECIFIED
|
83
84
|
else:
|
84
85
|
ready = fnv1.Ready.READY_FALSE
|
@@ -184,6 +185,7 @@ class Resource:
|
|
184
185
|
self.connection = Connection(observed)
|
185
186
|
self.unknownsFatal = None
|
186
187
|
self.autoReady = None
|
188
|
+
self.usages = None
|
187
189
|
|
188
190
|
def __call__(self, apiVersion=_notset, kind=_notset, namespace=_notset, name=_notset):
|
189
191
|
self.desired()
|
@@ -199,7 +201,7 @@ class Resource:
|
|
199
201
|
|
200
202
|
@property
|
201
203
|
def apiVersion(self):
|
202
|
-
return self.
|
204
|
+
return self.desired.apiVersion
|
203
205
|
|
204
206
|
@apiVersion.setter
|
205
207
|
def apiVersion(self, apiVersion):
|
@@ -207,7 +209,7 @@ class Resource:
|
|
207
209
|
|
208
210
|
@property
|
209
211
|
def kind(self):
|
210
|
-
return self.
|
212
|
+
return self.desired.kind
|
211
213
|
|
212
214
|
@kind.setter
|
213
215
|
def kind(self, kind):
|
@@ -265,7 +267,7 @@ class Resource:
|
|
265
267
|
def ready(self, ready):
|
266
268
|
if ready:
|
267
269
|
ready = fnv1.Ready.READY_TRUE
|
268
|
-
elif ready == None or (isinstance(ready, protobuf.
|
270
|
+
elif ready == None or (isinstance(ready, protobuf.Value) and ready._isUnknown):
|
269
271
|
ready = fnv1.Ready.READY_UNSPECIFIED
|
270
272
|
else:
|
271
273
|
ready = fnv1.Ready.READY_FALSE
|
@@ -376,8 +378,8 @@ class RequiredResources:
|
|
376
378
|
elif isinstance(entry, (list, tuple)):
|
377
379
|
self._selector.match_labels.labels[entry[0]] = entry[1]
|
378
380
|
|
379
|
-
def __getitem__(self,
|
380
|
-
return RequiredResource(self.name, self._resources.items[
|
381
|
+
def __getitem__(self, ix):
|
382
|
+
return RequiredResource(self.name, ix, self._resources.items[ix])
|
381
383
|
|
382
384
|
def __bool__(self):
|
383
385
|
return bool(self._resources.items)
|
@@ -391,8 +393,9 @@ class RequiredResources:
|
|
391
393
|
|
392
394
|
|
393
395
|
class RequiredResource:
|
394
|
-
def __init__(self, name, resource):
|
396
|
+
def __init__(self, name, ix, resource):
|
395
397
|
self.name = name
|
398
|
+
self.ix = ix
|
396
399
|
self.observed = resource.resource
|
397
400
|
self.apiVersion = self.observed.apiVersion
|
398
401
|
self.kind = self.observed.kind
|
@@ -487,7 +490,7 @@ class Condition(protobuf.ProtobufValue):
|
|
487
490
|
condition.status = fnv1.Status.STATUS_CONDITION_TRUE
|
488
491
|
elif status == None:
|
489
492
|
condition.status = fnv1.Status.STATUS_CONDITION_UNKNOWN
|
490
|
-
elif isinstance(status, protobuf.
|
493
|
+
elif isinstance(status, protobuf.Value) and status._isUnknown:
|
491
494
|
condition.status = fnv1.Status.STATUS_CONDITION_UNSPECIFIED
|
492
495
|
else:
|
493
496
|
condition.status = fnv1.Status.STATUS_CONDITION_FALSE
|
@@ -521,7 +524,7 @@ class Condition(protobuf.ProtobufValue):
|
|
521
524
|
if observed.type == self.type:
|
522
525
|
time = observed.lastTransitionTime
|
523
526
|
if time:
|
524
|
-
return datetime.datetime.fromisoformat(time)
|
527
|
+
return datetime.datetime.fromisoformat(str(time))
|
525
528
|
return None
|
526
529
|
|
527
530
|
@property
|
@@ -534,7 +537,7 @@ class Condition(protobuf.ProtobufValue):
|
|
534
537
|
condition = self._find_condition(True)
|
535
538
|
if claim:
|
536
539
|
condition.target = fnv1.Target.TARGET_COMPOSITE_AND_CLAIM
|
537
|
-
elif claim == None or (isinstance(claim, protobuf.
|
540
|
+
elif claim == None or (isinstance(claim, protobuf.Value) and claim._isUnknown):
|
538
541
|
condition.target = fnv1.Target.TARGET_UNSPECIFIED
|
539
542
|
else:
|
540
543
|
condition.target = fnv1.Target.TARGET_COMPOSITE
|
@@ -711,7 +714,7 @@ class Event:
|
|
711
714
|
if bool(self):
|
712
715
|
if claim:
|
713
716
|
self._result.target = fnv1.Target.TARGET_COMPOSITE_AND_CLAIM
|
714
|
-
elif claim == None or (isinstance(claim, protobuf.
|
717
|
+
elif claim == None or (isinstance(claim, protobuf.Value) and claim._isUnknown):
|
715
718
|
self._result.target = fnv1.Target.TARGET_UNSPECIFIED
|
716
719
|
else:
|
717
720
|
self._result.target = fnv1.Target.TARGET_COMPOSITE
|
crossplane/pythonic/function.py
CHANGED
@@ -99,7 +99,7 @@ class FunctionRunner(grpcv1.FunctionRunnerService):
|
|
99
99
|
return self.fatal(request, logger, 'Instantiate', e)
|
100
100
|
|
101
101
|
step = composite.context._pythonic[step]
|
102
|
-
iteration = (step.iteration
|
102
|
+
iteration = int(step.iteration) + 1
|
103
103
|
step.iteration = iteration
|
104
104
|
composite.context.iteration = iteration
|
105
105
|
logger.debug(f"Starting compose, {ordinal(len(composite.context._pythonic))} step, {ordinal(iteration)} pass")
|
@@ -111,7 +111,40 @@ class FunctionRunner(grpcv1.FunctionRunnerService):
|
|
111
111
|
except Exception as e:
|
112
112
|
return self.fatal(request, logger, 'Compose', e)
|
113
113
|
|
114
|
-
|
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')
|
121
|
+
|
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 = []
|
115
148
|
for name, required in composite.requireds:
|
116
149
|
if required.apiVersion and required.kind:
|
117
150
|
r = pythonic.Map(apiVersion=required.apiVersion, kind=required.kind)
|
@@ -123,11 +156,84 @@ class FunctionRunner(grpcv1.FunctionRunnerService):
|
|
123
156
|
r.matchLabels[key] = value
|
124
157
|
if r != step.requireds[name]:
|
125
158
|
step.requireds[name] = r
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
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
|
130
235
|
|
236
|
+
def process_unknowns(self, composite):
|
131
237
|
unknownResources = []
|
132
238
|
warningResources = []
|
133
239
|
fatalResources = []
|
@@ -148,11 +254,11 @@ class FunctionRunner(grpcv1.FunctionRunnerService):
|
|
148
254
|
destination = self.trimFullName(destination)
|
149
255
|
source = self.trimFullName(source)
|
150
256
|
if fatal:
|
151
|
-
logger.error(f'Observed unknown: {destination} = {source}')
|
257
|
+
composite.logger.error(f'Observed unknown: {destination} = {source}')
|
152
258
|
elif warning:
|
153
|
-
logger.warning(f'Observed unknown: {destination} = {source}')
|
259
|
+
composite.logger.warning(f'Observed unknown: {destination} = {source}')
|
154
260
|
else:
|
155
|
-
logger.debug(f'Desired unknown: {destination} = {source}')
|
261
|
+
composite.logger.debug(f'Desired unknown: {destination} = {source}')
|
156
262
|
if resource.observed:
|
157
263
|
resource.desired._patchUnknowns(resource.observed)
|
158
264
|
elif self.renderUnknowns:
|
@@ -161,19 +267,19 @@ class FunctionRunner(grpcv1.FunctionRunnerService):
|
|
161
267
|
del composite.resources[name]
|
162
268
|
|
163
269
|
if fatalResources:
|
164
|
-
level = logger.error
|
270
|
+
level = composite.logger.error
|
165
271
|
reason = 'FatalUnknowns'
|
166
272
|
message = f"Observed resources with unknowns: {','.join(fatalResources)}"
|
167
273
|
status = False
|
168
274
|
event = composite.events.fatal
|
169
275
|
elif warningResources:
|
170
|
-
level = logger.warning
|
276
|
+
level = composite.logger.warning
|
171
277
|
reason = 'ObservedUnknowns'
|
172
278
|
message = f"Observed resources with unknowns: {','.join(warningResources)}"
|
173
279
|
status = False
|
174
280
|
event = composite.events.warning
|
175
281
|
elif unknownResources:
|
176
|
-
level = logger.info
|
282
|
+
level = composite.logger.info
|
177
283
|
reason = 'DesiredUnknowns'
|
178
284
|
message = f"Desired resources with unknowns: {','.join(unknownResources)}"
|
179
285
|
status = False
|
@@ -190,42 +296,19 @@ class FunctionRunner(grpcv1.FunctionRunnerService):
|
|
190
296
|
if event:
|
191
297
|
event(reason, message)
|
192
298
|
|
299
|
+
def process_auto_readies(self, composite):
|
193
300
|
for name, resource in composite.resources:
|
194
301
|
if resource.autoReady or (resource.autoReady is None and composite.autoReady):
|
195
302
|
if resource.ready is None:
|
196
303
|
if resource.conditions.Ready.status:
|
197
304
|
resource.ready = True
|
198
305
|
|
199
|
-
logger.info('Completed compose')
|
200
|
-
return composite.response._message
|
201
|
-
|
202
|
-
def fatal(self, request, logger, message, exception=None):
|
203
|
-
if exception:
|
204
|
-
message += ' exceptiion'
|
205
|
-
logger.exception(message)
|
206
|
-
m = str(exception)
|
207
|
-
if not m:
|
208
|
-
m = exception.__class__.__name__
|
209
|
-
message += ': ' + m
|
210
|
-
else:
|
211
|
-
logger.error(message)
|
212
|
-
return fnv1.RunFunctionResponse(
|
213
|
-
meta=fnv1.ResponseMeta(
|
214
|
-
tag=request.meta.tag,
|
215
|
-
),
|
216
|
-
results=[
|
217
|
-
fnv1.Result(
|
218
|
-
severity=fnv1.SEVERITY_FATAL,
|
219
|
-
message=message,
|
220
|
-
)
|
221
|
-
]
|
222
|
-
)
|
223
|
-
|
224
306
|
def trimFullName(self, name):
|
225
307
|
name = name.split('.')
|
226
308
|
for values in (
|
309
|
+
('request', 'observed', 'composite', 'resource'),
|
227
310
|
('request', 'observed', 'resources', None, 'resource'),
|
228
|
-
('request', 'extra_resources', None, 'items', 'resource'),
|
311
|
+
('request', 'extra_resources', None, 'items', None, 'resource'),
|
229
312
|
('response', 'desired', 'resources', None, 'resource'),
|
230
313
|
):
|
231
314
|
if len(values) < len(name):
|