ominfra 0.0.0.dev131__py3-none-any.whl → 0.0.0.dev133__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.
Files changed (50) hide show
  1. ominfra/{deploy → manage/deploy}/_executor.py +10 -10
  2. ominfra/{deploy → manage/deploy}/poly/_main.py +6 -6
  3. ominfra/{deploy → manage/deploy}/remote.py +1 -1
  4. ominfra/pyremote.py +357 -0
  5. ominfra/scripts/journald2aws.py +8 -0
  6. ominfra/scripts/supervisor.py +173 -67
  7. ominfra/supervisor/dispatchersimpl.py +4 -3
  8. ominfra/supervisor/events.py +5 -2
  9. ominfra/supervisor/http.py +8 -1
  10. ominfra/supervisor/inject.py +8 -2
  11. ominfra/supervisor/io.py +2 -2
  12. ominfra/supervisor/main.py +13 -10
  13. ominfra/supervisor/processimpl.py +0 -1
  14. ominfra/supervisor/states.py +11 -0
  15. ominfra/supervisor/supervisor.py +26 -26
  16. ominfra/supervisor/types.py +2 -1
  17. {ominfra-0.0.0.dev131.dist-info → ominfra-0.0.0.dev133.dist-info}/METADATA +3 -3
  18. {ominfra-0.0.0.dev131.dist-info → ominfra-0.0.0.dev133.dist-info}/RECORD +46 -49
  19. ominfra/pyremote/__init__.py +0 -0
  20. ominfra/pyremote/_runcommands.py +0 -1201
  21. ominfra/pyremote/bootstrap.py +0 -149
  22. ominfra/pyremote/runcommands.py +0 -56
  23. /ominfra/{deploy → manage/deploy}/__init__.py +0 -0
  24. /ominfra/{deploy → manage/deploy}/configs.py +0 -0
  25. /ominfra/{deploy → manage/deploy}/executor/__init__.py +0 -0
  26. /ominfra/{deploy → manage/deploy}/executor/base.py +0 -0
  27. /ominfra/{deploy → manage/deploy}/executor/concerns/__init__.py +0 -0
  28. /ominfra/{deploy → manage/deploy}/executor/concerns/dirs.py +0 -0
  29. /ominfra/{deploy → manage/deploy}/executor/concerns/nginx.py +0 -0
  30. /ominfra/{deploy → manage/deploy}/executor/concerns/repo.py +0 -0
  31. /ominfra/{deploy → manage/deploy}/executor/concerns/supervisor.py +0 -0
  32. /ominfra/{deploy → manage/deploy}/executor/concerns/systemd.py +0 -0
  33. /ominfra/{deploy → manage/deploy}/executor/concerns/user.py +0 -0
  34. /ominfra/{deploy → manage/deploy}/executor/concerns/venv.py +0 -0
  35. /ominfra/{deploy → manage/deploy}/executor/main.py +0 -0
  36. /ominfra/{deploy → manage/deploy}/poly/__init__.py +0 -0
  37. /ominfra/{deploy → manage/deploy}/poly/base.py +0 -0
  38. /ominfra/{deploy → manage/deploy}/poly/configs.py +0 -0
  39. /ominfra/{deploy → manage/deploy}/poly/deploy.py +0 -0
  40. /ominfra/{deploy → manage/deploy}/poly/main.py +0 -0
  41. /ominfra/{deploy → manage/deploy}/poly/nginx.py +0 -0
  42. /ominfra/{deploy → manage/deploy}/poly/repo.py +0 -0
  43. /ominfra/{deploy → manage/deploy}/poly/runtime.py +0 -0
  44. /ominfra/{deploy → manage/deploy}/poly/site.py +0 -0
  45. /ominfra/{deploy → manage/deploy}/poly/supervisor.py +0 -0
  46. /ominfra/{deploy → manage/deploy}/poly/venv.py +0 -0
  47. {ominfra-0.0.0.dev131.dist-info → ominfra-0.0.0.dev133.dist-info}/LICENSE +0 -0
  48. {ominfra-0.0.0.dev131.dist-info → ominfra-0.0.0.dev133.dist-info}/WHEEL +0 -0
  49. {ominfra-0.0.0.dev131.dist-info → ominfra-0.0.0.dev133.dist-info}/entry_points.txt +0 -0
  50. {ominfra-0.0.0.dev131.dist-info → ominfra-0.0.0.dev133.dist-info}/top_level.txt +0 -0
