crossplane-function-pythonic 0.2.0__py3-none-any.whl → 0.2.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.
@@ -1,2 +1,2 @@
1
1
  # This is set at build time, using "hatch version"
2
- __version__ = "0.2.0"
2
+ __version__ = "0.2.1"
@@ -40,6 +40,7 @@ class BaseComposite:
40
40
  observed = self.request.observed.composite
41
41
  desired = self.response.desired.composite
42
42
  self.observed = observed.resource
43
+ self.observed._set_attribute('connection', self.request.observed.composite.connection_details)
43
44
  self.desired = desired.resource
44
45
  self.apiVersion = self.observed.apiVersion
45
46
  self.kind = self.observed.kind
@@ -434,6 +435,7 @@ class RequiredResource:
434
435
  self.data = self.observed.data
435
436
  self.status = self.observed.status
436
437
  self.conditions = Conditions(resource)
438
+ self.connection = self.observed.connection_details
437
439
 
438
440
  def __bool__(self):
439
441
  return bool(self.observed)
@@ -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,6 +81,7 @@ 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
@@ -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.__dict__['_parent'] = parent
62
- self.__dict__['_key'] = key
63
- self.__dict__['_descriptor'] = descriptor
64
- self.__dict__['_message'] = message
65
- self.__dict__['_readOnly'] = readOnly
66
- self.__dict__['_cache'] = {}
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.__dict__['_parent'] = parent
214
- self.__dict__['_key'] = key
215
- self.__dict__['_field'] = field
216
- self.__dict__['_messages'] = messages
217
- self.__dict__['_readOnly'] = readOnly
218
- self.__dict__['_cache'] = {}
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,13 +550,22 @@ 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.__dict__['_parent'] = parent
521
- self.__dict__['_key'] = key
522
- self.__dict__['_kind'] = kind
523
- self.__dict__['_value'] = value
565
+ self._parent = parent
566
+ self._key = key
567
+ self._kind = kind
568
+ self._value = value
524
569
 
525
570
  def __bool__(self):
526
571
  return bool(self._value)
@@ -539,7 +584,14 @@ class FieldMessage:
539
584
  return self._value == other._value
540
585
  return self._value == other
541
586
 
587
+ def __bytes__(self):
588
+ if isinstance(self._value, str):
589
+ return self._value.encode('utf-8')
590
+ return bytes(self._value)
591
+
542
592
  def __str__(self):
593
+ if isinstance(self._value, bytes):
594
+ return self._value.decode('utf-8')
543
595
  return str(self._value)
544
596
 
545
597
  def __format__(self, spec=''):
@@ -576,16 +628,16 @@ class ProtobufValue:
576
628
 
577
629
  class Value:
578
630
  def __init__(self, parent, key, value=_Unknown, readOnly=None):
579
- self.__dict__['_parent'] = parent
580
- self.__dict__['_key'] = key
581
- self.__dict__['_dependencies'] = {}
582
- self.__dict__['_unknowns'] = {}
583
- self.__dict__['_cache'] = {}
584
- self.__dict__['_readOnly'] = None
631
+ self._set_attribute('_parent', parent)
632
+ self._set_attribute('_key', key)
633
+ self._set_attribute('_dependencies', {})
634
+ self._set_attribute('_unknowns', {})
635
+ self._set_attribute('_cache', {})
636
+ self._set_attribute('_readOnly', None)
585
637
  if isinstance(value, (google.protobuf.struct_pb2.Value, google.protobuf.struct_pb2.Struct, google.protobuf.struct_pb2.ListValue)) or value is _Unknown:
586
- self.__dict__['_value'] = value
638
+ self._set_attribute('_value', value)
587
639
  else:
588
- self.__dict__['_value'] = google.protobuf.struct_pb2.Value()
640
+ self._set_attribute('_value', google.protobuf.struct_pb2.Value())
589
641
  if value is None:
590
642
  self._value.null_value = 0
591
643
  elif isinstance(value, dict):
@@ -604,12 +656,16 @@ class Value:
604
656
  self._value.string_value = value
