crossplane-function-pythonic 0.2.0__py3-none-any.whl → 0.3.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 -1
- crossplane/pythonic/auto_ready.py +153 -0
- crossplane/pythonic/command.py +5 -0
- crossplane/pythonic/composite.py +245 -63
- crossplane/pythonic/function.py +33 -23
- crossplane/pythonic/grpc.py +6 -1
- crossplane/pythonic/protobuf.py +125 -32
- crossplane/pythonic/render.py +73 -43
- crossplane/pythonic/version.py +1 -1
- {crossplane_function_pythonic-0.2.0.dist-info → crossplane_function_pythonic-0.3.0.dist-info}/METADATA +48 -14
- crossplane_function_pythonic-0.3.0.dist-info/RECORD +18 -0
- crossplane_function_pythonic-0.2.0.dist-info/RECORD +0 -17
- {crossplane_function_pythonic-0.2.0.dist-info → crossplane_function_pythonic-0.3.0.dist-info}/WHEEL +0 -0
- {crossplane_function_pythonic-0.2.0.dist-info → crossplane_function_pythonic-0.3.0.dist-info}/entry_points.txt +0 -0
- {crossplane_function_pythonic-0.2.0.dist-info → crossplane_function_pythonic-0.3.0.dist-info}/licenses/LICENSE +0 -0
crossplane/pythonic/__about__.py
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
# This is set at build time, using "hatch version"
|
|
2
|
-
__version__ = "0.
|
|
2
|
+
__version__ = "0.3.0"
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
|
|
2
|
+
|
|
3
|
+
def process(composite):
|
|
4
|
+
for name, resource in composite.resources:
|
|
5
|
+
if resource.observed:
|
|
6
|
+
if resource.autoReady or (resource.autoReady is None and composite.autoReady):
|
|
7
|
+
if resource.ready is None:
|
|
8
|
+
if _checks.get((resource.apiVersion, resource.kind), _check_default).ready(resource):
|
|
9
|
+
resource.ready = True
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class ConditionReady:
|
|
13
|
+
def ready(self, resource):
|
|
14
|
+
return bool(resource.conditions.Ready.status)
|
|
15
|
+
|
|
16
|
+
_checks = {}
|
|
17
|
+
_check_default = ConditionReady()
|
|
18
|
+
|
|
19
|
+
class Check:
|
|
20
|
+
@classmethod
|
|
21
|
+
def __init_subclass__(cls, **kwargs):
|
|
22
|
+
super().__init_subclass__(**kwargs)
|
|
23
|
+
if hasattr(cls, 'apiVersion'):
|
|
24
|
+
_checks[(cls.apiVersion, cls.__name__)] = cls()
|
|
25
|
+
|
|
26
|
+
def ready(self, resource):
|
|
27
|
+
raise NotImplementedError()
|
|
28
|
+
|
|
29
|
+
class AlwaysReady(Check):
|
|
30
|
+
def ready(self, resource):
|
|
31
|
+
return True
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class ClusterRole(AlwaysReady):
|
|
35
|
+
apiVersion = 'rbac.authorization.k8s.io/v1'
|
|
36
|
+
|
|
37
|
+
class ClusterRoleBinding(AlwaysReady):
|
|
38
|
+
apiVersion = 'rbac.authorization.k8s.io/v1'
|
|
39
|
+
|
|
40
|
+
class ConfigMap(AlwaysReady):
|
|
41
|
+
apiVersion = 'v1'
|
|
42
|
+
|
|
43
|
+
class CronJob(Check):
|
|
44
|
+
apiVersion = 'batch/v1'
|
|
45
|
+
def ready(self, resource):
|
|
46
|
+
if resource.observed.spec.suspend and len(resource.observed.spec.suspend):
|
|
47
|
+
return True
|
|
48
|
+
if not resource.status.lastScheduleTime:
|
|
49
|
+
return False
|
|
50
|
+
if resource.status.active:
|
|
51
|
+
return True
|
|
52
|
+
if not resource.status.lastSuccessfulTime:
|
|
53
|
+
return False
|
|
54
|
+
return str(resource.status.lastSuccessfulTime) >= str(resource.status.lastScheduleTime)
|
|
55
|
+
|
|
56
|
+
class DaemonSet(Check):
|
|
57
|
+
apiVersion = 'apps/v1'
|
|
58
|
+
def ready(self, resource):
|
|
59
|
+
if not resource.status.desiredNumberScheduled:
|
|
60
|
+
return False
|
|
61
|
+
scheduled = resource.status.desiredNumberScheduled
|
|
62
|
+
return (scheduled == resource.status.numberReady and
|
|
63
|
+
scheduled == resource.status.updatedNumberScheduled and
|
|
64
|
+
scheduled == resource.status.numberAvailable
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
class Deployment(Check):
|
|
68
|
+
apiVersion = 'apps/v1'
|
|
69
|
+
def ready(self, resource):
|
|
70
|
+
replicas = resource.observed.spec.replicas or 1
|
|
71
|
+
if resource.status.updatedReplicas != replicas or resource.status.availableReplicas != replicas:
|
|
72
|
+
return False
|
|
73
|
+
return bool(resource.conditions.Available.status)
|
|
74
|
+
|
|
75
|
+
class HorizontalPodAutoscaler(Check):
|
|
76
|
+
apiVersion = 'autoscaling/v2'
|
|
77
|
+
def ready(self, resource):
|
|
78
|
+
for type in ('FailedGetScale', 'FailedUpdateScale', 'FailedGetResourceMetric', 'InvalidSelector'):
|
|
79
|
+
if resource.conditions[type].status:
|
|
80
|
+
return False
|
|
81
|
+
for type in ('ScalingActive', 'ScalingLimited'):
|
|
82
|
+
if resource.conditions[type].status:
|
|
83
|
+
return True
|
|
84
|
+
return False
|
|
85
|
+
|
|
86
|
+
class Ingress(Check):
|
|
87
|
+
apiVersion = 'networking.k8s.io/v1'
|
|
88
|
+
def ready(self, resource):
|
|
89
|
+
return len(resource.status.loadBalancer.ingress) > 0
|
|
90
|
+
|
|
91
|
+
class Job(Check):
|
|
92
|
+
apiVersion = 'batch/v1'
|
|
93
|
+
def ready(self, resource):
|
|
94
|
+
for type in ('Failed', 'Suspended'):
|
|
95
|
+
if resource.conditions[type].status:
|
|
96
|
+
return False
|
|
97
|
+
return bool(resource.conditions.Complete.status)
|
|
98
|
+
|
|
99
|
+
class Namespace(AlwaysReady):
|
|
100
|
+
apiVersion = 'v1'
|
|
101
|
+
|
|
102
|
+
class PersistentVolumeClaim(Check):
|
|
103
|
+
apiVersion = 'v1'
|
|
104
|
+
def ready(self, resource):
|
|
105
|
+
return resource.status.phase == 'Bound'
|
|
106
|
+
|
|
107
|
+
class Pod(Check):
|
|
108
|
+
apiVersion = 'v1'
|
|
109
|
+
def ready(self, resource):
|
|
110
|
+
if resource.status.phase == 'Succeeded':
|
|
111
|
+
return True
|
|
112
|
+
if resource.status.phase == 'Running':
|
|
113
|
+
if resource.observed.spec.restartPolicy == 'Always':
|
|
114
|
+
if resource.conditions.Ready.status:
|
|
115
|
+
return True
|
|
116
|
+
return False
|
|
117
|
+
|
|
118
|
+
class ReplicaSet(Check):
|
|
119
|
+
apiVersion = 'v1'
|
|
120
|
+
def ready(self, resource):
|
|
121
|
+
if int(resource.status.observedGeneration) < int(resource.observed.metadata.generation):
|
|
122
|
+
return False
|
|
123
|
+
if resource.conditions.ReplicaFailure.status:
|
|
124
|
+
return False
|
|
125
|
+
return int(resource.status.availableReplicas) >= int(resource.observed.spec.replicas or 1)
|
|
126
|
+
|
|
127
|
+
class Role(AlwaysReady):
|
|
128
|
+
apiVersion = 'rbac.authorization.k8s.io/v1'
|
|
129
|
+
|
|
130
|
+
class RoleBinding(AlwaysReady):
|
|
131
|
+
apiVersion = 'rbac.authorization.k8s.io/v1'
|
|
132
|
+
|
|
133
|
+
class Secret(AlwaysReady):
|
|
134
|
+
apiVersion = 'v1'
|
|
135
|
+
|
|
136
|
+
class Service(Check):
|
|
137
|
+
apiVersion = 'v1'
|
|
138
|
+
def ready(self, resource):
|
|
139
|
+
if resource.observed.spec.type != 'LoadBalancer':
|
|
140
|
+
return True
|
|
141
|
+
return len(resource.status.loadBalancer.ingress) > 0
|
|
142
|
+
|
|
143
|
+
class ServiceAccount(AlwaysReady):
|
|
144
|
+
apiVersion = 'v1'
|
|
145
|
+
|
|
146
|
+
class StatefulSet(Check):
|
|
147
|
+
apiVersion = 'apps/v1'
|
|
148
|
+
def ready(self, resource):
|
|
149
|
+
replicas = resource.observed.spec.replicas or 1
|
|
150
|
+
return (resource.status.readyReplicas == replicas and
|
|
151
|
+
resource.status.currentReplicas == replicas and
|
|
152
|
+
resource.status.currentRevision == resource.status.updateRevision
|
|
153
|
+
)
|
crossplane/pythonic/command.py
CHANGED
|
@@ -50,6 +50,11 @@ class Command:
|
|
|
50
50
|
action='store_true',
|
|
51
51
|
help='Allow oversized protobuf messages',
|
|
52
52
|
)
|
|
53
|
+
parser.add_argument(
|
|
54
|
+
'--crossplane-v1',
|
|
55
|
+
action='store_true',
|
|
56
|
+
help='Enable Crossplane V1 compatibility mode',
|
|
57
|
+
)
|
|
53
58
|
|
|
54
59
|
def __init__(self, args):
|
|
55
60
|
self.args = args
|
crossplane/pythonic/composite.py
CHANGED
|
@@ -9,8 +9,86 @@ from . import protobuf
|
|
|
9
9
|
_notset = object()
|
|
10
10
|
|
|
11
11
|
|
|
12
|
+
class ConnectionSecret:
|
|
13
|
+
def __get__(self, composite, objtype=None):
|
|
14
|
+
if composite.crossplane_v1:
|
|
15
|
+
return composite.spec.writeConnectionSecretToRef
|
|
16
|
+
secret = getattr(composite, '_connectionSecret', None)
|
|
17
|
+
if not secret:
|
|
18
|
+
secret = protobuf.Map()
|
|
19
|
+
for key, value in composite.request.input.writeConnectionSecretToRef:
|
|
20
|
+
secret[key] = value
|
|
21
|
+
composite._connectionSecret = secret
|
|
22
|
+
return secret
|
|
23
|
+
|
|
24
|
+
def __set__(self, composite, values):
|
|
25
|
+
if composite.crossplane_v1:
|
|
26
|
+
if values != composite.spec.writeConnectionSecretToRef:
|
|
27
|
+
raise NotImplementedError('Connection Secret cannot be set in Crossplane V1')
|
|
28
|
+
return
|
|
29
|
+
secret = protobuf.Map()
|
|
30
|
+
for key, value in values:
|
|
31
|
+
secret[key] = value
|
|
32
|
+
composite._connectionSecret = secret
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class Connection:
|
|
36
|
+
def __get__(self, composite, objtype=None):
|
|
37
|
+
connection = getattr(composite, '_connection', None)
|
|
38
|
+
if not connection:
|
|
39
|
+
connection = _Connection(composite)
|
|
40
|
+
composite._connection = connection
|
|
41
|
+
return connection
|
|
42
|
+
|
|
43
|
+
def __set__(self, composite, values):
|
|
44
|
+
connection = self.__get__(composite)
|
|
45
|
+
coneection()
|
|
46
|
+
for key, value in values:
|
|
47
|
+
connection[key] = value
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
class TTL:
|
|
51
|
+
def __get__(self, composite, objtype=None):
|
|
52
|
+
if composite.response.meta.ttl.nanos:
|
|
53
|
+
return float(composite.response.meta.ttl.seconds) + (float(composite.response.meta.ttl.nanos) / 1000000000.0)
|
|
54
|
+
return int(composite.response.meta.ttl.seconds)
|
|
55
|
+
|
|
56
|
+
def __set__(self, composite, ttl):
|
|
57
|
+
if isinstance(ttl, int):
|
|
58
|
+
composite.response.meta.ttl.seconds = ttl
|
|
59
|
+
composite.response.meta.ttl.nanos = 0
|
|
60
|
+
elif isinstance(ttl, float):
|
|
61
|
+
composite.response.meta.ttl.seconds = int(ttl)
|
|
62
|
+
if ttl.is_integer():
|
|
63
|
+
composite.response.meta.ttl.nanos = 0
|
|
64
|
+
else:
|
|
65
|
+
composite.response.meta.ttl.nanos = int((ttl - int(composite.response.meta.ttl.seconds)) * 1000000000)
|
|
66
|
+
else:
|
|
67
|
+
raise ValueError('ttl must be an int or float')
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
class Ready:
|
|
71
|
+
def __get__(self, composite, objtype=None):
|
|
72
|
+
ready = composite.desired._parent.ready
|
|
73
|
+
if ready == fnv1.Ready.READY_TRUE:
|
|
74
|
+
return True
|
|
75
|
+
if ready == fnv1.Ready.READY_FALSE:
|
|
76
|
+
return False
|
|
77
|
+
return None
|
|
78
|
+
|
|
79
|
+
def __set__(self, composite, ready):
|
|
80
|
+
if ready:
|
|
81
|
+
ready = fnv1.Ready.READY_TRUE
|
|
82
|
+
elif ready == None or (isinstance(ready, protobuf.Value) and ready._isUnknown):
|
|
83
|
+
ready = fnv1.Ready.READY_UNSPECIFIED
|
|
84
|
+
else:
|
|
85
|
+
ready = fnv1.Ready.READY_FALSE
|
|
86
|
+
composite.desired._parent.ready = ready
|
|
87
|
+
|
|
88
|
+
|
|
12
89
|
class BaseComposite:
|
|
13
|
-
def __init__(self, request, single_use, logger):
|
|
90
|
+
def __init__(self, crossplane_v1, request, single_use, logger):
|
|
91
|
+
self.crossplane_v1 = crossplane_v1
|
|
14
92
|
self.request = protobuf.Message(None, 'request', request.DESCRIPTOR, request, 'Function Request')
|
|
15
93
|
response = fnv1.RunFunctionResponse(
|
|
16
94
|
meta=fnv1.ResponseMeta(
|
|
@@ -50,54 +128,10 @@ class BaseComposite:
|
|
|
50
128
|
self.results = Results(self.response)
|
|
51
129
|
self.events = Results(self.response) # Deprecated, use self.results
|
|
52
130
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
return int(self.response.meta.ttl.seconds)
|
|
58
|
-
|
|
59
|
-
@ttl.setter
|
|
60
|
-
def ttl(self, ttl):
|
|
61
|
-
if isinstance(ttl, int):
|
|
62
|
-
self.response.meta.ttl.seconds = ttl
|
|
63
|
-
self.response.meta.ttl.nanos = 0
|
|
64
|
-
elif isinstance(ttl, float):
|
|
65
|
-
self.response.meta.ttl.seconds = int(ttl)
|
|
66
|
-
if ttl.is_integer():
|
|
67
|
-
self.response.meta.ttl.nanos = 0
|
|
68
|
-
else:
|
|
69
|
-
self.response.meta.ttl.nanos = int((ttl - int(self.response.meta.ttl.seconds)) * 1000000000)
|
|
70
|
-
else:
|
|
71
|
-
raise ValueError('ttl must be an int or float')
|
|
72
|
-
|
|
73
|
-
@property
|
|
74
|
-
def connection(self):
|
|
75
|
-
return self.response.desired.composite.connection_details
|
|
76
|
-
|
|
77
|
-
@connection.setter
|
|
78
|
-
def connection(self, connection):
|
|
79
|
-
self.response.desired.composite.connection_details()
|
|
80
|
-
for key, value in connection:
|
|
81
|
-
self.response.desired.composite.connection_details[key] = value
|
|
82
|
-
|
|
83
|
-
@property
|
|
84
|
-
def ready(self):
|
|
85
|
-
ready = self.desired._parent.ready
|
|
86
|
-
if ready == fnv1.Ready.READY_TRUE:
|
|
87
|
-
return True
|
|
88
|
-
if ready == fnv1.Ready.READY_FALSE:
|
|
89
|
-
return False
|
|
90
|
-
return None
|
|
91
|
-
|
|
92
|
-
@ready.setter
|
|
93
|
-
def ready(self, ready):
|
|
94
|
-
if ready:
|
|
95
|
-
ready = fnv1.Ready.READY_TRUE
|
|
96
|
-
elif ready == None or (isinstance(ready, protobuf.Value) and ready._isUnknown):
|
|
97
|
-
ready = fnv1.Ready.READY_UNSPECIFIED
|
|
98
|
-
else:
|
|
99
|
-
ready = fnv1.Ready.READY_FALSE
|
|
100
|
-
self.desired._parent.ready = ready
|
|
131
|
+
ttl = TTL()
|
|
132
|
+
connectionSecret = ConnectionSecret()
|
|
133
|
+
connection = Connection()
|
|
134
|
+
ready = Ready()
|
|
101
135
|
|
|
102
136
|
async def compose(self):
|
|
103
137
|
raise NotImplementedError()
|
|
@@ -262,6 +296,14 @@ class Resource:
|
|
|
262
296
|
def spec(self, spec):
|
|
263
297
|
self.desired.spec = spec
|
|
264
298
|
|
|
299
|
+
@property
|
|
300
|
+
def type(self):
|
|
301
|
+
return self.desired.type
|
|
302
|
+
|
|
303
|
+
@type.setter
|
|
304
|
+
def type(self, type):
|
|
305
|
+
self.desired.type = type
|
|
306
|
+
|
|
265
307
|
@property
|
|
266
308
|
def data(self):
|
|
267
309
|
return self.desired.data
|
|
@@ -314,25 +356,43 @@ class Requireds:
|
|
|
314
356
|
|
|
315
357
|
def __len__(self):
|
|
316
358
|
names = set()
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
359
|
+
if self._composite.crossplane_v1:
|
|
360
|
+
for name, resource in self._composite.request.extra_resources:
|
|
361
|
+
names.add(name)
|
|
362
|
+
for name, resource in self._composite.response.requirements.extra_resources:
|
|
363
|
+
names.add(name)
|
|
364
|
+
else:
|
|
365
|
+
for name, resource in self._composite.request.required_resources:
|
|
366
|
+
names.add(name)
|
|
367
|
+
for name, resource in self._composite.response.requirements.resources:
|
|
368
|
+
names.add(name)
|
|
321
369
|
return len(names)
|
|
322
370
|
|
|
323
371
|
def __contains__(self, key):
|
|
324
|
-
if
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
372
|
+
if self._composite.crossplane_v1:
|
|
373
|
+
if key in self._composite.request.extra_resources:
|
|
374
|
+
return True
|
|
375
|
+
if key in self._composite.response.requirements.extra_resources:
|
|
376
|
+
return True
|
|
377
|
+
else:
|
|
378
|
+
if key in self._composite.request.required_resources:
|
|
379
|
+
return True
|
|
380
|
+
if key in self._composite.response.requirements.resources:
|
|
381
|
+
return True
|
|
328
382
|
return False
|
|
329
383
|
|
|
330
384
|
def __iter__(self):
|
|
331
385
|
names = set()
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
386
|
+
if self._composite.crossplane_v1:
|
|
387
|
+
for name, resource in self._composite.request.extra_resources:
|
|
388
|
+
names.add(name)
|
|
389
|
+
for name, resource in self._composite.response.requirements.extra_resources:
|
|
390
|
+
names.add(name)
|
|
391
|
+
else:
|
|
392
|
+
for name, resource in self._composite.request.required_resources:
|
|
393
|
+
names.add(name)
|
|
394
|
+
for name, resource in self._composite.response.requirements.resources:
|
|
395
|
+
names.add(name)
|
|
336
396
|
for name in sorted(names):
|
|
337
397
|
yield name, self[name]
|
|
338
398
|
|
|
@@ -340,8 +400,12 @@ class Requireds:
|
|
|
340
400
|
class RequiredResources:
|
|
341
401
|
def __init__(self, composite, name):
|
|
342
402
|
self.name = name
|
|
343
|
-
|
|
344
|
-
|
|
403
|
+
if composite.crossplane_v1:
|
|
404
|
+
self._selector = composite.response.requirements.extra_resources[name]
|
|
405
|
+
self._resources = composite.request.extra_resources[name]
|
|
406
|
+
else:
|
|
407
|
+
self._selector = composite.response.requirements.resources[name]
|
|
408
|
+
self._resources = composite.request.required_resources[name]
|
|
345
409
|
self._cache = {}
|
|
346
410
|
|
|
347
411
|
def __call__(self, apiVersion=_notset, kind=_notset, namespace=_notset, name=_notset, labels=_notset):
|
|
@@ -431,9 +495,11 @@ class RequiredResource:
|
|
|
431
495
|
self.kind = self.observed.kind
|
|
432
496
|
self.metadata = self.observed.metadata
|
|
433
497
|
self.spec = self.observed.spec
|
|
498
|
+
self.type = self.observed.type
|
|
434
499
|
self.data = self.observed.data
|
|
435
500
|
self.status = self.observed.status
|
|
436
501
|
self.conditions = Conditions(resource)
|
|
502
|
+
self.connection = self.observed.connection_details
|
|
437
503
|
|
|
438
504
|
def __bool__(self):
|
|
439
505
|
return bool(self.observed)
|
|
@@ -721,3 +787,119 @@ class Result:
|
|
|
721
787
|
self._result.target = fnv1.Target.TARGET_UNSPECIFIED
|
|
722
788
|
else:
|
|
723
789
|
self._result.target = fnv1.Target.TARGET_COMPOSITE
|
|
790
|
+
|
|
791
|
+
|
|
792
|
+
class _Connection:
|
|
793
|
+
def __init__(self, composite):
|
|
794
|
+
self._set_attribute('_composite', composite)
|
|
795
|
+
|
|
796
|
+
def _set_attribute(self, key, value):
|
|
797
|
+
self.__dict__[key] = value
|
|
798
|
+
|
|
799
|
+
@property
|
|
800
|
+
def _resource_name(self):
|
|
801
|
+
return self._composite.connectionSecret.resourceName or 'connection-secret'
|
|
802
|
+
|
|
803
|
+
@property
|
|
804
|
+
def observed(self):
|
|
805
|
+
if self._composite.crossplane_v1:
|
|
806
|
+
return self._composite.response.observed.composite.connection_details
|
|
807
|
+
data = protobuf.Map()
|
|
808
|
+
for key, value in self._composite.resources[self._resource_name].observed.data:
|
|
809
|
+
data[key] = protobuf.B64Decode(value)
|
|
810
|
+
return data
|
|
811
|
+
|
|
812
|
+
def __getattr__(self, key):
|
|
813
|
+
return self[key]
|
|
814
|
+
|
|
815
|
+
def __getitem__(self, key):
|
|
816
|
+
if self._composite.crossplane_v1:
|
|
817
|
+
return self._composite.response.desired.composite.connection_details[key]
|
|
818
|
+
value = self._composite.resources[self._resource_name].data[key]
|
|
819
|
+
if value:
|
|
820
|
+
value = protobuf.B64Decode(value)
|
|
821
|
+
return value
|
|
822
|
+
|
|
823
|
+
def __bool__(self):
|
|
824
|
+
if self._composite.crossplane_v1:
|
|
825
|
+
return bool(self._composite.response.desired.composite.connection_details)
|
|
826
|
+
return bool(self._composite.resources[self._resource_name].data)
|
|
827
|
+
|
|
828
|
+
def __len__(self):
|
|
829
|
+
if self._composite.crossplane_v1:
|
|
830
|
+
return len(self._composite.response.desired.composite.connection_details)
|
|
831
|
+
return len(self._composite.resources[self._resource_name].data)
|
|
832
|
+
|
|
833
|
+
def __contains__(self, key):
|
|
834
|
+
if self._composite.crossplane_v1:
|
|
835
|
+
return key in self._composite.response.desired.composite.connection_details
|
|
836
|
+
|
|
837
|
+
def __iter__(self):
|
|
838
|
+
keys = set()
|
|
839
|
+
if self._composite.crossplane_v1:
|
|
840
|
+
for key, value in self._composite.response.desired.composite.connection_details:
|
|
841
|
+
yield key, value
|
|
842
|
+
for key, value in self._composite.resources[self._resource_name].data:
|
|
843
|
+
yield key, protobuf.B64Decode(value)
|
|
844
|
+
|
|
845
|
+
def __str__(self):
|
|
846
|
+
return format(self)
|
|
847
|
+
|
|
848
|
+
def __format__(self, spec='yaml'):
|
|
849
|
+
if self._composite.crossplane_v1:
|
|
850
|
+
return format(self._composite.response.desired.composite.connection_details, spec)
|
|
851
|
+
data = protobuf.Map()
|
|
852
|
+
for key, value in self._composite.resources[self._resource_name].data:
|
|
853
|
+
data[key] = protobuf.B64Decode(value)
|
|
854
|
+
return format(data, spec)
|
|
855
|
+
|
|
856
|
+
def __call__(self, **kwargs):
|
|
857
|
+
if self._composite_v1:
|
|
858
|
+
self._composite.response.desired.composite.connection_details(**kwargs)
|
|
859
|
+
return
|
|
860
|
+
del self._composite.resources[self._resource_name]
|
|
861
|
+
for key, value in kwargs:
|
|
862
|
+
self[key] = value
|
|
863
|
+
|
|
864
|
+
def __setattr__(self, key, value):
|
|
865
|
+
self[key] = value
|
|
866
|
+
|
|
867
|
+
def __setitem__(self, key, value):
|
|
868
|
+
if not isinstance(value, str):
|
|
869
|
+
if value is None:
|
|
870
|
+
return
|
|
871
|
+
if isinstance(value, (protobuf.FieldMessage, protobuf.Value)):
|
|
872
|
+
if not value:
|
|
873
|
+
return
|
|
874
|
+
value = str(value)
|
|
875
|
+
if self._composite.crossplane_v1:
|
|
876
|
+
self._composite.response.desired.composite.connection_details[key] = value
|
|
877
|
+
return
|
|
878
|
+
#if not self._composite.connectionSecret.name:
|
|
879
|
+
# return
|
|
880
|
+
if self._resource_name in self._composite.resources:
|
|
881
|
+
secret = self._composite.resources[self._resource_name]
|
|
882
|
+
else:
|
|
883
|
+
secret = self._composite.resources[self._resource_name]('v1', 'Secret')
|
|
884
|
+
print(bool(self._composite.connectionSecret.name), len(self._composite.connectionSecret.name))
|
|
885
|
+
if self._composite.connectionSecret.name and len(self._composite.connectionSecret.name):
|
|
886
|
+
secret.metadata.name = self._composite.connectionSecret.name
|
|
887
|
+
if not self._composite.metadata.namespace:
|
|
888
|
+
if not self._composite.connectionSecret.namespace:
|
|
889
|
+
self._composite.results.fatal('ConnectionNoNamespace', 'Cluster scoped XR must specify connection secret namespace')
|
|
890
|
+
return
|
|
891
|
+
secret.metadata.namespace = self._composite.connectionSecret.namespace
|
|
892
|
+
secret.type = 'connection.crossplane.io/v1alpha1'
|
|
893
|
+
secret.data[key] = protobuf.B64Encode(value)
|
|
894
|
+
|
|
895
|
+
def __delattr__(self, key):
|
|
896
|
+
del self[key]
|
|
897
|
+
|
|
898
|
+
def __delitem__(self, key):
|
|
899
|
+
if self._composite.crossplane_v1:
|
|
900
|
+
del self._composite.response.desired.composite.connection_details[key]
|
|
901
|
+
return
|
|
902
|
+
if self._resource_name in self._composite.resources:
|
|
903
|
+
del self._composite.resources[self._resource_name].data[key]
|
|
904
|
+
if not len(self._composite.resources[self._resource_name].data):
|
|
905
|
+
del self._composite.resources[self._resource_name]
|
crossplane/pythonic/function.py
CHANGED
|
@@ -9,6 +9,7 @@ import sys
|
|
|
9
9
|
import grpc
|
|
10
10
|
from crossplane.function.proto.v1 import run_function_pb2 as fnv1
|
|
11
11
|
from crossplane.function.proto.v1 import run_function_pb2_grpc as grpcv1
|
|
12
|
+
from . import auto_ready
|
|
12
13
|
from .. import pythonic
|
|
13
14
|
|
|
14
15
|
logger = logging.getLogger(__name__)
|
|
@@ -17,10 +18,11 @@ logger = logging.getLogger(__name__)
|
|
|
17
18
|
class FunctionRunner(grpcv1.FunctionRunnerService):
|
|
18
19
|
"""A FunctionRunner handles gRPC RunFunctionRequests."""
|
|
19
20
|
|
|
20
|
-
def __init__(self, debug=False, renderUnknowns=False):
|
|
21
|
+
def __init__(self, debug=False, renderUnknowns=False, crossplane_v1=False):
|
|
21
22
|
"""Create a new FunctionRunner."""
|
|
22
23
|
self.debug = debug
|
|
23
24
|
self.renderUnknowns = renderUnknowns
|
|
25
|
+
self.crossplane_v1 = crossplane_v1
|
|
24
26
|
self.clazzes = {}
|
|
25
27
|
|
|
26
28
|
def invalidate_module(self, module):
|
|
@@ -100,7 +102,7 @@ class FunctionRunner(grpcv1.FunctionRunnerService):
|
|
|
100
102
|
self.clazzes[composite] = clazz
|
|
101
103
|
|
|
102
104
|
try:
|
|
103
|
-
composite = clazz(request, single_use, logger)
|
|
105
|
+
composite = clazz(self.crossplane_v1, request, single_use, logger)
|
|
104
106
|
except Exception as e:
|
|
105
107
|
return self.fatal(request, logger, 'Instantiate', e)
|
|
106
108
|
|
|
@@ -122,7 +124,7 @@ class FunctionRunner(grpcv1.FunctionRunnerService):
|
|
|
122
124
|
else:
|
|
123
125
|
self.process_usages(composite)
|
|
124
126
|
self.process_unknowns(composite)
|
|
125
|
-
|
|
127
|
+
auto_ready.process(composite)
|
|
126
128
|
logger.info('Completed compose')
|
|
127
129
|
|
|
128
130
|
return composite.response._message
|
|
@@ -152,11 +154,11 @@ class FunctionRunner(grpcv1.FunctionRunnerService):
|
|
|
152
154
|
def get_requireds(self, step, composite):
|
|
153
155
|
requireds = []
|
|
154
156
|
for name, required in composite.requireds:
|
|
155
|
-
if required.apiVersion and required.kind:
|
|
157
|
+
if len(required.apiVersion) and len(required.kind):
|
|
156
158
|
r = pythonic.Map(apiVersion=required.apiVersion, kind=required.kind)
|
|
157
|
-
if required.namespace:
|
|
159
|
+
if len(required.namespace):
|
|
158
160
|
r.namespace = required.namespace
|
|
159
|
-
if required.matchName:
|
|
161
|
+
if len(required.matchName):
|
|
160
162
|
r.matchName = required.matchName
|
|
161
163
|
for key, value in required.matchLabels:
|
|
162
164
|
r.matchLabels[key] = value
|
|
@@ -166,6 +168,10 @@ class FunctionRunner(grpcv1.FunctionRunnerService):
|
|
|
166
168
|
return requireds
|
|
167
169
|
|
|
168
170
|
def process_usages(self, composite):
|
|
171
|
+
if self.crossplane_v1:
|
|
172
|
+
apiVersion = 'apiextensions.crossplane.io/v1beta1'
|
|
173
|
+
else:
|
|
174
|
+
apiVersion = 'protection.crossplane.io/v1beta1'
|
|
169
175
|
for _, resource in sorted(entry for entry in composite.resources):
|
|
170
176
|
dependencies = resource.desired._getDependencies
|
|
171
177
|
if dependencies:
|
|
@@ -175,7 +181,6 @@ class FunctionRunner(grpcv1.FunctionRunnerService):
|
|
|
175
181
|
source = self.trimFullName(source)
|
|
176
182
|
composite.logger.debug(f"Dependency: {destination} = {source}")
|
|
177
183
|
if resource.usages or (resource.usages is None and composite.usages):
|
|
178
|
-
apiVersion = 'protection.crossplane.io/v1beta1' if composite.metadata.namespace else 'apiextensions.crossplane.io/v1beta1'
|
|
179
184
|
resources = {}
|
|
180
185
|
requireds = {}
|
|
181
186
|
for destination, source in sorted(dependencies.items()):
|
|
@@ -191,7 +196,7 @@ class FunctionRunner(grpcv1.FunctionRunnerService):
|
|
|
191
196
|
resources[name[3]].append(f"{'.'.join(destination.split('.')[5:])} = {'.'.join(name[5:])}")
|
|
192
197
|
elif (len(name) > 5 and
|
|
193
198
|
name[0] == 'request' and
|
|
194
|
-
name[1]
|
|
199
|
+
name[1] in ('required_resources', 'extra_resources') and
|
|
195
200
|
name[3].startswith('items[') and name[3][-1] == ']' and
|
|
196
201
|
name[4] == 'resource'
|
|
197
202
|
):
|
|
@@ -206,8 +211,6 @@ class FunctionRunner(grpcv1.FunctionRunnerService):
|
|
|
206
211
|
name.append(str(source.metadata.namespace))
|
|
207
212
|
name.append(str(source.observed.metadata.name))
|
|
208
213
|
usage = composite.resources['_'.join(name)](apiVersion, 'Usage')
|
|
209
|
-
if resource.metadata.namespace:
|
|
210
|
-
usage.metadata.namespace = resource.metadata.namespace
|
|
211
214
|
usage.spec.reason = '\n'.join(dependencies)
|
|
212
215
|
usage.spec.replayDeletion = True
|
|
213
216
|
usage.spec.by.apiVersion = resource.apiVersion
|
|
@@ -215,9 +218,17 @@ class FunctionRunner(grpcv1.FunctionRunnerService):
|
|
|
215
218
|
usage.spec.by.resourceRef.name = resource.observed.metadata.name
|
|
216
219
|
usage.spec.of.apiVersion = source.apiVersion
|
|
217
220
|
usage.spec.of.kind = source.kind
|
|
218
|
-
if source.metadata.namespace:
|
|
219
|
-
usage.spec.of.resourceRef.namespace = source.metadata.namespace
|
|
220
221
|
usage.spec.of.resourceRef.name = source.observed.metadata.name
|
|
222
|
+
if not self.crossplane_v1:
|
|
223
|
+
if composite.metadata.namespace:
|
|
224
|
+
if source.metadata.namespace and source.metadata.namespace != composite.metadata.namespace:
|
|
225
|
+
usage.spec.of.resourceRef.namespace = source.metadata.namespace
|
|
226
|
+
elif resource.metadata.namespace:
|
|
227
|
+
usage.metadata.namespace = resource.metadata.namespace
|
|
228
|
+
if source.metadata.namespace and source.metadata.namespace != resource.metadata.namespace:
|
|
229
|
+
usage.spec.of.resourceRef.namespace = source.metadata.namespace
|
|
230
|
+
else:
|
|
231
|
+
usage.kind = 'ClusterUsage'
|
|
221
232
|
for key, dependencies in requireds.items():
|
|
222
233
|
source = composite.requireds[key[0]][key[1]]
|
|
223
234
|
name = [resource.name, str(source.kind)]
|
|
@@ -225,8 +236,6 @@ class FunctionRunner(grpcv1.FunctionRunnerService):
|
|
|
225
236
|
name.append(str(source.metadata.namespace))
|
|
226
237
|
name.append(str(source.metadata.name))
|
|
227
238
|
usage = composite.resources['_'.join(name)](apiVersion, 'Usage')
|
|
228
|
-
if resource.metadata.namespace:
|
|
229
|
-
usage.metadata.namespace = resource.metadata.namespace
|
|
230
239
|
usage.spec.reason = '\n'.join(dependencies)
|
|
231
240
|
usage.spec.replayDeletion = True
|
|
232
241
|
usage.spec.by.apiVersion = resource.apiVersion
|
|
@@ -234,9 +243,17 @@ class FunctionRunner(grpcv1.FunctionRunnerService):
|
|
|
234
243
|
usage.spec.by.resourceRef.name = resource.observed.metadata.name
|
|
235
244
|
usage.spec.of.apiVersion = source.apiVersion
|
|
236
245
|
usage.spec.of.kind = source.kind
|
|
237
|
-
if source.metadata.namespace:
|
|
238
|
-
usage.spec.of.resourceRef.namespace = source.metadata.namespace
|
|
239
246
|
usage.spec.of.resourceRef.name = source.observed.metadata.name
|
|
247
|
+
if not self.crossplane_v1:
|
|
248
|
+
if composite.metadata.namespace:
|
|
249
|
+
if source.metadata.namespace and source.metadata.namespace != composite.metadata.namespace:
|
|
250
|
+
usage.spec.of.resourceRef.namespace = source.metadata.namespace
|
|
251
|
+
elif resource.metadata.namespace:
|
|
252
|
+
usage.metadata.namespace = resource.metadata.namespace
|
|
253
|
+
if source.metadata.namespace and source.metadata.namespace != resource.metadata.namespace:
|
|
254
|
+
usage.spec.of.resourceRef.namespace = source.metadata.namespace
|
|
255
|
+
else:
|
|
256
|
+
usage.kind = 'ClusterUsage'
|
|
240
257
|
|
|
241
258
|
def process_unknowns(self, composite):
|
|
242
259
|
unknownResources = []
|
|
@@ -301,13 +318,6 @@ class FunctionRunner(grpcv1.FunctionRunnerService):
|
|
|
301
318
|
if result:
|
|
302
319
|
result(reason, message)
|
|
303
320
|
|
|
304
|
-
def process_auto_readies(self, composite):
|
|
305
|
-
for name, resource in composite.resources:
|
|
306
|
-
if resource.autoReady or (resource.autoReady is None and composite.autoReady):
|
|
307
|
-
if resource.ready is None:
|
|
308
|
-
if resource.conditions.Ready.status:
|
|
309
|
-
resource.ready = True
|
|
310
|
-
|
|
311
321
|
def trimFullName(self, name):
|
|
312
322
|
name = name.split('.')
|
|
313
323
|
for values in (
|