ominfra 0.0.0.dev142__py3-none-any.whl → 0.0.0.dev143__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.
- 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
|
|