ominfra 0.0.0.dev137__py3-none-any.whl → 0.0.0.dev139__py3-none-any.whl

Sign up to get free protection for your applications and to get access to all the features.
Files changed (44) hide show
  1. ominfra/manage/__init__.py +13 -0
  2. ominfra/manage/{new/main.py → main.py} +68 -18
  3. ominfra/pyremote.py +196 -145
  4. ominfra/{manage/new/_manage.py → scripts/manage.py} +267 -170
  5. ominfra/scripts/supervisor.py +32 -31
  6. ominfra/supervisor/processimpl.py +32 -31
  7. {ominfra-0.0.0.dev137.dist-info → ominfra-0.0.0.dev139.dist-info}/METADATA +3 -3
  8. {ominfra-0.0.0.dev137.dist-info → ominfra-0.0.0.dev139.dist-info}/RECORD +15 -44
  9. ominfra/manage/deploy/_executor.py +0 -1415
  10. ominfra/manage/deploy/configs.py +0 -19
  11. ominfra/manage/deploy/executor/__init__.py +0 -1
  12. ominfra/manage/deploy/executor/base.py +0 -115
  13. ominfra/manage/deploy/executor/concerns/__init__.py +0 -0
  14. ominfra/manage/deploy/executor/concerns/dirs.py +0 -28
  15. ominfra/manage/deploy/executor/concerns/nginx.py +0 -47
  16. ominfra/manage/deploy/executor/concerns/repo.py +0 -17
  17. ominfra/manage/deploy/executor/concerns/supervisor.py +0 -46
  18. ominfra/manage/deploy/executor/concerns/systemd.py +0 -88
  19. ominfra/manage/deploy/executor/concerns/user.py +0 -25
  20. ominfra/manage/deploy/executor/concerns/venv.py +0 -22
  21. ominfra/manage/deploy/executor/main.py +0 -119
  22. ominfra/manage/deploy/poly/__init__.py +0 -1
  23. ominfra/manage/deploy/poly/_main.py +0 -975
  24. ominfra/manage/deploy/poly/base.py +0 -178
  25. ominfra/manage/deploy/poly/configs.py +0 -38
  26. ominfra/manage/deploy/poly/deploy.py +0 -25
  27. ominfra/manage/deploy/poly/main.py +0 -18
  28. ominfra/manage/deploy/poly/nginx.py +0 -60
  29. ominfra/manage/deploy/poly/repo.py +0 -41
  30. ominfra/manage/deploy/poly/runtime.py +0 -39
  31. ominfra/manage/deploy/poly/site.py +0 -11
  32. ominfra/manage/deploy/poly/supervisor.py +0 -64
  33. ominfra/manage/deploy/poly/venv.py +0 -52
  34. ominfra/manage/deploy/remote.py +0 -91
  35. ominfra/manage/manage.py +0 -12
  36. ominfra/manage/new/__init__.py +0 -1
  37. ominfra/manage/new/commands/__init__.py +0 -0
  38. /ominfra/manage/{deploy → commands}/__init__.py +0 -0
  39. /ominfra/manage/{new/commands → commands}/base.py +0 -0
  40. /ominfra/manage/{new/commands → commands}/subprocess.py +0 -0
  41. {ominfra-0.0.0.dev137.dist-info → ominfra-0.0.0.dev139.dist-info}/LICENSE +0 -0
  42. {ominfra-0.0.0.dev137.dist-info → ominfra-0.0.0.dev139.dist-info}/WHEEL +0 -0
  43. {ominfra-0.0.0.dev137.dist-info → ominfra-0.0.0.dev139.dist-info}/entry_points.txt +0 -0
  44. {ominfra-0.0.0.dev137.dist-info → ominfra-0.0.0.dev139.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,13 @@
1
+ # @omlish-lite
2
+ """
3
+ Jobs:
4
+ - globals
5
+ - pkgs
6
+ - pyenv
7
+ - tailscale
8
+ - docker
9
+ - system nginx
10
+ - system service manager - systemd / supervisor
11
+ - users
12
+ - firewall
13
+ """
@@ -1,6 +1,10 @@
1
1
  #!/usr/bin/env python3
2
- # @omlish-amalg ./_manage.py
2
+ # @omlish-amalg ../scripts/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
10
  import shlex
@@ -12,21 +16,59 @@ import typing as ta
12
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
- from ...pyremote import PyremoteBootstrapDriver
20
- from ...pyremote import pyremote_bootstrap_finalize
21
- from ...pyremote import pyremote_build_bootstrap_cmd
26
+ from ..pyremote import PyremoteBootstrapDriver
27
+ from ..pyremote import PyremoteBootstrapOptions
28
+ from ..pyremote import pyremote_bootstrap_finalize
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
- def _send_obj(f: ta.IO, o: ta.Any) -> None:
29
- j = json_dumps_compact(marshal_obj(o))
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,17 +76,17 @@ 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: type) -> ta.Any:
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 Exception
84
+ raise EOFError
43
85
 
