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