@@ -82,10 +82,10 @@ if sys.version_info < (3, 8):
82
82
  ########################################
83
83
 
84
84
 
85
- # ../../../omlish/lite/cached.py
85
+ # ../../../../omlish/lite/cached.py
86
86
  T = ta.TypeVar('T')
87
87
 
88
- # ../../../omlish/lite/check.py
88
+ # ../../../../omlish/lite/check.py
89
89
  SizedT = ta.TypeVar('SizedT', bound=ta.Sized)
90
90
 
91
91
 
@@ -112,7 +112,7 @@ class HostConfig:
112
112
 
113
113
 
114
114
  ########################################
115
- # ../../../../omlish/lite/cached.py
115
+ # ../../../../../omlish/lite/cached.py
116
116
 
117
117
 
118
118
  class _cached_nullary: # noqa
@@ -137,7 +137,7 @@ def cached_nullary(fn): # ta.Callable[..., T]) -> ta.Callable[..., T]:
137
137
 
138
138
 
139
139
  ########################################
140
- # ../../../../omlish/lite/check.py
140
+ # ../../../../../omlish/lite/check.py
141
141
 
142
142
 
143
143
  def check_isinstance(v: ta.Any, spec: ta.Union[ta.Type[T], tuple]) -> T:
@@ -234,7 +234,7 @@ def check_non_empty(v: SizedT) -> SizedT:
234
234
 
235
235
 
236
236
  ########################################
237
- # ../../../../omlish/lite/json.py
237
+ # ../../../../../omlish/lite/json.py
238
238
 
239
239
 
240
240
  ##