44
86
  sz = struct.unpack('<I', d)[0]
45
87
  d = f.read(sz)
46
- if not d:
47
- raise Exception
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)
@@ -57,13 +99,16 @@ def _remote_main() -> None:
57
99
  rt = pyremote_bootstrap_finalize() # noqa
58
100
 
59
101
  while True:
60
- i = _recv_obj(rt.input, SubprocessCommand.Input)
102
+ i = _recv_obj(rt.input, Command.Input)
61
103
  if i is None:
62
104
  break
63
105
 
64
- o = SubprocessCommand()._execute(i) # noqa
106
+ if isinstance(i, SubprocessCommand.Input):
107
+ o = SubprocessCommand()._execute(i) # noqa
108
+ else:
109
+ raise TypeError(i)
65
110
 
66
- _send_obj(rt.output, o)
111
+ _send_obj(rt.output, o, Command.Output)
67
112
 
68
113
 
69
114
  ##
@@ -95,7 +140,7 @@ def _get_amalg_src(*, amalg_file: ta.Optional[str]) -> str:
95
140
  return _get_self_src()
96
141
 
97
142
  import importlib.resources
98
- return importlib.resources.read_text(__package__, '_manage.py')
143
+ return importlib.resources.files(__package__.split('.')[0] + '.scripts').joinpath('manage.py').read_text()
99
144
 
100
145
 
101
146
  ##
@@ -150,8 +195,13 @@ def _main() -> None:
150
195
  stdin = check_not_none(proc.stdin)
151
196
  stdout = check_not_none(proc.stdout)
152
197
 
153
- res = PyremoteBootstrapDriver(remote_src).run(stdin, stdout)
154
- print(res)
198
+ res = PyremoteBootstrapDriver( # noqa
199
+ remote_src,
200
+ PyremoteBootstrapOptions(
201
+ # debug=True,
202
+ ),
203
+ ).run(stdin, stdout)
204
+ # print(res)
155
205
 
156
206
  #
157
207
 
@@ -166,9 +216,9 @@ def _main() -> None:
166
216
  capture_stdout=True,
167
217
  ),
168
218
  ]:
169
- _send_obj(stdin, ci)
219
+ _send_obj(stdin, ci, Command.Input)
170
220
 
171
- o = _recv_obj(stdout, SubprocessCommand.Output)
221
+ o = _recv_obj(stdout, Command.Output)
172
222
 
173
223
  print(o)
174
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
- main_src = zlib.decompress(os.fdopen(0, 'rb').read(main_z_len))
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 PyremoteEnvInfo:
157
- sys_base_prefix: str
158
- sys_byteorder: str
159
- sys_defaultencoding: str
160
- sys_exec_prefix: str
161
- sys_executable: str
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 _get_pyremote_env_info() -> PyremoteEnvInfo:
199
- os_uid = os.getuid()
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
- pw = pwd.getpwuid(os_uid)
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
- os_login: ta.Optional[str]
204
- try:
205
- os_login = os.getlogin()
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
- return PyremoteEnvInfo(
210
- sys_base_prefix=sys.base_prefix,
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
- platform_architecture=list(platform.architecture()),
223
- platform_machine=platform.machine(),
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
- site_userbase=site.getuserbase(),
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
- os_cwd=os.getcwd(),
233
- os_gid=os.getgid(),
234
- os_loadavg=list(os.getloadavg()),
235
- os_login=os_login,
236
- os_pgrp=os.getpgrp(),
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
- pw_name=pw.pw_name,
242
- pw_uid=pw.pw_uid,
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
- env_path=os.environ.get('PATH'),
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 and third ack
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 = stdout.read(go.sz)
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
- )