crossplane-function-pythonic 0.6.2__py3-none-any.whl → 0.7.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.
@@ -1,14 +1,18 @@
1
-
1
+ import argparse
2
2
  import asyncio
3
3
  import importlib
4
- import inflect
5
4
  import inspect
6
5
  import json
7
- import kr8s
8
6
  import logging
9
7
  import pathlib
10
8
  import sys
9
+ import typing
10
+ from typing import cast
11
+
12
+ import inflect
13
+ import kr8s
11
14
  import yaml
15
+ from kr8s._api import Api
12
16
 
13
17
  from . import (
14
18
  command,
@@ -18,105 +22,111 @@ from . import (
18
22
  from .composite import BaseComposite
19
23
  from .proto.v1 import run_function_pb2 as fnv1
20
24
 
21
-
22
25
  INFLECT = inflect.engine()
23
26
  INFLECT.classical(all=False)
24
27
 
25
28
 
26
29
  class Command(command.Command):
27
- name = 'render'
28
- help = 'Render a function-pythonic Composition'
30
+ name = "render"
31
+ help = "Render a function-pythonic Composition"
29
32
 
30
33
  @classmethod
31
- def add_parser_arguments(cls, parser):
34
+ def add_parser_arguments(cls, parser: argparse.ArgumentParser) -> None:
32
35
  cls.add_function_arguments(parser)
33
36
  parser.add_argument(
34
- 'composite',
37
+ "composite",
35
38
  type=pathlib.Path,
36
- metavar='COMPOSITE',
37
- help='A YAML file containing the Composite resource to render, or kind:apiVersion:namespace:name of cluster Composite.',
39
+ metavar="COMPOSITE",
40
+ help="A YAML file containing the Composite resource to render, or kind:apiVersion:namespace:name of cluster Composite.",
38
41
  )
39
42
  parser.add_argument(
40
- 'composition',
43
+ "composition",
41
44
  type=pathlib.Path,
42
- nargs='?',
43
- metavar='COMPOSITION',
44
- help='A YAML file containing the Composition resource, or the complete path of a function-pythonic BaseComposite subclass.',
45
+ nargs="?",
46
+ metavar="COMPOSITION",
47
+ help="A YAML file containing the Composition resource, or the complete path of a function-pythonic BaseComposite subclass.",
45
48
  )
46
49
  parser.add_argument(
47
- '--kube-context', '-k',
48
- metavar='CONTEXT',
49
- help='The kubectl context to use to obtain external resources from, such as required resources, connections, etc.'
50
+ "--kube-context",
51
+ "-k",
52
+ metavar="CONTEXT",
53
+ help="The kubectl context to use to obtain external resources from, such as required resources, connections, etc.",
50
54
  )
51
55
  parser.add_argument(
52
- '--context-files',
53
- action='append',
56
+ "--context-files",
57
+ action="append",
54
58
  default=[],
55
- metavar='KEY=PATH',
56
- help='Context key-value pairs to pass to the Function pipeline. Values must be files containing YAML/JSON.',
59
+ metavar="KEY=PATH",
60
+ help="Context key-value pairs to pass to the Function pipeline. Values must be files containing YAML/JSON.",
57
61
  )
58
62
  parser.add_argument(
59
- '--context-values',
60
- action='append',
63
+ "--context-values",
64
+ action="append",
61
65
  default=[],
62
- metavar='KEY=VALUE',
63
- help='Context key-value pairs to pass to the Function pipeline. Values must be YAML/JSON. Keys take precedence over --context-files.',
66
+ metavar="KEY=VALUE",
67
+ help="Context key-value pairs to pass to the Function pipeline. Values must be YAML/JSON. Keys take precedence over --context-files.",
64
68
  )
65
69
  parser.add_argument(
66
- '--observed-resources', '-o',
67
- action='append',
70
+ "--observed-resources",
71
+ "-o",
72
+ action="append",
68
73
  type=pathlib.Path,
69
74
  default=[],
70
- metavar='PATH',
71
- help='A YAML file or directory of YAML files specifying the observed state of composed resources.'
75
+ metavar="PATH",
76
+ help="A YAML file or directory of YAML files specifying the observed state of composed resources.",
72
77
  )
73
78
  parser.add_argument(
74
- '--required-resources', '-e',
75
- action='append',
79
+ "--required-resources",
80
+ "-e",
81
+ action="append",
76
82
  type=pathlib.Path,
77
83
  default=[],
78
- metavar='PATH',
79
- help='A YAML file or directory of YAML files specifying required resources to pass to the Function pipeline.',
84
+ metavar="PATH",
85
+ help="A YAML file or directory of YAML files specifying required resources to pass to the Function pipeline.",
80
86
  )
81
87
  parser.add_argument(
82
- '--required-schemas', '-s',
83
- action='append',
88
+ "--required-schemas",
89
+ "-s",
90
+ action="append",
84
91
  type=pathlib.Path,
85
92
  default=[],
86
- metavar='PATH',
87
- help='A JSON file or directory of JSON files specifying required schemas to pass to the Function pipeline.',
93
+ metavar="PATH",
94
+ help="A JSON file or directory of JSON files specifying required schemas to pass to the Function pipeline.",
88
95
  )
89
96
  parser.add_argument(
90
- '--include-full-xr', '-x',
91
- action='store_true',
97
+ "--include-full-xr",
98
+ "-x",
99
+ action="store_true",
92
100
  help="Include a direct copy of the input XR's spedc and metadata fields in the rendered output.",
93
101
  )
94
102
  parser.add_argument(
95
- '--include-connection-xr',
96
- action='store_true',
103
+ "--include-connection-xr",
104
+ action="store_true",
97
105
  help="Include the Composite connection values in the rendered output as a resource of kind: Connection.",
98
106
  )
99
107
  parser.add_argument(
100
- '--include-function-results', '-r',
101
- action='store_true',
102
- help='Include informational and warning messages from Functions in the rendered output as resources of kind: Result.',
108
+ "--include-function-results",
109
+ "-r",
110
+ action="store_true",
111
+ help="Include informational and warning messages from Functions in the rendered output as resources of kind: Result.",
103
112
  )
104
113
  parser.add_argument(
105
- '--include-context', '-c',
106
- action='store_true',
107
- help='Include the context in the rendered output as a resource of kind: Context.',
114
+ "--include-context",
115
+ "-c",
116
+ action="store_true",
117
+ help="Include the context in the rendered output as a resource of kind: Context.",
108
118
  )
109
119
 
110
- def initialize(self):
111
- if self.args:
120
+ def initialize(self) -> None:
121
+ if vars(self.args):
112
122
  self.initialize_function()
113
123
  self.logger = logging.getLogger(__name__)
114
124
 
115
- async def run(self):
125
+ async def run(self) -> None:
116
126
  if self.args.kube_context:
117
- api = await kr8s.asyncio.api(context=self.args.kube_context)
127
+ api: Api | None = await kr8s.asyncio.api(context=self.args.kube_context)
118
128
  else:
119
- api = None
129
+ api: Api | None = None
120
130
  composite = await self.setup_composite(api)
121
131
  observed = self.collect_resources(self.args.observed_resources)
122
132
  composition = await self.setup_composition(composite, api)
@@ -124,7 +134,17 @@ class Command(command.Command):
124
134
  schemas = self.collect_schemas(self.args.required_schemas)
125
135
  context = self.setup_context()
126
136
 
127
- render = await self.render(composite, observed, composition, resources, schemas, context, api, self.args.render_unknowns, self.args.crossplane_v1)
137
+ render = await self.render(
138
+ composite,
139
+ observed,
140
+ composition,
141
+ resources,
142
+ schemas,
143
+ context,
144
+ api,
145
+ self.args.render_unknowns,
146
+ self.args.crossplane_v1,
147
+ )
128
148
  if not render:
129
149
  sys.exit(1)
130
150
 
@@ -139,34 +159,40 @@ class Command(command.Command):
139
159
  render.composite.metadata.name = composite.metadata.name
140
160
 
141
161
  # Print the composite.
142
- print('---')
143
- print(str(render.composite), end='')
162
+ print("---")
163
+ print(str(render.composite), end="")
144
164
  # Print Composite connection if requested.
145
165
  if self.args.include_connection_xr:
146
- print('---')
147
- print(str(render.connection), end='')
166
+ print("---")
167
+ print(str(render.connection), end="")
148
168
  # Print the composed resources.
149
- for resource in sorted(render.resources, key=lambda resource: str(resource.metadata.annotations['crossplane.io/composition-resource-name'])):
150
- print('---')
151
- print(str(resource), end='')
169
+ for resource in sorted(
170
+ render.resources,
171
+ key=lambda resource: str(
172
+ resource.metadata.annotations["crossplane.io/composition-resource-name"]
173
+ ),
174
+ ):
175
+ print("---")
176
+ print(str(resource), end="")
152
177
  # Print the results (AKA events) if requested.
153
178
  if self.args.include_function_results:
154
179
  for result in render.results:
155
- print('---')
156
- print(str(result), end='')
180
+ print("---")
181
+ print(str(result), end="")
157
182
  # Print the final context if requested.
158
183
  if self.args.include_context:
159
- print('---')
160
- print(str(render.context), end='')
184
+ print("---")
185
+ print(str(render.context), end="")
161
186
 
162
- async def setup_composite(self, api=None):
187
+ async def setup_composite(self, api=Api | None) -> protobuf.Value:
163
188
  # Obtain the Composite to render.
164
189
  if self.args.composite.is_file():
165
- return protobuf.Yaml(self.args.composite.read_text())
190
+ return cast(protobuf.Value, protobuf.Yaml(self.args.composite.read_text()))
166
191
  if not api:
167
- print(f"Composite \"{self.args.composite}\" is not a file", file=sys.stderr)
192
+ print(f'Composite "{self.args.composite}" is not a file', file=sys.stderr)
168
193
  sys.exit(1)
169
- composite = str(self.args.composite).split(':')
194
+ return
195
+ composite = str(self.args.composite).split(":")
170
196
  if len(composite) == 3:
171
197
  namespace = None
172
198
  elif len(composite) == 4:
@@ -175,65 +201,94 @@ class Command(command.Command):
175
201
  else:
176
202
  namespace = None
177
203
  else:
178
- print(f"Composite \"{self.args.composite}\" is not kind:apiVersion:namespace:name", file=sys.stderr)
204
+ print(
205
+ f'Composite "{self.args.composite}" is not kind:apiVersion:namespace:name',
206
+ file=sys.stderr,
207
+ )
179
208
  sys.exit(1)
180
- composite = await self.kr8s_get(api, composite[0], composite[1], namespace, composite[-1])
209
+ composite = await self.kr8s_get(
210
+ cast(Api, api), composite[0], composite[1], namespace, composite[-1]
211
+ )
181
212
  if not composite:
182
- print(f"Composite \"{self.args.composite}\" not found", file=sys.stderr)
213
+ print(f'Composite "{self.args.composite}" not found', file=sys.stderr)
183
214
  sys.exit(1)
184
215
  return composite
185
216
 
186
- async def setup_composition(self, composite, api=None):
217
+ async def setup_composition(
218
+ self, composite: protobuf.Value, api: Api | None = None
219
+ ) -> protobuf.Value | None:
187
220
  # Obtain the Composition that will be used to render the Composite.
188
221
  if not self.args.composition:
189
222
  return None
190
223
  if self.args.composition.is_file():
191
224
  composition = self.args.composition.read_text()
192
- if self.args.composition.suffix == '.py':
193
- return self.create_composition(compsite, composition)
194
- composition = protobuf.Yaml(composition)
225
+ if self.args.composition.suffix == ".py":
226
+ return self.create_composition(composite, composition)
227
+ composition = cast(protobuf.Value, protobuf.Yaml(composition))
195
228
  if not len(composition.spec.pipeline):
196
- print(f"Composition file does not contain any pipeline steps: {self.args.composition}", file=sys.stderr)
229
+ print(
230
+ f"Composition file does not contain any pipeline steps: {self.args.composition}",
231
+ file=sys.stderr,
232
+ )
197
233
  sys.exit(1)
198
234
  return composition
199
- composition = str(self.args.composition).rsplit('.', 1)
235
+ composition = str(self.args.composition).rsplit(".", 1)
200
236
  if len(composition) == 1:
201
- print(f"Composition class name does not include module: {self.args.composition}", file=sys.stderr)
237
+ print(
238
+ f"Composition class name does not include module: {self.args.composition}",
239
+ file=sys.stderr,
240
+ )
202
241
  sys.exit(1)
203
242
  try:
204
243
  module = importlib.import_module(composition[0])
205
- except Exception as e:
244
+ except Exception as e: # noqa: BLE001
206
245
  print(e)
207
- print(f"Unable to import composition module: {composition[0]}", file=sys.stderr)
246
+ print(
247
+ f"Unable to import composition module: {composition[0]}",
248
+ file=sys.stderr,
249
+ )
208
250
  sys.exit(1)
209
251
  clazz = getattr(module, composition[1], None)
210
252
  if not clazz:
211
- print(f"Composition class {composition[0]} does not define: {composition[1]}", file=sys.stderr)
253
+ print(
254
+ f"Composition class {composition[0]} does not define: {composition[1]}",
255
+ file=sys.stderr,
256
+ )
212
257
  sys.exit(1)
213
258
  if not inspect.isclass(clazz):
214
- print(f"Composition class {self.args.composition} is not a class", file=sys.stderr)
259
+ print(
260
+ f"Composition class {self.args.composition} is not a class",
261
+ file=sys.stderr,
262
+ )
215
263
  sys.exit(1)
216
264
  if not issubclass(clazz, BaseComposite):
217
- print(f"Composition class {self.args.composition} is not a subclass of BaseComposite", file=sys.stderr)
265
+ print(
266
+ f"Composition class {self.args.composition} is not a subclass of BaseComposite",
267
+ file=sys.stderr,
268
+ )
218
269
  sys.exit(1)
219
270
  return self.create_composition(composite, self.args.composition)
220
271
 
221
- def create_composition(self, composite, module=''):
272
+ def create_composition(
273
+ self, composite: protobuf.Value, module: str = ""
274
+ ) -> protobuf.Value:
222
275
  composition = protobuf.Map()
223
- composition.apiVersion = 'apiextensions.crossplane.io/v1'
224
- composition.kind = 'Composition'
225
- composition.metadata.name = 'function-pythonic-render'
276
+ composition.apiVersion = "apiextensions.crossplane.io/v1"
277
+ composition.kind = "Composition"
278
+ composition.metadata.name = "function-pythonic-render"
226
279
  composition.spec.compositeTypeRef.apiVersion = composite.apiVersion
227
280
  composition.spec.compositeTypeRef.kind = composite.kind
228
- composition.spec.mode = 'Pipeline'
229
- composition.spec.pipeline[0].step = 'function-pythonic-render'
230
- composition.spec.pipeline[0].functionRef.name = 'function-pythonic'
231
- composition.spec.pipeline[0].input.apiVersion = 'pythonic.fn.crossplane.io/v1alpha1'
232
- composition.spec.pipeline[0].input.kind = 'Composite'
281
+ composition.spec.mode = "Pipeline"
282
+ composition.spec.pipeline[0].step = "function-pythonic-render"
283
+ composition.spec.pipeline[0].functionRef.name = "function-pythonic"
284
+ composition.spec.pipeline[
285
+ 0
286
+ ].input.apiVersion = "pythonic.fn.crossplane.io/v1alpha1"
287
+ composition.spec.pipeline[0].input.kind = "Composite"
233
288
  composition.spec.pipeline[0].input.composite = str(module)
234
289
  return composition
235
290
 
236
- def collect_resources(self, entries):
291
+ def collect_resources(self, entries: list[pathlib.Path]) -> list[protobuf.Value]:
237
292
  resources = []
238
293
  for entry in entries:
239
294
  if entry.is_file():
@@ -241,16 +296,19 @@ class Command(command.Command):
241
296
  resources.append(protobuf.Value(None, None, document))
242
297
  elif entry.is_dir():
243
298
  for file in entry.iterdir():
244
- if file.suffix in ('.yaml', '.yml'):
299
+ if file.suffix in (".yaml", ".yml"):
245
300
  for document in yaml.safe_load_all(file.read_text()):
246
301
  resources.append(protobuf.Value(None, None, document))
247
302
  else:
248
- print(f"Specified resource is not a file or a directory: {entry}", file=sys.stderr)
303
+ print(
304
+ f"Specified resource is not a file or a directory: {entry}",
305
+ file=sys.stderr,
306
+ )
249
307
  sys.exit(1)
250
308
  resources.sort(key=lambda resource: str(resource.metadata.name))
251
309
  return resources
252
310
 
253
- def collect_schemas(self, entries):
311
+ def collect_schemas(self, entries: list[pathlib.Path]) -> list[protobuf.Value]:
254
312
  schemas = []
255
313
  for entry in entries:
256
314
  if entry.is_file():
@@ -258,19 +316,22 @@ class Command(command.Command):
258
316
  schemas.append(protobuf.Value(None, None, document))
259
317
  elif entry.is_dir():
260
318
  for file in entry.iterdir():
261
- if file.suffix == '.json':
319
+ if file.suffix == ".json":
262
320
  document = json.loads(file.read_text())
263
321
  schemas.append(protobuf.Value(None, None, document))
264
322
  else:
265
- print(f"Specified resource is not a file or a directory: {entry}", file=sys.stderr)
323
+ print(
324
+ f"Specified resource is not a file or a directory: {entry}",
325
+ file=sys.stderr,
326
+ )
266
327
  sys.exit(1)
267
328
  return schemas
268
329
 
269
- def setup_context(self):
330
+ def setup_context(self) -> protobuf.Value:
270
331
  # Load the request context with any specified command line options.
271
332
  context = protobuf.Map()
272
333
  for entry in self.args.context_files:
273
- key_path = entry.split('=', 1)
334
+ key_path = entry.split("=", 1)
274
335
  if len(key_path) != 2:
275
336
  print(f"Invalid --context-files: {entry}", file=sys.stderr)
276
337
  sys.exit(1)
@@ -280,28 +341,62 @@ class Command(command.Command):
280
341
  sys.exit(1)
281
342
  context[key_path[0]] = protobuf.Yaml(path.read_text())
282
343
  for entry in self.args.context_values:
283
- key_value = entry.split('=', 1)
344
+ key_value = entry.split("=", 1)
284
345
  if len(key_value) != 2:
285
346
  print(f"Invalid --context-values: {entry}", file=sys.stderr)
286
347
  sys.exit(1)
287
348
  context[key_value[0]] = protobuf.Yaml(key_value[1])
288
349
  return context
289
350
 
290
- async def render(self, composite, observed=[], composition=None, resources=[], schemas=[], context=None, api=None, render_unknowns=False, crossplane_v1=False, composite_observeds=False):
351
+ async def render(
352
+ self,
353
+ composite: protobuf.Value,
354
+ observed: list[protobuf.Value] | None = None,
355
+ composition: protobuf.Value | None = None,
356
+ resources: list[protobuf.Value] | None = None,
357
+ schemas: list[protobuf.Value] | None = None,
358
+ context: protobuf.Value | None = None,
359
+ api: Api | None = None,
360
+ render_unknowns: bool = False,
361
+ crossplane_v1: bool = False,
362
+ composite_observeds: bool = False,
363
+ ) -> protobuf.Value | None:
291
364
  # Create the request used when running Composition steps.
292
- request = protobuf.Message(None, 'request', fnv1.RunFunctionRequest.DESCRIPTOR, fnv1.RunFunctionRequest())
365
+ if schemas is None:
366
+ schemas = []
367
+ if resources is None:
368
+ resources = []
369
+ if observed is None:
370
+ observed = []
371
+ request = protobuf.Message(
372
+ None,
373
+ "request",
374
+ fnv1.RunFunctionRequest.DESCRIPTOR,
375
+ fnv1.RunFunctionRequest(),
376
+ )
293
377
  if context is not None:
294
378
  request.context = context
295
379
 
296
380
  # Establish the request observed composite.
297
- await self.set_resource(composite, request.observed.composite, resources, api)
381
+ await self.set_resource(
382
+ composite, cast(protobuf.Value, request.observed.composite), resources, api
383
+ )
298
384
  # Establish the manually configured observed resources.
299
385
  if observed:
300
386
  async with asyncio.TaskGroup() as group:
301
387
  for resource in observed:
302
- name = resource.metadata.annotations['crossplane.io/composition-resource-name']
388
+ name = resource.metadata.annotations[
389
+ "crossplane.io/composition-resource-name"
390
+ ]
303
391
  if name:
304
- group.create_task(self.set_resource(resource, request.observed.resources[name], resources, api))
392
+ group.create_task(
393
+ self.set_resource(
394
+ resource,
395
+ cast(protobuf.Value, request.observed.resources[name]),
396
+ resources,
397
+ api,
398
+ )
399
+ )
305
400
  if api and composite_observeds:
306
401
  refs = composite.spec.crossplane.resourceRefs
307
402
  if not refs:
@@ -309,10 +404,18 @@ class Command(command.Command):
309
404
  if refs:
310
405
  async with asyncio.TaskGroup() as group:
311
406
  for ref in refs:
312
- group.create_task(self.get_composite_ref(composite, ref, request, resources, api))
407
+ group.create_task(
408
+ self.get_composite_ref(
409
+ composite, ref, request, resources, api
410
+ )
411
+ )
313
412
 
314
413
  if not composition:
315
- if composite.apiVersion in ('pythonic.crossplane.io/v1alpha1', 'pythonic.fortra.com/v1alpha1') and composite.kind == 'Composite':
414
+ if (
415
+ composite.apiVersion
416
+ in ("pythonic.crossplane.io/v1alpha1", "pythonic.fortra.com/v1alpha1")
417
+ and composite.kind == "Composite"
418
+ ):
316
419
  composition = self.create_composition(composite)
317
420
  else:
318
421
  if not api:
@@ -323,11 +426,20 @@ class Command(command.Command):
323
426
  # Crossplane V1 location
324
427
  revision = composite.spec.compositionRevisionRef
325
428
  if not revision.name:
326
- print('Composite does not contain a CompositionRevision name', file=sys.stderr)
429
+ print(
430
+ "Composite does not contain a CompositionRevision name",
431
+ file=sys.stderr,
432
+ )
327
433
  return None
328
- composition = await self.kr8s_get(api, 'CompositionRevision', 'apiextensions.crossplane.io/v1', None, revision.name)
434
+ composition = await self.kr8s_get(
435
+ api,
436
+ "CompositionRevision",
437
+ "apiextensions.crossplane.io/v1",
438
+ None,
439
+ revision.name,
440
+ )
329
441
  if not composition:
330
- print(f"Compositioin \"{revision.name}\" not found", file=sys.stderr)
442
+ print(f'Compositioin "{revision.name}" not found', file=sys.stderr)
331
443
  return None
332
444
 
333
445
  # These will hold the response conditions and results.
@@ -340,8 +452,11 @@ class Command(command.Command):
340
452
 
341
453
  # Process the composition pipeline steps.
342
454
  for step in composition.spec.pipeline:
343
- if step.functionRef.name != 'function-pythonic':
344
- print(f"Only function-pythonic functions can be run: {step.functionRef.name}", file=sys.stderr)
455
+ if step.functionRef.name != "function-pythonic":
456
+ print(
457
+ f"Only function-pythonic functions can be run: {step.functionRef.name}",
458
+ file=sys.stderr,
459
+ )
345
460
  return None
346
461
  if not step.input.step:
347
462
  step.input.step = step.step
@@ -350,46 +465,84 @@ class Command(command.Command):
350
465
  # Supply step requested credentials.
351
466
  request.credentials()
352
467
  for credential in step.credentials:
353
- if credential.source == 'Secret' and credential.secretRef:
468
+ if credential.source == "Secret" and credential.secretRef:
354
469
  namespace = credential.secretRef.namespace
355
470
  name = credential.secretRef.name
356
471
  if namespace and name:
357
472
  for resource in resources:
358
- if resource.kind == 'Secret' and resource.apiVersion == 'v1':
359
- if resource.metadata.namespace == namespace and resource.metadata.name == name:
360
- data = request.credentials[credential.name].credential_data.data
361
- data()
362
- for key, value in resource.data:
363
- data[key] = protobuf.B64Decode(value)
364
- break
473
+ if (
474
+ resource.kind == "Secret"
475
+ and resource.apiVersion == "v1"
476
+ and resource.metadata.namespace == namespace
477
+ and resource.metadata.name == name
478
+ ):
479
+ data = request.credentials[
480
+ credential.name
481
+ ].credential_data.data
482
+ data()
483
+ for key, value in resource.data:
484
+ data[key] = protobuf.B64Decode(value)
485
+ break
365
486
  else:
366
- print(f"Step \"{step.step}\" secret not found: {namespace}/{name}", file=sys.stderr)
487
+ print(
488
+ f'Step "{step.step}" secret not found: {namespace}/{name}',
489
+ file=sys.stderr,
490
+ )
367
491
  return None
368
492
 
369
493
  # Track what extra/required resources have been processed.
370
- requirements = protobuf.Message(None, 'requirements', fnv1.Requirements.DESCRIPTOR, fnv1.Requirements())
494
+ requirements = protobuf.Message(
495
+ None, "requirements", fnv1.Requirements.DESCRIPTOR, fnv1.Requirements()
496
+ )
371
497
  for _ in range(5):
372
498
  # Fetch the step bootstrap resources specified.
373
499
  request.required_resources()
374
500
  for requirement in step.requirements.requiredResources:
375
- await self.set_required(requirement.requirementName, requirement, request.required_resources, resources, api)
501
+ await self.set_required(
502
+ requirement.requirementName,
503
+ self.requirement_selector(requirement),
504
+ cast(protobuf.MapMessage, request.required_resources),
505
+ resources,
506
+ api,
507
+ )
376
508
  # Fetch the required resources requested.
377
509
  for name, selector in requirements.resources:
378
- await self.set_required(name, selector, request.required_resources, resources, api)
510
+ await self.set_required(
511
+ name,
512
+ selector,
513
+ cast(protobuf.MapMessage, request.required_resources),
514
+ resources,
515
+ api,
516
+ )
379
517
  # Fetch the now deprecated extra resources requested.
380
518
  request.extra_resources()
381
519
  for name, selector in requirements.extra_resources:
382
- await self.set_required(name, selector, request.extra_resources, resources, api)
520
+ await self.set_required(
521
+ name,
522
+ selector,
523
+ cast(protobuf.MapMessage, request.extra_resources),
524
+ resources,
525
+ api,
526
+ )
383
527
  # Fetch the schemas requested.
384
528
  request.required_schemas()
385
529
  for name, selector in requirements.schemas:
386
- await self.set_schema(name, selector, request.required_schemas, schemas, api)
530
+ await self.set_schema(
531
+ name,
532
+ selector,
533
+ cast(protobuf.MapMessage, request.required_schemas),
534
+ schemas,
535
+ api,
536
+ )
387
537
  # Run the step using the function-pythonic function runner.
388
538
  response = protobuf.Message(
389
539
  None,
390
- 'response',
540
+ "response",
391
541
  fnv1.RunFunctionResponse.DESCRIPTOR,
392
- await runner.RunFunction(request._message, None),
542
+ await runner.RunFunction(
543
+ cast(fnv1.RunFunctionRequest, request._message),
544
+ None,
545
+ ),
393
546
  )
394
547
  # Copy the response context to the request context to use in subsequent steps.
395
548
  request.context = response.context
@@ -408,19 +561,29 @@ class Command(command.Command):
408
561
 
409
562
  # Copy the response desired state to the request desired state to use in subsequent steps.
410
563
  request.desired.resources()
411
- self.copy_resource(response.desired.composite, request.desired.composite)
564
+ self.copy_resource(
565
+ cast(protobuf.Message, response.desired.composite),
566
+ cast(protobuf.Message, request.desired.composite),
567
+ )
412
568
  for name, resource in response.desired.resources:
413
- self.copy_resource(resource, request.desired.resources[name])
569
+ self.copy_resource(
570
+ resource, cast(protobuf.Message, request.desired.resources[name])
571
+ )
414
572
 
415
573
  # Collect the step's returned conditions.
416
574
  for condition in response.conditions:
417
- if condition.type not in ('Ready', 'Synced', 'Healthy'):
418
- conditions[protobuf.append] = self.create_condition(condition.type, condition.status, condition.reason, condition.message)
575
+ if condition.type not in ("Ready", "Synced", "Healthy"):
576
+ conditions[protobuf.append] = self.create_condition(
577
+ condition.type,
578
+ condition.status,
579
+ condition.reason,
580
+ condition.message,
581
+ )
419
582
  # Collect the step's returned results.
420
583
  for result in response.results:
421
584
  ix = len(results)
422
- results[ix].apiVersion = 'render.crossplane.io/v1beta1'
423
- results[ix].kind = 'Result'
585
+ results[ix].apiVersion = "render.crossplane.io/v1beta1"
586
+ results[ix].kind = "Result"
424
587
  results[ix].step = step.step
425
588
  results[ix].severity = fnv1.Severity.Name(result.severity._value)
426
589
  if result.reason:
@@ -433,9 +596,9 @@ class Command(command.Command):
433
596
  break
434
597
 
435
598
  # Collect and format all the returned desired composed resources.
436
- resources = protobuf.List()
437
- unready = protobuf.List()
438
- prefix = composite.metadata.labels['crossplane.io/composite']
599
+ resources: protobuf.Value = protobuf.List()
600
+ unready: protobuf.Value = protobuf.List()
601
+ prefix = composite.metadata.labels["crossplane.io/composite"]
439
602
  if not prefix:
440
603
  prefix = composite.metadata.name
441
604
  for name, resource in request.desired.resources:
@@ -448,29 +611,44 @@ class Command(command.Command):
448
611
  if not ready:
449
612
  unready[protobuf.append] = name
450
613
  resource = resource.resource
451
- observed = request.observed.resources[name].resource
614
+ observed: protobuf.Value = cast(
615
+ protobuf.Value, request.observed.resources[name].resource
616
+ )
452
617
  if observed:
453
- for key in ('namespace', 'generateName', 'name'):
618
+ for key in ("namespace", "generateName", "name"):
454
619
  if observed.metadata[key]:
455
620
  resource.metadata[key] = observed.metadata[key]
456
621
  if not resource.metadata.name and not resource.metadata.generateName:
457
622
  resource.metadata.generateName = f"{prefix}-"
458
623
  if composite.metadata.namespace:
459
624
  resource.metadata.namespace = composite.metadata.namespace
460
- resource.metadata.annotations['crossplane.io/composition-resource-name'] = name
461
- resource.metadata.labels['crossplane.io/composite'] = prefix
462
- if composite.metadata.labels['crossplane.io/claim-name'] and composite.metadata.labels['crossplane.io/claim-namespace']:
463
- resource.metadata.labels['crossplane.io/claim-namespace'] = composite.metadata.labels['crossplane.io/claim-namespace']
464
- resource.metadata.labels['crossplane.io/claim-name'] = composite.metadata.labels['crossplane.io/claim-name']
625
+ resource.metadata.annotations["crossplane.io/composition-resource-name"] = (
626
+ name
627
+ )
628
+ resource.metadata.labels["crossplane.io/composite"] = prefix
629
+ if (
630
+ composite.metadata.labels["crossplane.io/claim-name"]
631
+ and composite.metadata.labels["crossplane.io/claim-namespace"]
632
+ ):
633
+ resource.metadata.labels["crossplane.io/claim-namespace"] = (
634
+ composite.metadata.labels["crossplane.io/claim-namespace"]
635
+ )
636
+ resource.metadata.labels["crossplane.io/claim-name"] = (
637
+ composite.metadata.labels["crossplane.io/claim-name"]
638
+ )
465
639
  elif composite.spec.claimRef.namespace and composite.spec.claimRef.name:
466
- resource.metadata.labels['crossplane.io/claim-namespace'] = composite.spec.claimRef.namespace
467
- resource.metadata.labels['crossplane.io/claim-name'] = composite.spec.claimRef.name
640
+ resource.metadata.labels["crossplane.io/claim-namespace"] = (
641
+ composite.spec.claimRef.namespace
642
+ )
643
+ resource.metadata.labels["crossplane.io/claim-name"] = (
644
+ composite.spec.claimRef.name
645
+ )
468
646
  resource.metadata.ownerReferences[0].controller = True
469
647
  resource.metadata.ownerReferences[0].blockOwnerDeletion = True
470
648
  resource.metadata.ownerReferences[0].apiVersion = composite.apiVersion
471
649
  resource.metadata.ownerReferences[0].kind = composite.kind
472
650
  resource.metadata.ownerReferences[0].name = composite.metadata.name
473
- resource.metadata.ownerReferences[0].uid = ''
651
+ resource.metadata.ownerReferences[0].uid = ""
474
652
  resource.ready = ready
475
653
  resources[protobuf.append] = resource
476
654
 
@@ -482,11 +660,18 @@ class Command(command.Command):
482
660
  composite.kind = request.observed.composite.resource.kind
483
661
  # Add in the composite's status.conditions.
484
662
  if request.desired.composite.ready == fnv1.Ready.READY_FALSE:
485
- condition = self.create_condition('Ready', False, 'Creating')
486
- elif request.desired.composite.ready == fnv1.Ready.READY_UNSPECIFIED and len(unready):
487
- condition = self.create_condition('Ready', False, 'Creating', f"Unready resources: {','.join(str(name) for name in unready)}")
663
+ condition = self.create_condition("Ready", False, "Creating")
664
+ elif request.desired.composite.ready == fnv1.Ready.READY_UNSPECIFIED and len(
665
+ unready
666
+ ):
667
+ condition = self.create_condition(
668
+ "Ready",
669
+ False,
670
+ "Creating",
671
+ f"Unready resources: {','.join(str(name) for name in unready)}",
672
+ )
488
673
  else:
489
- condition = self.create_condition('Ready', True, 'Available')
674
+ condition = self.create_condition("Ready", True, "Available")
490
675
  composite.status.conditions[protobuf.append] = condition
491
676
  for condition in conditions:
492
677
  composite.status.conditions[protobuf.append] = condition
@@ -494,15 +679,18 @@ class Command(command.Command):
494
679
  response = protobuf.Map(
495
680
  composite=composite,
496
681
  connection=protobuf.Map(
497
- apiVersion='render.crossplane.io/v1beta1',
498
- kind='Connection',
499
- values={key: value for key, value in request.desired.composite.connection_details}
682
+ apiVersion="render.crossplane.io/v1beta1",
683
+ kind="Connection",
684
+ values={
685
+ key: value
686
+ for key, value in request.desired.composite.connection_details
687
+ },
500
688
  ),
501
689
  resources=resources,
502
690
  results=results,
503
691
  context=protobuf.Map(
504
- apiVersion='render.crossplane.io/v1beta1',
505
- kind='Context',
692
+ apiVersion="render.crossplane.io/v1beta1",
693
+ kind="Context",
506
694
  values=request.context,
507
695
  ),
508
696
  )
@@ -511,7 +699,14 @@ class Command(command.Command):
511
699
  response.observed[name] = resource.resource
512
700
  return response
513
701
 
514
- async def get_composite_ref(self, composite, ref, request, resources, api):
702
+ async def get_composite_ref(
703
+ self,
704
+ composite: protobuf.Value,
705
+ ref: protobuf.Value,
706
+ request: protobuf.Message,
707
+ resources: list[protobuf.Value],
708
+ api: Api,
709
+ ) -> None:
515
710
  namespace = ref.namespace
516
711
  if not namespace:
517
712
  namespace = composite.metadata.namespace
@@ -519,109 +714,203 @@ class Command(command.Command):
519
714
  namespace = None
520
715
  source = await self.kr8s_get(api, ref.kind, ref.apiVersion, namespace, ref.name)
521
716
  if source:
522
- name = source.metadata.annotations['crossplane.io/composition-resource-name']
717
+ name = source.metadata.annotations[
718
+ "crossplane.io/composition-resource-name"
719
+ ]
523
720
  if name:
524
721
  destination = request.observed.resources[name]
525
- if not destination: # Do not override manual observed
526
- await self.set_resource(source, destination, resources, api)
722
+ if not destination: # Do not override manual observed
723
+ await self.set_resource(
724
+ source, cast(protobuf.Value, destination), resources, api
725
+ )
527
726
 
528
- async def set_required(self, name, selector, requireds, resources=[], api=None):
727
+ async def set_required(
728
+ self,
729
+ name: protobuf.TypeString,
730
+ selector: protobuf.Message,
731
+ requireds: protobuf.MapMessage,
732
+ resources: list[protobuf.Value] | None = None,
733
+ api: Api | None = None,
734
+ ) -> None:
735
+ if resources is None:
736
+ resources = []
529
737
  if not name:
530
738
  return
531
739
  name = str(name)
532
740
  items = requireds[name].items
533
- items() # Force this to get created
741
+ items() # Force this to get created
534
742
  for resource in resources:
535
- if selector.api_version == resource.apiVersion and selector.kind == resource.kind:
536
- if ((not len(selector.namespace) and not len(resource.metadata.namespace))
743
+ if (
744
+ selector.api_version == resource.apiVersion
745
+ and selector.kind == resource.kind
746
+ and (
747
+ (
748
+ not len(selector.namespace)
749
+ and not len(resource.metadata.namespace)
750
+ )
537
751
  or (selector.namespace == resource.metadata.namespace)
538
- ):
539
- if selector.match_name == resource.metadata.name:
540
- await self.set_resource(resource, items[protobuf.append], resources, api)
541
- elif selector.match_labels.labels:
542
- for key, value in selector.match_labels.labels:
543
- if value != resource.metadata.labels[key]:
544
- break
545
- else:
546
- await self.set_resource(resource, items[protobuf.append], resources, api)
752
+ )
753
+ ):
754
+ if selector.match_name == resource.metadata.name:
755
+ await self.set_resource(
756
+ resource, items[protobuf.append], resources, api
757
+ )
758
+ elif selector.match_labels.labels:
759
+ for key, value in selector.match_labels.labels:
760
+ if value != resource.metadata.labels[key]:
761
+ break
762
+ else:
763
+ await self.set_resource(
764
+ resource, items[protobuf.append], resources, api
765
+ )
547
766
  if not len(items) and api:
548
767
  if len(selector.match_name):
549
- resource = await self.kr8s_get(api, selector.kind, selector.api_version, selector.namespace, selector.match_name)
768
+ resource = await self.kr8s_get(
769
+ api,
770
+ cast(protobuf.TypeString, selector.kind),
771
+ cast(protobuf.TypeString, selector.api_version),
772
+ cast(protobuf.TypeString, selector.namespace),
773
+ cast(protobuf.TypeString, selector.match_name),
774
+ )
550
775
  if resource:
551
- await self.set_resource(resource, items[protobuf.append], resources, api)
776
+ await self.set_resource(
777
+ resource, items[protobuf.append], resources, api
778
+ )
552
779
  elif len(selector.match_labels.labels):
553
- for resource in await self.kr8s_list(api, selector.kind, selector.api_version, selector.namespace, selector.match_labels.labels):
554
- await self.set_resource(resource, items[protobuf.append], resources, api)
780
+ for resource in await self.kr8s_list(
781
+ api,
782
+ cast(protobuf.TypeString, selector.kind),
783
+ cast(protobuf.TypeString, selector.api_version),
784
+ cast(protobuf.TypeString, selector.namespace),
785
+ cast(protobuf.MapMessage, selector.match_labels.labels),
786
+ ):
787
+ await self.set_resource(
788
+ resource, items[protobuf.append], resources, api
789
+ )
555
790
 
556
- async def set_resource(self, source, destination, resources=[], api=None):
791
+ def requirement_selector(self, requirement: protobuf.Value) -> protobuf.Message:
792
+ # Convert a Composition step requirement into a function ResourceSelector.
793
+ selector = protobuf.Message(
794
+ None,
795
+ "selector",
796
+ fnv1.ResourceSelector.DESCRIPTOR,
797
+ fnv1.ResourceSelector(),
798
+ )
799
+ selector.api_version = requirement.apiVersion
800
+ selector.kind = requirement.kind
801
+ if requirement.namespace:
802
+ selector.namespace = requirement.namespace
803
+ if requirement.name:
804
+ selector.match_name = requirement.name
805
+ elif requirement.matchLabels:
806
+ for key, value in requirement.matchLabels:
807
+ selector.match_labels.labels[key] = value
808
+ return selector
809
+
810
+ async def set_resource(
811
+ self,
812
+ source: protobuf.Value,
813
+ destination: protobuf.Value,
814
+ resources: list[protobuf.Value] | None = None,
815
+ api: Api | None = None,
816
+ ) -> None:
817
+ if resources is None:
818
+ resources = []
557
819
  destination.resource = source
558
- namespace = source.spec.writeConnectionSecretToRef.namespace or source.metadata.namespace
820
+ namespace = (
821
+ source.spec.writeConnectionSecretToRef.namespace
822
+ or source.metadata.namespace
823
+ )
559
824
  name = source.spec.writeConnectionSecretToRef.name
560
825
  if namespace and name:
561
826
  connection = None
562
827
  for resource in resources:
563
- if resource.kind == 'Secret' and resource.apiVersion == 'v1':
564
- if resource.metadata.namespace == namespace and resource.metadata.name == name:
565
- connection = resource
566
- break
828
+ if (
829
+ resource.kind == "Secret"
830
+ and resource.apiVersion == "v1"
831
+ and resource.metadata.namespace == namespace
832
+ and resource.metadata.name == name
833
+ ):
834
+ connection = resource
835
+ break
567
836
  else:
568
837
  if api:
569
- connection = await self.kr8s_get(api, 'Secret', 'v1', namespace, name)
838
+ connection = await self.kr8s_get(
839
+ api, "Secret", "v1", namespace, name
840
+ )
570
841
  if connection:
571
842
  destination.connection_details()
572
843
  for key, value in connection.data:
573
844
  destination.connection_details[key] = protobuf.B64Decode(value)
574
845
 
575
- async def set_schema(self, name, selector, schemas, documents=[], api=None):
846
+ async def set_schema(
847
+ self,
848
+ name: protobuf.TypeString,
849
+ selector: protobuf.Value,
850
+ schemas: protobuf.MapMessage,
851
+ documents: list[protobuf.Value] | None = None,
852
+ api: Api | None = None,
853
+ ) -> None:
854
+ if documents is None:
855
+ documents = []
576
856
  if not name:
577
857
  return
578
858
  name = str(name)
579
859
  schema = schemas[name].openapi_v3
580
- schema() # Force this to get created
860
+ schema() # Force this to get created
581
861
  gvk = protobuf.Map(kind=selector.kind)
582
862
  version = str(selector.api_version)
583
- if '/' in version:
584
- gvk.group, gvk.version = version.split('/', 1)
863
+ if "/" in version:
864
+ gvk.group, gvk.version = version.split("/", 1)
585
865
  else:
586
- gvk.group = ''
866
+ gvk.group = ""
587
867
  gvk.version = version
588
868
  for document in documents:
589
869
  if self.find_schema(gvk, document, schema):
590
870
  return
591
871
  if api:
592
- if gvk.group == '':
872
+ if gvk.group == "":
593
873
  url = f"api/{gvk.version}"
594
874
  else:
595
875
  url = f"apis/{gvk.group}/{gvk.version}"
596
876
  try:
597
- async with api.call_api(base='/openapi/v3', version='', url=url) as response:
877
+ async with api.call_api(
878
+ base="/openapi/v3", version="", url=url
879
+ ) as response:
598
880
  document = protobuf.Value(None, None, response.json())
599
881
  except kr8s.NotFoundError:
600
882
  return
601
883
  self.find_schema(gvk, document, schema)
602
884
 
603
- def find_schema(self, gvk, document, schema):
885
+ def find_schema(
886
+ self, gvk: protobuf.Value, document: protobuf.Value, schema: protobuf.Value
887
+ ) -> bool:
604
888
  for name, s in document.components.schemas:
605
- gvks = s['x-kubernetes-group-version-kind']
889
+ gvks = s["x-kubernetes-group-version-kind"]
606
890
  if len(gvks) == 1 and gvks[0] == gvk:
607
- self.resolve_ref(document, set(), f"#/components/schemas/{name}", schema)
891
+ self.resolve_ref(
892
+ document, set(), f"#/components/schemas/{name}", schema
893
+ )
608
894
  return True
609
895
  return False
610
896
 
611
- def resolve_ref(self, document, visiting, ref, schema):
897
+ def resolve_ref(
898
+ self,
899
+ document: protobuf.Value,
900
+ visiting: set,
901
+ ref: protobuf.TypeString,
902
+ schema: protobuf.Value,
903
+ ) -> None:
612
904
  if not ref:
613
905
  return
614
906
  ref = str(ref)
615
907
  if ref in visiting:
616
908
  return
617
- d = None
618
- for segment in ref.split('/'):
619
- if segment == '#':
620
- d = document
621
- else:
622
- d = d[segment]
623
- if not d:
909
+ if not ref.startswith("#/"):
624
910
  return
911
+ d = document
912
+ for segment in ref.split("/")[1:]:
913
+ d = d[segment]
625
914
  visiting.add(ref)
626
915
  try:
627
916
  for name, value in d:
@@ -629,12 +918,19 @@ class Command(command.Command):
629
918
  finally:
630
919
  visiting.remove(ref)
631
920
 
632
- def copy_schema(self, document, visiting, key, value, schema):
633
- if key == '$ref':
921
+ def copy_schema(
922
+ self,
923
+ document: protobuf.Value,
924
+ visiting: set,
925
+ key: str | int,
926
+ value: protobuf.Value,
927
+ schema: protobuf.Value,
928
+ ) -> None:
929
+ if key == "$ref":
634
930
  self.resolve_ref(document, visiting, value, schema)
635
- elif key == 'allOf':
931
+ elif key == "allOf":
636
932
  if value._isList and len(value) == 1:
637
- self.resolve_ref(document, visiting, value[0]['$ref'], schema)
933
+ self.resolve_ref(document, visiting, value[0]["$ref"], schema)
638
934
  else:
639
935
  if value._isMap:
640
936
  s = schema[key]
@@ -647,40 +943,55 @@ class Command(command.Command):
647
943
  else:
648
944
  schema[key] = value
649
945
 
650
- def copy_resource(self, source, destination):
946
+ def copy_resource(
947
+ self, source: protobuf.Message, destination: protobuf.Message
948
+ ) -> None:
651
949
  destination.resource = source.resource
652
950
  destination.connection_details()
653
951
  for key, value in source.connection_details:
654
952
  destination.connection_details[key] = value
655
953
  destination.ready = source.ready
656
954
 
657
- def create_condition(self, type, status, reason, message=None):
955
+ def create_condition(
956
+ self,
957
+ type: str,
958
+ status: protobuf.FieldMessage | bool | None,
959
+ reason: str,
960
+ message: str | None = None,
961
+ ) -> dict[str, str]:
658
962
  if isinstance(status, protobuf.FieldMessage):
659
963
  if status._value == fnv1.Status.STATUS_CONDITION_TRUE:
660
- status = 'True'
964
+ status: str = "True"
661
965
  elif status._value == fnv1.Status.STATUS_CONDITION_FALSE:
662
- status = 'False'
966
+ status: str = "False"
663
967
  else:
664
- status = 'Unknown'
968
+ status: str = "Unknown"
665
969
  elif isinstance(status, bool):
666
970
  if status:
667
- status = 'True'
971
+ status: str = "True"
668
972
  else:
669
- status = 'False'
973
+ status: str = "False"
670
974
  elif status is None:
671
- status = 'Unknown'
672
- condition = {
673
- 'type': type,
674
- 'status': status,
675
- 'reason': reason,
676
- 'lastTransitionTime': '2026-01-01T00:00:00Z'
975
+ status: str = "Unknown"
976
+ condition: dict[str, str] = {
977
+ "type": type,
978
+ "status": status,
979
+ "reason": reason,
980
+ "lastTransitionTime": "2026-01-01T00:00:00Z",
677
981
  }
678
982
  if message:
679
- condition['message'] = message
983
+ condition["message"] = message
680
984
  return condition
681
985
 
682
- async def kr8s_get(self, api, kind, apiVersion, namespace, name):
683
- namespaced = namespace and len(namespace)
986
+ async def kr8s_get(
987
+ self,
988
+ api: Api,
989
+ kind: protobuf.TypeString,
990
+ apiVersion: protobuf.TypeString,
991
+ namespace: protobuf.TypeString | None,
992
+ name: protobuf.TypeString,
993
+ ) -> protobuf.Value | None:
994
+ namespaced = bool(namespace) and bool(len(namespace))
684
995
  clazz = self.kr8s_class(kind, apiVersion, namespaced)
685
996
  try:
686
997
  fullName = [str(kind), str(apiVersion), str(name)]
@@ -690,42 +1001,64 @@ class Command(command.Command):
690
1001
  else:
691
1002
  resource = await clazz.get(str(name), api=api)
692
1003
  resource = protobuf.Value(None, None, resource.raw)
693
- result = 'found'
1004
+ result = "found"
694
1005
  except kr8s.NotFoundError:
695
1006
  resource = None
696
- result = 'missing'
1007
+ result = "missing"
697
1008
  self.logger.debug(f"Resource {result}: {':'.join(fullName)}")
698
1009
  return resource
699
1010
 
700
- async def kr8s_list(self, api, kind, apiVersion, namespace, labelSelector):
701
- namespaced = namespace and len(namespace)
1011
+ async def kr8s_list(
1012
+ self,
1013
+ api: Api,
1014
+ kind: protobuf.TypeString,
1015
+ apiVersion: protobuf.TypeString,
1016
+ namespace: protobuf.TypeString | None,
1017
+ labelSelector: protobuf.MapMessage,
1018
+ ) -> list[protobuf.Value]:
1019
+ namespaced = bool(namespace) and bool(len(namespace))
702
1020
  clazz = self.kr8s_class(kind, apiVersion, namespaced)
703
1021
  resources = [
704
1022
  protobuf.Value(None, None, resource.raw)
705
- async for resource in clazz.list(
1023
+ async for resource in cast(
1024
+ typing.AsyncGenerator[kr8s.APIObject],
1025
+ clazz.list(
706
1026
  namespace=str(namespace) if namespaced else None,
707
1027
  label_selector={
708
- label: str(value)
709
- for label, value in labelSelector
1028
+ label: str(value) for label, value in labelSelector
710
1029
  },
711
1030
  api=api,
1031
+ ),
712
1032
  )
713
1033
  ]
714
1034
  if self.logger.isEnabledFor(logging.DEBUG):
715
1035
  fullName = [str(kind), str(apiVersion)]
716
1036
  if namespaced:
717
1037
  fullName.append(str(namespace))
718
- fullName.append('&'.join(f"{label}={value}" for label, value in labelSelector))
1038
+ fullName.append(
1039
+ "&".join(f"{label}={value}" for label, value in labelSelector)
1040
+ )
719
1041
  if resources:
720
- result = f"found {INFLECT.number_to_words(len(resources))}"
1042
+ result = f"found {INFLECT.number_to_words(str(len(resources)))}"
721
1043
  else:
722
- result = 'missing'
1044
+ result = "missing"
723
1045
  self.logger.debug(f"Resources {result}: {':'.join(fullName)}")
724
1046
  return resources
725
1047
 
726
- def kr8s_class(self, kind, apiVersion, namespaced):
1048
+ def kr8s_class(
1049
+ self,
1050
+ kind: protobuf.TypeString,
1051
+ apiVersion: protobuf.TypeString,
1052
+ namespaced: bool,
1053
+ ) -> type[kr8s.APIObject]:
727
1054
  try:
728
1055
  return kr8s.asyncio.objects.get_class(str(kind), str(apiVersion), True)
729
1056
  except KeyError:
730
1057
  pass
731
- return kr8s.asyncio.objects.new_class(str(kind), str(apiVersion), True, namespaced, plural=INFLECT.plural_noun(str(kind)).lower())
1058
+ return kr8s.asyncio.objects.new_class(
1059
+ str(kind),
1060
+ str(apiVersion),
1061
+ True,
1062
+ namespaced,
1063
+ plural=INFLECT.plural_noun(str(kind)).lower(),
1064
+ )