605
657
  else:
606
658
  raise ValueError(f"Unexpected Value type: {value.__class__}")
607
- self.__dict__['_readOnly'] = readOnly
659
+ self._set_attribute('_readOnly', readOnly)
660
+
661
+ def _set_attribute(self, key, value):
662
+ self.__dict__[key] = value
608
663
 
609
664
  def __getattr__(self, key):
610
665
  return self[key]
611
666
 
612
667
  def __getitem__(self, key):
668
+ key = self._validate_key(key)
613
669
  if key in self._cache:
614
670
  return self._cache[key]
615
671
  if key in self._unknowns:
@@ -641,7 +697,7 @@ class Value:
641
697
  case _:
642
698
  raise ValueError(f"Invalid key \"{key}\" for kind: {self._kind}")
643
699
  else:
644
- raise ValueError(f"Unexpected key type: {key.__class__}")
700
+ raise NotImplementedError()
645
701
  value = Value(self, key, value, self._readOnly)
646
702
  self._cache[key] = value
647
703
  return value
@@ -860,6 +916,7 @@ class Value:
860
916
  def __setitem__(self, key, value):
861
917
  if self._readOnly:
862
918
  raise ValueError(f"{self._readOnly} is read only")
919
+ key = self._validate_key(key)
863
920
  if isinstance(key, str):
864
921
  if self._ensure_map() == 'struct_value':
865
922
  values = self._value.struct_value.fields
@@ -877,7 +934,7 @@ class Value:
877
934
  while key >= len(values):
878
935
  values.add()
879
936
  else:
880
- raise ValueError('Unexpected key type')
937
+ raise NotImplementedError()
881
938
  self._cache.pop(key, None)
882
939
  self._dependencies.pop(key, None)
883
940
  self._unknowns.pop(key, None)
@@ -887,6 +944,8 @@ class Value:
887
944
  values[key].null_value = 0
888
945
  elif isinstance(value, bool): # Must be before int check
889
946
  values[key].bool_value = value
947
+ elif isinstance(value, bytes):
948
+ values[key].string_value = value._value.decode('utf-8')
890
949
  elif isinstance(value, str):
891
950
  values[key].string_value = value
892
951
  elif isinstance(value, (int, float)):
@@ -995,6 +1054,7 @@ class Value:
995
1054
  kind = self._kind
996
1055
  if kind == 'Unknown':
997
1056
  return
1057
+ key = self._validate_key(key)
998
1058
  if isinstance(key, str):
999
1059
  match kind:
1000
1060
  case 'struct_value':
@@ -1036,11 +1096,12 @@ class Value:
1036
1096
  break
1037
1097
  del values[ix]
1038
1098
  else:
1039
- raise ValueError('Unexpected key type')
1099
+ raise NotImplementedError()
1040
1100
 
1041
1101
  def _create_child(self, key):
1042
1102
  if self._readOnly:
1043
1103
  raise ValueError(f"{self._readOnly} is read only")
1104
+ key = self._validate_key(key)
1044
1105
  if isinstance(key, str):
1045
1106
  if self._ensure_map() == 'struct_value':
1046
1107
  fields = self._value.struct_value.fields
@@ -1061,7 +1122,16 @@ class Value:
1061
1122
  values.add()
1062
1123
  values[key].Clear()
1063
1124
  return values[key]
1064
- raise ValueError('Unexpected key type')
1125
+ raise NotImplementedError()
1126
+
1127
+ def _validate_key(self, key):
1128
+ if isinstance(key, FieldMessage):
1129
+ key = key._value
1130
+ elif isinstance(key, Value):
1131
+ key = key._raw
1132
+ if not isinstance(key, (str, int)):
1133
+ raise TypeError(f"Unexpected key type: {key.__class__}")
1134
+ return key
1065
1135
 
1066
1136
  def _ensure_map(self):
1067
1137
  kind = self._kind
@@ -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
- '--function-credentials',
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 credentials to use for Functions to render the XR.',
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,22 +155,26 @@ class Command(command.Command):
158
155
  sys.exit(1)
