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,102 @@
|
|
|
1
|
+
|
|
2
|
+
import logging
|
|
3
|
+
import pathlib
|
|
4
|
+
import sys
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class Command:
|
|
8
|
+
name = None
|
|
9
|
+
command = None
|
|
10
|
+
description = None
|
|
11
|
+
|
|
12
|
+
@classmethod
|
|
13
|
+
def create(cls, subparsers):
|
|
14
|
+
parser = subparsers.add_parser(cls.name, help=cls.help, description=cls.description)
|
|
15
|
+
parser.set_defaults(command=cls)
|
|
16
|
+
cls.add_parser_arguments(parser)
|
|
17
|
+
|
|
18
|
+
@classmethod
|
|
19
|
+
def add_parser_arguments(cls, parser):
|
|
20
|
+
pass
|
|
21
|
+
|
|
22
|
+
@classmethod
|
|
23
|
+
def add_function_arguments(cls, parser):
|
|
24
|
+
parser.add_argument(
|
|
25
|
+
'--debug', '-d',
|
|
26
|
+
action='store_true',
|
|
27
|
+
help='Emit debug logs.',
|
|
28
|
+
)
|
|
29
|
+
parser.add_argument(
|
|
30
|
+
'--log-name-width',
|
|
31
|
+
type=int,
|
|
32
|
+
default=40,
|
|
33
|
+
metavar='WIDTH',
|
|
34
|
+
help='Width of the logger name in the log output, default 40.',
|
|
35
|
+
)
|
|
36
|
+
parser.add_argument(
|
|
37
|
+
'--python-path',
|
|
38
|
+
action='append',
|
|
39
|
+
default=[],
|
|
40
|
+
metavar='DIRECTORY',
|
|
41
|
+
help='Filing system directories to add to the python path.',
|
|
42
|
+
)
|
|
43
|
+
parser.add_argument(
|
|
44
|
+
'--render-unknowns', '-u',
|
|
45
|
+
action='store_true',
|
|
46
|
+
help='Render resources with unknowns, useful during local development.'
|
|
47
|
+
)
|
|
48
|
+
parser.add_argument(
|
|
49
|
+
'--allow-oversize-protos',
|
|
50
|
+
action='store_true',
|
|
51
|
+
help='Allow oversized protobuf messages',
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
def __init__(self, args):
|
|
55
|
+
self.args = args
|
|
56
|
+
self.initialize()
|
|
57
|
+
|
|
58
|
+
def initialize(self):
|
|
59
|
+
pass
|
|
60
|
+
|
|
61
|
+
def initialize_function(self):
|
|
62
|
+
formatter = Formatter(self.args.log_name_width)
|
|
63
|
+
handler = logging.StreamHandler(sys.stdout)
|
|
64
|
+
handler.setFormatter(formatter)
|
|
65
|
+
logger = logging.getLogger()
|
|
66
|
+
logger.handlers = [handler]
|
|
67
|
+
logger.setLevel(logging.DEBUG if self.args.debug else logging.INFO)
|
|
68
|
+
|
|
69
|
+
for path in reversed(self.args.python_path):
|
|
70
|
+
sys.path.insert(0, str(pathlib.Path(path).expanduser().resolve()))
|
|
71
|
+
|
|
72
|
+
if self.args.allow_oversize_protos:
|
|
73
|
+
from google.protobuf.internal import api_implementation
|
|
74
|
+
if api_implementation._c_module:
|
|
75
|
+
api_implementation._c_module.SetAllowOversizeProtos(True)
|
|
76
|
+
|
|
77
|
+
async def run(self):
|
|
78
|
+
raise NotImplementedError()
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
class Formatter(logging.Formatter):
|
|
82
|
+
def __init__(self, name_width):
|
|
83
|
+
super(Formatter, self).__init__(
|
|
84
|
+
f"[{{asctime}}.{{msecs:03.0f}}] {{sname:{name_width}.{name_width}}} [{{levelname:8.8}}] {{message}}",
|
|
85
|
+
'%Y-%m-%d %H:%M:%S',
|
|
86
|
+
'{',
|
|
87
|
+
)
|
|
88
|
+
self.name_width = name_width
|
|
89
|
+
|
|
90
|
+
def format(self, record):
|
|
91
|
+
record.sname = record.name
|
|
92
|
+
extra = len(record.sname) - self.name_width
|
|
93
|
+
if extra > 0:
|
|
94
|
+
names = record.sname.split('.')
|
|
95
|
+
for ix, name in enumerate(names):
|
|
96
|
+
if len(name) > extra:
|
|
97
|
+
names[ix] = name[extra:]
|
|
98
|
+
break
|
|
99
|
+
names[ix] = name[:1]
|
|
100
|
+
extra -= len(name) - 1
|
|
101
|
+
record.sname = '.'.join(names)
|
|
102
|
+
return super(Formatter, self).format(record)
|
crossplane/pythonic/composite.py
CHANGED
|
@@ -47,7 +47,8 @@ class BaseComposite:
|
|
|
47
47
|
self.spec = self.observed.spec
|
|
48
48
|
self.status = self.desired.status
|
|
49
49
|
self.conditions = Conditions(observed, self.response)
|
|
50
|
-
self.
|
|
50
|
+
self.results = Results(self.response)
|
|
51
|
+
self.events = Results(self.response) # Deprecated, use self.results
|
|
51
52
|
|
|
52
53
|
@property
|
|
53
54
|
def ttl(self):
|
|
@@ -449,6 +450,32 @@ class Conditions:
|
|
|
449
450
|
def __getitem__(self, type):
|
|
450
451
|
return Condition(self, type)
|
|
451
452
|
|
|
453
|
+
def __bool__(self):
|
|
454
|
+
if self._response is not None:
|
|
455
|
+
if self._response.conditions:
|
|
456
|
+
return True
|
|
457
|
+
if self._observed.resource.status.conditions:
|
|
458
|
+
return True
|
|
459
|
+
return False
|
|
460
|
+
|
|
461
|
+
def __len__(self):
|
|
462
|
+
return len(self._types())
|
|
463
|
+
|
|
464
|
+
def __iter__(self):
|
|
465
|
+
for type in self._types():
|
|
466
|
+
yield self[type]
|
|
467
|
+
|
|
468
|
+
def _types(self):
|
|
469
|
+
types = set()
|
|
470
|
+
if self._response is not None:
|
|
471
|
+
for condition in self._response.conditions:
|
|
472
|
+
if condition.type:
|
|
473
|
+
types.add(str(condition.type))
|
|
474
|
+
for condition in self._observed.resource.status.conditions:
|
|
475
|
+
if condition.type:
|
|
476
|
+
types.add(str(condition.type))
|
|
477
|
+
return sorted(types)
|
|
478
|
+
|
|
452
479
|
|
|
453
480
|
class Condition(protobuf.ProtobufValue):
|
|
454
481
|
def __init__(self, conditions, type):
|
|
@@ -567,42 +594,42 @@ class Condition(protobuf.ProtobufValue):
|
|
|
567
594
|
return self._conditions._response.conditions.append(condition)
|
|
568
595
|
|
|
569
596
|
|
|
570
|
-
class
|
|
597
|
+
class Results:
|
|
571
598
|
def __init__(self, response):
|
|
572
599
|
self._results = response.results
|
|
573
600
|
|
|
574
601
|
def info(self, reason=_notset, message=_notset, claim=_notset):
|
|
575
|
-
|
|
576
|
-
|
|
602
|
+
result = Result(self._results.append())
|
|
603
|
+
result.info = True
|
|
577
604
|
if reason != _notset:
|
|
578
|
-
|
|
605
|
+
result.reason = reason
|
|
579
606
|
if message != _notset:
|
|
580
|
-
|
|
607
|
+
result.message = message
|
|
581
608
|
if claim != _notset:
|
|
582
|
-
|
|
583
|
-
return
|
|
609
|
+
result.claim = claim
|
|
610
|
+
return result
|
|
584
611
|
|
|
585
612
|
def warning(self, reason=_notset, message=_notset, claim=_notset):
|
|
586
|
-
|
|
587
|
-
|
|
613
|
+
result = Result(self._results.append())
|
|
614
|
+
result.warning = True
|
|
588
615
|
if reason != _notset:
|
|
589
|
-
|
|
616
|
+
result.reason = reason
|
|
590
617
|
if message != _notset:
|
|
591
|
-
|
|
618
|
+
result.message = message
|
|
592
619
|
if claim != _notset:
|
|
593
|
-
|
|
594
|
-
return
|
|
620
|
+
result.claim = claim
|
|
621
|
+
return result
|
|
595
622
|
|
|
596
623
|
def fatal(self, reason=_notset, message=_notset, claim=_notset):
|
|
597
|
-
|
|
598
|
-
|
|
624
|
+
result = Result(self._results.append())
|
|
625
|
+
result.fatal = True
|
|
599
626
|
if reason != _notset:
|
|
600
|
-
|
|
627
|
+
result.reason = reason
|
|
601
628
|
if message != _notset:
|
|
602
|
-
|
|
629
|
+
result.message = message
|
|
603
630
|
if claim != _notset:
|
|
604
|
-
|
|
605
|
-
return
|
|
631
|
+
result.claim = claim
|
|
632
|
+
return result
|
|
606
633
|
|
|
607
634
|
def __bool__(self):
|
|
608
635
|
return len(self) > 0
|
|
@@ -612,15 +639,15 @@ class Events:
|
|
|
612
639
|
|
|
613
640
|
def __getitem__(self, key):
|
|
614
641
|
if key >= len(self._results):
|
|
615
|
-
return
|
|
616
|
-
return
|
|
642
|
+
return Result()
|
|
643
|
+
return Result(self._results[key])
|
|
617
644
|
|
|
618
645
|
def __iter__(self):
|
|
619
646
|
for ix in range(len(self._results)):
|
|
620
647
|
yield self[ix]
|
|
621
648
|
|
|
622
649
|
|
|
623
|
-
class
|
|
650
|
+
class Result:
|
|
624
651
|
def __init__(self, result=None):
|
|
625
652
|
self._result = result
|
|
626
653
|
|
crossplane/pythonic/function.py
CHANGED
|
@@ -24,10 +24,14 @@ class FunctionRunner(grpcv1.FunctionRunnerService):
|
|
|
24
24
|
self.clazzes = {}
|
|
25
25
|
|
|
26
26
|
def invalidate_module(self, module):
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
27
|
+
ix = len(module)
|
|
28
|
+
while ix > 0:
|
|
29
|
+
module = module[:ix]
|
|
30
|
+
if module in sys.modules:
|
|
31
|
+
del sys.modules[module]
|
|
32
|
+
ix = module.rfind('.')
|
|
30
33
|
importlib.invalidate_caches()
|
|
34
|
+
self.clazzes.clear()
|
|
31
35
|
|
|
32
36
|
async def RunFunction(
|
|
33
37
|
self, request: fnv1.RunFunctionRequest, _: grpc.aio.ServicerContext
|
|
@@ -44,7 +48,7 @@ class FunctionRunner(grpcv1.FunctionRunnerService):
|
|
|
44
48
|
name.append(composite['metadata']['name'])
|
|
45
49
|
logger = logging.getLogger('.'.join(name))
|
|
46
50
|
|
|
47
|
-
if composite['apiVersion']
|
|
51
|
+
if composite['apiVersion'] in ('pythonic.crossplane.io/v1alpha1', 'pythonic.fortra.com/v1alpha1') and composite['kind'] == 'Composite':
|
|
48
52
|
if 'spec' not in composite or 'composite' not in composite['spec']:
|
|
49
53
|
return self.fatal(request, logger, 'Missing spec "composite"')
|
|
50
54
|
single_use = True
|
|
@@ -272,30 +276,30 @@ class FunctionRunner(grpcv1.FunctionRunnerService):
|
|
|
272
276
|
reason = 'FatalUnknowns'
|
|
273
277
|
message = f"Observed resources with unknowns: {','.join(fatalResources)}"
|
|
274
278
|
status = False
|
|
275
|
-
|
|
279
|
+
result = composite.results.fatal
|
|
276
280
|
elif warningResources:
|
|
277
281
|
level = composite.logger.warning
|
|
278
282
|
reason = 'ObservedUnknowns'
|
|
279
283
|
message = f"Observed resources with unknowns: {','.join(warningResources)}"
|
|
280
284
|
status = False
|
|
281
|
-
|
|
285
|
+
result = composite.results.warning
|
|
282
286
|
elif unknownResources:
|
|
283
287
|
level = composite.logger.info
|
|
284
288
|
reason = 'DesiredUnknowns'
|
|
285
289
|
message = f"Desired resources with unknowns: {','.join(unknownResources)}"
|
|
286
290
|
status = False
|
|
287
|
-
|
|
291
|
+
result = composite.results.info
|
|
288
292
|
else:
|
|
289
293
|
level = None
|
|
290
294
|
reason = 'AllComposed'
|
|
291
295
|
message = 'All resources are composed'
|
|
292
296
|
status = True
|
|
293
|
-
|
|
297
|
+
result = None
|
|
294
298
|
if not self.debug and level:
|
|
295
299
|
level(message)
|
|
296
300
|
composite.conditions.ResourcesComposed(reason, message, status)
|
|
297
|
-
if
|
|
298
|
-
|
|
301
|
+
if result:
|
|
302
|
+
result(reason, message)
|
|
299
303
|
|
|
300
304
|
def process_auto_readies(self, composite):
|
|
301
305
|
for name, resource in composite.resources:
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
|
|
2
|
+
import asyncio
|
|
3
|
+
import os
|
|
4
|
+
import pathlib
|
|
5
|
+
import shlex
|
|
6
|
+
import signal
|
|
7
|
+
import sys
|
|
8
|
+
|
|
9
|
+
import crossplane.function.proto.v1.run_function_pb2_grpc as grpcv1
|
|
10
|
+
import grpc
|
|
11
|
+
|
|
12
|
+
from . import (
|
|
13
|
+
command,
|
|
14
|
+
function,
|
|
15
|
+
)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class Command(command.Command):
|
|
19
|
+
name = 'grpc'
|
|
20
|
+
help = 'Run function-pythonic gRPC server'
|
|
21
|
+
|
|
22
|
+
@classmethod
|
|
23
|
+
def add_parser_arguments(cls, parser):
|
|
24
|
+
cls.add_function_arguments(parser)
|
|
25
|
+
parser.add_argument(
|
|
26
|
+
'--address',
|
|
27
|
+
default='0.0.0.0:9443',
|
|
28
|
+
help='Address to listen on for gRPC connections, default: 0.0.0.0:9443',
|
|
29
|
+
)
|
|
30
|
+
parser.add_argument(
|
|
31
|
+
'--tls-certs-dir',
|
|
32
|
+
default=os.getenv('TLS_SERVER_CERTS_DIR'),
|
|
33
|
+
metavar='DIRECTORY',
|
|
34
|
+
help='Serve using TLS certificates.',
|
|
35
|
+
)
|
|
36
|
+
parser.add_argument(
|
|
37
|
+
'--insecure',
|
|
38
|
+
action='store_true',
|
|
39
|
+
help='Run without mTLS credentials, --tls-certs-dir will be ignored.',
|
|
40
|
+
)
|
|
41
|
+
parser.add_argument(
|
|
42
|
+
'--packages',
|
|
43
|
+
action='store_true',
|
|
44
|
+
help='Discover python packages from function-pythonic ConfigMaps.'
|
|
45
|
+
)
|
|
46
|
+
parser.add_argument(
|
|
47
|
+
'--packages-secrets',
|
|
48
|
+
action='store_true',
|
|
49
|
+
help='Also Discover python packages from function-pythonic Secrets.'
|
|
50
|
+
)
|
|
51
|
+
parser.add_argument(
|
|
52
|
+
'--packages-namespace',
|
|
53
|
+
action='append',
|
|
54
|
+
default=[],
|
|
55
|
+
metavar='NAMESPACE',
|
|
56
|
+
help='Namespaces to discover function-pythonic ConfigMaps in, default is cluster wide.',
|
|
57
|
+
)
|
|
58
|
+
parser.add_argument(
|
|
59
|
+
'--packages-dir',
|
|
60
|
+
default='./pythonic-packages',
|
|
61
|
+
metavar='DIRECTORY',
|
|
62
|
+
help='Directory to store discovered function-pythonic ConfigMaps to, defaults "<cwd>/pythonic-packages"'
|
|
63
|
+
)
|
|
64
|
+
parser.add_argument(
|
|
65
|
+
'--pip-install',
|
|
66
|
+
metavar='INSTALL',
|
|
67
|
+
help='Pip install command to install additional Python packages.'
|
|
68
|
+
)
|
|
69
|
+
|
|
70
|
+
def initialize(self):
|
|
71
|
+
if not self.args.tls_certs_dir and not self.args.insecure:
|
|
72
|
+
print('Either --tls-certs-dir or --insecure must be specified', file=sys.stderr)
|
|
73
|
+
sys.exit(1)
|
|
74
|
+
|
|
75
|
+
if self.args.pip_install:
|
|
76
|
+
import pip._internal.cli.main
|
|
77
|
+
pip._internal.cli.main.main(['install', '--user', *shlex.split(self.args.pip_install)])
|
|
78
|
+
|
|
79
|
+
self.initialize_function()
|
|
80
|
+
|
|
81
|
+
# enables read only volumes or mismatched uid volumes
|
|
82
|
+
sys.dont_write_bytecode = True
|
|
83
|
+
|
|
84
|
+
async def run(self):
|
|
85
|
+
grpc.aio.init_grpc_aio()
|
|
86
|
+
grpc_runner = function.FunctionRunner(self.args.debug, self.args.render_unknowns)
|
|
87
|
+
grpc_server = grpc.aio.server()
|
|
88
|
+
grpcv1.add_FunctionRunnerServiceServicer_to_server(grpc_runner, grpc_server)
|
|
89
|
+
if self.args.insecure:
|
|
90
|
+
grpc_server.add_insecure_port(self.args.address)
|
|
91
|
+
else:
|
|
92
|
+
certs = pathlib.Path(self.args.tls_certs_dir).expanduser().resolve()
|
|
93
|
+
grpc_server.add_secure_port(
|
|
94
|
+
self.args.address,
|
|
95
|
+
grpc.ssl_server_credentials(
|
|
96
|
+
private_key_certificate_chain_pairs=[(
|
|
97
|
+
(certs / 'tls.key').read_bytes(),
|
|
98
|
+
(certs / 'tls.crt').read_bytes(),
|
|
99
|
+
)],
|
|
100
|
+
root_certificates=(certs / 'ca.crt').read_bytes(),
|
|
101
|
+
require_client_auth=True,
|
|
102
|
+
),
|
|
103
|
+
)
|
|
104
|
+
await grpc_server.start()
|
|
105
|
+
|
|
106
|
+
if self.args.packages:
|
|
107
|
+
from . import packages
|
|
108
|
+
async with asyncio.TaskGroup() as tasks:
|
|
109
|
+
tasks.create_task(grpc_server.wait_for_termination())
|
|
110
|
+
tasks.create_task(packages.operator(
|
|
111
|
+
grpc_server,
|
|
112
|
+
grpc_runner,
|
|
113
|
+
self.args.packages_secrets,
|
|
114
|
+
self.args.packages_namespace,
|
|
115
|
+
self.args.packages_dir,
|
|
116
|
+
))
|
|
117
|
+
else:
|
|
118
|
+
def stop():
|
|
119
|
+
asyncio.ensure_future(grpc_server.stop(5))
|
|
120
|
+
loop = asyncio.get_event_loop()
|
|
121
|
+
loop.add_signal_handler(signal.SIGINT, stop)
|
|
122
|
+
loop.add_signal_handler(signal.SIGTERM, stop)
|
|
123
|
+
await grpc_server.wait_for_termination()
|
crossplane/pythonic/main.py
CHANGED
|
@@ -1,197 +1,27 @@
|
|
|
1
|
-
"""The
|
|
1
|
+
"""The function-pythonic's main CLI."""
|
|
2
2
|
|
|
3
3
|
import argparse
|
|
4
4
|
import asyncio
|
|
5
|
-
import logging
|
|
6
|
-
import os
|
|
7
|
-
import pathlib
|
|
8
|
-
import shlex
|
|
9
|
-
import signal
|
|
10
5
|
import sys
|
|
11
|
-
import traceback
|
|
12
6
|
|
|
13
|
-
import
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
7
|
+
from . import (
|
|
8
|
+
grpc,
|
|
9
|
+
render,
|
|
10
|
+
version,
|
|
11
|
+
)
|
|
18
12
|
|
|
19
13
|
|
|
20
14
|
def main():
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
)
|
|
32
|
-
parser.add_argument(
|
|
33
|
-
'--log-name-width',
|
|
34
|
-
type=int,
|
|
35
|
-
default=40,
|
|
36
|
-
metavar='WIDTH',
|
|
37
|
-
help='Width of the logger name in the log output, default 40',
|
|
38
|
-
)
|
|
39
|
-
parser.add_argument(
|
|
40
|
-
'--address',
|
|
41
|
-
default='0.0.0.0:9443',
|
|
42
|
-
help='Address to listen on for gRPC connections, default: 0.0.0.0:9443',
|
|
43
|
-
)
|
|
44
|
-
parser.add_argument(
|
|
45
|
-
'--tls-certs-dir',
|
|
46
|
-
default=os.getenv('TLS_SERVER_CERTS_DIR'),
|
|
47
|
-
metavar='DIRECTORY',
|
|
48
|
-
help='Serve using TLS certificates.',
|
|
49
|
-
)
|
|
50
|
-
parser.add_argument(
|
|
51
|
-
'--insecure',
|
|
52
|
-
action='store_true',
|
|
53
|
-
help='Run without mTLS credentials, --tls-certs-dir will be ignored.',
|
|
54
|
-
)
|
|
55
|
-
parser.add_argument(
|
|
56
|
-
'--packages',
|
|
57
|
-
action='store_true',
|
|
58
|
-
help='Discover python packages from function-pythonic ConfigMaps.'
|
|
59
|
-
)
|
|
60
|
-
parser.add_argument(
|
|
61
|
-
'--packages-secrets',
|
|
62
|
-
action='store_true',
|
|
63
|
-
help='Also Discover python packages from function-pythonic Secrets.'
|
|
64
|
-
)
|
|
65
|
-
parser.add_argument(
|
|
66
|
-
'--packages-namespace',
|
|
67
|
-
action='append',
|
|
68
|
-
default=[],
|
|
69
|
-
metavar='NAMESPACE',
|
|
70
|
-
help='Namespaces to discover function-pythonic ConfigMaps in, default is cluster wide.',
|
|
71
|
-
)
|
|
72
|
-
parser.add_argument(
|
|
73
|
-
'--packages-dir',
|
|
74
|
-
default='./pythonic-packages',
|
|
75
|
-
metavar='DIRECTORY',
|
|
76
|
-
help='Directory to store discovered function-pythonic ConfigMaps to, defaults "<cwd>/pythonic-packages"'
|
|
77
|
-
)
|
|
78
|
-
parser.add_argument(
|
|
79
|
-
'--pip-install',
|
|
80
|
-
metavar='COMMAND',
|
|
81
|
-
help='Pip install command to install additional Python packages.'
|
|
82
|
-
)
|
|
83
|
-
parser.add_argument(
|
|
84
|
-
'--python-path',
|
|
85
|
-
action='append',
|
|
86
|
-
default=[],
|
|
87
|
-
metavar='DIRECTORY',
|
|
88
|
-
help='Filing system directories to add to the python path',
|
|
89
|
-
)
|
|
90
|
-
parser.add_argument(
|
|
91
|
-
'--allow-oversize-protos',
|
|
92
|
-
action='store_true',
|
|
93
|
-
help='Allow oversized protobuf messages'
|
|
94
|
-
)
|
|
95
|
-
parser.add_argument(
|
|
96
|
-
'--render-unknowns',
|
|
97
|
-
action='store_true',
|
|
98
|
-
help='Render resources with unknowns, useful during local develomment'
|
|
99
|
-
)
|
|
100
|
-
args = parser.parse_args()
|
|
101
|
-
if not args.tls_certs_dir and not args.insecure:
|
|
102
|
-
print('Either --tls-certs-dir or --insecure must be specified', file=sys.stderr)
|
|
103
|
-
sys.exit(1)
|
|
104
|
-
|
|
105
|
-
if args.pip_install:
|
|
106
|
-
import pip._internal.cli.main
|
|
107
|
-
pip._internal.cli.main.main(['install', '--user', *shlex.split(args.pip_install)])
|
|
108
|
-
|
|
109
|
-
for path in reversed(args.python_path):
|
|
110
|
-
sys.path.insert(0, str(pathlib.Path(path).expanduser().resolve()))
|
|
111
|
-
|
|
112
|
-
self.configure_logging(args)
|
|
113
|
-
# enables read only volumes or mismatched uid volumes
|
|
114
|
-
sys.dont_write_bytecode = True
|
|
115
|
-
await self.run(args)
|
|
116
|
-
|
|
117
|
-
# Allow for independent running of function-pythonic
|
|
118
|
-
async def run(self, args):
|
|
119
|
-
if args.allow_oversize_protos:
|
|
120
|
-
from google.protobuf.internal import api_implementation
|
|
121
|
-
if api_implementation._c_module:
|
|
122
|
-
api_implementation._c_module.SetAllowOversizeProtos(True)
|
|
123
|
-
|
|
124
|
-
grpc.aio.init_grpc_aio()
|
|
125
|
-
grpc_runner = function.FunctionRunner(args.debug, args.render_unknowns)
|
|
126
|
-
grpc_server = grpc.aio.server()
|
|
127
|
-
grpcv1.add_FunctionRunnerServiceServicer_to_server(grpc_runner, grpc_server)
|
|
128
|
-
if args.insecure:
|
|
129
|
-
grpc_server.add_insecure_port(args.address)
|
|
130
|
-
else:
|
|
131
|
-
certs = pathlib.Path(args.tls_certs_dir).expanduser().resolve()
|
|
132
|
-
grpc_server.add_secure_port(
|
|
133
|
-
args.address,
|
|
134
|
-
grpc.ssl_server_credentials(
|
|
135
|
-
private_key_certificate_chain_pairs=[(
|
|
136
|
-
(certs / 'tls.key').read_bytes(),
|
|
137
|
-
(certs / 'tls.crt').read_bytes(),
|
|
138
|
-
)],
|
|
139
|
-
root_certificates=(certs / 'ca.crt').read_bytes(),
|
|
140
|
-
require_client_auth=True,
|
|
141
|
-
),
|
|
142
|
-
)
|
|
143
|
-
await grpc_server.start()
|
|
144
|
-
|
|
145
|
-
if args.packages:
|
|
146
|
-
from . import packages
|
|
147
|
-
async with asyncio.TaskGroup() as tasks:
|
|
148
|
-
tasks.create_task(grpc_server.wait_for_termination())
|
|
149
|
-
tasks.create_task(packages.operator(
|
|
150
|
-
grpc_server,
|
|
151
|
-
grpc_runner,
|
|
152
|
-
args.packages_secrets,
|
|
153
|
-
args.packages_namespace,
|
|
154
|
-
args.packages_dir,
|
|
155
|
-
))
|
|
156
|
-
else:
|
|
157
|
-
def stop():
|
|
158
|
-
asyncio.ensure_future(grpc_server.stop(5))
|
|
159
|
-
loop = asyncio.get_event_loop()
|
|
160
|
-
loop.add_signal_handler(signal.SIGINT, stop)
|
|
161
|
-
loop.add_signal_handler(signal.SIGTERM, stop)
|
|
162
|
-
await grpc_server.wait_for_termination()
|
|
163
|
-
|
|
164
|
-
def configure_logging(self, args):
|
|
165
|
-
formatter = Formatter(args.log_name_width)
|
|
166
|
-
handler = logging.StreamHandler()
|
|
167
|
-
handler.setFormatter(formatter)
|
|
168
|
-
logger = logging.getLogger()
|
|
169
|
-
logger.handlers = [handler]
|
|
170
|
-
logger.setLevel(logging.DEBUG if args.debug else logging.INFO)
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
class Formatter(logging.Formatter):
|
|
174
|
-
def __init__(self, name_width):
|
|
175
|
-
super(Formatter, self).__init__(
|
|
176
|
-
f"[{{asctime}}.{{msecs:03.0f}}] {{sname:{name_width}.{name_width}}} [{{levelname:8.8}}] {{message}}",
|
|
177
|
-
'%Y-%m-%d %H:%M:%S',
|
|
178
|
-
'{',
|
|
179
|
-
)
|
|
180
|
-
self.name_width = name_width
|
|
181
|
-
|
|
182
|
-
def format(self, record):
|
|
183
|
-
record.sname = record.name
|
|
184
|
-
extra = len(record.sname) - self.name_width
|
|
185
|
-
if extra > 0:
|
|
186
|
-
names = record.sname.split('.')
|
|
187
|
-
for ix, name in enumerate(names):
|
|
188
|
-
if len(name) > extra:
|
|
189
|
-
names[ix] = name[extra:]
|
|
190
|
-
break
|
|
191
|
-
names[ix] = name[:1]
|
|
192
|
-
extra -= len(name) - 1
|
|
193
|
-
record.sname = '.'.join(names)
|
|
194
|
-
return super(Formatter, self).format(record)
|
|
15
|
+
parser = argparse.ArgumentParser('Crossplane Function Pythonic')
|
|
16
|
+
subparsers = parser.add_subparsers(title='Command', metavar='')
|
|
17
|
+
grpc.Command.create(subparsers)
|
|
18
|
+
render.Command.create(subparsers)
|
|
19
|
+
version.Command.create(subparsers)
|
|
20
|
+
args = parser.parse_args()
|
|
21
|
+
if not hasattr(args, 'command'):
|
|
22
|
+
parser.print_help()
|
|
23
|
+
sys.exit(1)
|
|
24
|
+
asyncio.run(args.command(args).run())
|
|
195
25
|
|
|
196
26
|
|
|
197
27
|
if __name__ == '__main__':
|
crossplane/pythonic/protobuf.py
CHANGED
|
@@ -55,7 +55,6 @@ def B64Decode(string):
|
|
|
55
55
|
string = str(string)
|
|
56
56
|
return base64.b64decode(string.encode('utf-8')).decode('utf-8')
|
|
57
57
|
|
|
58
|
-
B64Decode = lambda s: base64.b64decode(s.encode('utf-8')).decode('utf-8')
|
|
59
58
|
|
|
60
59
|
class Message:
|
|
61
60
|
def __init__(self, parent, key, descriptor, message=_Unknown, readOnly=False):
|
|
@@ -81,7 +80,7 @@ class Message:
|
|
|
81
80
|
value = getattr(self._message, key)
|
|
82
81
|
if value is _Unknown and field.has_default_value:
|
|
83
82
|
value = field.default_value
|
|
84
|
-
if field.
|
|
83
|
+
if field.is_repeated:
|
|
85
84
|
if field.type == field.TYPE_MESSAGE and field.message_type.GetOptions().map_entry:
|
|
86
85
|
value = MapMessage(self, key, field.message_type.fields_by_name['value'], value, self._readOnly)
|
|
87
86
|
else:
|
|
@@ -450,6 +449,10 @@ class RepeatedMessage:
|
|
|
450
449
|
raise ValueError(f"{self._readOnly} is read only")
|
|
451
450
|
if self._messages is _Unknown:
|
|
452
451
|
self.__dict__['_messages'] = self._parent._create_child(self._key)
|
|
452
|
+
if key == append:
|
|
453
|
+
key = len(self._messages)
|
|
454
|
+
elif key < 0:
|
|
455
|
+
key = len(self._messages) + key
|
|
453
456
|
while key >= len(self._messages):
|
|
454
457
|
self._messages.add()
|
|
455
458
|
return self._messages[key]
|
|
@@ -1050,6 +1053,10 @@ class Value:
|
|
|
1050
1053
|
values = self._value.list_value.values
|
|
1051
1054
|
else:
|
|
1052
1055
|
values = self._value.values
|
|
1056
|
+
if key == append:
|
|
1057
|
+
key = len(values)
|
|
1058
|
+
elif key < 0:
|
|
1059
|
+
key = len(values) + key
|
|
1053
1060
|
while key >= len(values):
|
|
1054
1061
|
values.add()
|
|
1055
1062
|
values[key].Clear()
|