crossplane-function-pythonic 0.4.2__py3-none-any.whl → 0.6.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- crossplane/pythonic/__about__.py +1 -1
- crossplane/pythonic/composite.py +154 -5
- crossplane/pythonic/function.py +24 -11
- crossplane/pythonic/grpc.py +24 -3
- crossplane/pythonic/packages.py +128 -96
- crossplane/pythonic/protobuf.py +4 -0
- crossplane/pythonic/render.py +165 -79
- {crossplane_function_pythonic-0.4.2.dist-info → crossplane_function_pythonic-0.6.0.dist-info}/METADATA +179 -25
- crossplane_function_pythonic-0.6.0.dist-info/RECORD +18 -0
- {crossplane_function_pythonic-0.4.2.dist-info → crossplane_function_pythonic-0.6.0.dist-info}/WHEEL +1 -1
- crossplane_function_pythonic-0.4.2.dist-info/RECORD +0 -18
- {crossplane_function_pythonic-0.4.2.dist-info → crossplane_function_pythonic-0.6.0.dist-info}/entry_points.txt +0 -0
- {crossplane_function_pythonic-0.4.2.dist-info → crossplane_function_pythonic-0.6.0.dist-info}/licenses/LICENSE +0 -0
crossplane/pythonic/__about__.py
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
# This is set at build time, using "hatch version"
|
|
2
|
-
__version__ = "0.
|
|
2
|
+
__version__ = "0.6.0"
|
crossplane/pythonic/composite.py
CHANGED
|
@@ -89,7 +89,7 @@ class Ready:
|
|
|
89
89
|
|
|
90
90
|
|
|
91
91
|
class BaseComposite:
|
|
92
|
-
def __init__(self, crossplane_v1, request,
|
|
92
|
+
def __init__(self, crossplane_v1, request, logger):
|
|
93
93
|
self.crossplane_v1 = crossplane_v1
|
|
94
94
|
self.request = protobuf.Message(None, 'request', request.DESCRIPTOR, request, 'Function Request')
|
|
95
95
|
response = fnv1.RunFunctionResponse(
|
|
@@ -104,14 +104,13 @@ class BaseComposite:
|
|
|
104
104
|
)
|
|
105
105
|
self.response = protobuf.Message(None, 'response', response.DESCRIPTOR, response)
|
|
106
106
|
self.logger = logger
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
else:
|
|
110
|
-
self.parameters = self.request.input.parameters
|
|
107
|
+
self.capabilities = Capabilities(self.request.meta.capabilities)
|
|
108
|
+
self.parameters = self.request.input.parameters
|
|
111
109
|
self.credentials = Credentials(self.request)
|
|
112
110
|
self.context = self.response.context
|
|
113
111
|
self.environment = self.context['apiextensions.crossplane.io/environment']
|
|
114
112
|
self.requireds = Requireds(self)
|
|
113
|
+
self.schemas = Schemas(self)
|
|
115
114
|
self.resources = Resources(self)
|
|
116
115
|
self.autoReady = True
|
|
117
116
|
self.usages = False
|
|
@@ -126,6 +125,7 @@ class BaseComposite:
|
|
|
126
125
|
self.metadata = self.observed.metadata
|
|
127
126
|
self.spec = self.observed.spec
|
|
128
127
|
self.status = self.desired.status
|
|
128
|
+
self.output = self.response.output
|
|
129
129
|
self.conditions = Conditions(observed, self.response)
|
|
130
130
|
self.results = Results(self.response)
|
|
131
131
|
self.events = Results(self.response) # Deprecated, use self.results
|
|
@@ -139,6 +139,30 @@ class BaseComposite:
|
|
|
139
139
|
raise NotImplementedError()
|
|
140
140
|
|
|
141
141
|
|
|
142
|
+
class Capabilities:
|
|
143
|
+
def __init__(self, capabilities):
|
|
144
|
+
self._capabilities = capabilities
|
|
145
|
+
|
|
146
|
+
def __bool__(self):
|
|
147
|
+
return fnv1.CAPABILITY_CAPABILITIES in self._capabilities
|
|
148
|
+
|
|
149
|
+
@property
|
|
150
|
+
def requireds(self):
|
|
151
|
+
return fnv1.CAPABILITY_REQUIRED_RESOURCES in self._capabilities if self else None
|
|
152
|
+
|
|
153
|
+
@property
|
|
154
|
+
def credentials(self):
|
|
155
|
+
return fnv1.CAPABILITY_CREDENTIALS in self._capabilities if self else None
|
|
156
|
+
|
|
157
|
+
@property
|
|
158
|
+
def conditions(self):
|
|
159
|
+
return fnv1.CAPABILITY_CONDITIONS in self._capabilities if self else None
|
|
160
|
+
|
|
161
|
+
@property
|
|
162
|
+
def schemas(self):
|
|
163
|
+
return fnv1.CAPABILITY_REQUIRED_SCHEMAS in self._capabilities if self else None
|
|
164
|
+
|
|
165
|
+
|
|
142
166
|
class Credentials:
|
|
143
167
|
def __init__(self, request):
|
|
144
168
|
self.__dict__['_request'] = request
|
|
@@ -561,6 +585,131 @@ class RequiredResource:
|
|
|
561
585
|
return bool(self.observed)
|
|
562
586
|
|
|
563
587
|
|
|
588
|
+
class Schemas:
|
|
589
|
+
def __init__(self, composite):
|
|
590
|
+
self._composite = composite
|
|
591
|
+
self._cache = {}
|
|
592
|
+
|
|
593
|
+
def __getattr__(self, key):
|
|
594
|
+
return self[key]
|
|
595
|
+
|
|
596
|
+
def __getitem__(self, key):
|
|
597
|
+
schema = self._cache.get(key)
|
|
598
|
+
if not schema:
|
|
599
|
+
schema = Schema(self._composite, key)
|
|
600
|
+
self._cache[key] = schema
|
|
601
|
+
return schema
|
|
602
|
+
|
|
603
|
+
def __bool__(self):
|
|
604
|
+
return bool(len(self))
|
|
605
|
+
|
|
606
|
+
def __len__(self):
|
|
607
|
+
names = set()
|
|
608
|
+
for name, schema in self._composite.request.required_schemas:
|
|
609
|
+
names.add(name)
|
|
610
|
+
for name, selector in self._composite.response.requirements.schemas:
|
|
611
|
+
names.add(name)
|
|
612
|
+
return len(names)
|
|
613
|
+
|
|
614
|
+
def __contains__(self, key):
|
|
615
|
+
if key in self._composite.request.required_schemas:
|
|
616
|
+
return True
|
|
617
|
+
if key in self._composite.response.requirements.schemas:
|
|
618
|
+
return True
|
|
619
|
+
return False
|
|
620
|
+
|
|
621
|
+
def __iter__(self):
|
|
622
|
+
names = set()
|
|
623
|
+
for name, schema in self._composite.request.required_schemas:
|
|
624
|
+
names.add(name)
|
|
625
|
+
for name, selector in self._composite.response.requirements.schemas:
|
|
626
|
+
names.add(name)
|
|
627
|
+
for name in sorted(names):
|
|
628
|
+
yield name, self[name]
|
|
629
|
+
|
|
630
|
+
|
|
631
|
+
class Schema:
|
|
632
|
+
def __init__(self, composite, name):
|
|
633
|
+
self.name = name
|
|
634
|
+
self._selector = composite.response.requirements.schemas[name]
|
|
635
|
+
self._schema = composite.request.required_schemas[name].openapi_v3
|
|
636
|
+
|
|
637
|
+
def __call__(self, kind=_notset, apiVersion=_notset):
|
|
638
|
+
self._selector()
|
|
639
|
+
if kind != _notset:
|
|
640
|
+
# Allow for apiVersion in the first arg and kind in the second arg
|
|
641
|
+
if '/' in kind or kind == 'v1':
|
|
642
|
+
if apiVersion != _notset:
|
|
643
|
+
self.kind = apiVersion
|
|
644
|
+
apiVersion = kind
|
|
645
|
+
else:
|
|
646
|
+
self.kind = kind
|
|
647
|
+
if apiVersion != _notset:
|
|
648
|
+
self.apiVersion = apiVersion
|
|
649
|
+
return self
|
|
650
|
+
|
|
651
|
+
@property
|
|
652
|
+
def apiVersion(self):
|
|
653
|
+
return self._selector.api_version
|
|
654
|
+
|
|
655
|
+
@apiVersion.setter
|
|
656
|
+
def apiVersion(self, apiVersion):
|
|
657
|
+
self._selector.api_version = apiVersion
|
|
658
|
+
|
|
659
|
+
@property
|
|
660
|
+
def kind(self):
|
|
661
|
+
return self._selector.kind
|
|
662
|
+
|
|
663
|
+
@kind.setter
|
|
664
|
+
def kind(self, kind):
|
|
665
|
+
self._selector.kind = kind
|
|
666
|
+
|
|
667
|
+
def __enter__(self):
|
|
668
|
+
return self
|
|
669
|
+
|
|
670
|
+
def __exit__(self, exc_type, exc_value, traceback):
|
|
671
|
+
pass
|
|
672
|
+
|
|
673
|
+
def __aenter__(self):
|
|
674
|
+
return self
|
|
675
|
+
|
|
676
|
+
def __aexit__(self, exc_type, exc_value, traceback):
|
|
677
|
+
pass
|
|
678
|
+
|
|
679
|
+
def __getattr__(self, key):
|
|
680
|
+
return self[key]
|
|
681
|
+
|
|
682
|
+
def __getitem__(self, key):
|
|
683
|
+
return self._schema[key]
|
|
684
|
+
|
|
685
|
+
def __bool__(self):
|
|
686
|
+
return bool(self._schema)
|
|
687
|
+
|
|
688
|
+
def __len__(self):
|
|
689
|
+
return len(self._schema)
|
|
690
|
+
|
|
691
|
+
def __contains__(self, item):
|
|
692
|
+
return item in self._schema
|
|
693
|
+
|
|
694
|
+
def __iter__(self):
|
|
695
|
+
for key, value in self._schema:
|
|
696
|
+
yield key, value
|
|
697
|
+
|
|
698
|
+
def __hash__(self):
|
|
699
|
+
return hash(self._schema)
|
|
700
|
+
|
|
701
|
+
def __eq__(self, other):
|
|
702
|
+
if instance(other, Schema):
|
|
703
|
+
other = other._schema
|
|
704
|
+
return self._schema == other
|
|
705
|
+
|
|
706
|
+
def __str__(self):
|
|
707
|
+
return str(self._schema)
|
|
708
|
+
|
|
709
|
+
def __format__(self, spec='yaml'):
|
|
710
|
+
return format(self,_schema, spec)
|
|
711
|
+
|
|
712
|
+
|
|
564
713
|
class Conditions:
|
|
565
714
|
def __init__(self, observed, response=None):
|
|
566
715
|
self._observed = observed
|
crossplane/pythonic/function.py
CHANGED
|
@@ -48,15 +48,13 @@ class FunctionRunner(grpcv1.FunctionRunnerService):
|
|
|
48
48
|
name.append(composite['metadata']['name'])
|
|
49
49
|
logger = logging.getLogger('.'.join(name))
|
|
50
50
|
|
|
51
|
-
if
|
|
52
|
-
if 'spec' not in composite or '
|
|
53
|
-
return self.fatal(request, logger,
|
|
54
|
-
|
|
55
|
-
composite = composite['spec']['composite']
|
|
51
|
+
if 'inlined' in request.input and request.input['inlined']:
|
|
52
|
+
if 'spec' not in composite or request.input['inlined'] not in composite['spec']:
|
|
53
|
+
return self.fatal(request, logger, f"Missing inlined spec.{request.input['inlined']}")
|
|
54
|
+
composite = composite['spec'][request.input['inlined']]
|
|
56
55
|
else:
|
|
57
56
|
if 'composite' not in request.input:
|
|
58
57
|
return self.fatal(request, logger, 'Missing input "composite"')
|
|
59
|
-
single_use = False
|
|
60
58
|
composite = request.input['composite']
|
|
61
59
|
|
|
62
60
|
# Ideally this is something the Function API provides
|
|
@@ -100,7 +98,7 @@ class FunctionRunner(grpcv1.FunctionRunnerService):
|
|
|
100
98
|
self.clazzes[composite] = clazz
|
|
101
99
|
|
|
102
100
|
try:
|
|
103
|
-
composite = clazz(self.crossplane_v1, request,
|
|
101
|
+
composite = clazz(self.crossplane_v1, request, logger)
|
|
104
102
|
except Exception as e:
|
|
105
103
|
return self.fatal(request, logger, 'Instantiate', e)
|
|
106
104
|
|
|
@@ -123,8 +121,13 @@ class FunctionRunner(grpcv1.FunctionRunnerService):
|
|
|
123
121
|
except Exception as e:
|
|
124
122
|
return self.fatal(request, logger, 'Compose', e)
|
|
125
123
|
|
|
126
|
-
|
|
127
|
-
|
|
124
|
+
schemas = self.get_schemas(step, composite)
|
|
125
|
+
requireds = self.get_requireds(step, composite)
|
|
126
|
+
if schemas or requireds:
|
|
127
|
+
if schemas:
|
|
128
|
+
logger.debug(f"Required schemas: {','.join(schemas)}")
|
|
129
|
+
if requireds:
|
|
130
|
+
logger.debug(f"Required resources: {','.join(requireds)}")
|
|
128
131
|
else:
|
|
129
132
|
self.process_usages(composite)
|
|
130
133
|
self.process_unknowns(composite)
|
|
@@ -157,11 +160,21 @@ class FunctionRunner(grpcv1.FunctionRunnerService):
|
|
|
157
160
|
]
|
|
158
161
|
)
|
|
159
162
|
|
|
163
|
+
def get_schemas(self, step, composite):
|
|
164
|
+
schemas = []
|
|
165
|
+
for name, schema in composite.schemas:
|
|
166
|
+
if len(schema.kind) and len(schema.apiVersion):
|
|
167
|
+
s = pythonic.Map(kind=schema.kind, apiVersion=schema.apiVersion)
|
|
168
|
+
if s != step.schemas[name]:
|
|
169
|
+
step.schemas[name] = s
|
|
170
|
+
schemas.append(name)
|
|
171
|
+
return schemas
|
|
172
|
+
|
|
160
173
|
def get_requireds(self, step, composite):
|
|
161
174
|
requireds = []
|
|
162
175
|
for name, required in composite.requireds:
|
|
163
|
-
if len(required.
|
|
164
|
-
r = pythonic.Map(
|
|
176
|
+
if len(required.kind) and len(required.apiVersion):
|
|
177
|
+
r = pythonic.Map(kind=required.kind, apiVersion=required.apiVersion)
|
|
165
178
|
if len(required.namespace):
|
|
166
179
|
r.namespace = required.namespace
|
|
167
180
|
if len(required.matchName):
|
crossplane/pythonic/grpc.py
CHANGED
|
@@ -45,6 +45,12 @@ class Command(command.Command):
|
|
|
45
45
|
parser.add_argument(
|
|
46
46
|
'--packages',
|
|
47
47
|
action='store_true',
|
|
48
|
+
dest='packages_configmaps',
|
|
49
|
+
help='Discover python packages from function-pythonic ConfigMaps, deprecated use --packages-configmaps'
|
|
50
|
+
)
|
|
51
|
+
parser.add_argument(
|
|
52
|
+
'--packages-configmaps',
|
|
53
|
+
action='store_true',
|
|
48
54
|
help='Discover python packages from function-pythonic ConfigMaps.'
|
|
49
55
|
)
|
|
50
56
|
parser.add_argument(
|
|
@@ -57,7 +63,17 @@ class Command(command.Command):
|
|
|
57
63
|
action='append',
|
|
58
64
|
default=[],
|
|
59
65
|
metavar='NAMESPACE',
|
|
60
|
-
help='Namespaces to discover function-pythonic ConfigMaps in, default is cluster wide.',
|
|
66
|
+
help='Namespaces to discover function-pythonic ConfigMaps and Secrets in, default is cluster wide.',
|
|
67
|
+
)
|
|
68
|
+
parser.add_argument(
|
|
69
|
+
'--packages-environmentconfigs',
|
|
70
|
+
action='store_true',
|
|
71
|
+
help='Also Discover python packages from function-pythonic EnvironmentConfigs.'
|
|
72
|
+
)
|
|
73
|
+
parser.add_argument(
|
|
74
|
+
'--packages-compositions',
|
|
75
|
+
action='store_true',
|
|
76
|
+
help='Also Discover python packages from function-pythonic Compositions.'
|
|
61
77
|
)
|
|
62
78
|
parser.add_argument(
|
|
63
79
|
'--packages-dir',
|
|
@@ -75,7 +91,9 @@ class Command(command.Command):
|
|
|
75
91
|
if not self.args.tls_certs_dir and not self.args.insecure:
|
|
76
92
|
print('Either --tls-certs-dir or --insecure must be specified', file=sys.stderr)
|
|
77
93
|
sys.exit(1)
|
|
78
|
-
|
|
94
|
+
if (self.args.packages_environmentconfigs or self.args.packages_compositions) and self.args.packages_namespace:
|
|
95
|
+
print('--packages-namespace cannot be used with --packages-environment-configs or --packages-compositions', file=sys.stderr)
|
|
96
|
+
sys.exit(1)
|
|
79
97
|
if self.args.pip_install:
|
|
80
98
|
import pip._internal.cli.main
|
|
81
99
|
pip._internal.cli.main.main(['install', '--user', *shlex.split(self.args.pip_install)])
|
|
@@ -108,15 +126,18 @@ class Command(command.Command):
|
|
|
108
126
|
)
|
|
109
127
|
await grpc_server.start()
|
|
110
128
|
|
|
111
|
-
if self.args.
|
|
129
|
+
if self.args.packages_configmaps or self.args.packages_secrets or self.args.packages_environmentconfigs or self.args.packages_compositions:
|
|
112
130
|
from . import packages
|
|
113
131
|
async with asyncio.TaskGroup() as tasks:
|
|
114
132
|
tasks.create_task(grpc_server.wait_for_termination())
|
|
115
133
|
tasks.create_task(packages.operator(
|
|
116
134
|
grpc_server,
|
|
117
135
|
grpc_runner,
|
|
136
|
+
self.args.packages_configmaps,
|
|
118
137
|
self.args.packages_secrets,
|
|
119
138
|
self.args.packages_namespace,
|
|
139
|
+
self.args.packages_environmentconfigs,
|
|
140
|
+
self.args.packages_compositions,
|
|
120
141
|
self.args.packages_dir,
|
|
121
142
|
))
|
|
122
143
|
else:
|
crossplane/pythonic/packages.py
CHANGED
|
@@ -10,27 +10,38 @@ import kopf
|
|
|
10
10
|
GRPC_SERVER = None
|
|
11
11
|
GRPC_RUNNER = None
|
|
12
12
|
PACKAGES_DIR = None
|
|
13
|
-
PACKAGE_LABEL =
|
|
13
|
+
PACKAGE_LABEL = 'function-pythonic.package'
|
|
14
|
+
PACKAGE_LABELS = {PACKAGE_LABEL: kopf.PRESENT}
|
|
14
15
|
|
|
15
16
|
|
|
16
|
-
def operator(grpc_server, grpc_runner, packages_secrets, packages_namespaces, packages_dir):
|
|
17
|
+
def operator(grpc_server, grpc_runner, packages_configmaps, packages_secrets, packages_namespaces, packages_environmentconfigs, packages_compositions, packages_dir):
|
|
17
18
|
logging.getLogger('kopf.objects').setLevel(logging.INFO)
|
|
18
19
|
global GRPC_SERVER, GRPC_RUNNER, PACKAGES_DIR
|
|
19
20
|
GRPC_SERVER = grpc_server
|
|
20
21
|
GRPC_RUNNER = grpc_runner
|
|
21
22
|
PACKAGES_DIR = pathlib.Path(packages_dir).expanduser().resolve()
|
|
22
23
|
sys.path.insert(0, str(PACKAGES_DIR))
|
|
24
|
+
if packages_configmaps:
|
|
25
|
+
on_resource('', 'v1', 'configmaps')
|
|
23
26
|
if packages_secrets:
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
27
|
+
on_resource('', 'v1', 'secrets')
|
|
28
|
+
if not packages_namespaces:
|
|
29
|
+
if packages_environmentconfigs:
|
|
30
|
+
on_resource('apiextensions.crossplane.io', 'v1beta1', 'environmentconfigs')
|
|
31
|
+
if packages_compositions:
|
|
32
|
+
on_resource('apiextensions.crossplane.io', 'v1', 'compositions')
|
|
28
33
|
return kopf.operator(
|
|
29
34
|
standalone=True,
|
|
30
35
|
clusterwide=not packages_namespaces,
|
|
31
36
|
namespaces=packages_namespaces,
|
|
32
37
|
)
|
|
33
38
|
|
|
39
|
+
def on_resource(group, version, plural):
|
|
40
|
+
kopf.on.create(group, version, plural, labels=PACKAGE_LABELS)(create)
|
|
41
|
+
kopf.on.resume(group, version, plural, labels=PACKAGE_LABELS)(create)
|
|
42
|
+
kopf.on.update(group, version, plural, labels=PACKAGE_LABELS)(update)
|
|
43
|
+
kopf.on.delete(group, version, plural, labels=PACKAGE_LABELS)(delete)
|
|
44
|
+
|
|
34
45
|
|
|
35
46
|
@kopf.on.startup()
|
|
36
47
|
async def startup(settings, **_):
|
|
@@ -42,107 +53,128 @@ async def cleanup(**_):
|
|
|
42
53
|
await GRPC_SERVER.stop(5)
|
|
43
54
|
|
|
44
55
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
if
|
|
68
|
-
action
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
def get_package_dir(body, logger=None):
|
|
88
|
-
package = body.get('metadata', {}).get('labels', {}).get('function-pythonic.package', None)
|
|
56
|
+
async def create(resource, labels, body, logger, **_):
|
|
57
|
+
resource_create(resource, labels, 'Created', body, logger)
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
async def update(resource, labels, body, old, logger, **_):
|
|
61
|
+
resource_delete(resource, labels, 'Removed', old, logger)
|
|
62
|
+
resource_create(resource, labels, 'Added', body, logger)
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
async def delete(resource, labels, body, logger, **_):
|
|
66
|
+
resource_delete(resource, labels, 'Deleted', body, logger)
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def resource_create(resource, labels, action, body, logger):
|
|
70
|
+
package_dir = resource_package_dir(resource, labels, logger)
|
|
71
|
+
if not package_dir:
|
|
72
|
+
return
|
|
73
|
+
if resource.plural in ('configmaps', 'secrets', 'environmentconfigs'):
|
|
74
|
+
package_create(resource, action, package_dir, body.get('data', {}), logger)
|
|
75
|
+
elif resource.plural == 'compositions':
|
|
76
|
+
for step in body.get('spec', {}).get('pipeline', []):
|
|
77
|
+
input = step.get('input')
|
|
78
|
+
if input and input.get('apiVersion') == 'pythonic.fn.crossplane.io/v1alpha1':
|
|
79
|
+
package_create(resource, action, package_dir, input.get('packages', {}), logger)
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def resource_delete(resource, labels, action, body, logger):
|
|
83
|
+
package_dir = resource_package_dir(resource, labels, logger)
|
|
84
|
+
if not package_dir:
|
|
85
|
+
return
|
|
86
|
+
if resource.plural in ('configmaps', 'secrets', 'environmentconfigs'):
|
|
87
|
+
package_delete(action, package_dir, body.get('data', {}), logger)
|
|
88
|
+
elif resource.plural == 'compositions':
|
|
89
|
+
for step in body.get('spec', {}).get('pipeline', []):
|
|
90
|
+
input = step.get('input')
|
|
91
|
+
if input and input.get('apiVersion') == 'pythonic.fn.crossplane.io/v1alpha1':
|
|
92
|
+
package_delete(action, package_dir, step.get('input', {}).get('packages', {}), logger)
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def resource_package_dir(resource, labels, logger):
|
|
96
|
+
package = labels.get(PACKAGE_LABEL)
|
|
89
97
|
if package is None:
|
|
90
98
|
if logger:
|
|
91
|
-
logger.error(
|
|
99
|
+
logger.error(f"{PACKAGE_LABEL} label is missing")
|
|
92
100
|
return None
|
|
93
101
|
package_dir = PACKAGES_DIR
|
|
94
|
-
if package:
|
|
102
|
+
if resource.plural in ('configmaps', 'secrets') and package:
|
|
95
103
|
for segment in package.split('.'):
|
|
96
104
|
if not segment.isidentifier():
|
|
97
|
-
|
|
98
|
-
logger.error('Package has invalid package name: %s', package)
|
|
105
|
+
logger.error('Package has invalid package name: %s', package)
|
|
99
106
|
return None
|
|
100
107
|
package_dir = package_dir / segment
|
|
101
108
|
return package_dir
|
|
102
109
|
|
|
103
110
|
|
|
104
|
-
def
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
111
|
+
def package_create(resource, action, package_dir, package, logger):
|
|
112
|
+
for name, value in package.items():
|
|
113
|
+
if validate_entry(name, value, logger):
|
|
114
|
+
package_name = package_dir / name
|
|
115
|
+
if isinstance(value, str):
|
|
116
|
+
package_name.parent.mkdir(parents=True, exist_ok=True)
|
|
117
|
+
if resource.plural == 'secrets':
|
|
118
|
+
package_name.write_bytes(base64.b64decode(value.encode('utf-8')))
|
|
119
|
+
else:
|
|
120
|
+
package_name.write_text(value)
|
|
121
|
+
module, name = package_file_name(package_name)
|
|
122
|
+
if module:
|
|
123
|
+
GRPC_RUNNER.invalidate_module(name)
|
|
124
|
+
logger.info(f"{action} module: {name}")
|
|
125
|
+
else:
|
|
126
|
+
logger.info(f"{action} file: {name}")
|
|
127
|
+
elif isinstance(value, dict):
|
|
128
|
+
package_create(resource, action, package_name, value, logger)
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
def package_delete(action, package_dir, package, logger):
|
|
132
|
+
for name, value in package.items():
|
|
133
|
+
if validate_entry(name, value, logger):
|
|
134
|
+
package_name = package_dir / name
|
|
135
|
+
if isinstance(value, str):
|
|
136
|
+
package_name.unlink(missing_ok=True)
|
|
137
|
+
module, name = package_file_name(package_name)
|
|
138
|
+
if module:
|
|
139
|
+
GRPC_RUNNER.invalidate_module(name)
|
|
140
|
+
logger.info(f"{action} module: {name}")
|
|
141
|
+
else:
|
|
142
|
+
logger.info(f"{action} file: {name}")
|
|
143
|
+
parent = package_name.parent
|
|
144
|
+
while (
|
|
145
|
+
parent.is_relative_to(PACKAGES_DIR)
|
|
146
|
+
and parent.is_dir()
|
|
147
|
+
and not list(parent.iterdir())
|
|
148
|
+
):
|
|
149
|
+
parent.rmdir()
|
|
150
|
+
module = str(parent.relative_to(PACKAGES_DIR)).replace('/', '.')
|
|
151
|
+
if module != '.':
|
|
152
|
+
GRPC_RUNNER.invalidate_module(module)
|
|
153
|
+
logger.info(f"{action} package: {module}")
|
|
154
|
+
parent = parent.parent
|
|
155
|
+
elif isinstance(value, dict):
|
|
156
|
+
package_delete(action, package_name, value, logger)
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
def validate_entry(name, value, logger):
|
|
160
|
+
if isinstance(value, str):
|
|
161
|
+
if not name.endswith('.py'):
|
|
162
|
+
if '.' in name or '/' in name:
|
|
163
|
+
logger.error(f"Python package file name is not valid: {name}")
|
|
164
|
+
return False
|
|
165
|
+
return True
|
|
166
|
+
name = name[:-3]
|
|
167
|
+
elif not isinstance(value, dict):
|
|
168
|
+
logger.error(f"Python package \"{name}\" value is not a valid type: {value.__class__}")
|
|
169
|
+
return False
|
|
170
|
+
if name.isidentifier():
|
|
171
|
+
return True
|
|
172
|
+
logger.error(f"Python package name is not an identifier: {name}")
|
|
173
|
+
return False
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
def package_file_name(package_name):
|
|
177
|
+
name = str(package_name.relative_to(PACKAGES_DIR))
|
|
146
178
|
if name.endswith('.py'):
|
|
147
179
|
return True, name[:-3].replace('/', '.')
|
|
148
180
|
return False, name
|
crossplane/pythonic/protobuf.py
CHANGED
|
@@ -796,8 +796,10 @@ class Value:
|
|
|
796
796
|
def __contains__(self, item):
|
|
797
797
|
match self._kind:
|
|
798
798
|
case 'struct_value':
|
|
799
|
+
item = self._validate_key(item)
|
|
799
800
|
return item in self._value.struct_value.fields or item in self._unknowns
|
|
800
801
|
case 'Struct':
|
|
802
|
+
item = self._validate_key(item)
|
|
801
803
|
return item in self._value.fields or item in self._unknowns
|
|
802
804
|
case 'list_value' | 'ListValue':
|
|
803
805
|
for value in self:
|
|
@@ -984,6 +986,8 @@ class Value:
|
|
|
984
986
|
elif len(args):
|
|
985
987
|
for key in range(len(args)):
|
|
986
988
|
self[key] = args[key]
|
|
989
|
+
else:
|
|
990
|
+
self._ensure_map()
|
|
987
991
|
return self
|
|
988
992
|
|
|
989
993
|
def __setattr__(self, key, value):
|