ominfra 0.0.0.dev147__py3-none-any.whl → 0.0.0.dev148__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/configs.py +30 -5
- ominfra/manage/commands/base.py +5 -5
- ominfra/manage/commands/execution.py +2 -2
- ominfra/manage/commands/inject.py +1 -1
- ominfra/manage/commands/interp.py +2 -2
- ominfra/manage/commands/subprocess.py +22 -14
- ominfra/manage/deploy/command.py +1 -1
- ominfra/manage/main.py +48 -32
- ominfra/manage/remote/_main.py +172 -0
- ominfra/manage/remote/channel.py +41 -16
- ominfra/manage/remote/config.py +10 -0
- ominfra/manage/remote/connection.py +106 -0
- ominfra/manage/remote/execution.py +244 -155
- ominfra/manage/remote/inject.py +7 -3
- ominfra/manage/remote/spawning.py +51 -33
- ominfra/pyremote.py +28 -3
- ominfra/scripts/journald2aws.py +127 -28
- ominfra/scripts/manage.py +1360 -485
- ominfra/scripts/supervisor.py +59 -10
- {ominfra-0.0.0.dev147.dist-info → ominfra-0.0.0.dev148.dist-info}/METADATA +3 -3
- {ominfra-0.0.0.dev147.dist-info → ominfra-0.0.0.dev148.dist-info}/RECORD +25 -23
- {ominfra-0.0.0.dev147.dist-info → ominfra-0.0.0.dev148.dist-info}/LICENSE +0 -0
- {ominfra-0.0.0.dev147.dist-info → ominfra-0.0.0.dev148.dist-info}/WHEEL +0 -0
- {ominfra-0.0.0.dev147.dist-info → ominfra-0.0.0.dev148.dist-info}/entry_points.txt +0 -0
- {ominfra-0.0.0.dev147.dist-info → ominfra-0.0.0.dev148.dist-info}/top_level.txt +0 -0
ominfra/configs.py
CHANGED
@@ -1,6 +1,7 @@
|
|
1
1
|
# ruff: noqa: UP006 UP007
|
2
2
|
# @omlish-lite
|
3
3
|
import json
|
4
|
+
import os.path
|
4
5
|
import typing as ta
|
5
6
|
|
6
7
|
from omdev.toml.parser import toml_loads
|
@@ -14,6 +15,33 @@ T = ta.TypeVar('T')
|
|
14
15
|
ConfigMapping = ta.Mapping[str, ta.Any]
|
15
16
|
|
16
17
|
|
18
|
+
def parse_config_file(
|
19
|
+
name: str,
|
20
|
+
f: ta.TextIO,
|
21
|
+
) -> ConfigMapping:
|
22
|
+
if name.endswith('.toml'):
|
23
|
+
return toml_loads(f.read())
|
24
|
+
|
25
|
+
elif any(name.endswith(e) for e in ('.yml', '.yaml')):
|
26
|
+
yaml = __import__('yaml')
|
27
|
+
return yaml.safe_load(f)
|
28
|
+
|
29
|
+
elif name.endswith('.ini'):
|
30
|
+
import configparser
|
31
|
+
cp = configparser.ConfigParser()
|
32
|
+
cp.read_file(f)
|
33
|
+
config_dct: ta.Dict[str, ta.Any] = {}
|
34
|
+
for sec in cp.sections():
|
35
|
+
cd = config_dct
|
36
|
+
for k in sec.split('.'):
|
37
|
+
cd = cd.setdefault(k, {})
|
38
|
+
cd.update(cp.items(sec))
|
39
|
+
return config_dct
|
40
|
+
|
41
|
+
else:
|
42
|
+
return json.loads(f.read())
|
43
|
+
|
44
|
+
|
17
45
|
def read_config_file(
|
18
46
|
path: str,
|
19
47
|
cls: ta.Type[T],
|
@@ -21,13 +49,10 @@ def read_config_file(
|
|
21
49
|
prepare: ta.Optional[ta.Callable[[ConfigMapping], ConfigMapping]] = None,
|
22
50
|
) -> T:
|
23
51
|
with open(path) as cf:
|
24
|
-
|
25
|
-
config_dct = toml_loads(cf.read())
|
26
|
-
else:
|
27
|
-
config_dct = json.loads(cf.read())
|
52
|
+
config_dct = parse_config_file(os.path.basename(path), cf)
|
28
53
|
|
29
54
|
if prepare is not None:
|
30
|
-
config_dct = prepare(config_dct)
|
55
|
+
config_dct = prepare(config_dct)
|
31
56
|
|
32
57
|
return unmarshal_obj(config_dct, cls)
|
33
58
|
|
ominfra/manage/commands/base.py
CHANGED
@@ -23,8 +23,8 @@ class Command(abc.ABC, ta.Generic[CommandOutputT]):
|
|
23
23
|
pass
|
24
24
|
|
25
25
|
@ta.final
|
26
|
-
def execute(self, executor: 'CommandExecutor') -> CommandOutputT:
|
27
|
-
return check_isinstance(executor.execute(self), self.Output) # type: ignore[return-value]
|
26
|
+
async def execute(self, executor: 'CommandExecutor') -> CommandOutputT:
|
27
|
+
return check_isinstance(await executor.execute(self), self.Output) # type: ignore[return-value]
|
28
28
|
|
29
29
|
|
30
30
|
##
|
@@ -85,10 +85,10 @@ class CommandOutputOrExceptionData(CommandOutputOrException):
|
|
85
85
|
|
86
86
|
class CommandExecutor(abc.ABC, ta.Generic[CommandT, CommandOutputT]):
|
87
87
|
@abc.abstractmethod
|
88
|
-
def execute(self, cmd: CommandT) -> CommandOutputT:
|
88
|
+
def execute(self, cmd: CommandT) -> ta.Awaitable[CommandOutputT]:
|
89
89
|
raise NotImplementedError
|
90
90
|
|
91
|
-
def try_execute(
|
91
|
+
async def try_execute(
|
92
92
|
self,
|
93
93
|
cmd: CommandT,
|
94
94
|
*,
|
@@ -96,7 +96,7 @@ class CommandExecutor(abc.ABC, ta.Generic[CommandT, CommandOutputT]):
|
|
96
96
|
omit_exc_object: bool = False,
|
97
97
|
) -> CommandOutputOrException[CommandOutputT]:
|
98
98
|
try:
|
99
|
-
o = self.execute(cmd)
|
99
|
+
o = await self.execute(cmd)
|
100
100
|
|
101
101
|
except Exception as e: # noqa
|
102
102
|
if log is not None:
|
@@ -18,6 +18,6 @@ class LocalCommandExecutor(CommandExecutor):
|
|
18
18
|
|
19
19
|
self._command_executors = command_executors
|
20
20
|
|
21
|
-
def execute(self, cmd: Command) -> Command.Output:
|
21
|
+
async def execute(self, cmd: Command) -> Command.Output:
|
22
22
|
ce: CommandExecutor = self._command_executors[type(cmd)]
|
23
|
-
return ce.execute(cmd)
|
23
|
+
return await ce.execute(cmd)
|
@@ -54,7 +54,7 @@ def bind_command(
|
|
54
54
|
class _FactoryCommandExecutor(CommandExecutor):
|
55
55
|
factory: ta.Callable[[], CommandExecutor]
|
56
56
|
|
57
|
-
def execute(self, i: Command) -> Command.Output:
|
57
|
+
def execute(self, i: Command) -> ta.Awaitable[Command.Output]:
|
58
58
|
return self.factory().execute(i)
|
59
59
|
|
60
60
|
|
@@ -29,9 +29,9 @@ class InterpCommand(Command['InterpCommand.Output']):
|
|
29
29
|
|
30
30
|
|
31
31
|
class InterpCommandExecutor(CommandExecutor[InterpCommand, InterpCommand.Output]):
|
32
|
-
def execute(self, cmd: InterpCommand) -> InterpCommand.Output:
|
32
|
+
async def execute(self, cmd: InterpCommand) -> InterpCommand.Output:
|
33
33
|
i = InterpSpecifier.parse(check_not_none(cmd.spec))
|
34
|
-
o = check_not_none(DEFAULT_INTERP_RESOLVER.resolve(i, install=cmd.install))
|
34
|
+
o = check_not_none(await DEFAULT_INTERP_RESOLVER.resolve(i, install=cmd.install))
|
35
35
|
return InterpCommand.Output(
|
36
36
|
exe=o.exe,
|
37
37
|
version=str(o.version.version),
|
@@ -1,11 +1,15 @@
|
|
1
|
-
# ruff: noqa: UP006 UP007
|
1
|
+
# ruff: noqa: TC003 UP006 UP007
|
2
|
+
import asyncio.subprocess
|
2
3
|
import dataclasses as dc
|
3
4
|
import os
|
4
5
|
import subprocess
|
5
6
|
import time
|
6
7
|
import typing as ta
|
7
8
|
|
9
|
+
from omlish.lite.asyncio.subprocesses import asyncio_subprocess_communicate
|
10
|
+
from omlish.lite.asyncio.subprocesses import asyncio_subprocess_popen
|
8
11
|
from omlish.lite.check import check_not_isinstance
|
12
|
+
from omlish.lite.check import check_not_none
|
9
13
|
from omlish.lite.subprocesses import SUBPROCESS_CHANNEL_OPTION_VALUES
|
10
14
|
from omlish.lite.subprocesses import SubprocessChannelOption
|
11
15
|
from omlish.lite.subprocesses import subprocess_maybe_shell_wrap_exec
|
@@ -49,27 +53,31 @@ class SubprocessCommand(Command['SubprocessCommand.Output']):
|
|
49
53
|
|
50
54
|
|
51
55
|
class SubprocessCommandExecutor(CommandExecutor[SubprocessCommand, SubprocessCommand.Output]):
|
52
|
-
def execute(self,
|
53
|
-
|
54
|
-
|
56
|
+
async def execute(self, cmd: SubprocessCommand) -> SubprocessCommand.Output:
|
57
|
+
proc: asyncio.subprocess.Process
|
58
|
+
async with asyncio_subprocess_popen(
|
59
|
+
*subprocess_maybe_shell_wrap_exec(*cmd.cmd),
|
55
60
|
|
56
|
-
shell=
|
57
|
-
cwd=
|
58
|
-
env={**os.environ, **(
|
61
|
+
shell=cmd.shell,
|
62
|
+
cwd=cmd.cwd,
|
63
|
+
env={**os.environ, **(cmd.env or {})},
|
59
64
|
|
60
|
-
stdin=subprocess.PIPE if
|
61
|
-
stdout=SUBPROCESS_CHANNEL_OPTION_VALUES[ta.cast(SubprocessChannelOption,
|
62
|
-
stderr=SUBPROCESS_CHANNEL_OPTION_VALUES[ta.cast(SubprocessChannelOption,
|
65
|
+
stdin=subprocess.PIPE if cmd.input is not None else None,
|
66
|
+
stdout=SUBPROCESS_CHANNEL_OPTION_VALUES[ta.cast(SubprocessChannelOption, cmd.stdout)],
|
67
|
+
stderr=SUBPROCESS_CHANNEL_OPTION_VALUES[ta.cast(SubprocessChannelOption, cmd.stderr)],
|
68
|
+
|
69
|
+
timeout=cmd.timeout,
|
63
70
|
) as proc:
|
64
71
|
start_time = time.time()
|
65
|
-
stdout, stderr =
|
66
|
-
|
67
|
-
|
72
|
+
stdout, stderr = await asyncio_subprocess_communicate(
|
73
|
+
proc,
|
74
|
+
input=cmd.input,
|
75
|
+
timeout=cmd.timeout,
|
68
76
|
)
|
69
77
|
end_time = time.time()
|
70
78
|
|
71
79
|
return SubprocessCommand.Output(
|
72
|
-
rc=proc.returncode,
|
80
|
+
rc=check_not_none(proc.returncode),
|
73
81
|
pid=proc.pid,
|
74
82
|
|
75
83
|
elapsed_s=end_time - start_time,
|
ominfra/manage/deploy/command.py
CHANGED
@@ -21,7 +21,7 @@ class DeployCommand(Command['DeployCommand.Output']):
|
|
21
21
|
|
22
22
|
|
23
23
|
class DeployCommandExecutor(CommandExecutor[DeployCommand, DeployCommand.Output]):
|
24
|
-
def execute(self, cmd: DeployCommand) -> DeployCommand.Output:
|
24
|
+
async def execute(self, cmd: DeployCommand) -> DeployCommand.Output:
|
25
25
|
log.info('Deploying!')
|
26
26
|
|
27
27
|
return DeployCommand.Output()
|
ominfra/manage/main.py
CHANGED
@@ -5,6 +5,7 @@
|
|
5
5
|
manage.py -s 'docker run -i python:3.12'
|
6
6
|
manage.py -s 'ssh -i /foo/bar.pem foo@bar.baz' -q --python=python3.8
|
7
7
|
"""
|
8
|
+
import asyncio
|
8
9
|
import contextlib
|
9
10
|
import json
|
10
11
|
import typing as ta
|
@@ -21,38 +22,14 @@ from .commands.base import CommandExecutor
|
|
21
22
|
from .commands.execution import LocalCommandExecutor
|
22
23
|
from .config import MainConfig
|
23
24
|
from .remote.config import RemoteConfig
|
24
|
-
from .remote.
|
25
|
+
from .remote.connection import RemoteExecutionConnector
|
25
26
|
from .remote.spawning import RemoteSpawning
|
26
27
|
|
27
28
|
|
28
29
|
##
|
29
30
|
|
30
31
|
|
31
|
-
def
|
32
|
-
import argparse
|
33
|
-
|
34
|
-
parser = argparse.ArgumentParser()
|
35
|
-
|
36
|
-
parser.add_argument('--_payload-file')
|
37
|
-
|
38
|
-
parser.add_argument('-s', '--shell')
|
39
|
-
parser.add_argument('-q', '--shell-quote', action='store_true')
|
40
|
-
parser.add_argument('--python', default='python3')
|
41
|
-
|
42
|
-
parser.add_argument('--pycharm-debug-port', type=int)
|
43
|
-
parser.add_argument('--pycharm-debug-host')
|
44
|
-
parser.add_argument('--pycharm-debug-version')
|
45
|
-
|
46
|
-
parser.add_argument('--debug', action='store_true')
|
47
|
-
|
48
|
-
parser.add_argument('--local', action='store_true')
|
49
|
-
|
50
|
-
parser.add_argument('command', nargs='+')
|
51
|
-
|
52
|
-
args = parser.parse_args()
|
53
|
-
|
54
|
-
#
|
55
|
-
|
32
|
+
async def _async_main(args: ta.Any) -> None:
|
56
33
|
bs = MainBootstrap(
|
57
34
|
main_config=MainConfig(
|
58
35
|
log_level='DEBUG' if args.debug else 'INFO',
|
@@ -65,12 +42,16 @@ def _main() -> None:
|
|
65
42
|
|
66
43
|
pycharm_remote_debug=PycharmRemoteDebug(
|
67
44
|
port=args.pycharm_debug_port,
|
68
|
-
host=args.pycharm_debug_host,
|
45
|
+
**(dict(host=args.pycharm_debug_host) if args.pycharm_debug_host is not None else {}),
|
69
46
|
install_version=args.pycharm_debug_version,
|
70
47
|
) if args.pycharm_debug_port is not None else None,
|
48
|
+
|
49
|
+
timebomb_delay_s=args.remote_timebomb_delay_s,
|
71
50
|
),
|
72
51
|
)
|
73
52
|
|
53
|
+
#
|
54
|
+
|
74
55
|
injector = main_bootstrap(
|
75
56
|
bs,
|
76
57
|
)
|
@@ -89,7 +70,7 @@ def _main() -> None:
|
|
89
70
|
|
90
71
|
#
|
91
72
|
|
92
|
-
with contextlib.
|
73
|
+
async with contextlib.AsyncExitStack() as es:
|
93
74
|
ce: CommandExecutor
|
94
75
|
|
95
76
|
if args.local:
|
@@ -102,16 +83,51 @@ def _main() -> None:
|
|
102
83
|
python=args.python,
|
103
84
|
)
|
104
85
|
|
105
|
-
ce = es.
|
86
|
+
ce = await es.enter_async_context(injector[RemoteExecutionConnector].connect(tgt, bs)) # noqa
|
106
87
|
|
107
|
-
|
108
|
-
|
88
|
+
async def run_command(cmd: Command) -> None:
|
89
|
+
res = await ce.try_execute(
|
109
90
|
cmd,
|
110
91
|
log=log,
|
111
92
|
omit_exc_object=True,
|
112
93
|
)
|
113
94
|
|
114
|
-
print(msh.marshal_obj(
|
95
|
+
print(msh.marshal_obj(res, opts=ObjMarshalOptions(raw_bytes=True)))
|
96
|
+
|
97
|
+
await asyncio.gather(*[
|
98
|
+
run_command(cmd)
|
99
|
+
for cmd in cmds
|
100
|
+
])
|
101
|
+
|
102
|
+
|
103
|
+
def _main() -> None:
|
104
|
+
import argparse
|
105
|
+
|
106
|
+
parser = argparse.ArgumentParser()
|
107
|
+
|
108
|
+
parser.add_argument('--_payload-file')
|
109
|
+
|
110
|
+
parser.add_argument('-s', '--shell')
|
111
|
+
parser.add_argument('-q', '--shell-quote', action='store_true')
|
112
|
+
parser.add_argument('--python', default='python3')
|
113
|
+
|
114
|
+
parser.add_argument('--pycharm-debug-port', type=int)
|
115
|
+
parser.add_argument('--pycharm-debug-host')
|
116
|
+
parser.add_argument('--pycharm-debug-version')
|
117
|
+
|
118
|
+
parser.add_argument('--remote-timebomb-delay-s', type=float)
|
119
|
+
|
120
|
+
parser.add_argument('--debug', action='store_true')
|
121
|
+
|
122
|
+
parser.add_argument('--local', action='store_true')
|
123
|
+
|
124
|
+
parser.add_argument('command', nargs='+')
|
125
|
+
|
126
|
+
args = parser.parse_args()
|
127
|
+
|
128
|
+
#
|
129
|
+
|
130
|
+
asyncio.run(_async_main(args))
|
115
131
|
|
116
132
|
|
117
133
|
if __name__ == '__main__':
|
@@ -0,0 +1,172 @@
|
|
1
|
+
# ruff: noqa: UP006 UP007
|
2
|
+
import asyncio
|
3
|
+
import functools
|
4
|
+
import logging
|
5
|
+
import os
|
6
|
+
import signal
|
7
|
+
import threading
|
8
|
+
import time
|
9
|
+
import typing as ta
|
10
|
+
|
11
|
+
from omlish.lite.asyncio.asyncio import asyncio_open_stream_reader
|
12
|
+
from omlish.lite.asyncio.asyncio import asyncio_open_stream_writer
|
13
|
+
from omlish.lite.cached import cached_nullary
|
14
|
+
from omlish.lite.check import check_none
|
15
|
+
from omlish.lite.check import check_not_none
|
16
|
+
from omlish.lite.deathsig import set_process_deathsig
|
17
|
+
from omlish.lite.inject import Injector
|
18
|
+
from omlish.lite.logs import log
|
19
|
+
from omlish.lite.marshal import ObjMarshalerManager
|
20
|
+
from omlish.lite.pycharm import pycharm_debug_connect
|
21
|
+
|
22
|
+
from ...pyremote import pyremote_bootstrap_finalize
|
23
|
+
from ..bootstrap import MainBootstrap
|
24
|
+
from ..commands.execution import LocalCommandExecutor
|
25
|
+
from .channel import RemoteChannel
|
26
|
+
from .channel import RemoteChannelImpl
|
27
|
+
from .execution import _RemoteCommandHandler
|
28
|
+
from .execution import _RemoteLogHandler
|
29
|
+
|
30
|
+
|
31
|
+
if ta.TYPE_CHECKING:
|
32
|
+
from ..bootstrap_ import main_bootstrap
|
33
|
+
else:
|
34
|
+
main_bootstrap: ta.Any = None
|
35
|
+
|
36
|
+
|
37
|
+
##
|
38
|
+
|
39
|
+
|
40
|
+
class _RemoteExecutionLogHandler(logging.Handler):
|
41
|
+
def __init__(self, fn: ta.Callable[[str], None]) -> None:
|
42
|
+
super().__init__()
|
43
|
+
self._fn = fn
|
44
|
+
|
45
|
+
def emit(self, record):
|
46
|
+
msg = self.format(record)
|
47
|
+
self._fn(msg)
|
48
|
+
|
49
|
+
|
50
|
+
##
|
51
|
+
|
52
|
+
|
53
|
+
class _RemoteExecutionMain:
|
54
|
+
def __init__(
|
55
|
+
self,
|
56
|
+
chan: RemoteChannel,
|
57
|
+
) -> None:
|
58
|
+
super().__init__()
|
59
|
+
|
60
|
+
self._chan = chan
|
61
|
+
|
62
|
+
self.__bootstrap: ta.Optional[MainBootstrap] = None
|
63
|
+
self.__injector: ta.Optional[Injector] = None
|
64
|
+
|
65
|
+
@property
|
66
|
+
def _bootstrap(self) -> MainBootstrap:
|
67
|
+
return check_not_none(self.__bootstrap)
|
68
|
+
|
69
|
+
@property
|
70
|
+
def _injector(self) -> Injector:
|
71
|
+
return check_not_none(self.__injector)
|
72
|
+
|
73
|
+
#
|
74
|
+
|
75
|
+
def _timebomb_main(
|
76
|
+
self,
|
77
|
+
delay_s: float,
|
78
|
+
*,
|
79
|
+
sig: int = signal.SIGINT,
|
80
|
+
code: int = 1,
|
81
|
+
) -> None:
|
82
|
+
time.sleep(delay_s)
|
83
|
+
|
84
|
+
if (pgid := os.getpgid(0)) == os.getpid():
|
85
|
+
os.killpg(pgid, sig)
|
86
|
+
|
87
|
+
os._exit(code) # noqa
|
88
|
+
|
89
|
+
@cached_nullary
|
90
|
+
def _timebomb_thread(self) -> ta.Optional[threading.Thread]:
|
91
|
+
if (tbd := self._bootstrap.remote_config.timebomb_delay_s) is None:
|
92
|
+
return None
|
93
|
+
|
94
|
+
thr = threading.Thread(
|
95
|
+
target=functools.partial(self._timebomb_main, tbd),
|
96
|
+
name=f'{self.__class__.__name__}.timebomb',
|
97
|
+
daemon=True,
|
98
|
+
)
|
99
|
+
|
100
|
+
thr.start()
|
101
|
+
|
102
|
+
log.debug('Started timebomb thread: %r', thr)
|
103
|
+
|
104
|
+
return thr
|
105
|
+
|
106
|
+
#
|
107
|
+
|
108
|
+
@cached_nullary
|
109
|
+
def _log_handler(self) -> _RemoteLogHandler:
|
110
|
+
return _RemoteLogHandler(self._chan)
|
111
|
+
|
112
|
+
#
|
113
|
+
|
114
|
+
async def _setup(self) -> None:
|
115
|
+
check_none(self.__bootstrap)
|
116
|
+
check_none(self.__injector)
|
117
|
+
|
118
|
+
# Bootstrap
|
119
|
+
|
120
|
+
self.__bootstrap = check_not_none(await self._chan.recv_obj(MainBootstrap))
|
121
|
+
|
122
|
+
if (prd := self._bootstrap.remote_config.pycharm_remote_debug) is not None:
|
123
|
+
pycharm_debug_connect(prd)
|
124
|
+
|
125
|
+
self.__injector = main_bootstrap(self._bootstrap)
|
126
|
+
|
127
|
+
self._chan.set_marshaler(self._injector[ObjMarshalerManager])
|
128
|
+
|
129
|
+
# Post-bootstrap
|
130
|
+
|
131
|
+
if self._bootstrap.remote_config.set_pgid:
|
132
|
+
if os.getpgid(0) != os.getpid():
|
133
|
+
log.debug('Setting pgid')
|
134
|
+
os.setpgid(0, 0)
|
135
|
+
|
136
|
+
if (ds := self._bootstrap.remote_config.deathsig) is not None:
|
137
|
+
log.debug('Setting deathsig: %s', ds)
|
138
|
+
set_process_deathsig(int(signal.Signals[f'SIG{ds.upper()}']))
|
139
|
+
|
140
|
+
self._timebomb_thread()
|
141
|
+
|
142
|
+
if self._bootstrap.remote_config.forward_logging:
|
143
|
+
log.debug('Installing log forwarder')
|
144
|
+
logging.root.addHandler(self._log_handler())
|
145
|
+
|
146
|
+
#
|
147
|
+
|
148
|
+
async def run(self) -> None:
|
149
|
+
await self._setup()
|
150
|
+
|
151
|
+
executor = self._injector[LocalCommandExecutor]
|
152
|
+
|
153
|
+
handler = _RemoteCommandHandler(self._chan, executor)
|
154
|
+
|
155
|
+
await handler.run()
|
156
|
+
|
157
|
+
|
158
|
+
def _remote_execution_main() -> None:
|
159
|
+
rt = pyremote_bootstrap_finalize() # noqa
|
160
|
+
|
161
|
+
async def inner() -> None:
|
162
|
+
input = await asyncio_open_stream_reader(rt.input) # noqa
|
163
|
+
output = await asyncio_open_stream_writer(rt.output)
|
164
|
+
|
165
|
+
chan = RemoteChannelImpl(
|
166
|
+
input,
|
167
|
+
output,
|
168
|
+
)
|
169
|
+
|
170
|
+
await _RemoteExecutionMain(chan).run()
|
171
|
+
|
172
|
+
asyncio.run(inner())
|
ominfra/manage/remote/channel.py
CHANGED
@@ -1,7 +1,8 @@
|
|
1
1
|
# ruff: noqa: UP006 UP007
|
2
|
+
import abc
|
3
|
+
import asyncio
|
2
4
|
import json
|
3
5
|
import struct
|
4
|
-
import threading
|
5
6
|
import typing as ta
|
6
7
|
|
7
8
|
from omlish.lite.json import json_dumps_compact
|
@@ -12,11 +13,30 @@ from omlish.lite.marshal import ObjMarshalerManager
|
|
12
13
|
T = ta.TypeVar('T')
|
13
14
|
|
14
15
|
|
15
|
-
|
16
|
+
##
|
17
|
+
|
18
|
+
|
19
|
+
class RemoteChannel(abc.ABC):
|
20
|
+
@abc.abstractmethod
|
21
|
+
def send_obj(self, o: ta.Any, ty: ta.Any = None) -> ta.Awaitable[None]:
|
22
|
+
raise NotImplementedError
|
23
|
+
|
24
|
+
@abc.abstractmethod
|
25
|
+
def recv_obj(self, ty: ta.Type[T]) -> ta.Awaitable[ta.Optional[T]]:
|
26
|
+
raise NotImplementedError
|
27
|
+
|
28
|
+
def set_marshaler(self, msh: ObjMarshalerManager) -> None: # noqa
|
29
|
+
pass
|
30
|
+
|
31
|
+
|
32
|
+
##
|
33
|
+
|
34
|
+
|
35
|
+
class RemoteChannelImpl(RemoteChannel):
|
16
36
|
def __init__(
|
17
37
|
self,
|
18
|
-
input:
|
19
|
-
output:
|
38
|
+
input: asyncio.StreamReader, # noqa
|
39
|
+
output: asyncio.StreamWriter,
|
20
40
|
*,
|
21
41
|
msh: ObjMarshalerManager = OBJ_MARSHALER_MANAGER,
|
22
42
|
) -> None:
|
@@ -26,38 +46,43 @@ class RemoteChannel:
|
|
26
46
|
self._output = output
|
27
47
|
self._msh = msh
|
28
48
|
|
29
|
-
self.
|
49
|
+
self._input_lock = asyncio.Lock()
|
50
|
+
self._output_lock = asyncio.Lock()
|
30
51
|
|
31
52
|
def set_marshaler(self, msh: ObjMarshalerManager) -> None:
|
32
53
|
self._msh = msh
|
33
54
|
|
34
|
-
|
55
|
+
#
|
56
|
+
|
57
|
+
async def _send_obj(self, o: ta.Any, ty: ta.Any = None) -> None:
|
35
58
|
j = json_dumps_compact(self._msh.marshal_obj(o, ty))
|
36
59
|
d = j.encode('utf-8')
|
37
60
|
|
38
61
|
self._output.write(struct.pack('<I', len(d)))
|
39
62
|
self._output.write(d)
|
40
|
-
self._output.
|
63
|
+
await self._output.drain()
|
64
|
+
|
65
|
+
async def send_obj(self, o: ta.Any, ty: ta.Any = None) -> None:
|
66
|
+
async with self._output_lock:
|
67
|
+
return await self._send_obj(o, ty)
|
41
68
|
|
42
|
-
|
43
|
-
with self._lock:
|
44
|
-
return self._send_obj(o, ty)
|
69
|
+
#
|
45
70
|
|
46
|
-
def _recv_obj(self, ty: ta.Type[T]) -> ta.Optional[T]:
|
47
|
-
d = self._input.read(4)
|
71
|
+
async def _recv_obj(self, ty: ta.Type[T]) -> ta.Optional[T]:
|
72
|
+
d = await self._input.read(4)
|
48
73
|
if not d:
|
49
74
|
return None
|
50
75
|
if len(d) != 4:
|
51
76
|
raise EOFError
|
52
77
|
|
53
78
|
sz = struct.unpack('<I', d)[0]
|
54
|
-
d = self._input.read(sz)
|
79
|
+
d = await self._input.read(sz)
|
55
80
|
if len(d) != sz:
|
56
81
|
raise EOFError
|
57
82
|
|
58
83
|
j = json.loads(d.decode('utf-8'))
|
59
84
|
return self._msh.unmarshal_obj(j, ty)
|
60
85
|
|
61
|
-
def recv_obj(self, ty: ta.Type[T]) -> ta.Optional[T]:
|
62
|
-
with self.
|
63
|
-
return self._recv_obj(ty)
|
86
|
+
async def recv_obj(self, ty: ta.Type[T]) -> ta.Optional[T]:
|
87
|
+
async with self._input_lock:
|
88
|
+
return await self._recv_obj(ty)
|
ominfra/manage/remote/config.py
CHANGED
@@ -9,4 +9,14 @@ from omlish.lite.pycharm import PycharmRemoteDebug
|
|
9
9
|
class RemoteConfig:
|
10
10
|
payload_file: ta.Optional[str] = None
|
11
11
|
|
12
|
+
set_pgid: bool = True
|
13
|
+
|
14
|
+
deathsig: ta.Optional[str] = 'KILL'
|
15
|
+
|
12
16
|
pycharm_remote_debug: ta.Optional[PycharmRemoteDebug] = None
|
17
|
+
|
18
|
+
forward_logging: bool = True
|
19
|
+
|
20
|
+
timebomb_delay_s: ta.Optional[float] = 60 * 60.
|
21
|
+
|
22
|
+
heartbeat_interval_s: float = 3.
|