ominfra 0.0.0.dev136__py3-none-any.whl → 0.0.0.dev138__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/new/_manage.py +293 -174
- ominfra/manage/new/main.py +103 -31
- ominfra/pyremote.py +196 -145
- ominfra/scripts/supervisor.py +32 -31
- ominfra/supervisor/processimpl.py +32 -31
- {ominfra-0.0.0.dev136.dist-info → ominfra-0.0.0.dev138.dist-info}/METADATA +3 -3
- {ominfra-0.0.0.dev136.dist-info → ominfra-0.0.0.dev138.dist-info}/RECORD +11 -11
- {ominfra-0.0.0.dev136.dist-info → ominfra-0.0.0.dev138.dist-info}/LICENSE +0 -0
- {ominfra-0.0.0.dev136.dist-info → ominfra-0.0.0.dev138.dist-info}/WHEEL +0 -0
- {ominfra-0.0.0.dev136.dist-info → ominfra-0.0.0.dev138.dist-info}/entry_points.txt +0 -0
- {ominfra-0.0.0.dev136.dist-info → ominfra-0.0.0.dev138.dist-info}/top_level.txt +0 -0
ominfra/manage/new/main.py
CHANGED
@@ -1,32 +1,74 @@
|
|
1
1
|
#!/usr/bin/env python3
|
2
2
|
# @omlish-amalg ./_manage.py
|
3
3
|
# ruff: noqa: UP006 UP007
|
4
|
+
"""
|
5
|
+
manage.py -s 'docker run -i python:3.12'
|
6
|
+
manage.py -qs 'ssh -i foo/bar foo@bar.baz' --python=python3.8
|
7
|
+
"""
|
4
8
|
import inspect
|
5
9
|
import json
|
6
|
-
import os
|
7
10
|
import shlex
|
8
11
|
import struct
|
9
12
|
import subprocess
|
10
13
|
import sys
|
11
14
|
import typing as ta
|
12
15
|
|
16
|
+
from omlish.lite.cached import cached_nullary
|
13
17
|
from omlish.lite.check import check_not_none
|
14
18
|
from omlish.lite.json import json_dumps_compact
|
19
|
+
from omlish.lite.marshal import PolymorphicObjMarshaler
|
20
|
+
from omlish.lite.marshal import get_obj_marshaler
|
15
21
|
from omlish.lite.marshal import marshal_obj
|
22
|
+
from omlish.lite.marshal import register_opj_marshaler
|
16
23
|
from omlish.lite.marshal import unmarshal_obj
|
17
24
|
from omlish.lite.subprocesses import subprocess_maybe_shell_wrap_exec
|
18
25
|
|
19
26
|
from ...pyremote import PyremoteBootstrapDriver
|
27
|
+
from ...pyremote import PyremoteBootstrapOptions
|
20
28
|
from ...pyremote import pyremote_bootstrap_finalize
|
21
29
|
from ...pyremote import pyremote_build_bootstrap_cmd
|
30
|
+
from .commands.base import Command
|
22
31
|
from .commands.subprocess import SubprocessCommand
|
23
32
|
|
24
33
|
|
25
34
|
##
|
26
35
|
|
27
36
|
|
28
|
-
|
29
|
-
|
37
|
+
_COMMAND_TYPES = {
|
38
|
+
'subprocess': SubprocessCommand,
|
39
|
+
}
|
40
|
+
|
41
|
+
|
42
|
+
register_opj_marshaler(
|
43
|
+
Command.Input,
|
44
|
+
PolymorphicObjMarshaler.of([
|
45
|
+
PolymorphicObjMarshaler.Impl(
|
46
|
+
cty.Input,
|
47
|
+
k,
|
48
|
+
get_obj_marshaler(cty.Input),
|
49
|
+
)
|
50
|
+
for k, cty in _COMMAND_TYPES.items()
|
51
|
+
]),
|
52
|
+
)
|
53
|
+
|
54
|
+
register_opj_marshaler(
|
55
|
+
Command.Output,
|
56
|
+
PolymorphicObjMarshaler.of([
|
57
|
+
PolymorphicObjMarshaler.Impl(
|
58
|
+
cty.Output,
|
59
|
+
k,
|
60
|
+
get_obj_marshaler(cty.Output),
|
61
|
+
)
|
62
|
+
for k, cty in _COMMAND_TYPES.items()
|
63
|
+
]),
|
64
|
+
)
|
65
|
+
|
66
|
+
|
67
|
+
##
|
68
|
+
|
69
|
+
|
70
|
+
def _send_obj(f: ta.IO, o: ta.Any, ty: ta.Any = None) -> None:
|
71
|
+
j = json_dumps_compact(marshal_obj(o, ty))
|
30
72
|
d = j.encode('utf-8')
|
31
73
|
|
32
74
|
f.write(struct.pack('<I', len(d)))
|
@@ -34,33 +76,74 @@ def _send_obj(f: ta.IO, o: ta.Any) -> None:
|
|
34
76
|
f.flush()
|
35
77
|
|
36
78
|
|
37
|
-
def _recv_obj(f: ta.IO, ty:
|
79
|
+
def _recv_obj(f: ta.IO, ty: ta.Any) -> ta.Any:
|
38
80
|
d = f.read(4)
|
39
81
|
if not d:
|
40
82
|
return None
|
41
83
|
if len(d) != 4:
|
42
|
-
raise
|
84
|
+
raise EOFError
|
43
85
|
|
44
86
|
sz = struct.unpack('<I', d)[0]
|
45
87
|
d = f.read(sz)
|
46
|
-
if
|
47
|
-
raise
|
88
|
+
if len(d) != sz:
|
89
|
+
raise EOFError
|
48
90
|
|
49
91
|
j = json.loads(d.decode('utf-8'))
|
50
92
|
return unmarshal_obj(j, ty)
|
51
93
|
|
52
94
|
|
95
|
+
##
|
96
|
+
|
97
|
+
|
53
98
|
def _remote_main() -> None:
|
54
99
|
rt = pyremote_bootstrap_finalize() # noqa
|
55
100
|
|
56
101
|
while True:
|
57
|
-
i = _recv_obj(rt.input,
|
102
|
+
i = _recv_obj(rt.input, Command.Input)
|
58
103
|
if i is None:
|
59
104
|
break
|
60
105
|
|
61
|
-
|
106
|
+
if isinstance(i, SubprocessCommand.Input):
|
107
|
+
o = SubprocessCommand()._execute(i) # noqa
|
108
|
+
else:
|
109
|
+
raise TypeError(i)
|
110
|
+
|
111
|
+
_send_obj(rt.output, o, Command.Output)
|
112
|
+
|
113
|
+
|
114
|
+
##
|
115
|
+
|
116
|
+
|
117
|
+
@cached_nullary
|
118
|
+
def _get_self_src() -> str:
|
119
|
+
return inspect.getsource(sys.modules[__name__])
|
120
|
+
|
121
|
+
|
122
|
+
def _is_src_amalg(src: str) -> bool:
|
123
|
+
for l in src.splitlines(): # noqa
|
124
|
+
if l.startswith('# @omlish-amalg-output '):
|
125
|
+
return True
|
126
|
+
return False
|
127
|
+
|
128
|
+
|
129
|
+
@cached_nullary
|
130
|
+
def _is_self_amalg() -> bool:
|
131
|
+
return _is_src_amalg(_get_self_src())
|
62
132
|
|
63
|
-
|
133
|
+
|
134
|
+
def _get_amalg_src(*, amalg_file: ta.Optional[str]) -> str:
|
135
|
+
if amalg_file is not None:
|
136
|
+
with open(amalg_file) as f:
|
137
|
+
return f.read()
|
138
|
+
|
139
|
+
if _is_self_amalg():
|
140
|
+
return _get_self_src()
|
141
|
+
|
142
|
+
import importlib.resources
|
143
|
+
return importlib.resources.read_text(__package__, '_manage.py')
|
144
|
+
|
145
|
+
|
146
|
+
##
|
64
147
|
|
65
148
|
|
66
149
|
def _main() -> None:
|
@@ -77,23 +160,7 @@ def _main() -> None:
|
|
77
160
|
|
78
161
|
#
|
79
162
|
|
80
|
-
|
81
|
-
self_src_lines = self_src.splitlines()
|
82
|
-
for l in self_src_lines:
|
83
|
-
if l.startswith('# @omlish-amalg-output '):
|
84
|
-
is_self_amalg = True
|
85
|
-
break
|
86
|
-
else:
|
87
|
-
is_self_amalg = False
|
88
|
-
|
89
|
-
if is_self_amalg:
|
90
|
-
amalg_src = self_src
|
91
|
-
else:
|
92
|
-
amalg_file = args._amalg_file # noqa
|
93
|
-
if amalg_file is None:
|
94
|
-
amalg_file = os.path.join(os.path.dirname(__file__), '_manage.py')
|
95
|
-
with open(amalg_file) as f:
|
96
|
-
amalg_src = f.read()
|
163
|
+
amalg_src = _get_amalg_src(amalg_file=args._amalg_file) # noqa
|
97
164
|
|
98
165
|
#
|
99
166
|
|
@@ -128,8 +195,13 @@ def _main() -> None:
|
|
128
195
|
stdin = check_not_none(proc.stdin)
|
129
196
|
stdout = check_not_none(proc.stdout)
|
130
197
|
|
131
|
-
res = PyremoteBootstrapDriver(
|
132
|
-
|
198
|
+
res = PyremoteBootstrapDriver( # noqa
|
199
|
+
remote_src,
|
200
|
+
PyremoteBootstrapOptions(
|
201
|
+
# debug=True,
|
202
|
+
),
|
203
|
+
).run(stdin, stdout)
|
204
|
+
# print(res)
|
133
205
|
|
134
206
|
#
|
135
207
|
|
@@ -144,9 +216,9 @@ def _main() -> None:
|
|
144
216
|
capture_stdout=True,
|
145
217
|
),
|
146
218
|
]:
|
147
|
-
_send_obj(stdin, ci)
|
219
|
+
_send_obj(stdin, ci, Command.Input)
|
148
220
|
|
149
|
-
o = _recv_obj(stdout,
|
221
|
+
o = _recv_obj(stdout, Command.Output)
|
150
222
|
|
151
223
|
print(o)
|
152
224
|
|
ominfra/pyremote.py
CHANGED
@@ -5,7 +5,6 @@ Basically this: https://mitogen.networkgenomics.com/howitworks.html
|
|
5
5
|
"""
|
6
6
|
import base64
|
7
7
|
import dataclasses as dc
|
8
|
-
import inspect
|
9
8
|
import json
|
10
9
|
import os
|
11
10
|
import platform
|
@@ -13,7 +12,6 @@ import pwd
|
|
13
12
|
import site
|
14
13
|
import struct
|
15
14
|
import sys
|
16
|
-
import textwrap
|
17
15
|
import typing as ta
|
18
16
|
import zlib
|
19
17
|
|
@@ -21,11 +19,120 @@ import zlib
|
|
21
19
|
##
|
22
20
|
|
23
21
|
|
22
|
+
@dc.dataclass(frozen=True)
|
23
|
+
class PyremoteBootstrapOptions:
|
24
|
+
debug: bool = False
|
25
|
+
|
26
|
+
|
27
|
+
##
|
28
|
+
|
29
|
+
|
30
|
+
@dc.dataclass(frozen=True)
|
31
|
+
class PyremoteEnvInfo:
|
32
|
+
sys_base_prefix: str
|
33
|
+
sys_byteorder: str
|
34
|
+
sys_defaultencoding: str
|
35
|
+
sys_exec_prefix: str
|
36
|
+
sys_executable: str
|
37
|
+
sys_implementation_name: str
|
38
|
+
sys_path: ta.List[str]
|
39
|
+
sys_platform: str
|
40
|
+
sys_prefix: str
|
41
|
+
sys_version: str
|
42
|
+
sys_version_info: ta.List[ta.Union[int, str]]
|
43
|
+
|
44
|
+
platform_architecture: ta.List[str]
|
45
|
+
platform_machine: str
|
46
|
+
platform_platform: str
|
47
|
+
platform_processor: str
|
48
|
+
platform_system: str
|
49
|
+
platform_release: str
|
50
|
+
platform_version: str
|
51
|
+
|
52
|
+
site_userbase: str
|
53
|
+
|
54
|
+
os_cwd: str
|
55
|
+
os_gid: int
|
56
|
+
os_loadavg: ta.List[float]
|
57
|
+
os_login: ta.Optional[str]
|
58
|
+
os_pgrp: int
|
59
|
+
os_pid: int
|
60
|
+
os_ppid: int
|
61
|
+
os_uid: int
|
62
|
+
|
63
|
+
pw_name: str
|
64
|
+
pw_uid: int
|
65
|
+
pw_gid: int
|
66
|
+
pw_gecos: str
|
67
|
+
pw_dir: str
|
68
|
+
pw_shell: str
|
69
|
+
|
70
|
+
env_path: ta.Optional[str]
|
71
|
+
|
72
|
+
|
73
|
+
def _get_pyremote_env_info() -> PyremoteEnvInfo:
|
74
|
+
os_uid = os.getuid()
|
75
|
+
|
76
|
+
pw = pwd.getpwuid(os_uid)
|
77
|
+
|
78
|
+
os_login: ta.Optional[str]
|
79
|
+
try:
|
80
|
+
os_login = os.getlogin()
|
81
|
+
except OSError:
|
82
|
+
os_login = None
|
83
|
+
|
84
|
+
return PyremoteEnvInfo(
|
85
|
+
sys_base_prefix=sys.base_prefix,
|
86
|
+
sys_byteorder=sys.byteorder,
|
87
|
+
sys_defaultencoding=sys.getdefaultencoding(),
|
88
|
+
sys_exec_prefix=sys.exec_prefix,
|
89
|
+
sys_executable=sys.executable,
|
90
|
+
sys_implementation_name=sys.implementation.name,
|
91
|
+
sys_path=sys.path,
|
92
|
+
sys_platform=sys.platform,
|
93
|
+
sys_prefix=sys.prefix,
|
94
|
+
sys_version=sys.version,
|
95
|
+
sys_version_info=list(sys.version_info),
|
96
|
+
|
97
|
+
platform_architecture=list(platform.architecture()),
|
98
|
+
platform_machine=platform.machine(),
|
99
|
+
platform_platform=platform.platform(),
|
100
|
+
platform_processor=platform.processor(),
|
101
|
+
platform_system=platform.system(),
|
102
|
+
platform_release=platform.release(),
|
103
|
+
platform_version=platform.version(),
|
104
|
+
|
105
|
+
site_userbase=site.getuserbase(),
|
106
|
+
|
107
|
+
os_cwd=os.getcwd(),
|
108
|
+
os_gid=os.getgid(),
|
109
|
+
os_loadavg=list(os.getloadavg()),
|
110
|
+
os_login=os_login,
|
111
|
+
os_pgrp=os.getpgrp(),
|
112
|
+
os_pid=os.getpid(),
|
113
|
+
os_ppid=os.getppid(),
|
114
|
+
os_uid=os_uid,
|
115
|
+
|
116
|
+
pw_name=pw.pw_name,
|
117
|
+
pw_uid=pw.pw_uid,
|
118
|
+
pw_gid=pw.pw_gid,
|
119
|
+
pw_gecos=pw.pw_gecos,
|
120
|
+
pw_dir=pw.pw_dir,
|
121
|
+
pw_shell=pw.pw_shell,
|
122
|
+
|
123
|
+
env_path=os.environ.get('PATH'),
|
124
|
+
)
|
125
|
+
|
126
|
+
|
127
|
+
##
|
128
|
+
|
129
|
+
|
24
130
|
_PYREMOTE_BOOTSTRAP_INPUT_FD = 100
|
25
131
|
_PYREMOTE_BOOTSTRAP_SRC_FD = 101
|
26
132
|
|
27
133
|
_PYREMOTE_BOOTSTRAP_CHILD_PID_VAR = '_OPYR_CHILD_PID'
|
28
134
|
_PYREMOTE_BOOTSTRAP_ARGV0_VAR = '_OPYR_ARGV0'
|
135
|
+
_PYREMOTE_BOOTSTRAP_OPTIONS_JSON_VAR = '_OPYR_OPTIONS_JSON'
|
29
136
|
|
30
137
|
_PYREMOTE_BOOTSTRAP_ACK0 = b'OPYR000\n'
|
31
138
|
_PYREMOTE_BOOTSTRAP_ACK1 = b'OPYR001\n'
|
@@ -87,7 +194,9 @@ def _pyremote_bootstrap_main(context_name: str) -> None:
|
|
87
194
|
|
88
195
|
# Read main src from stdin
|
89
196
|
main_z_len = struct.unpack('<I', os.read(0, 4))[0]
|
90
|
-
|
197
|
+
if len(main_z := os.fdopen(0, 'rb').read(main_z_len)) != main_z_len:
|
198
|
+
raise EOFError
|
199
|
+
main_src = zlib.decompress(main_z)
|
91
200
|
|
92
201
|
# Write both copies of main src. Must write to w0 (parent stdin) before w1 (copy pipe) as pipe will likely fill
|
93
202
|
# and block and need to be drained by pyremote_bootstrap_finalize running in parent.
|
@@ -110,6 +219,8 @@ def pyremote_build_bootstrap_cmd(context_name: str) -> str:
|
|
110
219
|
if any(c in context_name for c in '\'"'):
|
111
220
|
raise NameError(context_name)
|
112
221
|
|
222
|
+
import inspect
|
223
|
+
import textwrap
|
113
224
|
bs_src = textwrap.dedent(inspect.getsource(_pyremote_bootstrap_main))
|
114
225
|
|
115
226
|
for gl in [
|
@@ -136,9 +247,6 @@ def pyremote_build_bootstrap_cmd(context_name: str) -> str:
|
|
136
247
|
bs_z = zlib.compress(bs_src.encode('utf-8'))
|
137
248
|
bs_z64 = base64.encodebytes(bs_z).replace(b'\n', b'')
|
138
249
|
|
139
|
-
def dq_repr(o: ta.Any) -> str:
|
140
|
-
return '"' + repr(o)[1:-1] + '"'
|
141
|
-
|
142
250
|
stmts = [
|
143
251
|
f'import {", ".join(_PYREMOTE_BOOTSTRAP_IMPORTS)}',
|
144
252
|
f'exec(zlib.decompress(base64.decodebytes({bs_z64!r})))',
|
@@ -153,99 +261,83 @@ def pyremote_build_bootstrap_cmd(context_name: str) -> str:
|
|
153
261
|
|
154
262
|
|
155
263
|
@dc.dataclass(frozen=True)
|
156
|
-
class
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
sys_implementation_name: str
|
163
|
-
sys_path: ta.List[str]
|
164
|
-
sys_platform: str
|
165
|
-
sys_prefix: str
|
166
|
-
sys_version: str
|
167
|
-
sys_version_info: ta.List[ta.Union[int, str]]
|
168
|
-
|
169
|
-
platform_architecture: ta.List[str]
|
170
|
-
platform_machine: str
|
171
|
-
platform_platform: str
|
172
|
-
platform_processor: str
|
173
|
-
platform_system: str
|
174
|
-
platform_release: str
|
175
|
-
platform_version: str
|
176
|
-
|
177
|
-
site_userbase: str
|
178
|
-
|
179
|
-
os_cwd: str
|
180
|
-
os_gid: int
|
181
|
-
os_loadavg: ta.List[float]
|
182
|
-
os_login: ta.Optional[str]
|
183
|
-
os_pgrp: int
|
184
|
-
os_pid: int
|
185
|
-
os_ppid: int
|
186
|
-
os_uid: int
|
187
|
-
|
188
|
-
pw_name: str
|
189
|
-
pw_uid: int
|
190
|
-
pw_gid: int
|
191
|
-
pw_gecos: str
|
192
|
-
pw_dir: str
|
193
|
-
pw_shell: str
|
194
|
-
|
195
|
-
env_path: ta.Optional[str]
|
264
|
+
class PyremotePayloadRuntime:
|
265
|
+
input: ta.BinaryIO
|
266
|
+
output: ta.BinaryIO
|
267
|
+
main_src: str
|
268
|
+
options: PyremoteBootstrapOptions
|
269
|
+
env_info: PyremoteEnvInfo
|
196
270
|
|
197
271
|
|
198
|
-
def
|
199
|
-
|
272
|
+
def pyremote_bootstrap_finalize() -> PyremotePayloadRuntime:
|
273
|
+
# If json options var is not present we need to do initial finalization
|
274
|
+
if _PYREMOTE_BOOTSTRAP_OPTIONS_JSON_VAR not in os.environ:
|
275
|
+
# Read second copy of main src
|
276
|
+
r1 = os.fdopen(_PYREMOTE_BOOTSTRAP_SRC_FD, 'rb', 0)
|
277
|
+
main_src = r1.read().decode('utf-8')
|
278
|
+
r1.close()
|
279
|
+
|
280
|
+
# Reap boostrap child. Must be done after reading second copy of source because source may be too big to fit in
|
281
|
+
# a pipe at once.
|
282
|
+
os.waitpid(int(os.environ.pop(_PYREMOTE_BOOTSTRAP_CHILD_PID_VAR)), 0)
|
283
|
+
|
284
|
+
# Read options
|
285
|
+
options_json_len = struct.unpack('<I', os.read(_PYREMOTE_BOOTSTRAP_INPUT_FD, 4))[0]
|
286
|
+
if len(options_json := os.read(_PYREMOTE_BOOTSTRAP_INPUT_FD, options_json_len)) != options_json_len:
|
287
|
+
raise EOFError
|
288
|
+
options = PyremoteBootstrapOptions(**json.loads(options_json.decode('utf-8')))
|
289
|
+
|
290
|
+
# If debugging, re-exec as file
|
291
|
+
if options.debug:
|
292
|
+
# Write temp source file
|
293
|
+
import tempfile
|
294
|
+
tfd, tfn = tempfile.mkstemp('-pyremote.py')
|
295
|
+
os.write(tfd, main_src.encode('utf-8'))
|
296
|
+
os.close(tfd)
|
297
|
+
|
298
|
+
# Set json options var
|
299
|
+
os.environ[_PYREMOTE_BOOTSTRAP_OPTIONS_JSON_VAR] = options_json.decode('utf-8')
|
300
|
+
|
301
|
+
# Re-exec temp file
|
302
|
+
os.execl(os.environ[_PYREMOTE_BOOTSTRAP_ARGV0_VAR], sys.orig_argv[0], tfn)
|
200
303
|
|
201
|
-
|
304
|
+
else:
|
305
|
+
# Load options json var
|
306
|
+
options_json_str = os.environ.pop(_PYREMOTE_BOOTSTRAP_OPTIONS_JSON_VAR)
|
307
|
+
options = PyremoteBootstrapOptions(**json.loads(options_json_str))
|
202
308
|
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
except OSError:
|
207
|
-
os_login = None
|
309
|
+
# Read temp source file
|
310
|
+
with open(sys.orig_argv[1]) as sf:
|
311
|
+
main_src = sf.read()
|
208
312
|
|
209
|
-
|
210
|
-
|
211
|
-
sys_byteorder=sys.byteorder,
|
212
|
-
sys_defaultencoding=sys.getdefaultencoding(),
|
213
|
-
sys_exec_prefix=sys.exec_prefix,
|
214
|
-
sys_executable=sys.executable,
|
215
|
-
sys_implementation_name=sys.implementation.name,
|
216
|
-
sys_path=sys.path,
|
217
|
-
sys_platform=sys.platform,
|
218
|
-
sys_prefix=sys.prefix,
|
219
|
-
sys_version=sys.version,
|
220
|
-
sys_version_info=list(sys.version_info),
|
313
|
+
# Restore original argv0
|
314
|
+
sys.executable = os.environ.pop(_PYREMOTE_BOOTSTRAP_ARGV0_VAR)
|
221
315
|
|
222
|
-
|
223
|
-
|
224
|
-
platform_platform=platform.platform(),
|
225
|
-
platform_processor=platform.processor(),
|
226
|
-
platform_system=platform.system(),
|
227
|
-
platform_release=platform.release(),
|
228
|
-
platform_version=platform.version(),
|
316
|
+
# Write third ack
|
317
|
+
os.write(1, _PYREMOTE_BOOTSTRAP_ACK2)
|
229
318
|
|
230
|
-
|
319
|
+
# Write env info
|
320
|
+
env_info = _get_pyremote_env_info()
|
321
|
+
env_info_json = json.dumps(dc.asdict(env_info), indent=None, separators=(',', ':')) # noqa
|
322
|
+
os.write(1, struct.pack('<I', len(env_info_json)))
|
323
|
+
os.write(1, env_info_json.encode('utf-8'))
|
231
324
|
|
232
|
-
|
233
|
-
|
234
|
-
|
235
|
-
|
236
|
-
|
237
|
-
os_pid=os.getpid(),
|
238
|
-
os_ppid=os.getppid(),
|
239
|
-
os_uid=os_uid,
|
325
|
+
# Setup IO
|
326
|
+
input = os.fdopen(_PYREMOTE_BOOTSTRAP_INPUT_FD, 'rb', 0) # noqa
|
327
|
+
output = os.fdopen(os.dup(1), 'wb', 0) # noqa
|
328
|
+
os.dup2(nfd := os.open('/dev/null', os.O_WRONLY), 1)
|
329
|
+
os.close(nfd)
|
240
330
|
|
241
|
-
|
242
|
-
|
243
|
-
pw_gid=pw.pw_gid,
|
244
|
-
pw_gecos=pw.pw_gecos,
|
245
|
-
pw_dir=pw.pw_dir,
|
246
|
-
pw_shell=pw.pw_shell,
|
331
|
+
# Write fourth ack
|
332
|
+
output.write(_PYREMOTE_BOOTSTRAP_ACK3)
|
247
333
|
|
248
|
-
|
334
|
+
# Return
|
335
|
+
return PyremotePayloadRuntime(
|
336
|
+
input=input,
|
337
|
+
output=output,
|
338
|
+
main_src=main_src,
|
339
|
+
options=options,
|
340
|
+
env_info=env_info,
|
249
341
|
)
|
250
342
|
|
251
343
|
|
@@ -253,12 +345,15 @@ def _get_pyremote_env_info() -> PyremoteEnvInfo:
|
|
253
345
|
|
254
346
|
|
255
347
|
class PyremoteBootstrapDriver:
|
256
|
-
def __init__(self, main_src: str) -> None:
|
348
|
+
def __init__(self, main_src: str, options: PyremoteBootstrapOptions = PyremoteBootstrapOptions()) -> None:
|
257
349
|
super().__init__()
|
258
350
|
|
259
351
|
self._main_src = main_src
|
260
352
|
self._main_z = zlib.compress(main_src.encode('utf-8'))
|
261
353
|
|
354
|
+
self._options = options
|
355
|
+
self._options_json = json.dumps(dc.asdict(options), indent=None, separators=(',', ':')).encode('utf-8') # noqa
|
356
|
+
|
262
357
|
#
|
263
358
|
|
264
359
|
@dc.dataclass(frozen=True)
|
@@ -278,7 +373,7 @@ class PyremoteBootstrapDriver:
|
|
278
373
|
env_info: PyremoteEnvInfo
|
279
374
|
|
280
375
|
def gen(self) -> ta.Generator[ta.Union[Read, Write], ta.Optional[bytes], Result]:
|
281
|
-
# Read first ack
|
376
|
+
# Read first ack (after fork)
|
282
377
|
yield from self._expect(_PYREMOTE_BOOTSTRAP_ACK0)
|
283
378
|
|
284
379
|
# Read pid
|
@@ -289,8 +384,14 @@ class PyremoteBootstrapDriver:
|
|
289
384
|
yield from self._write(struct.pack('<I', len(self._main_z)))
|
290
385
|
yield from self._write(self._main_z)
|
291
386
|
|
292
|
-
# Read second
|
387
|
+
# Read second ack (after writing src copies)
|
293
388
|
yield from self._expect(_PYREMOTE_BOOTSTRAP_ACK1)
|
389
|
+
|
390
|
+
# Write options
|
391
|
+
yield from self._write(struct.pack('<I', len(self._options_json)))
|
392
|
+
yield from self._write(self._options_json)
|
393
|
+
|
394
|
+
# Read third ack (after reaping child process)
|
294
395
|
yield from self._expect(_PYREMOTE_BOOTSTRAP_ACK2)
|
295
396
|
|
296
397
|
# Read env info
|
@@ -300,7 +401,7 @@ class PyremoteBootstrapDriver:
|
|
300
401
|
env_info_json = d.decode('utf-8')
|
301
402
|
env_info = PyremoteEnvInfo(**json.loads(env_info_json))
|
302
403
|
|
303
|
-
# Read fourth ack
|
404
|
+
# Read fourth ack (after finalization completed)
|
304
405
|
yield from self._expect(_PYREMOTE_BOOTSTRAP_ACK3)
|
305
406
|
|
306
407
|
# Return
|
@@ -343,61 +444,11 @@ class PyremoteBootstrapDriver:
|
|
343
444
|
return e.value
|
344
445
|
|
345
446
|
if isinstance(go, self.Read):
|
346
|
-
gi
|
447
|
+
if len(gi := stdout.read(go.sz)) != go.sz:
|
448
|
+
raise EOFError
|
347
449
|
elif isinstance(go, self.Write):
|
348
450
|
gi = None
|
349
451
|
stdin.write(go.d)
|
350
452
|
stdin.flush()
|
351
453
|
else:
|
352
454
|
raise TypeError(go)
|
353
|
-
|
354
|
-
|
355
|
-
##
|
356
|
-
|
357
|
-
|
358
|
-
@dc.dataclass(frozen=True)
|
359
|
-
class PyremotePayloadRuntime:
|
360
|
-
input: ta.BinaryIO
|
361
|
-
output: ta.BinaryIO
|
362
|
-
main_src: str
|
363
|
-
env_info: PyremoteEnvInfo
|
364
|
-
|
365
|
-
|
366
|
-
def pyremote_bootstrap_finalize() -> PyremotePayloadRuntime:
|
367
|
-
# Restore original argv0
|
368
|
-
sys.executable = os.environ.pop(_PYREMOTE_BOOTSTRAP_ARGV0_VAR)
|
369
|
-
|
370
|
-
# Read second copy of main src
|
371
|
-
r1 = os.fdopen(_PYREMOTE_BOOTSTRAP_SRC_FD, 'rb', 0)
|
372
|
-
main_src = r1.read().decode('utf-8')
|
373
|
-
r1.close()
|
374
|
-
|
375
|
-
# Reap boostrap child. Must be done after reading second copy of source because source may be too big to fit in a
|
376
|
-
# pipe at once.
|
377
|
-
os.waitpid(int(os.environ.pop(_PYREMOTE_BOOTSTRAP_CHILD_PID_VAR)), 0)
|
378
|
-
|
379
|
-
# Write third ack
|
380
|
-
os.write(1, _PYREMOTE_BOOTSTRAP_ACK2)
|
381
|
-
|
382
|
-
# Write env info
|
383
|
-
env_info = _get_pyremote_env_info()
|
384
|
-
env_info_json = json.dumps(dc.asdict(env_info), indent=None, separators=(',', ':')) # noqa
|
385
|
-
os.write(1, struct.pack('<I', len(env_info_json)))
|
386
|
-
os.write(1, env_info_json.encode('utf-8'))
|
387
|
-
|
388
|
-
# Setup IO
|
389
|
-
input = os.fdopen(_PYREMOTE_BOOTSTRAP_INPUT_FD, 'rb', 0) # noqa
|
390
|
-
output = os.fdopen(os.dup(1), 'wb', 0) # noqa
|
391
|
-
os.dup2(nfd := os.open('/dev/null', os.O_WRONLY), 1)
|
392
|
-
os.close(nfd)
|
393
|
-
|
394
|
-
# Write fourth ack
|
395
|
-
output.write(_PYREMOTE_BOOTSTRAP_ACK3)
|
396
|
-
|
397
|
-
# Return
|
398
|
-
return PyremotePayloadRuntime(
|
399
|
-
input=input,
|
400
|
-
output=output,
|
401
|
-
main_src=main_src,
|
402
|
-
env_info=env_info,
|
403
|
-
)
|