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/grpc.py
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
|
|
2
2
|
import asyncio
|
|
3
|
+
import logging
|
|
3
4
|
import os
|
|
4
5
|
import pathlib
|
|
5
6
|
import shlex
|
|
@@ -10,10 +11,13 @@ import crossplane.function.proto.v1.run_function_pb2_grpc as grpcv1
|
|
|
10
11
|
import grpc
|
|
11
12
|
|
|
12
13
|
from . import (
|
|
14
|
+
__about__,
|
|
13
15
|
command,
|
|
14
16
|
function,
|
|
15
17
|
)
|
|
16
18
|
|
|
19
|
+
logger = logging.getLogger(__name__)
|
|
20
|
+
|
|
17
21
|
|
|
18
22
|
class Command(command.Command):
|
|
19
23
|
name = 'grpc'
|
|
@@ -77,13 +81,14 @@ class Command(command.Command):
|
|
|
77
81
|
pip._internal.cli.main.main(['install', '--user', *shlex.split(self.args.pip_install)])
|
|
78
82
|
|
|
79
83
|
self.initialize_function()
|
|
84
|
+
logger.info(f"Version: {__about__.__version__}")
|
|
80
85
|
|
|
81
86
|
# enables read only volumes or mismatched uid volumes
|
|
82
87
|
sys.dont_write_bytecode = True
|
|
83
88
|
|
|
84
89
|
async def run(self):
|
|
85
90
|
grpc.aio.init_grpc_aio()
|
|
86
|
-
grpc_runner = function.FunctionRunner(self.args.debug, self.args.render_unknowns)
|
|
91
|
+
grpc_runner = function.FunctionRunner(self.args.debug, self.args.render_unknowns, self.args.crossplane_v1)
|
|
87
92
|
grpc_server = grpc.aio.server()
|
|
88
93
|
grpcv1.add_FunctionRunnerServiceServicer_to_server(grpc_runner, grpc_server)
|
|
89
94
|
if self.args.insecure:
|
crossplane/pythonic/protobuf.py
CHANGED
|
@@ -58,17 +58,21 @@ def B64Decode(string):
|
|
|
58
58
|
|
|
59
59
|
class Message:
|
|
60
60
|
def __init__(self, parent, key, descriptor, message=_Unknown, readOnly=False):
|
|
61
|
-
self.
|
|
62
|
-
self.
|
|
63
|
-
self.
|
|
64
|
-
self.
|
|
65
|
-
self.
|
|
66
|
-
self.
|
|
61
|
+
self._set_attribute('_parent', parent)
|
|
62
|
+
self._set_attribute('_key', key)
|
|
63
|
+
self._set_attribute('_descriptor', descriptor)
|
|
64
|
+
self._set_attribute('_message', message)
|
|
65
|
+
self._set_attribute('_readOnly', readOnly)
|
|
66
|
+
self._set_attribute('_cache', {})
|
|
67
|
+
|
|
68
|
+
def _set_attribute(self, key, value):
|
|
69
|
+
self.__dict__[key] = value
|
|
67
70
|
|
|
68
71
|
def __getattr__(self, key):
|
|
69
72
|
return self[key]
|
|
70
73
|
|
|
71
74
|
def __getitem__(self, key):
|
|
75
|
+
key = self._validate_key(key)
|
|
72
76
|
if key in self._cache:
|
|
73
77
|
return self._cache[key]
|
|
74
78
|
field = self._descriptor.fields_by_name.get(key)
|
|
@@ -156,6 +160,7 @@ class Message:
|
|
|
156
160
|
def _create_child(self, key):
|
|
157
161
|
if self._readOnly:
|
|
158
162
|
raise ValueError(f"{self._readOnly} is read only")
|
|
163
|
+
key = self._validate_key(key)
|
|
159
164
|
if self._message is _Unknown:
|
|
160
165
|
self.__dict__['_message'] = self._parent._create_child(self._key)
|
|
161
166
|
return getattr(self._message, key)
|
|
@@ -177,6 +182,7 @@ class Message:
|
|
|
177
182
|
def __setitem__(self, key, value):
|
|
178
183
|
if self._readOnly:
|
|
179
184
|
raise ValueError(f"{self._readOnly} is read only")
|
|
185
|
+
key = self._validate_key(key)
|
|
180
186
|
if key not in self._descriptor.fields_by_name:
|
|
181
187
|
raise AttributeError(obj=self, name=key)
|
|
182
188
|
field = self._descriptor.fields_by_name[key]
|
|
@@ -201,26 +207,40 @@ class Message:
|
|
|
201
207
|
def __delitem__(self, key):
|
|
202
208
|
if self._readOnly:
|
|
203
209
|
raise ValueError(f"{self._readOnly} is read only")
|
|
210
|
+
key = self._validate_key(key)
|
|
204
211
|
if key not in self._descriptor.fields_by_name:
|
|
205
212
|
raise AttributeError(obj=self, name=key)
|
|
206
213
|
if self._message is not _Unknown:
|
|
207
214
|
self._message.ClearField(key)
|
|
208
215
|
self._cache.pop(key, None)
|
|
209
216
|
|
|
217
|
+
def _validate_key(self, key):
|
|
218
|
+
if isinstance(key, FieldMessage):
|
|
219
|
+
key = key._value
|
|
220
|
+
elif isinstance(key, Value):
|
|
221
|
+
key = key._raw
|
|
222
|
+
if not isinstance(key, str):
|
|
223
|
+
raise TypeError(f"Unexpected key type: {key.__class__}")
|
|
224
|
+
return key
|
|
225
|
+
|
|
210
226
|
|
|
211
227
|
class MapMessage:
|
|
212
228
|
def __init__(self, parent, key, field, messages=_Unknown, readOnly=False):
|
|
213
|
-
self.
|
|
214
|
-
self.
|
|
215
|
-
self.
|
|
216
|
-
self.
|
|
217
|
-
self.
|
|
218
|
-
self.
|
|
229
|
+
self._set_attribute('_parent', parent)
|
|
230
|
+
self._set_attribute('_key', key)
|
|
231
|
+
self._set_attribute('_field', field)
|
|
232
|
+
self._set_attribute('_messages', messages)
|
|
233
|
+
self._set_attribute('_readOnly', readOnly)
|
|
234
|
+
self._set_attribute('_cache', {})
|
|
235
|
+
|
|
236
|
+
def _set_attribute(self, key, value):
|
|
237
|
+
self.__dict__[key] = value
|
|
219
238
|
|
|
220
239
|
def __getattr__(self, key):
|
|
221
240
|
return self[key]
|
|
222
241
|
|
|
223
242
|
def __getitem__(self, key):
|
|
243
|
+
key = self._validate_key(key)
|
|
224
244
|
if key in self._cache:
|
|
225
245
|
return self._cache[key]
|
|
226
246
|
if self._messages is _Unknown or key not in self._messages:
|
|
@@ -304,6 +324,7 @@ class MapMessage:
|
|
|
304
324
|
def _create_child(self, key):
|
|
305
325
|
if self._readOnly:
|
|
306
326
|
raise ValueError(f"{self._readOnly} is read only")
|
|
327
|
+
key = self._validate_key(key)
|
|
307
328
|
if self._messages is _Unknown:
|
|
308
329
|
self.__dict__['_messages'] = self._parent._create_child(self._key)
|
|
309
330
|
return self._messages[key]
|
|
@@ -325,6 +346,7 @@ class MapMessage:
|
|
|
325
346
|
def __setitem__(self, key, message):
|
|
326
347
|
if self._readOnly:
|
|
327
348
|
raise ValueError(f"{self._readOnly} is read only")
|
|
349
|
+
key = self._validate_key(key)
|
|
328
350
|
if self._messages is _Unknown:
|
|
329
351
|
self._messages = self._parent._create_child(self._key)
|
|
330
352
|
if isinstance(message, Message):
|
|
@@ -349,11 +371,21 @@ class MapMessage:
|
|
|
349
371
|
def __delitem__(self, key):
|
|
350
372
|
if self._readOnly:
|
|
351
373
|
raise ValueError(f"{self._readOnly} is read only")
|
|
374
|
+
key = self._validate_key(key)
|
|
352
375
|
if self._messages is not _Unknown:
|
|
353
376
|
if key in self._messages:
|
|
354
377
|
del self._messages[key]
|
|
355
378
|
self._cache.pop(key, None)
|
|
356
379
|
|
|
380
|
+
def _validate_key(self, key):
|
|
381
|
+
if isinstance(key, FieldMessage):
|
|
382
|
+
key = key._value
|
|
383
|
+
elif isinstance(key, Value):
|
|
384
|
+
key = key._raw
|
|
385
|
+
if not isinstance(key, str):
|
|
386
|
+
raise TypeError(f"Unexpected key type: {key.__class__}")
|
|
387
|
+
return key
|
|
388
|
+
|
|
357
389
|
|
|
358
390
|
class RepeatedMessage:
|
|
359
391
|
def __init__(self, parent, key, field, messages=_Unknown, readOnly=False):
|
|
@@ -365,6 +397,7 @@ class RepeatedMessage:
|
|
|
365
397
|
self._cache = {}
|
|
366
398
|
|
|
367
399
|
def __getitem__(self, key):
|
|
400
|
+
key = self._validate_key(key)
|
|
368
401
|
if key in self._cache:
|
|
369
402
|
return self._cache[key]
|
|
370
403
|
if self._messages is _Unknown or key >= len(self._messages):
|
|
@@ -447,6 +480,7 @@ class RepeatedMessage:
|
|
|
447
480
|
def _create_child(self, key):
|
|
448
481
|
if self._readOnly:
|
|
449
482
|
raise ValueError(f"{self._readOnly} is read only")
|
|
483
|
+
key = self._validate_key(key)
|
|
450
484
|
if self._messages is _Unknown:
|
|
451
485
|
self.__dict__['_messages'] = self._parent._create_child(self._key)
|
|
452
486
|
if key == append:
|
|
@@ -471,6 +505,7 @@ class RepeatedMessage:
|
|
|
471
505
|
def __setitem__(self, key, message):
|
|
472
506
|
if self._readOnly:
|
|
473
507
|
raise ValueError(f"{self._readOnly} is read only")
|
|
508
|
+
key = self._validate_key(key)
|
|
474
509
|
if self._messages is _Unknown:
|
|
475
510
|
self._messages = self._parent._create_child(self._key)
|
|
476
511
|
if key < 0:
|
|
@@ -499,6 +534,7 @@ class RepeatedMessage:
|
|
|
499
534
|
def __delitem__(self, key):
|
|
500
535
|
if self._readOnly:
|
|
501
536
|
raise ValueError(f"{self._readOnly} is read only")
|
|
537
|
+
key = self._validate_key(key)
|
|
502
538
|
if self._messages is not _Unknown:
|
|
503
539
|
del self._messages[key]
|
|
504
540
|
self._cache.pop(key, None)
|
|
@@ -514,41 +550,75 @@ class RepeatedMessage:
|
|
|
514
550
|
message = self._messages.append(message)
|
|
515
551
|
return self[len(self._messages) - 1]
|
|
516
552
|
|
|
553
|
+
def _validate_key(self, key):
|
|
554
|
+
if isinstance(key, FieldMessage):
|
|
555
|
+
key = key._value
|
|
556
|
+
elif isinstance(key, Value):
|
|
557
|
+
key = key._raw
|
|
558
|
+
if not isinstance(key, int):
|
|
559
|
+
raise TypeError(f"Unexpected key type: {key.__class__}")
|
|
560
|
+
return key
|
|
561
|
+
|
|
517
562
|
|
|
518
563
|
class FieldMessage:
|
|
519
564
|
def __init__(self, parent, key, kind, value):
|
|
520
|
-
self.
|
|
521
|
-
self.
|
|
522
|
-
self.
|
|
523
|
-
self.
|
|
565
|
+
self._parent = parent
|
|
566
|
+
self._key = key
|
|
567
|
+
self._kind = kind
|
|
568
|
+
self._value = value
|
|
524
569
|
|
|
525
570
|
def __bool__(self):
|
|
526
|
-
return
|
|
571
|
+
return self._value is not _Unknown
|
|
527
572
|
|
|
528
573
|
def __len__(self):
|
|
574
|
+
if self._value is _Unknown:
|
|
575
|
+
return 0
|
|
529
576
|
return len(self._value)
|
|
530
577
|
|
|
531
578
|
def __contains__(self, key):
|
|
579
|
+
if self._value is _Unknown:
|
|
580
|
+
return False
|
|
532
581
|
return key in self._value
|
|
533
582
|
|
|
534
583
|
def __hash__(self):
|
|
584
|
+
if self._value is _Unknown:
|
|
585
|
+
return 0
|
|
535
586
|
return hash(self._value)
|
|
536
587
|
|
|
537
588
|
def __eq__(self, other):
|
|
589
|
+
if self._value is _Unknown:
|
|
590
|
+
return False
|
|
538
591
|
if isinstance(other, FieldMessage):
|
|
539
592
|
return self._value == other._value
|
|
540
593
|
return self._value == other
|
|
541
594
|
|
|
595
|
+
def __bytes__(self):
|
|
596
|
+
if self._value is _Unknown:
|
|
597
|
+
return None
|
|
598
|
+
if isinstance(self._value, str):
|
|
599
|
+
return self._value.encode('utf-8')
|
|
600
|
+
return bytes(self._value)
|
|
601
|
+
|
|
542
602
|
def __str__(self):
|
|
603
|
+
if self._value is _Unknown:
|
|
604
|
+
return None
|
|
605
|
+
if isinstance(self._value, bytes):
|
|
606
|
+
return self._value.decode('utf-8')
|
|
543
607
|
return str(self._value)
|
|
544
608
|
|
|
545
609
|
def __format__(self, spec=''):
|
|
610
|
+
if self._value is _Unknown:
|
|
611
|
+
return None
|
|
546
612
|
return format(self._value, spec)
|
|
547
613
|
|
|
548
614
|
def __int__(self):
|
|
615
|
+
if self._value is _Unknown:
|
|
616
|
+
return None
|
|
549
617
|
return int(self._value)
|
|
550
618
|
|
|
551
619
|
def __float__(self):
|
|
620
|
+
if self._value is _Unknown:
|
|
621
|
+
return None
|
|
552
622
|
return float(self._value)
|
|
553
623
|
|
|
554
624
|
def _fullName(self, key=None):
|
|
@@ -576,16 +646,16 @@ class ProtobufValue:
|
|
|
576
646
|
|
|
577
647
|
class Value:
|
|
578
648
|
def __init__(self, parent, key, value=_Unknown, readOnly=None):
|
|
579
|
-
self.
|
|
580
|
-
self.
|
|
581
|
-
self.
|
|
582
|
-
self.
|
|
583
|
-
self.
|
|
584
|
-
self.
|
|
649
|
+
self._set_attribute('_parent', parent)
|
|
650
|
+
self._set_attribute('_key', key)
|
|
651
|
+
self._set_attribute('_dependencies', {})
|
|
652
|
+
self._set_attribute('_unknowns', {})
|
|
653
|
+
self._set_attribute('_cache', {})
|
|
654
|
+
self._set_attribute('_readOnly', None)
|
|
585
655
|
if isinstance(value, (google.protobuf.struct_pb2.Value, google.protobuf.struct_pb2.Struct, google.protobuf.struct_pb2.ListValue)) or value is _Unknown:
|
|
586
|
-
self.
|
|
656
|
+
self._set_attribute('_value', value)
|
|
587
657
|
else:
|
|
588
|
-
self.
|
|
658
|
+
self._set_attribute('_value', google.protobuf.struct_pb2.Value())
|
|
589
659
|
if value is None:
|
|
590
660
|
self._value.null_value = 0
|
|
591
661
|
elif isinstance(value, dict):
|
|
@@ -604,12 +674,16 @@ class Value:
|
|
|
604
674
|
self._value.string_value = value
|
|
605
675
|
else:
|
|
606
676
|
raise ValueError(f"Unexpected Value type: {value.__class__}")
|
|
607
|
-
self.
|
|
677
|
+
self._set_attribute('_readOnly', readOnly)
|
|
678
|
+
|
|
679
|
+
def _set_attribute(self, key, value):
|
|
680
|
+
self.__dict__[key] = value
|
|
608
681
|
|
|
609
682
|
def __getattr__(self, key):
|
|
610
683
|
return self[key]
|
|
611
684
|
|
|
612
685
|
def __getitem__(self, key):
|
|
686
|
+
key = self._validate_key(key)
|
|
613
687
|
if key in self._cache:
|
|
614
688
|
return self._cache[key]
|
|
615
689
|
if key in self._unknowns:
|
|
@@ -641,7 +715,7 @@ class Value:
|
|
|
641
715
|
case _:
|
|
642
716
|
raise ValueError(f"Invalid key \"{key}\" for kind: {self._kind}")
|
|
643
717
|
else:
|
|
644
|
-
raise
|
|
718
|
+
raise NotImplementedError()
|
|
645
719
|
value = Value(self, key, value, self._readOnly)
|
|
646
720
|
self._cache[key] = value
|
|
647
721
|
return value
|
|
@@ -659,6 +733,10 @@ class Value:
|
|
|
659
733
|
return len(self._value.list_value.values) + len(self._unknowns)
|
|
660
734
|
case 'ListValue':
|
|
661
735
|
return len(self._value.values) + len(self._unknowns)
|
|
736
|
+
case 'string_value':
|
|
737
|
+
return len(self._value.string_value)
|
|
738
|
+
case 'bool_value':
|
|
739
|
+
return 1 if self._value.bool_value else 0
|
|
662
740
|
return 0
|
|
663
741
|
|
|
664
742
|
def __contains__(self, item):
|
|
@@ -860,6 +938,7 @@ class Value:
|
|
|
860
938
|
def __setitem__(self, key, value):
|
|
861
939
|
if self._readOnly:
|
|
862
940
|
raise ValueError(f"{self._readOnly} is read only")
|
|
941
|
+
key = self._validate_key(key)
|
|
863
942
|
if isinstance(key, str):
|
|
864
943
|
if self._ensure_map() == 'struct_value':
|
|
865
944
|
values = self._value.struct_value.fields
|
|
@@ -877,7 +956,7 @@ class Value:
|
|
|
877
956
|
while key >= len(values):
|
|
878
957
|
values.add()
|
|
879
958
|
else:
|
|
880
|
-
raise
|
|
959
|
+
raise NotImplementedError()
|
|
881
960
|
self._cache.pop(key, None)
|
|
882
961
|
self._dependencies.pop(key, None)
|
|
883
962
|
self._unknowns.pop(key, None)
|
|
@@ -887,6 +966,8 @@ class Value:
|
|
|
887
966
|
values[key].null_value = 0
|
|
888
967
|
elif isinstance(value, bool): # Must be before int check
|
|
889
968
|
values[key].bool_value = value
|
|
969
|
+
elif isinstance(value, bytes):
|
|
970
|
+
values[key].string_value = value._value.decode('utf-8')
|
|
890
971
|
elif isinstance(value, str):
|
|
891
972
|
values[key].string_value = value
|
|
892
973
|
elif isinstance(value, (int, float)):
|
|
@@ -995,6 +1076,7 @@ class Value:
|
|
|
995
1076
|
kind = self._kind
|
|
996
1077
|
if kind == 'Unknown':
|
|
997
1078
|
return
|
|
1079
|
+
key = self._validate_key(key)
|
|
998
1080
|
if isinstance(key, str):
|
|
999
1081
|
match kind:
|
|
1000
1082
|
case 'struct_value':
|
|
@@ -1036,11 +1118,12 @@ class Value:
|
|
|
1036
1118
|
break
|
|
1037
1119
|
del values[ix]
|
|
1038
1120
|
else:
|
|
1039
|
-
raise
|
|
1121
|
+
raise NotImplementedError()
|
|
1040
1122
|
|
|
1041
1123
|
def _create_child(self, key):
|
|
1042
1124
|
if self._readOnly:
|
|
1043
1125
|
raise ValueError(f"{self._readOnly} is read only")
|
|
1126
|
+
key = self._validate_key(key)
|
|
1044
1127
|
if isinstance(key, str):
|
|
1045
1128
|
if self._ensure_map() == 'struct_value':
|
|
1046
1129
|
fields = self._value.struct_value.fields
|
|
@@ -1061,7 +1144,16 @@ class Value:
|
|
|
1061
1144
|
values.add()
|
|
1062
1145
|
values[key].Clear()
|
|
1063
1146
|
return values[key]
|
|
1064
|
-
raise
|
|
1147
|
+
raise NotImplementedError()
|
|
1148
|
+
|
|
1149
|
+
def _validate_key(self, key):
|
|
1150
|
+
if isinstance(key, FieldMessage):
|
|
1151
|
+
key = key._value
|
|
1152
|
+
elif isinstance(key, Value):
|
|
1153
|
+
key = key._raw
|
|
1154
|
+
if not isinstance(key, (str, int)):
|
|
1155
|
+
raise TypeError(f"Unexpected key type: {key.__class__}")
|
|
1156
|
+
return key
|
|
1065
1157
|
|
|
1066
1158
|
def _ensure_map(self):
|
|
1067
1159
|
kind = self._kind
|
|
@@ -1154,13 +1246,14 @@ class Value:
|
|
|
1154
1246
|
for key, value in self:
|
|
1155
1247
|
if isinstance(value, Value) and len(value):
|
|
1156
1248
|
patch = patches[key]
|
|
1157
|
-
|
|
1249
|
+
print(patch.__class__, str(patch))
|
|
1250
|
+
if isinstance(patch, Value) and patch._kind == value._kind and len(patch):
|
|
1158
1251
|
value._patchUnknowns(patch)
|
|
1159
1252
|
elif self._isList:
|
|
1160
1253
|
for ix, value in enumerate(self):
|
|
1161
1254
|
if isinstance(value, Value) and len(value):
|
|
1162
1255
|
patch = patches[ix]
|
|
1163
|
-
if isinstance(patch, Value) and patch.
|
|
1256
|
+
if isinstance(patch, Value) and patch._kind == value._kind and len(patch):
|
|
1164
1257
|
value._patchUnknowns(patch)
|
|
1165
1258
|
|
|
1166
1259
|
def _renderUnknowns(self, trimFullName):
|
crossplane/pythonic/render.py
CHANGED
|
@@ -53,14 +53,6 @@ class Command(command.Command):
|
|
|
53
53
|
metavar='PATH',
|
|
54
54
|
help='A YAML file or directory of YAML files specifying the observed state of composed resources.'
|
|
55
55
|
)
|
|
56
|
-
parser.add_argument(
|
|
57
|
-
'--extra-resources',
|
|
58
|
-
action='append',
|
|
59
|
-
type=pathlib.Path,
|
|
60
|
-
default=[],
|
|
61
|
-
metavar='PATH',
|
|
62
|
-
help='A YAML file or directory of YAML files specifying required resources (deprecated, use --required-resources).',
|
|
63
|
-
)
|
|
64
56
|
parser.add_argument(
|
|
65
57
|
'--required-resources', '-e',
|
|
66
58
|
action='append',
|
|
@@ -70,18 +62,23 @@ class Command(command.Command):
|
|
|
70
62
|
help='A YAML file or directory of YAML files specifying required resources to pass to the Function pipeline.',
|
|
71
63
|
)
|
|
72
64
|
parser.add_argument(
|
|
73
|
-
'--
|
|
65
|
+
'--secret-store', '-s',
|
|
74
66
|
action='append',
|
|
75
67
|
type=pathlib.Path,
|
|
76
68
|
default=[],
|
|
77
69
|
metavar='PATH',
|
|
78
|
-
help='A YAML file or directory of YAML files specifying
|
|
70
|
+
help='A YAML file or directory of YAML files specifying Secrets to use to resolve connections and credentials.',
|
|
79
71
|
)
|
|
80
72
|
parser.add_argument(
|
|
81
73
|
'--include-full-xr', '-x',
|
|
82
74
|
action='store_true',
|
|
83
75
|
help="Include a direct copy of the input XR's spedc and metadata fields in the rendered output.",
|
|
84
76
|
)
|
|
77
|
+
parser.add_argument(
|
|
78
|
+
'--include-connection-xr',
|
|
79
|
+
action='store_true',
|
|
80
|
+
help="Include the Composite connection values in the rendered output as a resource of kind: Connection.",
|
|
81
|
+
)
|
|
85
82
|
parser.add_argument(
|
|
86
83
|
'--include-function-results', '-r',
|
|
87
84
|
action='store_true',
|
|
@@ -158,29 +155,33 @@ class Command(command.Command):
|
|
|
158
155
|
sys.exit(1)
|
|
159
156
|
request.context[key_value[0]] = protobuf.Yaml(key_value[1])
|
|
160
157
|
|
|
161
|
-
#
|
|
162
|
-
|
|
158
|
+
# Collect specified required/extra resources. Sort for stable order when processed.
|
|
159
|
+
requireds = sorted(
|
|
160
|
+
self.collect_resources(self.args.required_resources),
|
|
161
|
+
key=lambda required: str(required.metadata.name),
|
|
162
|
+
)
|
|
163
|
+
|
|
164
|
+
# Collect specified connection and credential secrets.
|
|
165
|
+
secrets = []
|
|
166
|
+
for secret in self.collect_resources(self.args.secret_store):
|
|
167
|
+
if secret.apiVersion == 'v1' and secret.kind == 'Secret':
|
|
168
|
+
secrets.append(secret)
|
|
169
|
+
|
|
170
|
+
# Establish the request observed composite.
|
|
171
|
+
self.setup_resource(composite, secrets, request.observed.composite)
|
|
172
|
+
|
|
173
|
+
# Establish the configured observed resources.
|
|
163
174
|
for resource in self.collect_resources(self.args.observed_resources):
|
|
164
175
|
name = resource.metadata.annotations['crossplane.io/composition-resource-name']
|
|
165
176
|
if name:
|
|
166
|
-
request.observed.resources[
|
|
167
|
-
|
|
168
|
-
# Collect specified required/extra resources.
|
|
169
|
-
requireds = [resource for resource in self.collect_resources(self.args.required_resources)]
|
|
170
|
-
requireds += [resource for resource in self.collect_resources(self.args.extra_resources)]
|
|
171
|
-
|
|
172
|
-
# Collect specified credential secrets.
|
|
173
|
-
credentials = []
|
|
174
|
-
for credential in self.collect_resources(self.args.function_credentials):
|
|
175
|
-
if credential.apiVersion == 'v1' and credential.kind == 'Secret':
|
|
176
|
-
credentials.append(credential)
|
|
177
|
+
self.setup_resource(resource, secrets, request.observed.resources[name])
|
|
177
178
|
|
|
178
179
|
# These will hold the response conditions and results.
|
|
179
180
|
conditions = protobuf.List()
|
|
180
181
|
results = protobuf.List()
|
|
181
182
|
|
|
182
183
|
# Create a function-pythonic function runner used to run pipeline steps.
|
|
183
|
-
runner = function.FunctionRunner(self.args.debug, self.args.render_unknowns)
|
|
184
|
+
runner = function.FunctionRunner(self.args.debug, self.args.render_unknowns, self.args.crossplane_v1)
|
|
184
185
|
fatal = False
|
|
185
186
|
|
|
186
187
|
# Process the composition pipeline steps.
|
|
@@ -194,18 +195,21 @@ class Command(command.Command):
|
|
|
194
195
|
|
|
195
196
|
# Supply step requested credentials.
|
|
196
197
|
request.credentials()
|
|
197
|
-
for
|
|
198
|
-
if
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
data
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
198
|
+
for credential in step.credentials:
|
|
199
|
+
if credential.source == 'Secret' and credential.secretRef:
|
|
200
|
+
namespace = credential.secretRef.namespace
|
|
201
|
+
name = credential.secretRef.name
|
|
202
|
+
if namespace and name:
|
|
203
|
+
for secret in secrets:
|
|
204
|
+
if secret.metadata.namespace == namespace and secret.metadata.name == name:
|
|
205
|
+
data = request.credentials[credential.name].credential_data.data
|
|
206
|
+
data()
|
|
207
|
+
for key, value in secret.data:
|
|
208
|
+
data[key] = protobuf.B64Decode(value)
|
|
209
|
+
break
|
|
210
|
+
else:
|
|
211
|
+
print(f"Step \"{step.step}\" secret not found: {namespace}/{name}", file=sys.stderr)
|
|
212
|
+
sys.exit(1)
|
|
209
213
|
|
|
210
214
|
# Track what extra/required resources have been processed.
|
|
211
215
|
requirements = protobuf.Message(None, 'requirements', fnv1.Requirements.DESCRIPTOR, fnv1.Requirements())
|
|
@@ -213,14 +217,14 @@ class Command(command.Command):
|
|
|
213
217
|
# Fetch the step bootstrap resources specified.
|
|
214
218
|
request.required_resources()
|
|
215
219
|
for requirement in step.requirements:
|
|
216
|
-
self.fetch_requireds(requireds, requirement.requirementName, requirement, request.required_resources)
|
|
220
|
+
self.fetch_requireds(requireds, secrets, requirement.requirementName, requirement, request.required_resources)
|
|
217
221
|
# Fetch the required resources requested.
|
|
218
222
|
for name, selector in requirements.resources:
|
|
219
|
-
self.fetch_requireds(requireds, name, selector, request.required_resources)
|
|
223
|
+
self.fetch_requireds(requireds, secrets, name, selector, request.required_resources)
|
|
220
224
|
# Fetch the now deprecated extra resources requested.
|
|
221
225
|
request.extra_resources()
|
|
222
226
|
for name, selector in requirements.extra_resources:
|
|
223
|
-
self.fetch_requireds(requireds, name, selector, request.extra_resources)
|
|
227
|
+
self.fetch_requireds(requireds, secrets, name, selector, request.extra_resources)
|
|
224
228
|
# Run the step using the function-pythonic function runner.
|
|
225
229
|
response = protobuf.Message(
|
|
226
230
|
None,
|
|
@@ -330,15 +334,29 @@ class Command(command.Command):
|
|
|
330
334
|
# Print the composite.
|
|
331
335
|
print('---')
|
|
332
336
|
print(str(composite), end='')
|
|
337
|
+
|
|
338
|
+
# Print Composite connection if requested.
|
|
339
|
+
if self.args.include_connection_xr:
|
|
340
|
+
connection = protobuf.Map(
|
|
341
|
+
apiVersion = 'render.crossplane.io/v1beta1',
|
|
342
|
+
kind = 'Connection',
|
|
343
|
+
)
|
|
344
|
+
for key, value in request.desired.composite.connection_details:
|
|
345
|
+
connection.values[key] = value
|
|
346
|
+
print('---')
|
|
347
|
+
print(str(connection), end='')
|
|
348
|
+
|
|
333
349
|
# Print the composed resources.
|
|
334
350
|
for resource in sorted(resources, key=lambda resource: str(resource.metadata.annotations['crossplane.io/composition-resource-name'])):
|
|
335
351
|
print('---')
|
|
336
352
|
print(str(resource), end='')
|
|
353
|
+
|
|
337
354
|
# Print the results (AKA events) if requested.
|
|
338
355
|
if self.args.include_function_results:
|
|
339
356
|
for result in results:
|
|
340
357
|
print('---')
|
|
341
358
|
print(str(result), end='')
|
|
359
|
+
|
|
342
360
|
# Print the final context if requested.
|
|
343
361
|
if self.args.include_context:
|
|
344
362
|
print('---')
|
|
@@ -346,7 +364,7 @@ class Command(command.Command):
|
|
|
346
364
|
str(protobuf.Map(
|
|
347
365
|
apiVersion = 'render.crossplane.io/v1beta1',
|
|
348
366
|
kind = 'Context',
|
|
349
|
-
|
|
367
|
+
values = request.context,
|
|
350
368
|
)),
|
|
351
369
|
end='',
|
|
352
370
|
)
|
|
@@ -382,7 +400,19 @@ class Command(command.Command):
|
|
|
382
400
|
for document in yaml.safe_load_all(file.read_text()):
|
|
383
401
|
yield protobuf.Value(None, None, document)
|
|
384
402
|
|
|
385
|
-
def
|
|
403
|
+
def setup_resource(self, source, secrets, resource):
|
|
404
|
+
resource.resource = source
|
|
405
|
+
namespace = source.spec.writeConnectionSecretToRef.namespace or source.metadata.namespace
|
|
406
|
+
name = source.spec.writeConnectionSecretToRef.name
|
|
407
|
+
if namespace and name:
|
|
408
|
+
for secret in secrets:
|
|
409
|
+
if secret.metadata.namespace == namespace and secret.metadata.name == name:
|
|
410
|
+
resource.connection_details()
|
|
411
|
+
for key, value in secret.data:
|
|
412
|
+
resource.connection_details[key] = protobuf.B64Decode(value)
|
|
413
|
+
break
|
|
414
|
+
|
|
415
|
+
def fetch_requireds(self, requireds, secrets, name, selector, resources):
|
|
386
416
|
if not name:
|
|
387
417
|
return
|
|
388
418
|
name = str(name)
|
|
@@ -391,13 +421,13 @@ class Command(command.Command):
|
|
|
391
421
|
for required in requireds:
|
|
392
422
|
if selector.api_version == required.apiVersion and selector.kind == required.kind:
|
|
393
423
|
if selector.match_name == required.metadata.name:
|
|
394
|
-
items[protobuf.append]
|
|
424
|
+
self.setup_resource(required, secrets, items[protobuf.append])
|
|
395
425
|
elif selector.match_labels.labels:
|
|
396
426
|
for key, value in selector.match_labels.labels:
|
|
397
427
|
if value != required.metadata.labels[key]:
|
|
398
428
|
break
|
|
399
429
|
else:
|
|
400
|
-
items[protobuf.append]
|
|
430
|
+
self.setup_resource(required, secrets, items[protobuf.append])
|
|
401
431
|
|
|
402
432
|
def copy_resource(self, source, destination):
|
|
403
433
|
destination.resource = source.resource
|