ominfra 0.0.0.dev141__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/commands/subprocess.py +18 -17
- ominfra/manage/config.py +10 -0
- ominfra/manage/inject.py +55 -0
- ominfra/manage/main.py +54 -132
- ominfra/manage/marshal.py +12 -0
- ominfra/manage/remote/__init__.py +0 -0
- ominfra/manage/remote/channel.py +52 -0
- 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} +31 -35
- ominfra/pyremote.py +53 -14
- ominfra/scripts/journald2aws.py +216 -131
- ominfra/scripts/manage.py +2180 -452
- ominfra/scripts/supervisor.py +203 -130
- ominfra/supervisor/inject.py +2 -1
- {ominfra-0.0.0.dev141.dist-info → ominfra-0.0.0.dev143.dist-info}/METADATA +3 -3
- {ominfra-0.0.0.dev141.dist-info → ominfra-0.0.0.dev143.dist-info}/RECORD +31 -17
- {ominfra-0.0.0.dev141.dist-info → ominfra-0.0.0.dev143.dist-info}/LICENSE +0 -0
- {ominfra-0.0.0.dev141.dist-info → ominfra-0.0.0.dev143.dist-info}/WHEEL +0 -0
- {ominfra-0.0.0.dev141.dist-info → ominfra-0.0.0.dev143.dist-info}/entry_points.txt +0 -0
- {ominfra-0.0.0.dev141.dist-info → ominfra-0.0.0.dev143.dist-info}/top_level.txt +0 -0
ominfra/manage/main.py
CHANGED
@@ -5,101 +5,18 @@
|
|
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
|
9
|
-
import
|
10
|
-
|
11
|
-
|
12
|
-
from omlish.lite.cached import static_init
|
13
|
-
from omlish.lite.json import json_dumps_compact
|
14
|
-
from omlish.lite.marshal import PolymorphicObjMarshaler
|
15
|
-
from omlish.lite.marshal import get_obj_marshaler
|
16
|
-
from omlish.lite.marshal import marshal_obj
|
17
|
-
from omlish.lite.marshal import register_opj_marshaler
|
18
|
-
from omlish.lite.marshal import unmarshal_obj
|
19
|
-
|
20
|
-
from ..pyremote import PyremoteBootstrapDriver
|
21
|
-
from ..pyremote import PyremoteBootstrapOptions
|
22
|
-
from ..pyremote import pyremote_bootstrap_finalize
|
23
|
-
from ..pyremote import pyremote_build_bootstrap_cmd
|
24
|
-
from .commands.base import Command
|
25
|
-
from .commands.subprocess import SubprocessCommand
|
26
|
-
from .commands.subprocess import SubprocessCommandExecutor
|
27
|
-
from .payload import get_payload_src
|
28
|
-
from .spawning import PySpawner
|
29
|
-
|
30
|
-
|
31
|
-
##
|
32
|
-
|
33
|
-
|
34
|
-
_COMMAND_TYPES = {
|
35
|
-
'subprocess': SubprocessCommand,
|
36
|
-
}
|
37
|
-
|
38
|
-
|
39
|
-
@static_init
|
40
|
-
def _register_command_marshaling() -> None:
|
41
|
-
for fn in [
|
42
|
-
lambda c: c,
|
43
|
-
lambda c: c.Output,
|
44
|
-
]:
|
45
|
-
register_opj_marshaler(
|
46
|
-
fn(Command),
|
47
|
-
PolymorphicObjMarshaler.of([
|
48
|
-
PolymorphicObjMarshaler.Impl(
|
49
|
-
fn(cty),
|
50
|
-
k,
|
51
|
-
get_obj_marshaler(fn(cty)),
|
52
|
-
)
|
53
|
-
for k, cty in _COMMAND_TYPES.items()
|
54
|
-
]),
|
55
|
-
)
|
56
|
-
|
57
|
-
|
58
|
-
##
|
59
|
-
|
60
|
-
|
61
|
-
def _send_obj(f: ta.IO, o: ta.Any, ty: ta.Any = None) -> None:
|
62
|
-
j = json_dumps_compact(marshal_obj(o, ty))
|
63
|
-
d = j.encode('utf-8')
|
64
|
-
|
65
|
-
f.write(struct.pack('<I', len(d)))
|
66
|
-
f.write(d)
|
67
|
-
f.flush()
|
68
|
-
|
69
|
-
|
70
|
-
def _recv_obj(f: ta.IO, ty: ta.Any) -> ta.Any:
|
71
|
-
d = f.read(4)
|
72
|
-
if not d:
|
73
|
-
return None
|
74
|
-
if len(d) != 4:
|
75
|
-
raise EOFError
|
76
|
-
|
77
|
-
sz = struct.unpack('<I', d)[0]
|
78
|
-
d = f.read(sz)
|
79
|
-
if len(d) != sz:
|
80
|
-
raise EOFError
|
81
|
-
|
82
|
-
j = json.loads(d.decode('utf-8'))
|
83
|
-
return unmarshal_obj(j, ty)
|
84
|
-
|
85
|
-
|
86
|
-
##
|
8
|
+
from omlish.lite.logs import log # noqa
|
9
|
+
from omlish.lite.marshal import ObjMarshalOptions
|
10
|
+
from omlish.lite.marshal import ObjMarshalerManager
|
11
|
+
from omlish.lite.pycharm import PycharmRemoteDebug
|
87
12
|
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
break
|
96
|
-
|
97
|
-
if isinstance(i, SubprocessCommand):
|
98
|
-
o = SubprocessCommandExecutor().execute(i) # noqa
|
99
|
-
else:
|
100
|
-
raise TypeError(i)
|
101
|
-
|
102
|
-
_send_obj(rt.output, o, Command.Output)
|
13
|
+
from .bootstrap import MainBootstrap
|
14
|
+
from .bootstrap_ import main_bootstrap
|
15
|
+
from .commands.subprocess import SubprocessCommand
|
16
|
+
from .config import MainConfig
|
17
|
+
from .remote.config import RemoteConfig
|
18
|
+
from .remote.execution import RemoteExecution
|
19
|
+
from .remote.spawning import RemoteSpawning
|
103
20
|
|
104
21
|
|
105
22
|
##
|
@@ -110,65 +27,70 @@ def _main() -> None:
|
|
110
27
|
|
111
28
|
parser = argparse.ArgumentParser()
|
112
29
|
|
30
|
+
parser.add_argument('--_payload-file')
|
31
|
+
|
113
32
|
parser.add_argument('-s', '--shell')
|
114
33
|
parser.add_argument('-q', '--shell-quote', action='store_true')
|
115
34
|
parser.add_argument('--python', default='python3')
|
35
|
+
|
36
|
+
parser.add_argument('--pycharm-debug-port', type=int)
|
37
|
+
parser.add_argument('--pycharm-debug-host')
|
38
|
+
parser.add_argument('--pycharm-debug-version')
|
39
|
+
|
116
40
|
parser.add_argument('--debug', action='store_true')
|
117
|
-
|
41
|
+
|
42
|
+
parser.add_argument('command', nargs='+')
|
118
43
|
|
119
44
|
args = parser.parse_args()
|
120
45
|
|
121
46
|
#
|
122
47
|
|
123
|
-
|
48
|
+
bs = MainBootstrap(
|
49
|
+
main_config=MainConfig(
|
50
|
+
log_level='DEBUG' if args.debug else 'INFO',
|
124
51
|
|
125
|
-
|
52
|
+
debug=bool(args.debug),
|
53
|
+
),
|
54
|
+
|
55
|
+
remote_config=RemoteConfig(
|
56
|
+
payload_file=args._payload_file, # noqa
|
126
57
|
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
58
|
+
pycharm_remote_debug=PycharmRemoteDebug(
|
59
|
+
port=args.pycharm_debug_port,
|
60
|
+
host=args.pycharm_debug_host,
|
61
|
+
install_version=args.pycharm_debug_version,
|
62
|
+
) if args.pycharm_debug_port is not None else None,
|
63
|
+
),
|
64
|
+
)
|
65
|
+
|
66
|
+
injector = main_bootstrap(
|
67
|
+
bs,
|
68
|
+
)
|
132
69
|
|
133
70
|
#
|
134
71
|
|
135
|
-
|
72
|
+
cmds = [
|
73
|
+
SubprocessCommand([c])
|
74
|
+
for c in args.command
|
75
|
+
]
|
136
76
|
|
137
77
|
#
|
138
78
|
|
139
|
-
|
140
|
-
bs_src,
|
79
|
+
tgt = RemoteSpawning.Target(
|
141
80
|
shell=args.shell,
|
142
81
|
shell_quote=args.shell_quote,
|
143
82
|
python=args.python,
|
144
83
|
)
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
)
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
for ci in [
|
157
|
-
SubprocessCommand(
|
158
|
-
args=['python3', '-'],
|
159
|
-
input=b'print(1)\n',
|
160
|
-
capture_stdout=True,
|
161
|
-
),
|
162
|
-
SubprocessCommand(
|
163
|
-
args=['uname'],
|
164
|
-
capture_stdout=True,
|
165
|
-
),
|
166
|
-
]:
|
167
|
-
_send_obj(proc.stdin, ci, Command)
|
168
|
-
|
169
|
-
o = _recv_obj(proc.stdout, Command.Output)
|
170
|
-
|
171
|
-
print(o)
|
84
|
+
|
85
|
+
with injector[RemoteExecution].connect(tgt, bs) as rce:
|
86
|
+
for cmd in cmds:
|
87
|
+
r = rce.try_execute(cmd)
|
88
|
+
|
89
|
+
print(injector[ObjMarshalerManager].marshal_obj(r, opts=ObjMarshalOptions(raw_bytes=True)))
|
90
|
+
|
91
|
+
#
|
92
|
+
|
93
|
+
print('Success')
|
172
94
|
|
173
95
|
|
174
96
|
if __name__ == '__main__':
|
@@ -0,0 +1,12 @@
|
|
1
|
+
import dataclasses as dc
|
2
|
+
import typing as ta
|
3
|
+
|
4
|
+
from omlish.lite.marshal import ObjMarshalerManager
|
5
|
+
|
6
|
+
|
7
|
+
@dc.dataclass(frozen=True)
|
8
|
+
class ObjMarshalerInstaller:
|
9
|
+
fn: ta.Callable[[ObjMarshalerManager], None]
|
10
|
+
|
11
|
+
|
12
|
+
ObjMarshalerInstallers = ta.NewType('ObjMarshalerInstallers', ta.Sequence[ObjMarshalerInstaller])
|
File without changes
|
@@ -0,0 +1,52 @@
|
|
1
|
+
# ruff: noqa: UP006 UP007
|
2
|
+
import json
|
3
|
+
import struct
|
4
|
+
import typing as ta
|
5
|
+
|
6
|
+
from omlish.lite.json import json_dumps_compact
|
7
|
+
from omlish.lite.marshal import OBJ_MARSHALER_MANAGER
|
8
|
+
from omlish.lite.marshal import ObjMarshalerManager
|
9
|
+
|
10
|
+
|
11
|
+
T = ta.TypeVar('T')
|
12
|
+
|
13
|
+
|
14
|
+
class RemoteChannel:
|
15
|
+
def __init__(
|
16
|
+
self,
|
17
|
+
input: ta.IO, # noqa
|
18
|
+
output: ta.IO,
|
19
|
+
*,
|
20
|
+
msh: ObjMarshalerManager = OBJ_MARSHALER_MANAGER,
|
21
|
+
) -> None:
|
22
|
+
super().__init__()
|
23
|
+
|
24
|
+
self._input = input
|
25
|
+
self._output = output
|
26
|
+
self._msh = msh
|
27
|
+
|
28
|
+
def set_marshaler(self, msh: ObjMarshalerManager) -> None:
|
29
|
+
self._msh = msh
|
30
|
+
|
31
|
+
def send_obj(self, o: ta.Any, ty: ta.Any = None) -> None:
|
32
|
+
j = json_dumps_compact(self._msh.marshal_obj(o, ty))
|
33
|
+
d = j.encode('utf-8')
|
34
|
+
|
35
|
+
self._output.write(struct.pack('<I', len(d)))
|
36
|
+
self._output.write(d)
|
37
|
+
self._output.flush()
|
38
|
+
|
39
|
+
def recv_obj(self, ty: ta.Type[T]) -> ta.Optional[T]:
|
40
|
+
d = self._input.read(4)
|
41
|
+
if not d:
|
42
|
+
return None
|
43
|
+
if len(d) != 4:
|
44
|
+
raise EOFError
|
45
|
+
|
46
|
+
sz = struct.unpack('<I', d)[0]
|
47
|
+
d = self._input.read(sz)
|
48
|
+
if len(d) != sz:
|
49
|
+
raise EOFError
|
50
|
+
|
51
|
+
j = json.loads(d.decode('utf-8'))
|
52
|
+
return self._msh.unmarshal_obj(j, ty)
|
@@ -0,0 +1,12 @@
|
|
1
|
+
# ruff: noqa: UP006 UP007
|
2
|
+
import dataclasses as dc
|
3
|
+
import typing as ta
|
4
|
+
|
5
|
+
from omlish.lite.pycharm import PycharmRemoteDebug
|
6
|
+
|
7
|
+
|
8
|
+
@dc.dataclass(frozen=True)
|
9
|
+
class RemoteConfig:
|
10
|
+
payload_file: ta.Optional[str] = None
|
11
|
+
|
12
|
+
pycharm_remote_debug: ta.Optional[PycharmRemoteDebug] = None
|
@@ -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()
|
@@ -6,28 +6,21 @@ import subprocess
|
|
6
6
|
import typing as ta
|
7
7
|
|
8
8
|
from omlish.lite.check import check_not_none
|
9
|
+
from omlish.lite.subprocesses import SUBPROCESS_CHANNEL_OPTION_VALUES
|
10
|
+
from omlish.lite.subprocesses import SubprocessChannelOption
|
9
11
|
from omlish.lite.subprocesses import subprocess_maybe_shell_wrap_exec
|
10
12
|
|
11
13
|
|
12
|
-
class
|
13
|
-
|
14
|
+
class RemoteSpawning:
|
15
|
+
@dc.dataclass(frozen=True)
|
16
|
+
class Target:
|
17
|
+
shell: ta.Optional[str] = None
|
18
|
+
shell_quote: bool = False
|
14
19
|
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
shell: ta.Optional[str] = None,
|
20
|
-
shell_quote: bool = False,
|
21
|
-
python: str = DEFAULT_PYTHON,
|
22
|
-
stderr: ta.Optional[ta.Literal['pipe', 'stdout', 'devnull']] = None,
|
23
|
-
) -> None:
|
24
|
-
super().__init__()
|
25
|
-
|
26
|
-
self._src = src
|
27
|
-
self._shell = shell
|
28
|
-
self._shell_quote = shell_quote
|
29
|
-
self._python = python
|
30
|
-
self._stderr = stderr
|
20
|
+
DEFAULT_PYTHON: ta.ClassVar[str] = 'python3'
|
21
|
+
python: str = DEFAULT_PYTHON
|
22
|
+
|
23
|
+
stderr: ta.Optional[str] = None # SubprocessChannelOption
|
31
24
|
|
32
25
|
#
|
33
26
|
|
@@ -35,31 +28,29 @@ class PySpawner:
|
|
35
28
|
cmd: ta.Sequence[str]
|
36
29
|
shell: bool
|
37
30
|
|
38
|
-
def _prepare_cmd(
|
39
|
-
|
40
|
-
|
41
|
-
|
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:
|
42
39
|
sh_src = shlex.quote(sh_src)
|
43
|
-
sh_cmd = f'{
|
44
|
-
return
|
40
|
+
sh_cmd = f'{tgt.shell} {sh_src}'
|
41
|
+
return RemoteSpawning._PreparedCmd(
|
45
42
|
cmd=[sh_cmd],
|
46
43
|
shell=True,
|
47
44
|
)
|
48
45
|
|
49
46
|
else:
|
50
|
-
return
|
51
|
-
cmd=[
|
47
|
+
return RemoteSpawning._PreparedCmd(
|
48
|
+
cmd=[tgt.python, '-c', src],
|
52
49
|
shell=False,
|
53
50
|
)
|
54
51
|
|
55
52
|
#
|
56
53
|
|
57
|
-
_STDERR_KWARG_MAP: ta.Mapping[str, int] = {
|
58
|
-
'pipe': subprocess.PIPE,
|
59
|
-
'stdout': subprocess.STDOUT,
|
60
|
-
'devnull': subprocess.DEVNULL,
|
61
|
-
}
|
62
|
-
|
63
54
|
@dc.dataclass(frozen=True)
|
64
55
|
class Spawned:
|
65
56
|
stdin: ta.IO
|
@@ -69,23 +60,28 @@ class PySpawner:
|
|
69
60
|
@contextlib.contextmanager
|
70
61
|
def spawn(
|
71
62
|
self,
|
63
|
+
tgt: Target,
|
64
|
+
src: str,
|
72
65
|
*,
|
73
66
|
timeout: ta.Optional[float] = None,
|
74
67
|
) -> ta.Generator[Spawned, None, None]:
|
75
|
-
pc = self._prepare_cmd()
|
68
|
+
pc = self._prepare_cmd(tgt, src)
|
76
69
|
|
77
70
|
with subprocess.Popen(
|
78
71
|
subprocess_maybe_shell_wrap_exec(*pc.cmd),
|
79
72
|
shell=pc.shell,
|
80
73
|
stdin=subprocess.PIPE,
|
81
74
|
stdout=subprocess.PIPE,
|
82
|
-
stderr=
|
75
|
+
stderr=(
|
76
|
+
SUBPROCESS_CHANNEL_OPTION_VALUES[ta.cast(SubprocessChannelOption, tgt.stderr)]
|
77
|
+
if tgt.stderr is not None else None
|
78
|
+
),
|
83
79
|
) as proc:
|
84
80
|
stdin = check_not_none(proc.stdin)
|
85
81
|
stdout = check_not_none(proc.stdout)
|
86
82
|
|
87
83
|
try:
|
88
|
-
yield
|
84
|
+
yield RemoteSpawning.Spawned(
|
89
85
|
stdin=stdin,
|
90
86
|
stdout=stdout,
|
91
87
|
stderr=proc.stderr,
|