ominfra 0.0.0.dev142__py3-none-any.whl → 0.0.0.dev143__py3-none-any.whl
Sign up to get free protection for your applications and to get access to all the features.
- ominfra/manage/__init__.py +10 -0
- ominfra/manage/__main__.py +4 -0
- ominfra/manage/bootstrap.py +11 -0
- ominfra/manage/bootstrap_.py +18 -0
- ominfra/manage/commands/base.py +133 -1
- ominfra/manage/commands/execution.py +23 -0
- ominfra/manage/commands/inject.py +122 -0
- ominfra/manage/commands/marshal.py +26 -0
- ominfra/manage/config.py +10 -0
- ominfra/manage/inject.py +55 -0
- ominfra/manage/main.py +49 -89
- ominfra/manage/marshal.py +12 -0
- ominfra/manage/remote/__init__.py +0 -0
- ominfra/manage/{protocol.py → remote/channel.py} +9 -2
- ominfra/manage/remote/config.py +12 -0
- ominfra/manage/remote/execution.py +193 -0
- ominfra/manage/remote/inject.py +29 -0
- ominfra/manage/{payload.py → remote/payload.py} +7 -1
- ominfra/manage/{spawning.py → remote/spawning.py} +29 -29
- ominfra/pyremote.py +40 -3
- ominfra/scripts/journald2aws.py +98 -50
- ominfra/scripts/manage.py +1854 -169
- ominfra/scripts/supervisor.py +99 -50
- ominfra/supervisor/inject.py +2 -1
- {ominfra-0.0.0.dev142.dist-info → ominfra-0.0.0.dev143.dist-info}/METADATA +3 -3
- {ominfra-0.0.0.dev142.dist-info → ominfra-0.0.0.dev143.dist-info}/RECORD +30 -17
- {ominfra-0.0.0.dev142.dist-info → ominfra-0.0.0.dev143.dist-info}/LICENSE +0 -0
- {ominfra-0.0.0.dev142.dist-info → ominfra-0.0.0.dev143.dist-info}/WHEEL +0 -0
- {ominfra-0.0.0.dev142.dist-info → ominfra-0.0.0.dev143.dist-info}/entry_points.txt +0 -0
- {ominfra-0.0.0.dev142.dist-info → ominfra-0.0.0.dev143.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,193 @@
|
|
1
|
+
# ruff: noqa: UP006 UP007
|
2
|
+
import contextlib
|
3
|
+
import dataclasses as dc
|
4
|
+
import logging
|
5
|
+
import typing as ta
|
6
|
+
|
7
|
+
from omlish.lite.cached import cached_nullary
|
8
|
+
from omlish.lite.check import check_not_none
|
9
|
+
from omlish.lite.logs import log
|
10
|
+
from omlish.lite.marshal import ObjMarshalerManager
|
11
|
+
from omlish.lite.pycharm import pycharm_debug_connect
|
12
|
+
|
13
|
+
from ...pyremote import PyremoteBootstrapDriver
|
14
|
+
from ...pyremote import PyremoteBootstrapOptions
|
15
|
+
from ...pyremote import pyremote_bootstrap_finalize
|
16
|
+
from ...pyremote import pyremote_build_bootstrap_cmd
|
17
|
+
from ..bootstrap import MainBootstrap
|
18
|
+
from ..commands.base import Command
|
19
|
+
from ..commands.base import CommandException
|
20
|
+
from ..commands.base import CommandExecutor
|
21
|
+
from ..commands.base import CommandOutputOrException
|
22
|
+
from ..commands.base import CommandOutputOrExceptionData
|
23
|
+
from .channel import RemoteChannel
|
24
|
+
from .payload import RemoteExecutionPayloadFile
|
25
|
+
from .payload import get_remote_payload_src
|
26
|
+
from .spawning import RemoteSpawning
|
27
|
+
|
28
|
+
|
29
|
+
if ta.TYPE_CHECKING:
|
30
|
+
from ..bootstrap_ import main_bootstrap
|
31
|
+
else:
|
32
|
+
main_bootstrap: ta.Any = None
|
33
|
+
|
34
|
+
|
35
|
+
##
|
36
|
+
|
37
|
+
|
38
|
+
def _remote_execution_main() -> None:
|
39
|
+
rt = pyremote_bootstrap_finalize() # noqa
|
40
|
+
|
41
|
+
chan = RemoteChannel(
|
42
|
+
rt.input,
|
43
|
+
rt.output,
|
44
|
+
)
|
45
|
+
|
46
|
+
bs = check_not_none(chan.recv_obj(MainBootstrap))
|
47
|
+
|
48
|
+
if (prd := bs.remote_config.pycharm_remote_debug) is not None:
|
49
|
+
pycharm_debug_connect(prd)
|
50
|
+
|
51
|
+
injector = main_bootstrap(bs)
|
52
|
+
|
53
|
+
chan.set_marshaler(injector[ObjMarshalerManager])
|
54
|
+
|
55
|
+
ce = injector[CommandExecutor]
|
56
|
+
|
57
|
+
while True:
|
58
|
+
i = chan.recv_obj(Command)
|
59
|
+
if i is None:
|
60
|
+
break
|
61
|
+
|
62
|
+
r = ce.try_execute(
|
63
|
+
i,
|
64
|
+
log=log,
|
65
|
+
omit_exc_object=True,
|
66
|
+
)
|
67
|
+
|
68
|
+
chan.send_obj(r)
|
69
|
+
|
70
|
+
|
71
|
+
##
|
72
|
+
|
73
|
+
|
74
|
+
@dc.dataclass()
|
75
|
+
class RemoteCommandError(Exception):
|
76
|
+
e: CommandException
|
77
|
+
|
78
|
+
|
79
|
+
class RemoteCommandExecutor(CommandExecutor):
|
80
|
+
def __init__(self, chan: RemoteChannel) -> None:
|
81
|
+
super().__init__()
|
82
|
+
|
83
|
+
self._chan = chan
|
84
|
+
|
85
|
+
def _remote_execute(self, cmd: Command) -> CommandOutputOrException:
|
86
|
+
self._chan.send_obj(cmd, Command)
|
87
|
+
|
88
|
+
if (r := self._chan.recv_obj(CommandOutputOrExceptionData)) is None:
|
89
|
+
raise EOFError
|
90
|
+
|
91
|
+
return r
|
92
|
+
|
93
|
+
# @ta.override
|
94
|
+
def execute(self, cmd: Command) -> Command.Output:
|
95
|
+
r = self._remote_execute(cmd)
|
96
|
+
if (e := r.exception) is not None:
|
97
|
+
raise RemoteCommandError(e)
|
98
|
+
else:
|
99
|
+
return check_not_none(r.output)
|
100
|
+
|
101
|
+
# @ta.override
|
102
|
+
def try_execute(
|
103
|
+
self,
|
104
|
+
cmd: Command,
|
105
|
+
*,
|
106
|
+
log: ta.Optional[logging.Logger] = None,
|
107
|
+
omit_exc_object: bool = False,
|
108
|
+
) -> CommandOutputOrException:
|
109
|
+
try:
|
110
|
+
r = self._remote_execute(cmd)
|
111
|
+
|
112
|
+
except Exception as e: # noqa
|
113
|
+
if log is not None:
|
114
|
+
log.exception('Exception executing remote command: %r', type(cmd))
|
115
|
+
|
116
|
+
return CommandOutputOrExceptionData(exception=CommandException.of(
|
117
|
+
e,
|
118
|
+
omit_exc_object=omit_exc_object,
|
119
|
+
cmd=cmd,
|
120
|
+
))
|
121
|
+
|
122
|
+
else:
|
123
|
+
return r
|
124
|
+
|
125
|
+
|
126
|
+
##
|
127
|
+
|
128
|
+
|
129
|
+
class RemoteExecution:
|
130
|
+
def __init__(
|
131
|
+
self,
|
132
|
+
*,
|
133
|
+
spawning: RemoteSpawning,
|
134
|
+
msh: ObjMarshalerManager,
|
135
|
+
payload_file: ta.Optional[RemoteExecutionPayloadFile] = None,
|
136
|
+
) -> None:
|
137
|
+
super().__init__()
|
138
|
+
|
139
|
+
self._spawning = spawning
|
140
|
+
self._msh = msh
|
141
|
+
self._payload_file = payload_file
|
142
|
+
|
143
|
+
#
|
144
|
+
|
145
|
+
@cached_nullary
|
146
|
+
def _payload_src(self) -> str:
|
147
|
+
return get_remote_payload_src(file=self._payload_file)
|
148
|
+
|
149
|
+
@cached_nullary
|
150
|
+
def _remote_src(self) -> ta.Sequence[str]:
|
151
|
+
return [
|
152
|
+
self._payload_src(),
|
153
|
+
'_remote_execution_main()',
|
154
|
+
]
|
155
|
+
|
156
|
+
@cached_nullary
|
157
|
+
def _spawn_src(self) -> str:
|
158
|
+
return pyremote_build_bootstrap_cmd(__package__ or 'manage')
|
159
|
+
|
160
|
+
#
|
161
|
+
|
162
|
+
@contextlib.contextmanager
|
163
|
+
def connect(
|
164
|
+
self,
|
165
|
+
tgt: RemoteSpawning.Target,
|
166
|
+
bs: MainBootstrap,
|
167
|
+
) -> ta.Generator[RemoteCommandExecutor, None, None]:
|
168
|
+
spawn_src = self._spawn_src()
|
169
|
+
remote_src = self._remote_src()
|
170
|
+
|
171
|
+
with self._spawning.spawn(
|
172
|
+
tgt,
|
173
|
+
spawn_src,
|
174
|
+
) as proc:
|
175
|
+
res = PyremoteBootstrapDriver( # noqa
|
176
|
+
remote_src,
|
177
|
+
PyremoteBootstrapOptions(
|
178
|
+
debug=bs.main_config.debug,
|
179
|
+
),
|
180
|
+
).run(
|
181
|
+
proc.stdout,
|
182
|
+
proc.stdin,
|
183
|
+
)
|
184
|
+
|
185
|
+
chan = RemoteChannel(
|
186
|
+
proc.stdout,
|
187
|
+
proc.stdin,
|
188
|
+
msh=self._msh,
|
189
|
+
)
|
190
|
+
|
191
|
+
chan.send_obj(bs)
|
192
|
+
|
193
|
+
yield RemoteCommandExecutor(chan)
|
@@ -0,0 +1,29 @@
|
|
1
|
+
# ruff: noqa: UP006 UP007
|
2
|
+
import typing as ta
|
3
|
+
|
4
|
+
from omlish.lite.inject import InjectorBindingOrBindings
|
5
|
+
from omlish.lite.inject import InjectorBindings
|
6
|
+
from omlish.lite.inject import inj
|
7
|
+
|
8
|
+
from .config import RemoteConfig
|
9
|
+
from .execution import RemoteExecution
|
10
|
+
from .payload import RemoteExecutionPayloadFile
|
11
|
+
from .spawning import RemoteSpawning
|
12
|
+
|
13
|
+
|
14
|
+
def bind_remote(
|
15
|
+
*,
|
16
|
+
remote_config: RemoteConfig,
|
17
|
+
) -> InjectorBindings:
|
18
|
+
lst: ta.List[InjectorBindingOrBindings] = [
|
19
|
+
inj.bind(remote_config),
|
20
|
+
|
21
|
+
inj.bind(RemoteSpawning, singleton=True),
|
22
|
+
|
23
|
+
inj.bind(RemoteExecution, singleton=True),
|
24
|
+
]
|
25
|
+
|
26
|
+
if (pf := remote_config.payload_file) is not None:
|
27
|
+
lst.append(inj.bind(pf, to_key=RemoteExecutionPayloadFile))
|
28
|
+
|
29
|
+
return inj.as_bindings(*lst)
|
@@ -6,6 +6,9 @@ import typing as ta
|
|
6
6
|
from omlish.lite.cached import cached_nullary
|
7
7
|
|
8
8
|
|
9
|
+
RemoteExecutionPayloadFile = ta.NewType('RemoteExecutionPayloadFile', str)
|
10
|
+
|
11
|
+
|
9
12
|
@cached_nullary
|
10
13
|
def _get_self_src() -> str:
|
11
14
|
return inspect.getsource(sys.modules[__name__])
|
@@ -23,7 +26,10 @@ def _is_self_amalg() -> bool:
|
|
23
26
|
return _is_src_amalg(_get_self_src())
|
24
27
|
|
25
28
|
|
26
|
-
def
|
29
|
+
def get_remote_payload_src(
|
30
|
+
*,
|
31
|
+
file: ta.Optional[RemoteExecutionPayloadFile],
|
32
|
+
) -> str:
|
27
33
|
if file is not None:
|
28
34
|
with open(file) as f:
|
29
35
|
return f.read()
|
@@ -11,25 +11,16 @@ from omlish.lite.subprocesses import SubprocessChannelOption
|
|
11
11
|
from omlish.lite.subprocesses import subprocess_maybe_shell_wrap_exec
|
12
12
|
|
13
13
|
|
14
|
-
class
|
15
|
-
|
14
|
+
class RemoteSpawning:
|
15
|
+
@dc.dataclass(frozen=True)
|
16
|
+
class Target:
|
17
|
+
shell: ta.Optional[str] = None
|
18
|
+
shell_quote: bool = False
|
16
19
|
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
shell: ta.Optional[str] = None,
|
22
|
-
shell_quote: bool = False,
|
23
|
-
python: str = DEFAULT_PYTHON,
|
24
|
-
stderr: ta.Optional[SubprocessChannelOption] = None,
|
25
|
-
) -> None:
|
26
|
-
super().__init__()
|
27
|
-
|
28
|
-
self._src = src
|
29
|
-
self._shell = shell
|
30
|
-
self._shell_quote = shell_quote
|
31
|
-
self._python = python
|
32
|
-
self._stderr = stderr
|
20
|
+
DEFAULT_PYTHON: ta.ClassVar[str] = 'python3'
|
21
|
+
python: str = DEFAULT_PYTHON
|
22
|
+
|
23
|
+
stderr: ta.Optional[str] = None # SubprocessChannelOption
|
33
24
|
|
34
25
|
#
|
35
26
|
|
@@ -37,20 +28,24 @@ class PySpawner:
|
|
37
28
|
cmd: ta.Sequence[str]
|
38
29
|
shell: bool
|
39
30
|
|
40
|
-
def _prepare_cmd(
|
41
|
-
|
42
|
-
|
43
|
-
|
31
|
+
def _prepare_cmd(
|
32
|
+
self,
|
33
|
+
tgt: Target,
|
34
|
+
src: str,
|
35
|
+
) -> _PreparedCmd:
|
36
|
+
if tgt.shell is not None:
|
37
|
+
sh_src = f'{tgt.python} -c {shlex.quote(src)}'
|
38
|
+
if tgt.shell_quote:
|
44
39
|
sh_src = shlex.quote(sh_src)
|
45
|
-
sh_cmd = f'{
|
46
|
-
return
|
40
|
+
sh_cmd = f'{tgt.shell} {sh_src}'
|
41
|
+
return RemoteSpawning._PreparedCmd(
|
47
42
|
cmd=[sh_cmd],
|
48
43
|
shell=True,
|
49
44
|
)
|
50
45
|
|
51
46
|
else:
|
52
|
-
return
|
53
|
-
cmd=[
|
47
|
+
return RemoteSpawning._PreparedCmd(
|
48
|
+
cmd=[tgt.python, '-c', src],
|
54
49
|
shell=False,
|
55
50
|
)
|
56
51
|
|
@@ -65,23 +60,28 @@ class PySpawner:
|
|
65
60
|
@contextlib.contextmanager
|
66
61
|
def spawn(
|
67
62
|
self,
|
63
|
+
tgt: Target,
|
64
|
+
src: str,
|
68
65
|
*,
|
69
66
|
timeout: ta.Optional[float] = None,
|
70
67
|
) -> ta.Generator[Spawned, None, None]:
|
71
|
-
pc = self._prepare_cmd()
|
68
|
+
pc = self._prepare_cmd(tgt, src)
|
72
69
|
|
73
70
|
with subprocess.Popen(
|
74
71
|
subprocess_maybe_shell_wrap_exec(*pc.cmd),
|
75
72
|
shell=pc.shell,
|
76
73
|
stdin=subprocess.PIPE,
|
77
74
|
stdout=subprocess.PIPE,
|
78
|
-
stderr=
|
75
|
+
stderr=(
|
76
|
+
SUBPROCESS_CHANNEL_OPTION_VALUES[ta.cast(SubprocessChannelOption, tgt.stderr)]
|
77
|
+
if tgt.stderr is not None else None
|
78
|
+
),
|
79
79
|
) as proc:
|
80
80
|
stdin = check_not_none(proc.stdin)
|
81
81
|
stdout = check_not_none(proc.stdout)
|
82
82
|
|
83
83
|
try:
|
84
|
-
yield
|
84
|
+
yield RemoteSpawning.Spawned(
|
85
85
|
stdin=stdin,
|
86
86
|
stdout=stdout,
|
87
87
|
stderr=proc.stderr,
|
ominfra/pyremote.py
CHANGED
@@ -2,6 +2,9 @@
|
|
2
2
|
# @omlish-lite
|
3
3
|
"""
|
4
4
|
Basically this: https://mitogen.networkgenomics.com/howitworks.html
|
5
|
+
|
6
|
+
TODO:
|
7
|
+
- log: ta.Optional[logging.Logger] = None + log.debug's
|
5
8
|
"""
|
6
9
|
import base64
|
7
10
|
import dataclasses as dc
|
@@ -23,6 +26,9 @@ import zlib
|
|
23
26
|
class PyremoteBootstrapOptions:
|
24
27
|
debug: bool = False
|
25
28
|
|
29
|
+
DEFAULT_MAIN_NAME_OVERRIDE: ta.ClassVar[str] = '__pyremote__'
|
30
|
+
main_name_override: ta.Optional[str] = DEFAULT_MAIN_NAME_OVERRIDE
|
31
|
+
|
26
32
|
|
27
33
|
##
|
28
34
|
|
@@ -339,6 +345,10 @@ def pyremote_bootstrap_finalize() -> PyremotePayloadRuntime:
|
|
339
345
|
os.dup2(nfd := os.open('/dev/null', os.O_WRONLY), 1)
|
340
346
|
os.close(nfd)
|
341
347
|
|
348
|
+
if (mn := options.main_name_override) is not None:
|
349
|
+
# Inspections like typing.get_type_hints need an entry in sys.modules.
|
350
|
+
sys.modules[mn] = sys.modules['__main__']
|
351
|
+
|
342
352
|
# Write fourth ack
|
343
353
|
output.write(_PYREMOTE_BOOTSTRAP_ACK3)
|
344
354
|
|
@@ -357,14 +367,41 @@ def pyremote_bootstrap_finalize() -> PyremotePayloadRuntime:
|
|
357
367
|
|
358
368
|
|
359
369
|
class PyremoteBootstrapDriver:
|
360
|
-
def __init__(
|
370
|
+
def __init__(
|
371
|
+
self,
|
372
|
+
main_src: ta.Union[str, ta.Sequence[str]],
|
373
|
+
options: PyremoteBootstrapOptions = PyremoteBootstrapOptions(),
|
374
|
+
) -> None:
|
361
375
|
super().__init__()
|
362
376
|
|
363
377
|
self._main_src = main_src
|
364
|
-
self._main_z = zlib.compress(main_src.encode('utf-8'))
|
365
|
-
|
366
378
|
self._options = options
|
379
|
+
|
380
|
+
self._prepared_main_src = self._prepare_main_src(main_src, options)
|
381
|
+
self._main_z = zlib.compress(self._prepared_main_src.encode('utf-8'))
|
382
|
+
|
367
383
|
self._options_json = json.dumps(dc.asdict(options), indent=None, separators=(',', ':')).encode('utf-8') # noqa
|
384
|
+
#
|
385
|
+
|
386
|
+
@classmethod
|
387
|
+
def _prepare_main_src(
|
388
|
+
cls,
|
389
|
+
main_src: ta.Union[str, ta.Sequence[str]],
|
390
|
+
options: PyremoteBootstrapOptions,
|
391
|
+
) -> str:
|
392
|
+
parts: ta.List[str]
|
393
|
+
if isinstance(main_src, str):
|
394
|
+
parts = [main_src]
|
395
|
+
else:
|
396
|
+
parts = list(main_src)
|
397
|
+
|
398
|
+
if (mn := options.main_name_override) is not None:
|
399
|
+
parts.insert(0, f'__name__ = {mn!r}')
|
400
|
+
|
401
|
+
if len(parts) == 1:
|
402
|
+
return parts[0]
|
403
|
+
else:
|
404
|
+
return '\n\n'.join(parts)
|
368
405
|
|
369
406
|
#
|
370
407
|
|