crossplane-function-pythonic 0.1.4__py3-none-any.whl → 0.2.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- crossplane/pythonic/__about__.py +2 -0
- crossplane/pythonic/__main__.py +2 -0
- crossplane/pythonic/command.py +102 -0
- crossplane/pythonic/composite.py +50 -23
- crossplane/pythonic/function.py +14 -10
- crossplane/pythonic/grpc.py +123 -0
- crossplane/pythonic/main.py +16 -186
- crossplane/pythonic/protobuf.py +9 -2
- crossplane/pythonic/render.py +432 -0
- crossplane/pythonic/version.py +13 -0
- {crossplane_function_pythonic-0.1.4.dist-info → crossplane_function_pythonic-0.2.0.dist-info}/METADATA +92 -58
- crossplane_function_pythonic-0.2.0.dist-info/RECORD +17 -0
- {crossplane_function_pythonic-0.1.4.dist-info → crossplane_function_pythonic-0.2.0.dist-info}/WHEEL +1 -1
- crossplane_function_pythonic-0.1.4.dist-info/RECORD +0 -11
- {crossplane_function_pythonic-0.1.4.dist-info → crossplane_function_pythonic-0.2.0.dist-info}/entry_points.txt +0 -0
- {crossplane_function_pythonic-0.1.4.dist-info → crossplane_function_pythonic-0.2.0.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,432 @@
|
|
|
1
|
+
|
|
2
|
+
import pathlib
|
|
3
|
+
import sys
|
|
4
|
+
import yaml
|
|
5
|
+
from crossplane.function.proto.v1 import run_function_pb2 as fnv1
|
|
6
|
+
|
|
7
|
+
from . import (
|
|
8
|
+
command,
|
|
9
|
+
function,
|
|
10
|
+
protobuf,
|
|
11
|
+
)
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class Command(command.Command):
|
|
15
|
+
name = 'render'
|
|
16
|
+
help = 'Render a function-pythonic Composition'
|
|
17
|
+
|
|
18
|
+
@classmethod
|
|
19
|
+
def add_parser_arguments(cls, parser):
|
|
20
|
+
cls.add_function_arguments(parser)
|
|
21
|
+
parser.add_argument(
|
|
22
|
+
'composite',
|
|
23
|
+
type=pathlib.Path,
|
|
24
|
+
metavar='PATH',
|
|
25
|
+
help='A YAML file containing the Composite resource to render.',
|
|
26
|
+
)
|
|
27
|
+
parser.add_argument(
|
|
28
|
+
'composition',
|
|
29
|
+
type=pathlib.Path,
|
|
30
|
+
nargs='?',
|
|
31
|
+
metavar='PATH/CLASS',
|
|
32
|
+
help='A YAML file containing the Composition resource or the complete path of a function=-pythonic BaseComposite subclass.',
|
|
33
|
+
)
|
|
34
|
+
parser.add_argument(
|
|
35
|
+
'--context-files',
|
|
36
|
+
action='append',
|
|
37
|
+
default=[],
|
|
38
|
+
metavar='KEY=PATH',
|
|
39
|
+
help='Context key-value pairs to pass to the Function pipeline. Values must be files containing YAML/JSON.',
|
|
40
|
+
)
|
|
41
|
+
parser.add_argument(
|
|
42
|
+
'--context-values',
|
|
43
|
+
action='append',
|
|
44
|
+
default=[],
|
|
45
|
+
metavar='KEY=VALUE',
|
|
46
|
+
help='Context key-value pairs to pass to the Function pipeline. Values must be YAML/JSON. Keys take precedence over --context-files.',
|
|
47
|
+
)
|
|
48
|
+
parser.add_argument(
|
|
49
|
+
'--observed-resources', '-o',
|
|
50
|
+
action='append',
|
|
51
|
+
type=pathlib.Path,
|
|
52
|
+
default=[],
|
|
53
|
+
metavar='PATH',
|
|
54
|
+
help='A YAML file or directory of YAML files specifying the observed state of composed resources.'
|
|
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
|
+
parser.add_argument(
|
|
65
|
+
'--required-resources', '-e',
|
|
66
|
+
action='append',
|
|
67
|
+
type=pathlib.Path,
|
|
68
|
+
default=[],
|
|
69
|
+
metavar='PATH',
|
|
70
|
+
help='A YAML file or directory of YAML files specifying required resources to pass to the Function pipeline.',
|
|
71
|
+
)
|
|
72
|
+
parser.add_argument(
|
|
73
|
+
'--function-credentials',
|
|
74
|
+
action='append',
|
|
75
|
+
type=pathlib.Path,
|
|
76
|
+
default=[],
|
|
77
|
+
metavar='PATH',
|
|
78
|
+
help='A YAML file or directory of YAML files specifying credentials to use for Functions to render the XR.',
|
|
79
|
+
)
|
|
80
|
+
parser.add_argument(
|
|
81
|
+
'--include-full-xr', '-x',
|
|
82
|
+
action='store_true',
|
|
83
|
+
help="Include a direct copy of the input XR's spedc and metadata fields in the rendered output.",
|
|
84
|
+
)
|
|
85
|
+
parser.add_argument(
|
|
86
|
+
'--include-function-results', '-r',
|
|
87
|
+
action='store_true',
|
|
88
|
+
help='Include informational and warning messages from Functions in the rendered output as resources of kind: Result..',
|
|
89
|
+
)
|
|
90
|
+
parser.add_argument(
|
|
91
|
+
'--include-context', '-c',
|
|
92
|
+
action='store_true',
|
|
93
|
+
help='Include the context in the rendered output as a resource of kind: Context.',
|
|
94
|
+
)
|
|
95
|
+
|
|
96
|
+
def initialize(self):
|
|
97
|
+
self.initialize_function()
|
|
98
|
+
|
|
99
|
+
async def run(self):
|
|
100
|
+
# Obtain the Composite to render.
|
|
101
|
+
if not self.args.composite.is_file():
|
|
102
|
+
print(f"Composite \"{self.args.composite}\" is not a file", file=sys.stderr)
|
|
103
|
+
sys.exit(1)
|
|
104
|
+
composite = protobuf.Yaml(self.args.composite.read_text())
|
|
105
|
+
|
|
106
|
+
# Obtain the Composition that will be used to render the Composite.
|
|
107
|
+
if composite.apiVersion in ('pythonic.crossplane.io/v1alpha1', 'pythonic.fortra.com/v1alpha1') and composite.kind == 'Composite':
|
|
108
|
+
if self.args.composition:
|
|
109
|
+
print('Composite type of "composite.pythonic.crossplane.io" does not use "composition" argument', file=sys.stderr)
|
|
110
|
+
sys.exit(1)
|
|
111
|
+
composition = self.create_composition(composite, '')
|
|
112
|
+
else:
|
|
113
|
+
if not self.args.composition:
|
|
114
|
+
print('"composition" argument required', file=sys.stderr)
|
|
115
|
+
sys.exit(1)
|
|
116
|
+
if self.args.composition.is_file():
|
|
117
|
+
composition = protobuf.Yaml(self.args.composition.read_text())
|
|
118
|
+
else:
|
|
119
|
+
composite = self.args.composition.rsplit('.', 1)
|
|
120
|
+
if len(composite) == 1:
|
|
121
|
+
print(f"Composition class name does not include module: {self.args.composition}", file=sys.stderr)
|
|
122
|
+
sys.exit(1)
|
|
123
|
+
try:
|
|
124
|
+
module = importlib.import_module(composite[0])
|
|
125
|
+
except Exception as e:
|
|
126
|
+
print(f"Unable to import composition class: {composite[0]}", file=sys.stderr)
|
|
127
|
+
sys.exit(1)
|
|
128
|
+
clazz = getattr(module, composite[1], None)
|
|
129
|
+
if not clazz:
|
|
130
|
+
print(f"Composition class {composite[0]} does not define: {composite[1]}", file=sys.stderr)
|
|
131
|
+
sys.exit(1)
|
|
132
|
+
if not inspect.isclass(clazz):
|
|
133
|
+
print(f"Composition class {self.args.composition} is not a class", file=sys.stderr)
|
|
134
|
+
sys.exit(1)
|
|
135
|
+
if not issubclass(clazz, pythonic.BaseComposite):
|
|
136
|
+
print(f"Composition class {self.args.composition} is not a subclass of BaseComposite", file=sys.stderr)
|
|
137
|
+
sys.exit(1)
|
|
138
|
+
composition = self.create_composition(composite, str(self.args.composition))
|
|
139
|
+
|
|
140
|
+
# Build up the RunFunctionRequest protobuf message used to call function-pythonic.
|
|
141
|
+
request = protobuf.Message(None, 'request', fnv1.RunFunctionRequest.DESCRIPTOR, fnv1.RunFunctionRequest())
|
|
142
|
+
|
|
143
|
+
# Load the request context with any specified command line options.
|
|
144
|
+
for entry in self.args.context_files:
|
|
145
|
+
key_path = entry.split('=', 1)
|
|
146
|
+
if len(key_path) != 2:
|
|
147
|
+
print(f"Invalid --context-files: {entry}", file=sys.stderr)
|
|
148
|
+
sys.exit(1)
|
|
149
|
+
path = pathlib.Path(key_path[1])
|
|
150
|
+
if not path.is_file():
|
|
151
|
+
print(f"Invalid --context-files {path} is not a file", file=sys.stderr)
|
|
152
|
+
sys.exit(1)
|
|
153
|
+
request.context[key_path[0]] = protobuf.Yaml(path.read_text())
|
|
154
|
+
for entry in self.args.context_values:
|
|
155
|
+
key_value = entry.split('=', 1)
|
|
156
|
+
if len(key_value) != 2:
|
|
157
|
+
print(f"Invalid --context-values: {entry}", file=sys.stderr)
|
|
158
|
+
sys.exit(1)
|
|
159
|
+
request.context[key_value[0]] = protobuf.Yaml(key_value[1])
|
|
160
|
+
|
|
161
|
+
# Establish the request observed composite and specifed observed resources.
|
|
162
|
+
request.observed.composite.resource = composite
|
|
163
|
+
for resource in self.collect_resources(self.args.observed_resources):
|
|
164
|
+
name = resource.metadata.annotations['crossplane.io/composition-resource-name']
|
|
165
|
+
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
|
+
|
|
178
|
+
# These will hold the response conditions and results.
|
|
179
|
+
conditions = protobuf.List()
|
|
180
|
+
results = protobuf.List()
|
|
181
|
+
|
|
182
|
+
# Create a function-pythonic function runner used to run pipeline steps.
|
|
183
|
+
runner = function.FunctionRunner(self.args.debug, self.args.render_unknowns)
|
|
184
|
+
fatal = False
|
|
185
|
+
|
|
186
|
+
# Process the composition pipeline steps.
|
|
187
|
+
for step in composition.spec.pipeline:
|
|
188
|
+
if step.functionRef.name != 'function-pythonic':
|
|
189
|
+
print(f"Only function-pythonic functions can be run: {step.functionRef.name}", file=sys.stderr)
|
|
190
|
+
sys.exit(1)
|
|
191
|
+
if not step.input.step:
|
|
192
|
+
step.input.step = step.step
|
|
193
|
+
request.input = step.input
|
|
194
|
+
|
|
195
|
+
# Supply step requested credentials.
|
|
196
|
+
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)
|
|
209
|
+
|
|
210
|
+
# Track what extra/required resources have been processed.
|
|
211
|
+
requirements = protobuf.Message(None, 'requirements', fnv1.Requirements.DESCRIPTOR, fnv1.Requirements())
|
|
212
|
+
for _ in range(5):
|
|
213
|
+
# Fetch the step bootstrap resources specified.
|
|
214
|
+
request.required_resources()
|
|
215
|
+
for requirement in step.requirements:
|
|
216
|
+
self.fetch_requireds(requireds, requirement.requirementName, requirement, request.required_resources)
|
|
217
|
+
# Fetch the required resources requested.
|
|
218
|
+
for name, selector in requirements.resources:
|
|
219
|
+
self.fetch_requireds(requireds, name, selector, request.required_resources)
|
|
220
|
+
# Fetch the now deprecated extra resources requested.
|
|
221
|
+
request.extra_resources()
|
|
222
|
+
for name, selector in requirements.extra_resources:
|
|
223
|
+
self.fetch_requireds(requireds, name, selector, request.extra_resources)
|
|
224
|
+
# Run the step using the function-pythonic function runner.
|
|
225
|
+
response = protobuf.Message(
|
|
226
|
+
None,
|
|
227
|
+
'response',
|
|
228
|
+
fnv1.RunFunctionResponse.DESCRIPTOR,
|
|
229
|
+
await runner.RunFunction(request._message, None),
|
|
230
|
+
)
|
|
231
|
+
# All done if there is a fatal result.
|
|
232
|
+
for result in response.results:
|
|
233
|
+
if result.severity == fnv1.Severity.SEVERITY_FATAL:
|
|
234
|
+
fatal = True
|
|
235
|
+
break
|
|
236
|
+
# Copy the response context to the request context to use in subsequent steps.
|
|
237
|
+
request.context = response.context
|
|
238
|
+
# Exit this loop if the function has not requested additional extra/required resources.
|
|
239
|
+
if response.requirements == requirements:
|
|
240
|
+
break
|
|
241
|
+
# Establish the new set of requested extra/required resoruces.
|
|
242
|
+
requirements = response.requirements
|
|
243
|
+
|
|
244
|
+
# Copy the response desired state to the request desired state to use in subsequent steps.
|
|
245
|
+
request.desired.resources()
|
|
246
|
+
self.copy_resource(response.desired.composite, request.desired.composite)
|
|
247
|
+
for name, resource in response.desired.resources:
|
|
248
|
+
self.copy_resource(resource, request.desired.resources[name])
|
|
249
|
+
|
|
250
|
+
# Collect the step's returned conditions.
|
|
251
|
+
for condition in response.conditions:
|
|
252
|
+
if condition.type not in ('Ready', 'Synced', 'Healthy'):
|
|
253
|
+
conditions[protobuf.append] = self.create_condition(condition.type, condition.status, condition.reason, condition.message)
|
|
254
|
+
# Collect the step's returned results.
|
|
255
|
+
for result in response.results:
|
|
256
|
+
ix = len(results)
|
|
257
|
+
results[ix].apiVersion = 'render.crossplane.io/v1beta1'
|
|
258
|
+
results[ix].kind = 'Result'
|
|
259
|
+
results[ix].step = step.step
|
|
260
|
+
results[ix].severity = fnv1.Severity.Name(result.severity._value)
|
|
261
|
+
if result.reason:
|
|
262
|
+
results[ix].reason = result.reason
|
|
263
|
+
if result.message:
|
|
264
|
+
results[ix].message = result.message
|
|
265
|
+
|
|
266
|
+
# All done if a fatal result was returned
|
|
267
|
+
if fatal:
|
|
268
|
+
break
|
|
269
|
+
|
|
270
|
+
# Collect and format all the returned desired composed resources.
|
|
271
|
+
resources = protobuf.List()
|
|
272
|
+
unready = protobuf.List()
|
|
273
|
+
prefix = composite.metadata.labels['crossplane.io/composite']
|
|
274
|
+
if not prefix:
|
|
275
|
+
prefix = composite.metadata.name
|
|
276
|
+
for name, resource in request.desired.resources:
|
|
277
|
+
if resource.ready != fnv1.Ready.READY_TRUE:
|
|
278
|
+
unready[protobuf.append] = name
|
|
279
|
+
resource = resource.resource
|
|
280
|
+
observed = request.observed.resources[name].resource
|
|
281
|
+
if observed:
|
|
282
|
+
for key in ('namespace', 'generateName', 'name'):
|
|
283
|
+
if observed.metadata[key]:
|
|
284
|
+
resource.metadata[key] = observed.metadata[key]
|
|
285
|
+
if not resource.metadata.name and not resource.metadata.generateName:
|
|
286
|
+
resource.metadata.generateName = f"{prefix}-"
|
|
287
|
+
if composite.metadata.namespace:
|
|
288
|
+
resource.metadata.namespace = composite.metadata.namespace
|
|
289
|
+
resource.metadata.annotations['crossplane.io/composition-resource-name'] = name
|
|
290
|
+
resource.metadata.labels['crossplane.io/composite'] = prefix
|
|
291
|
+
if composite.metadata.labels['crossplane.io/claim-name'] and composite.metadata.labels['crossplane.io/claim-namespace']:
|
|
292
|
+
resource.metadata.labels['crossplane.io/claim-namespace'] = composite.metadata.labels['crossplane.io/claim-namespace']
|
|
293
|
+
resource.metadata.labels['crossplane.io/claim-name'] = composite.metadata.labels['crossplane.io/claim-name']
|
|
294
|
+
elif composite.spec.claimRef.namespace and composite.spec.claimRef.name:
|
|
295
|
+
resource.metadata.labels['crossplane.io/claim-namespace'] = composite.spec.claimRef.namespace
|
|
296
|
+
resource.metadata.labels['crossplane.io/claim-name'] = composite.spec.claimRef.name
|
|
297
|
+
resource.metadata.ownerReferences[0].controller = True
|
|
298
|
+
resource.metadata.ownerReferences[0].blockOwnerDeletion = True
|
|
299
|
+
resource.metadata.ownerReferences[0].apiVersion = composite.apiVersion
|
|
300
|
+
resource.metadata.ownerReferences[0].kind = composite.kind
|
|
301
|
+
resource.metadata.ownerReferences[0].name = composite.metadata.name
|
|
302
|
+
resource.metadata.ownerReferences[0].uid = ''
|
|
303
|
+
resources[protobuf.append] = resource
|
|
304
|
+
|
|
305
|
+
# Format the returned desired composite
|
|
306
|
+
composite = protobuf.Map()
|
|
307
|
+
for name, value in request.desired.composite.resource:
|
|
308
|
+
composite[name] = value
|
|
309
|
+
composite.apiVersion = request.observed.composite.resource.apiVersion
|
|
310
|
+
composite.kind = request.observed.composite.resource.kind
|
|
311
|
+
if self.args.include_full_xr:
|
|
312
|
+
composite.metadata = request.observed.composite.resource.metadata
|
|
313
|
+
if request.observed.composite.resource.spec:
|
|
314
|
+
composite.spec = request.observed.composite.resource.spec
|
|
315
|
+
else:
|
|
316
|
+
if request.observed.composite.resource.metadata.namespace:
|
|
317
|
+
composite.metadata.namespace = request.observed.composite.resource.metadata.namespace
|
|
318
|
+
composite.metadata.name = request.observed.composite.resource.metadata.name
|
|
319
|
+
# Add in the composite's status.conditions.
|
|
320
|
+
if request.desired.composite.ready == fnv1.Ready.READY_FALSE:
|
|
321
|
+
condition = self.create_condition('Ready', False, 'Creating')
|
|
322
|
+
elif request.desired.composite.ready == fnv1.Ready.READY_UNSPECIFIED and len(unready):
|
|
323
|
+
condition = self.create_condition('Ready', False, 'Creating', f"Unready resources: {', '.join(str(name) for name in unready)}")
|
|
324
|
+
else:
|
|
325
|
+
condition = self.create_condition('Ready', True, 'Available')
|
|
326
|
+
composite.status.conditions[protobuf.append] = condition
|
|
327
|
+
for condition in conditions:
|
|
328
|
+
composite.status.conditions[protobuf.append] = condition
|
|
329
|
+
|
|
330
|
+
# Print the composite.
|
|
331
|
+
print('---')
|
|
332
|
+
print(str(composite), end='')
|
|
333
|
+
# Print the composed resources.
|
|
334
|
+
for resource in sorted(resources, key=lambda resource: str(resource.metadata.annotations['crossplane.io/composition-resource-name'])):
|
|
335
|
+
print('---')
|
|
336
|
+
print(str(resource), end='')
|
|
337
|
+
# Print the results (AKA events) if requested.
|
|
338
|
+
if self.args.include_function_results:
|
|
339
|
+
for result in results:
|
|
340
|
+
print('---')
|
|
341
|
+
print(str(result), end='')
|
|
342
|
+
# Print the final context if requested.
|
|
343
|
+
if self.args.include_context:
|
|
344
|
+
print('---')
|
|
345
|
+
print(
|
|
346
|
+
str(protobuf.Map(
|
|
347
|
+
apiVersion = 'render.crossplane.io/v1beta1',
|
|
348
|
+
kind = 'Context',
|
|
349
|
+
fields = request.context,
|
|
350
|
+
)),
|
|
351
|
+
end='',
|
|
352
|
+
)
|
|
353
|
+
|
|
354
|
+
def create_composition(self, composite, module):
|
|
355
|
+
composition = protobuf.Map()
|
|
356
|
+
composition.apiVersion = 'apiextensions.crossplane.io/v1'
|
|
357
|
+
composition.kind = 'Composition'
|
|
358
|
+
composition.metadata.name = 'function-pythonic-render'
|
|
359
|
+
composition.spec.compositeTypeRef.apiVersion = composite.apiVersion
|
|
360
|
+
composition.spec.compositeTypeRef.kind = composite.kind
|
|
361
|
+
composition.spec.mode = 'Pipeline'
|
|
362
|
+
composition.spec.pipeline[0].step = 'function-pythonic-render'
|
|
363
|
+
composition.spec.pipeline[0].functionRef.name = 'function-pythonic'
|
|
364
|
+
composition.spec.pipeline[0].input.apiVersion = 'pythonic.fn.crossplane.io/v1alpha1'
|
|
365
|
+
composition.spec.pipeline[0].input.kind = 'Composite'
|
|
366
|
+
composition.spec.pipeline[0].input.composite = module
|
|
367
|
+
return composition
|
|
368
|
+
|
|
369
|
+
def collect_resources(self, resources):
|
|
370
|
+
files = []
|
|
371
|
+
for resource in resources:
|
|
372
|
+
if resource.is_file():
|
|
373
|
+
files.append(resource)
|
|
374
|
+
elif resource.is_dir():
|
|
375
|
+
for file in resource.iterdir():
|
|
376
|
+
if file.suffix in ('.yaml', '.yml'):
|
|
377
|
+
files.append(file)
|
|
378
|
+
else:
|
|
379
|
+
print(f"Specified resource is not a file or a directory: {resource}", file=sys.stderr)
|
|
380
|
+
sys.exit(1)
|
|
381
|
+
for file in files:
|
|
382
|
+
for document in yaml.safe_load_all(file.read_text()):
|
|
383
|
+
yield protobuf.Value(None, None, document)
|
|
384
|
+
|
|
385
|
+
def fetch_requireds(self, requireds, name, selector, resources):
|
|
386
|
+
if not name:
|
|
387
|
+
return
|
|
388
|
+
name = str(name)
|
|
389
|
+
items = resources[name].items
|
|
390
|
+
items() # Force this to get created
|
|
391
|
+
for required in requireds:
|
|
392
|
+
if selector.api_version == required.apiVersion and selector.kind == required.kind:
|
|
393
|
+
if selector.match_name == required.metadata.name:
|
|
394
|
+
items[protobuf.append].resource = required
|
|
395
|
+
elif selector.match_labels.labels:
|
|
396
|
+
for key, value in selector.match_labels.labels:
|
|
397
|
+
if value != required.metadata.labels[key]:
|
|
398
|
+
break
|
|
399
|
+
else:
|
|
400
|
+
items[protobuf.append].resource = required
|
|
401
|
+
|
|
402
|
+
def copy_resource(self, source, destination):
|
|
403
|
+
destination.resource = source.resource
|
|
404
|
+
destination.connection_details()
|
|
405
|
+
for key, value in source.connection_details:
|
|
406
|
+
destination.connection_details[key] = value
|
|
407
|
+
destination.ready = source.ready
|
|
408
|
+
|
|
409
|
+
def create_condition(self, type, status, reason, message=None):
|
|
410
|
+
if isinstance(status, protobuf.FieldMessage):
|
|
411
|
+
if status._value == fnv1.Status.STATUS_CONDITION_TRUE:
|
|
412
|
+
status = 'True'
|
|
413
|
+
elif status._value == fnv1.Status.STATUS_CONDITION_FALSE:
|
|
414
|
+
status = 'False'
|
|
415
|
+
else:
|
|
416
|
+
status = 'Unknown'
|
|
417
|
+
elif isinstance(status, bool):
|
|
418
|
+
if status:
|
|
419
|
+
status = 'True'
|
|
420
|
+
else:
|
|
421
|
+
status = 'False'
|
|
422
|
+
elif status is None:
|
|
423
|
+
status = 'Unknown'
|
|
424
|
+
condition = {
|
|
425
|
+
'type': type,
|
|
426
|
+
'status': status,
|
|
427
|
+
'reason': reason,
|
|
428
|
+
'lastTransitionTime': '2026-01-01T00:00:00Z'
|
|
429
|
+
}
|
|
430
|
+
if message:
|
|
431
|
+
condition['message'] = message
|
|
432
|
+
return condition
|