159
156
  request.context[key_value[0]] = protobuf.Yaml(key_value[1])
160
157
 
161
- # Establish the request observed composite and specifed observed resources.
162
- request.observed.composite.resource = composite
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(resource.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[str(name)].resource = resource
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()
@@ -194,18 +195,21 @@ class Command(command.Command):
194
195
 
195
196
  # Supply step requested credentials.
196
197
  request.credentials()
197
- for fn_credential in step.credentials:
198
- if fn_credential.source == 'Secret' and fn_credential.secretRef:
199
- for credential in credentials:
200
- if credential.metadata.namespace == fn_credential.secretRef.namespace and credential.metadata.name == fn_credential.secretRef.name:
201
- data = request.credentials[str(fn_credential.name)].credential_data.data
202
- data()
203
- for key, value in credential.data:
204
- data[key] = protobuf.B64Decode(value)
205
- break
206
- else:
207
- print(f"Step \"{step.step}\" secret not found: {fn_credential.secretRef.namespace} {fn_credential.secretRef.name}", file=sys.stderr)
208
- sys.exit(1)
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
- fields = request.context,
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 fetch_requireds(self, requireds, name, selector, resources):
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].resource = required
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].resource = required
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
@@ -1,7 +1,7 @@
1
1
 
2
2
  from . import (
3
- command,
4
3
  __about__,
4
+ command,
5
5
  )
6
6
 
7
7
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: crossplane-function-pythonic
3
- Version: 0.2.0
3
+ Version: 0.2.1
4
4
  Summary: A Python centric Crossplane Function
5
5
  Project-URL: Documentation, https://github.com/crossplane-contrib/function-pythonic#readme
6
6
  Project-URL: Issues, https://github.com/crossplane-contrib/function-pythonic/issues
@@ -81,7 +81,7 @@ kind: Function
81
81
  metadata:
82
82
  name: function-pythonic
83
83
  spec:
84
- package: xpkg.upbound.io/crossplane-contrib/function-pythonic:v0.2.0
84
+ package: xpkg.upbound.io/crossplane-contrib/function-pythonic:v0.2.1
85
85
  ```
86
86
  ## Composed Resource Dependencies
87
87
 
@@ -228,8 +228,9 @@ The BaseComposite class provides the following fields for manipulating the Compo
228
228
  | self.status | Map | The composite desired and observed status, read from observed if not in desired |
229
229
  | self.conditions | Conditions | The composite desired and observed conditions, read from observed if not in desired |
230
230
  | self.results | Results | Returned results applied to the Composite and optionally on the Claim |
231
- | self.connection | Connection | The composite desired and observed connection detials, read from observed if not in desired |
231
+ | self.connection | Map | The composite desired connection detials |
232
232
  | self.ready | Boolean | The composite desired ready state |
233
+ | self.observed.connection | Map | The composite observed connection detials |
233
234
 
234
235
  The BaseComposite also provides access to the following Crossplane Function level features:
235
236
 
@@ -270,7 +271,7 @@ Resource class:
270
271
  | Resource.data | Map | The resource data |
271
272
  | Resource.status | Map | The resource status |
272
273
  | Resource.conditions | Conditions | The resource conditions |
273
- | Resource.connection | Connection | The resource connection details |
274
+ | Resource.connection | Map | The resource observed connection details |
274
275
  | Resource.ready | Boolean | The resource ready state |
275
276
  | Resource.unknownsFatal | Boolean | Terminate the composition if this resource has been created and is assigned unknown values, default is Composite.unknownsFatal |
276
277
  | Resource.usages | Boolean | Generate Crossplane Usages for this resource, default is Composite.autoReady |
@@ -310,6 +311,7 @@ Each resource in the list is the following RequiredResource class:
310
311
  | RequiredResource.data | Map | The required resource data |
311
312
  | RequiredResource.status | Map | The required resource status |
312
313
  | RequiredResource.conditions | Map | The required resource conditions |
314
+ | RequiredResource.connection | Map | The required resource connection details |
313
315
 
314
316
  ### Conditions
315
317
 
@@ -372,11 +374,11 @@ $ pip install crossplane-function-pythonic
372
374
  Then to render function-pythonic Compositions, use the `function-pythonic render ...`
373
375
  command.
374
376
  ```shell