@@ -265,7 +265,7 @@ json_dumps_compact: ta.Callable[..., str] = functools.partial(json.dumps, **JSON
265
265
 
266
266
 
267
267
  ########################################
268
- # ../../../../omlish/lite/reflect.py
268
+ # ../../../../../omlish/lite/reflect.py
269
269
 
270
270
 
271
271
  _GENERIC_ALIAS_TYPES = (
@@ -320,7 +320,7 @@ def deep_subclasses(cls: ta.Type[T]) -> ta.Iterator[ta.Type[T]]:
320
320
 
321
321
 
322
322
  ########################################
323
- # ../../../../omlish/lite/logs.py
323
+ # ../../../../../omlish/lite/logs.py
324
324
  """
325
325
  TODO:
326
326
  - translate json keys
@@ -590,7 +590,7 @@ def configure_standard_logging(
590
590
 
591
591
 
592
592
  ########################################
593
- # ../../../../omlish/lite/marshal.py
593
+ # ../../../../../omlish/lite/marshal.py
594
594
  """
595
595
  TODO:
596
596
  - pickle stdlib objs? have to pin to 3.8 pickle protocol, will be cross-version
@@ -934,7 +934,7 @@ def unmarshal_obj(o: ta.Any, ty: ta.Union[ta.Type[T], ta.Any]) -> T:
934
934
 
935
935
 
936
936
  ########################################
937
- # ../../../../omlish/lite/runtime.py
937
+ # ../../../../../omlish/lite/runtime.py
938
938
 
939
939
 
940
940
  @cached_nullary
@@ -951,7 +951,7 @@ def check_runtime_version() -> None:
951
951
 
952
952
 
953
953
  ########################################
954
- # ../../../../omlish/lite/subprocesses.py
954
+ # ../../../../../omlish/lite/subprocesses.py
955
955
 
956
956
 
957
957
  ##
@@ -33,7 +33,7 @@ if sys.version_info < (3, 8):
33
33
  ########################################
34
34
 
35
35
 
36
- # ../../../omlish/lite/cached.py
36
+ # ../../../../omlish/lite/cached.py
37
37
  T = ta.TypeVar('T')
38
38
 
39
39
  # base.py
@@ -84,7 +84,7 @@ class DeployConfig:
84
84
 
85
85
 
86
86
  ########################################
87
- # ../../../../omlish/lite/cached.py
87
+ # ../../../../../omlish/lite/cached.py
88
88
 
89
89
 
90
90
  class _cached_nullary: # noqa
@@ -109,7 +109,7 @@ def cached_nullary(fn): # ta.Callable[..., T]) -> ta.Callable[..., T]:
109
109
 
110
110
 
111
111
  ########################################
112
- # ../../../../omlish/lite/json.py
112
+ # ../../../../../omlish/lite/json.py
113
113
 
114
114
 
115
115
  ##
@@ -301,7 +301,7 @@ class Deploy(ConcernsContainer[DeployConcern, DeployConfig]):
301
301
 
302
302
 
303
303
  ########################################
304
- # ../../../../omlish/lite/logs.py
304
+ # ../../../../../omlish/lite/logs.py
305
305
  """
306
306
  TODO:
307
307
  - translate json keys
@@ -571,7 +571,7 @@ def configure_standard_logging(
571
571
 
572
572
 
573
573
  ########################################
574
- # ../../../../omlish/lite/runtime.py
574
+ # ../../../../../omlish/lite/runtime.py
575
575
 
576
576
 
577
577
  @cached_nullary
@@ -708,7 +708,7 @@ class SiteImpl(Site):
708
708
 
709
709
 
710
710
  ########################################
711
- # ../../../../omlish/lite/subprocesses.py
711
+ # ../../../../../omlish/lite/subprocesses.py
712
712
 
713
713
 
714
714
  ##
@@ -23,7 +23,7 @@ import tempfile
23
23
 
24
24
  from omlish import check
25
25
 
26
- from .. import cmds
26
+ from ... import cmds
27
27
 
28
28
 
29
29
  def render_script(*cs: list[str] | tuple[str, ...]) -> str:
ominfra/pyremote.py ADDED
@@ -0,0 +1,357 @@
1
+ # ruff: noqa: UP006 UP007
2
+ # @omlish-lite
3
+ """
4
+ Basically this: https://mitogen.networkgenomics.com/howitworks.html
5
+ """
6
+ import base64
7
+ import dataclasses as dc
8
+ import inspect
9
+ import json
10
+ import os
11
+ import platform
12
+ import pwd
13
+ import site
14
+ import struct
15
+ import sys
16
+ import textwrap
17
+ import typing as ta
18
+ import zlib
19
+
20
+ from omlish.lite.check import check_isinstance
21
+ from omlish.lite.check import check_none
22
+
23
+
24
+ ##
25
+
26
+
27
+ _PYREMOTE_BOOTSTRAP_COMM_FD = 100
28
+ _PYREMOTE_BOOTSTRAP_SRC_FD = 101
29
+
30
+ _PYREMOTE_BOOTSTRAP_CHILD_PID_VAR = '_OPYR_CPID'
31
+ _PYREMOTE_BOOTSTRAP_ARGV0_VAR = '_OPYR_ARGV0'
32
+
33
+ _PYREMOTE_BOOTSTRAP_ACK0 = b'OPYR000\n'
34
+ _PYREMOTE_BOOTSTRAP_ACK1 = b'OPYR001\n'
35
+ _PYREMOTE_BOOTSTRAP_ACK2 = b'OPYR001\n'
36
+ _PYREMOTE_BOOTSTRAP_ACK3 = b'OPYR001\n'
37
+
38
+ _PYREMOTE_BOOTSTRAP_PROC_TITLE_FMT = '(pyremote:%s)'
39
+
40
+ _PYREMOTE_BOOTSTRAP_IMPORTS = [
41
+ 'base64',
42
+ 'os',
43
+ 'struct',
44
+ 'sys',
45
+ 'zlib',
46
+ ]
47
+
48
+
49
+ def _pyremote_bootstrap_main(context_name: str) -> None:
50
+ # Get pid
51
+ pid = os.getpid()
52
+
53
+ # Two copies of main src to be sent to parent
54
+ r0, w0 = os.pipe()
55
+ r1, w1 = os.pipe()
56
+
57
+ if (cp := os.fork()):
58
+ # Parent process
59
+
60
+ # Dup original stdin to comm_fd for use as comm channel
61
+ os.dup2(0, _PYREMOTE_BOOTSTRAP_COMM_FD)
62
+
63
+ # Overwrite stdin (fed to python repl) with first copy of src
64
+ os.dup2(r0, 0)
65
+
66
+ # Dup second copy of src to src_fd to recover after launch
67
+ os.dup2(r1, _PYREMOTE_BOOTSTRAP_SRC_FD)
68
+
69
+ # Close remaining fd's
70
+ for f in [r0, w0, r1, w1]:
71
+ os.close(f)
72
+
73
+ # Save child pid to close after relaunch
74
+ os.environ[_PYREMOTE_BOOTSTRAP_CHILD_PID_VAR] = str(cp)
75
+
76
+ # Save original argv0
77
+ os.environ[_PYREMOTE_BOOTSTRAP_ARGV0_VAR] = sys.executable
78
+
79
+ # Start repl reading stdin from r0
80
+ os.execl(sys.executable, sys.executable + (_PYREMOTE_BOOTSTRAP_PROC_TITLE_FMT % (context_name,)))
81
+
82
+ else:
83
+ # Child process
84
+
85
+ # Write first ack
86
+ os.write(1, _PYREMOTE_BOOTSTRAP_ACK0)
87
+
88
+ # Write pid
89
+ os.write(1, struct.pack('<Q', pid))
90
+
91
+ # Read main src from stdin
92
+ main_z_len = struct.unpack('<I', os.read(0, 4))[0]
93
+ main_src = zlib.decompress(os.fdopen(0, 'rb').read(main_z_len))
94
+
95
+ # Write both copies of main src. Must write to w0 (parent stdin) before w1 (copy pipe) as pipe will likely fill
96
+ # and block and need to be drained by pyremote_bootstrap_finalize running in parent.
97
+ for w in [w0, w1]:
98
+ fp = os.fdopen(w, 'wb', 0)
99
+ fp.write(main_src)
100
+ fp.close()
101
+
102
+ # Write second ack
103
+ os.write(1, _PYREMOTE_BOOTSTRAP_ACK1)
104
+
105
+ # Exit child
106
+ sys.exit(0)
107
+
108
+
109
+ ##
110
+
111
+
112
+ def pyremote_build_bootstrap_cmd(context_name: str) -> str:
113
+ bs_src = textwrap.dedent(inspect.getsource(_pyremote_bootstrap_main))
114
+
115
+ for gl in [
116
+ '_PYREMOTE_BOOTSTRAP_COMM_FD',
117
+ '_PYREMOTE_BOOTSTRAP_SRC_FD',
118
+
119
+ '_PYREMOTE_BOOTSTRAP_CHILD_PID_VAR',
120
+ '_PYREMOTE_BOOTSTRAP_ARGV0_VAR',
121
+
122
+ '_PYREMOTE_BOOTSTRAP_ACK0',
123
+ '_PYREMOTE_BOOTSTRAP_ACK1',
124
+
125
+ '_PYREMOTE_BOOTSTRAP_PROC_TITLE_FMT',
126
+ ]:
127
+ bs_src = bs_src.replace(gl, repr(globals()[gl]))
128
+
129
+ bs_src = '\n'.join(
130
+ cl
131
+ for l in bs_src.splitlines()
132
+ if (cl := (l.split('#')[0]).rstrip())
133
+ if cl.strip()
134
+ )
135
+
136
+ bs_z = zlib.compress(bs_src.encode('utf-8'))
137
+ bs_z64 = base64.encodebytes(bs_z).replace(b'\n', b'')
138
+
139
+ stmts = [
140
+ f'import {", ".join(_PYREMOTE_BOOTSTRAP_IMPORTS)}',
141
+ f'exec(zlib.decompress(base64.decodebytes({bs_z64!r})))',
142
+ f'_pyremote_bootstrap_main({context_name!r})',
143
+ ]
144
+
145
+ cmd = '; '.join(stmts)
146
+ return cmd
147
+
148
+
149
+ ##
150
+
151
+
152
+ @dc.dataclass(frozen=True)
153
+ class PyremoteEnvInfo:
154
+ sys_base_prefix: str
155
+ sys_byteorder: str
156
+ sys_defaultencoding: str
157
+ sys_exec_prefix: str
158
+ sys_executable: str
159
+ sys_implementation_name: str
160
+ sys_path: ta.List[str]
161
+ sys_platform: str
162
+ sys_prefix: str
163
+ sys_version: str
164
+ sys_version_info: ta.List[ta.Union[int, str]]
165
+
166
+ platform_architecture: ta.List[str]
167
+ platform_machine: str
168
+ platform_platform: str
169
+ platform_processor: str
170
+ platform_system: str
171
+ platform_release: str
172
+ platform_version: str
173
+
174
+ site_userbase: str
175
+
176
+ os_cwd: str
177
+ os_gid: int
178
+ os_loadavg: ta.List[float]
179
+ os_login: ta.Optional[str]
180
+ os_pgrp: int
181
+ os_pid: int
182
+ os_ppid: int
183
+ os_uid: int
184
+
185
+ pw_name: str
186
+ pw_uid: int
187
+ pw_gid: int
188
+ pw_gecos: str
189
+ pw_dir: str
190
+ pw_shell: str
191
+
192
+ env_path: ta.Optional[str]
193
+
194
+
195
+ def _get_pyremote_env_info() -> PyremoteEnvInfo:
196
+ os_uid = os.getuid()
197
+
198
+ pw = pwd.getpwuid(os_uid)
199
+
200
+ os_login: ta.Optional[str]
201
+ try:
202
+ os_login = os.getlogin()
203
+ except OSError:
204
+ os_login = None
205
+
206
+ return PyremoteEnvInfo(
207
+ sys_base_prefix=sys.base_prefix,
208
+ sys_byteorder=sys.byteorder,
209
+ sys_defaultencoding=sys.getdefaultencoding(),
210
+ sys_exec_prefix=sys.exec_prefix,
211
+ sys_executable=sys.executable,
212
+ sys_implementation_name=sys.implementation.name,
213
+ sys_path=sys.path,
214
+ sys_platform=sys.platform,
215
+ sys_prefix=sys.prefix,
216
+ sys_version=sys.version,
217
+ sys_version_info=list(sys.version_info),
218
+
219
+ platform_architecture=list(platform.architecture()),
220
+ platform_machine=platform.machine(),
221
+ platform_platform=platform.platform(),
222
+ platform_processor=platform.processor(),
223
+ platform_system=platform.system(),
224
+ platform_release=platform.release(),
225
+ platform_version=platform.version(),
226
+
227
+ site_userbase=site.getuserbase(),
228
+
229
+ os_cwd=os.getcwd(),
230
+ os_gid=os.getgid(),
231
+ os_loadavg=list(os.getloadavg()),
232
+ os_login=os_login,
233
+ os_pgrp=os.getpgrp(),
234
+ os_pid=os.getpid(),
235
+ os_ppid=os.getppid(),
236
+ os_uid=os_uid,
237
+
238
+ pw_name=pw.pw_name,
239
+ pw_uid=pw.pw_uid,
240
+ pw_gid=pw.pw_gid,
241
+ pw_gecos=pw.pw_gecos,
242
+ pw_dir=pw.pw_dir,
243
+ pw_shell=pw.pw_shell,
244
+
245
+ env_path=os.environ.get('PATH'),
246
+ )
247
+
248
+
249
+ ##
250
+
251
+
252
+ class PyremoteBootstrapDriver:
253
+ def __init__(self, main_src: str) -> None:
254
+ super().__init__()
255
+
256
+ self._main_src = main_src
257
+ self._main_z = zlib.compress(main_src.encode('utf-8'))
258
+
259
+ @dc.dataclass(frozen=True)
260
+ class Read:
261
+ sz: int
262
+
263
+ @dc.dataclass(frozen=True)
264
+ class Write:
265
+ d: bytes
266
+
267
+ class ProtocolError(Exception):
268
+ pass
269
+
270
+ @dc.dataclass(frozen=True)
271
+ class Result:
272
+ pid: int
273
+ env_info: PyremoteEnvInfo
274
+
275
+ def __call__(self) -> ta.Generator[ta.Union[Read, Write], ta.Optional[bytes], Result]:
276
+ # Read first ack
277
+ yield from self._expect(_PYREMOTE_BOOTSTRAP_ACK0)
278
+
279
+ # Read pid
280
+ d = yield from self._read(8)
281
+ pid = struct.unpack('<Q', d)[0]
282
+
283
+ # Write main src
284
+ check_none((yield self.Write(struct.pack('<I', len(self._main_z)))))
285
+ check_none((yield self.Write(self._main_z)))
286
+
287
+ # Read second and third ack
288
+ yield from self._expect(_PYREMOTE_BOOTSTRAP_ACK1)
289
+ yield from self._expect(_PYREMOTE_BOOTSTRAP_ACK2)
290
+
291
+ # Read env info
292
+ d = yield from self._read(4)
293
+ env_info_json_len = struct.unpack('<I', d)[0]
294
+ d = yield from self._read(env_info_json_len)
295
+ env_info_json = d.decode('utf-8')
296
+ env_info = PyremoteEnvInfo(**json.loads(env_info_json))
297
+
298
+ # Read fourth ack
299
+ yield from self._expect(_PYREMOTE_BOOTSTRAP_ACK3)
300
+
301
+ # Return
302
+ return self.Result(
303
+ pid=pid,
304
+ env_info=env_info,
305
+ )
306
+
307
+ def _read(self, sz: int) -> ta.Generator[Read, bytes, bytes]:
308
+ d = check_isinstance((yield self.Read(sz)), bytes)
309
+ if len(d) != sz:
310
+ raise self.ProtocolError(f'Read {len(d)} bytes, expected {sz}')
311
+ return d
312
+
313
+ def _expect(self, e: bytes) -> ta.Generator[Read, bytes, None]:
314
+ d = yield from self._read(len(e))
315
+ if d != e:
316
+ raise self.ProtocolError(f'Read {d!r}, expected {e!r}')
317
+
318
+
319
+ ##
320
+
321
+
322
+ @dc.dataclass(frozen=True)
323
+ class PyremoteBootstrapPayloadRuntime:
324
+ input: ta.BinaryIO
325
+ main_src: str
326
+
327
+
328
+ def pyremote_bootstrap_finalize() -> PyremoteBootstrapPayloadRuntime:
329
+ # Restore original argv0
330
+ sys.executable = os.environ.pop(_PYREMOTE_BOOTSTRAP_ARGV0_VAR)
331
+
332
+ # Read second copy of main src
333
+ r1 = os.fdopen(_PYREMOTE_BOOTSTRAP_SRC_FD, 'rb', 0)
334
+ main_src = r1.read().decode('utf-8')
335
+ r1.close()
336
+
337
+ # Reap boostrap child. Must be done after reading second copy of source because source may be too big to fit in a
338
+ # pipe at once.
339
+ os.waitpid(int(os.environ.pop(_PYREMOTE_BOOTSTRAP_CHILD_PID_VAR)), 0)
340
+
341
+ # Write third ack
342
+ os.write(1, _PYREMOTE_BOOTSTRAP_ACK2)
343
+
344
+ # Write env info
345
+ env_info = _get_pyremote_env_info()
346
+ env_info_json = json.dumps(dc.asdict(env_info), indent=None, separators=(',', ':')) # noqa
347
+ os.write(1, struct.pack('<I', len(env_info_json)))
348
+ os.write(1, env_info_json.encode('utf-8'))
349
+
350
+ # Write fourth ack
351
+ os.write(1, _PYREMOTE_BOOTSTRAP_ACK3)
352
+
353
+ # Return
354
+ return PyremoteBootstrapPayloadRuntime(
355
+ input=os.fdopen(_PYREMOTE_BOOTSTRAP_COMM_FD, 'rb', 0),
356
+ main_src=main_src,
357
+ )
@@ -1627,6 +1627,14 @@ class ExitStacked:
1627
1627
  ##
1628
1628
 
1629
1629
 
1630
+ @contextlib.contextmanager
1631
+ def defer(fn: ta.Callable) -> ta.Generator[ta.Callable, None, None]:
1632
+ try:
1633
+ yield fn
1634
+ finally:
1635
+ fn()
1636
+
1637
+
1630
1638
  @contextlib.contextmanager
1631
1639
  def attr_setting(obj, attr, val, *, default=None): # noqa
1632
1640
  not_set = object()