ominfra 0.0.0.dev147__py3-none-any.whl → 0.0.0.dev148__py3-none-any.whl
Sign up to get free protection for your applications and to get access to all the features.
- 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.
|