375
- $ function-pythonic render --help
377
+ $ function-pythonic render -h
376
378
  usage: Crossplane Function Pythonic render [-h] [--debug] [--log-name-width WIDTH] [--python-path DIRECTORY] [--render-unknowns]
377
379
  [--allow-oversize-protos] [--context-files KEY=PATH] [--context-values KEY=VALUE]
378
- [--observed-resources PATH] [--extra-resources PATH] [--required-resources PATH]
379
- [--function-credentials PATH] [--include-full-xr] [--include-function-results] [--include-context]
380
+ [--observed-resources PATH] [--required-resources PATH] [--secret-store PATH] [--include-full-xr]
381
+ [--include-connection-xr] [--include-function-results] [--include-context]
380
382
  PATH [PATH/CLASS]
381
383
 
382
384
  positional arguments:
@@ -400,14 +402,14 @@ options:
400
402
  Context key-value pairs to pass to the Function pipeline. Values must be YAML/JSON. Keys take precedence over --context-files.
401
403
  --observed-resources, -o PATH
402
404
  A YAML file or directory of YAML files specifying the observed state of composed resources.
403
- --extra-resources PATH
404
- A YAML file or directory of YAML files specifying required resources (deprecated, use --required-resources).
405
405
  --required-resources, -e PATH
406
406
  A YAML file or directory of YAML files specifying required resources to pass to the Function pipeline.
407
- --function-credentials PATH
408
- A YAML file or directory of YAML files specifying credentials to use for Functions to render the XR.
407
+ --secret-store, -s PATH
408
+ A YAML file or directory of YAML files specifying Secrets to use to resolve connections and credentials.
409
409
  --include-full-xr, -x
410
410
  Include a direct copy of the input XR's spedc and metadata fields in the rendered output.
411
+ --include-connection-xr
412
+ Include the Composite connection values in the rendered output as a resource of kind: Connection.
411
413
  --include-function-results, -r
412
414
  Include informational and warning messages from Functions in the rendered output as resources of kind: Result..
413
415
  --include-context, -c
@@ -545,7 +547,7 @@ kind: Function
545
547
  metadata:
546
548
  name: function-pythonic
547
549
  spec:
548
- package: xpkg.upbound.io/crossplane-contrib/function-pythonic:v0.2.0
550
+ package: xpkg.upbound.io/crossplane-contrib/function-pythonic:v0.2.1
549
551
  runtimeConfigRef:
550
552
  name: function-pythonic
551
553
  ---
@@ -0,0 +1,17 @@
1
+ crossplane/pythonic/__about__.py,sha256=QBcrW1XnbdqF5kVtJfA-yvTFAU-t00iGaaoo6kjjIDY,73
2
+ crossplane/pythonic/__init__.py,sha256=A9U4-azc4DjSsOnOnjQxCkoTzsZMRBb_AvqzR_Bd95A,268
3
+ crossplane/pythonic/__main__.py,sha256=6vYRlYDJtqFgLyiTamnl3htiNOtz8QlDl5WlIP98I8o,31
4
+ crossplane/pythonic/command.py,sha256=s69oOVCgbm39gowvTy7ieaE-Vosckf24vcznJ6fO0Q4,3146
5
+ crossplane/pythonic/composite.py,sha256=PJD3uRbanrkUgCUxnN_lKhg5n3QO1OpGfLC71UkEwRg,22129
6
+ crossplane/pythonic/function.py,sha256=ShHhSJYblTB9BRx7NEtHgiyOconax7Lro0tLzzRyZdY,16564
7
+ crossplane/pythonic/grpc.py,sha256=yc-hVnw7K0iuuJS25z6aZkfXjUuvciWfGCdHnWv4I78,4443
8
+ crossplane/pythonic/main.py,sha256=ujUa_FYElQSGqnhZ-0NJrD3kSyYjfRbIp79FV2Yl7hs,599
9
+ crossplane/pythonic/packages.py,sha256=4TxyT6V79R0m4tJbC8R1gwU_vgHGLXKSBzeTTKd8xGo,5120
10
+ crossplane/pythonic/protobuf.py,sha256=Wha3hMSDbJPALtMu-ZcZEHaV5CosfYW8SJh7ueAbqVk,50528
11
+ crossplane/pythonic/render.py,sha256=yqqk9DambTBRgQ51ZEQWOLg1xvos1KGETHei6rimus8,22238
12
+ crossplane/pythonic/version.py,sha256=-RiB0p146ayqJj0SXfYxTNv49u9Fx9pPgm59Ji2blhc,214
13
+ crossplane_function_pythonic-0.2.1.dist-info/METADATA,sha256=pIsTA_rzQ_HBpVlYZZI67AmHz27qcqAfxYoY5j1iNW0,28659
14
+ crossplane_function_pythonic-0.2.1.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
15
+ crossplane_function_pythonic-0.2.1.dist-info/entry_points.txt,sha256=jJ4baywFDviB9WyAhyhNYF2VOCb6XtbRSjKf7bnBwhg,68
16
+ crossplane_function_pythonic-0.2.1.dist-info/licenses/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
17
+ crossplane_function_pythonic-0.2.1.dist-info/RECORD,,
@@ -1,17 +0,0 @@
1
- crossplane/pythonic/__about__.py,sha256=gXUN1UoV7TWafLuqL_UsLD_UlnDY6JmtTV7bvwphX6Q,73
2
- crossplane/pythonic/__init__.py,sha256=A9U4-azc4DjSsOnOnjQxCkoTzsZMRBb_AvqzR_Bd95A,268
3
- crossplane/pythonic/__main__.py,sha256=6vYRlYDJtqFgLyiTamnl3htiNOtz8QlDl5WlIP98I8o,31
4
- crossplane/pythonic/command.py,sha256=s69oOVCgbm39gowvTy7ieaE-Vosckf24vcznJ6fO0Q4,3146
5
- crossplane/pythonic/composite.py,sha256=SVgyhyg4MDNm_rGSrHUi81bd9Zqgnv3zaMKnaCMKB2s,21967
6
- crossplane/pythonic/function.py,sha256=ShHhSJYblTB9BRx7NEtHgiyOconax7Lro0tLzzRyZdY,16564
7
- crossplane/pythonic/grpc.py,sha256=Ts_-JywIr4Xzjjf_hr2uOlPK0O8A1SNN6ZaEjRckjL8,4318
8
- crossplane/pythonic/main.py,sha256=ujUa_FYElQSGqnhZ-0NJrD3kSyYjfRbIp79FV2Yl7hs,599
9
- crossplane/pythonic/packages.py,sha256=4TxyT6V79R0m4tJbC8R1gwU_vgHGLXKSBzeTTKd8xGo,5120
10
- crossplane/pythonic/protobuf.py,sha256=Wf2VJteKR_0S8l96JKLoOI78yrcH258DVj03re_epFI,48163
11
- crossplane/pythonic/render.py,sha256=4mq-slOSGoGbSPJiioeepWbdImSyVbas_hIEnJO_p5s,21069
12
- crossplane/pythonic/version.py,sha256=jUi8Wk7malaNDdW3FemkcsqlSGygQCrRFvgWB3Sve8M,214
13
- crossplane_function_pythonic-0.2.0.dist-info/METADATA,sha256=w5BMWBdP1wvuPR-zeE28frxeBi-S8f9UfDE2gcj2OHk,28576
14
- crossplane_function_pythonic-0.2.0.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
15
- crossplane_function_pythonic-0.2.0.dist-info/entry_points.txt,sha256=jJ4baywFDviB9WyAhyhNYF2VOCb6XtbRSjKf7bnBwhg,68
16
- crossplane_function_pythonic-0.2.0.dist-info/licenses/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
17
- crossplane_function_pythonic-0.2.0.dist-info/RECORD,,