ominfra 0.0.0.dev126__py3-none-any.whl → 0.0.0.dev127__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/clouds/aws/auth.py +1 -1
- ominfra/deploy/_executor.py +1 -1
- ominfra/deploy/poly/_main.py +1 -1
- ominfra/pyremote/_runcommands.py +1 -1
- ominfra/scripts/journald2aws.py +2 -2
- ominfra/scripts/supervisor.py +1796 -1218
- ominfra/supervisor/collections.py +52 -0
- ominfra/supervisor/context.py +2 -336
- ominfra/supervisor/datatypes.py +1 -63
- ominfra/supervisor/dispatchers.py +20 -324
- ominfra/supervisor/dispatchersimpl.py +342 -0
- ominfra/supervisor/groups.py +33 -111
- ominfra/supervisor/groupsimpl.py +86 -0
- ominfra/supervisor/inject.py +44 -19
- ominfra/supervisor/main.py +1 -1
- ominfra/supervisor/pipes.py +83 -0
- ominfra/supervisor/poller.py +6 -3
- ominfra/supervisor/privileges.py +65 -0
- ominfra/supervisor/processes.py +18 -0
- ominfra/supervisor/{process.py → processesimpl.py} +96 -330
- ominfra/supervisor/setup.py +38 -0
- ominfra/supervisor/setupimpl.py +261 -0
- ominfra/supervisor/signals.py +24 -16
- ominfra/supervisor/spawning.py +31 -0
- ominfra/supervisor/spawningimpl.py +347 -0
- ominfra/supervisor/supervisor.py +52 -77
- ominfra/supervisor/types.py +101 -45
- ominfra/supervisor/users.py +64 -0
- {ominfra-0.0.0.dev126.dist-info → ominfra-0.0.0.dev127.dist-info}/METADATA +3 -3
- {ominfra-0.0.0.dev126.dist-info → ominfra-0.0.0.dev127.dist-info}/RECORD +34 -23
- {ominfra-0.0.0.dev126.dist-info → ominfra-0.0.0.dev127.dist-info}/LICENSE +0 -0
- {ominfra-0.0.0.dev126.dist-info → ominfra-0.0.0.dev127.dist-info}/WHEEL +0 -0
- {ominfra-0.0.0.dev126.dist-info → ominfra-0.0.0.dev127.dist-info}/entry_points.txt +0 -0
- {ominfra-0.0.0.dev126.dist-info → ominfra-0.0.0.dev127.dist-info}/top_level.txt +0 -0
ominfra/supervisor/supervisor.py
CHANGED
@@ -3,25 +3,27 @@ import signal
|
|
3
3
|
import time
|
4
4
|
import typing as ta
|
5
5
|
|
6
|
-
from omlish.lite.cached import cached_nullary
|
7
6
|
from omlish.lite.check import check_isinstance
|
8
7
|
from omlish.lite.check import check_not_none
|
9
8
|
from omlish.lite.logs import log
|
10
|
-
from omlish.lite.typing import
|
9
|
+
from omlish.lite.typing import Func1
|
11
10
|
|
12
11
|
from .configs import ProcessGroupConfig
|
13
12
|
from .context import ServerContextImpl
|
13
|
+
from .dispatchers import Dispatchers
|
14
14
|
from .events import TICK_EVENTS
|
15
15
|
from .events import EventCallbacks
|
16
16
|
from .events import SupervisorRunningEvent
|
17
17
|
from .events import SupervisorStoppingEvent
|
18
18
|
from .groups import ProcessGroup
|
19
|
-
from .groups import
|
19
|
+
from .groups import ProcessGroupManager
|
20
20
|
from .poller import Poller
|
21
|
+
from .processes import PidHistory
|
22
|
+
from .setup import SupervisorSetup
|
21
23
|
from .signals import SignalReceiver
|
22
24
|
from .signals import sig_name
|
23
25
|
from .states import SupervisorState
|
24
|
-
from .types import
|
26
|
+
from .types import OutputDispatcher
|
25
27
|
from .types import Process
|
26
28
|
from .utils import ExitNow
|
27
29
|
from .utils import as_string
|
@@ -38,7 +40,7 @@ class SignalHandler:
|
|
38
40
|
*,
|
39
41
|
context: ServerContextImpl,
|
40
42
|
signal_receiver: SignalReceiver,
|
41
|
-
process_groups:
|
43
|
+
process_groups: ProcessGroupManager,
|
42
44
|
) -> None:
|
43
45
|
super().__init__()
|
44
46
|
|
@@ -78,8 +80,10 @@ class SignalHandler:
|
|
78
80
|
elif sig == signal.SIGUSR2:
|
79
81
|
log.info('received %s indicating log reopen request', sig_name(sig))
|
80
82
|
|
81
|
-
for
|
82
|
-
|
83
|
+
for p in self._process_groups.all_processes():
|
84
|
+
for d in p.get_dispatchers():
|
85
|
+
if isinstance(d, OutputDispatcher):
|
86
|
+
d.reopen_logs()
|
83
87
|
|
84
88
|
else:
|
85
89
|
log.debug('received %s indicating nothing', sig_name(sig))
|
@@ -88,7 +92,8 @@ class SignalHandler:
|
|
88
92
|
##
|
89
93
|
|
90
94
|
|
91
|
-
ProcessGroupFactory
|
95
|
+
class ProcessGroupFactory(Func1[ProcessGroupConfig, ProcessGroup]):
|
96
|
+
pass
|
92
97
|
|
93
98
|
|
94
99
|
class Supervisor:
|
@@ -97,10 +102,12 @@ class Supervisor:
|
|
97
102
|
*,
|
98
103
|
context: ServerContextImpl,
|
99
104
|
poller: Poller,
|
100
|
-
process_groups:
|
105
|
+
process_groups: ProcessGroupManager,
|
101
106
|
signal_handler: SignalHandler,
|
102
107
|
event_callbacks: EventCallbacks,
|
103
108
|
process_group_factory: ProcessGroupFactory,
|
109
|
+
pid_history: PidHistory,
|
110
|
+
setup: SupervisorSetup,
|
104
111
|
) -> None:
|
105
112
|
super().__init__()
|
106
113
|
|
@@ -110,6 +117,8 @@ class Supervisor:
|
|
110
117
|
self._signal_handler = signal_handler
|
111
118
|
self._event_callbacks = event_callbacks
|
112
119
|
self._process_group_factory = process_group_factory
|
120
|
+
self._pid_history = pid_history
|
121
|
+
self._setup = setup
|
113
122
|
|
114
123
|
self._ticks: ta.Dict[int, float] = {}
|
115
124
|
self._stop_groups: ta.Optional[ta.List[ProcessGroup]] = None # list used for priority ordered shutdown
|
@@ -127,31 +136,13 @@ class Supervisor:
|
|
127
136
|
|
128
137
|
#
|
129
138
|
|
130
|
-
class DiffToActive(ta.NamedTuple):
|
131
|
-
added: ta.List[ProcessGroupConfig]
|
132
|
-
changed: ta.List[ProcessGroupConfig]
|
133
|
-
removed: ta.List[ProcessGroupConfig]
|
134
|
-
|
135
|
-
def diff_to_active(self) -> DiffToActive:
|
136
|
-
new = self._context.config.groups or []
|
137
|
-
cur = [group.config for group in self._process_groups]
|
138
|
-
|
139
|
-
curdict = dict(zip([cfg.name for cfg in cur], cur))
|
140
|
-
newdict = dict(zip([cfg.name for cfg in new], new))
|
141
|
-
|
142
|
-
added = [cand for cand in new if cand.name not in curdict]
|
143
|
-
removed = [cand for cand in cur if cand.name not in newdict]
|
144
|
-
|
145
|
-
changed = [cand for cand in new if cand != curdict.get(cand.name, cand)]
|
146
|
-
|
147
|
-
return Supervisor.DiffToActive(added, changed, removed)
|
148
|
-
|
149
139
|
def add_process_group(self, config: ProcessGroupConfig) -> bool:
|
150
140
|
if self._process_groups.get(config.name) is not None:
|
151
141
|
return False
|
152
142
|
|
153
143
|
group = check_isinstance(self._process_group_factory(config), ProcessGroup)
|
154
|
-
group
|
144
|
+
for process in group:
|
145
|
+
process.after_setuid()
|
155
146
|
|
156
147
|
self._process_groups.add(group)
|
157
148
|
|
@@ -165,11 +156,7 @@ class Supervisor:
|
|
165
156
|
|
166
157
|
return True
|
167
158
|
|
168
|
-
|
169
|
-
process_map: ta.Dict[int, Dispatcher] = {}
|
170
|
-
for group in self._process_groups:
|
171
|
-
process_map.update(group.get_dispatchers())
|
172
|
-
return process_map
|
159
|
+
#
|
173
160
|
|
174
161
|
def shutdown_report(self) -> ta.List[Process]:
|
175
162
|
unstopped: ta.List[Process] = []
|
@@ -192,25 +179,12 @@ class Supervisor:
|
|
192
179
|
|
193
180
|
#
|
194
181
|
|
195
|
-
def main(self) -> None:
|
196
|
-
self.setup()
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
if not self._context.first:
|
202
|
-
# prevent crash on libdispatch-based systems, at least for the first request
|
203
|
-
self._context.cleanup_fds()
|
204
|
-
|
205
|
-
self._context.set_uid_or_exit()
|
206
|
-
|
207
|
-
if self._context.first:
|
208
|
-
self._context.set_rlimits_or_exit()
|
209
|
-
|
210
|
-
# this sets the options.logger object delay logger instantiation until after setuid
|
211
|
-
if not self._context.config.nocleanup:
|
212
|
-
# clean up old automatic logs
|
213
|
-
self._context.clear_auto_child_logdir()
|
182
|
+
def main(self, **kwargs: ta.Any) -> None:
|
183
|
+
self._setup.setup()
|
184
|
+
try:
|
185
|
+
self.run(**kwargs)
|
186
|
+
finally:
|
187
|
+
self._setup.cleanup()
|
214
188
|
|
215
189
|
def run(
|
216
190
|
self,
|
@@ -228,12 +202,6 @@ class Supervisor:
|
|
228
202
|
|
229
203
|
self._signal_handler.set_signals()
|
230
204
|
|
231
|
-
if not self._context.config.nodaemon and self._context.first:
|
232
|
-
self._context.daemonize()
|
233
|
-
|
234
|
-
# writing pid file needs to come *after* daemonizing or pid will be wrong
|
235
|
-
self._context.write_pidfile()
|
236
|
-
|
237
205
|
self._event_callbacks.notify(SupervisorRunningEvent())
|
238
206
|
|
239
207
|
while True:
|
@@ -243,7 +211,7 @@ class Supervisor:
|
|
243
211
|
self._run_once()
|
244
212
|
|
245
213
|
finally:
|
246
|
-
self.
|
214
|
+
self._poller.close()
|
247
215
|
|
248
216
|
#
|
249
217
|
|
@@ -272,18 +240,24 @@ class Supervisor:
|
|
272
240
|
# down, so push it back on to the end of the stop group queue
|
273
241
|
self._stop_groups.append(group)
|
274
242
|
|
243
|
+
def get_dispatchers(self) -> Dispatchers:
|
244
|
+
return Dispatchers(
|
245
|
+
d
|
246
|
+
for p in self._process_groups.all_processes()
|
247
|
+
for d in p.get_dispatchers()
|
248
|
+
)
|
249
|
+
|
275
250
|
def _poll(self) -> None:
|
276
|
-
|
277
|
-
combined_map.update(self.get_process_map())
|
251
|
+
dispatchers = self.get_dispatchers()
|
278
252
|
|
279
|
-
|
280
|
-
|
253
|
+
sorted_groups = list(self._process_groups)
|
254
|
+
sorted_groups.sort()
|
281
255
|
|
282
256
|
if self._context.state < SupervisorState.RUNNING:
|
283
257
|
if not self._stopping:
|
284
258
|
# first time, set the stopping flag, do a notification and set stop_groups
|
285
259
|
self._stopping = True
|
286
|
-
self._stop_groups =
|
260
|
+
self._stop_groups = sorted_groups[:]
|
287
261
|
self._event_callbacks.notify(SupervisorStoppingEvent())
|
288
262
|
|
289
263
|
self._ordered_stop_groups_phase_1()
|
@@ -292,7 +266,7 @@ class Supervisor:
|
|
292
266
|
# if there are no unstopped processes (we're done killing everything), it's OK to shutdown or reload
|
293
267
|
raise ExitNow
|
294
268
|
|
295
|
-
for fd, dispatcher in
|
269
|
+
for fd, dispatcher in dispatchers.items():
|
296
270
|
if dispatcher.readable():
|
297
271
|
self._poller.register_readable(fd)
|
298
272
|
if dispatcher.writable():
|
@@ -302,9 +276,9 @@ class Supervisor:
|
|
302
276
|
r, w = self._poller.poll(timeout)
|
303
277
|
|
304
278
|
for fd in r:
|
305
|
-
if fd in
|
279
|
+
if fd in dispatchers:
|
306
280
|
try:
|
307
|
-
dispatcher =
|
281
|
+
dispatcher = dispatchers[fd]
|
308
282
|
log.debug('read event caused by %r', dispatcher)
|
309
283
|
dispatcher.handle_read_event()
|
310
284
|
if not dispatcher.readable():
|
@@ -312,9 +286,9 @@ class Supervisor:
|
|
312
286
|
except ExitNow:
|
313
287
|
raise
|
314
288
|
except Exception: # noqa
|
315
|
-
|
289
|
+
dispatchers[fd].handle_error()
|
316
290
|
else:
|
317
|
-
# if the fd is not in
|
291
|
+
# if the fd is not in combined map, we should unregister it. otherwise, it will be polled every
|
318
292
|
# time, which may cause 100% cpu usage
|
319
293
|
log.debug('unexpected read event from fd %r', fd)
|
320
294
|
try:
|
@@ -323,9 +297,9 @@ class Supervisor:
|
|
323
297
|
pass
|
324
298
|
|
325
299
|
for fd in w:
|
326
|
-
if fd in
|
300
|
+
if fd in dispatchers:
|
327
301
|
try:
|
328
|
-
dispatcher =
|
302
|
+
dispatcher = dispatchers[fd]
|
329
303
|
log.debug('write event caused by %r', dispatcher)
|
330
304
|
dispatcher.handle_write_event()
|
331
305
|
if not dispatcher.writable():
|
@@ -333,7 +307,7 @@ class Supervisor:
|
|
333
307
|
except ExitNow:
|
334
308
|
raise
|
335
309
|
except Exception: # noqa
|
336
|
-
|
310
|
+
dispatchers[fd].handle_error()
|
337
311
|
else:
|
338
312
|
log.debug('unexpected write event from fd %r', fd)
|
339
313
|
try:
|
@@ -341,8 +315,9 @@ class Supervisor:
|
|
341
315
|
except Exception: # noqa
|
342
316
|
pass
|
343
317
|
|
344
|
-
for group in
|
345
|
-
group
|
318
|
+
for group in sorted_groups:
|
319
|
+
for process in group:
|
320
|
+
process.transition()
|
346
321
|
|
347
322
|
def _reap(self, *, once: bool = False, depth: int = 0) -> None:
|
348
323
|
if depth >= 100:
|
@@ -352,13 +327,13 @@ class Supervisor:
|
|
352
327
|
if not pid:
|
353
328
|
return
|
354
329
|
|
355
|
-
process = self.
|
330
|
+
process = self._pid_history.get(pid, None)
|
356
331
|
if process is None:
|
357
332
|
_, msg = decode_wait_status(check_not_none(sts))
|
358
333
|
log.info('reaped unknown pid %s (%s)', pid, msg)
|
359
334
|
else:
|
360
335
|
process.finish(check_not_none(sts))
|
361
|
-
del self.
|
336
|
+
del self._pid_history[pid]
|
362
337
|
|
363
338
|
if not once:
|
364
339
|
# keep reaping until no more kids to reap, but don't recurse infinitely
|
ominfra/supervisor/types.py
CHANGED
@@ -3,6 +3,7 @@ import abc
|
|
3
3
|
import functools
|
4
4
|
import typing as ta
|
5
5
|
|
6
|
+
from .collections import KeyedCollectionAccessors
|
6
7
|
from .configs import ProcessConfig
|
7
8
|
from .configs import ProcessGroupConfig
|
8
9
|
from .configs import ServerConfig
|
@@ -10,6 +11,36 @@ from .states import ProcessState
|
|
10
11
|
from .states import SupervisorState
|
11
12
|
|
12
13
|
|
14
|
+
if ta.TYPE_CHECKING:
|
15
|
+
from .dispatchers import Dispatchers
|
16
|
+
|
17
|
+
|
18
|
+
##
|
19
|
+
|
20
|
+
|
21
|
+
ServerEpoch = ta.NewType('ServerEpoch', int)
|
22
|
+
|
23
|
+
|
24
|
+
##
|
25
|
+
|
26
|
+
|
27
|
+
@functools.total_ordering
|
28
|
+
class ConfigPriorityOrdered(abc.ABC):
|
29
|
+
@property
|
30
|
+
@abc.abstractmethod
|
31
|
+
def config(self) -> ta.Any:
|
32
|
+
raise NotImplementedError
|
33
|
+
|
34
|
+
def __lt__(self, other):
|
35
|
+
return self.config.priority < other.config.priority
|
36
|
+
|
37
|
+
def __eq__(self, other):
|
38
|
+
return self.config.priority == other.config.priority
|
39
|
+
|
40
|
+
|
41
|
+
##
|
42
|
+
|
43
|
+
|
13
44
|
class ServerContext(abc.ABC):
|
14
45
|
@property
|
15
46
|
@abc.abstractmethod
|
@@ -31,7 +62,42 @@ class ServerContext(abc.ABC):
|
|
31
62
|
raise NotImplementedError
|
32
63
|
|
33
64
|
|
65
|
+
##
|
66
|
+
|
67
|
+
|
34
68
|
class Dispatcher(abc.ABC):
|
69
|
+
@property
|
70
|
+
@abc.abstractmethod
|
71
|
+
def process(self) -> 'Process':
|
72
|
+
raise NotImplementedError
|
73
|
+
|
74
|
+
@property
|
75
|
+
@abc.abstractmethod
|
76
|
+
def channel(self) -> str:
|
77
|
+
raise NotImplementedError
|
78
|
+
|
79
|
+
@property
|
80
|
+
@abc.abstractmethod
|
81
|
+
def fd(self) -> int:
|
82
|
+
raise NotImplementedError
|
83
|
+
|
84
|
+
@property
|
85
|
+
@abc.abstractmethod
|
86
|
+
def closed(self) -> bool:
|
87
|
+
raise NotImplementedError
|
88
|
+
|
89
|
+
#
|
90
|
+
|
91
|
+
@abc.abstractmethod
|
92
|
+
def close(self) -> None:
|
93
|
+
raise NotImplementedError
|
94
|
+
|
95
|
+
@abc.abstractmethod
|
96
|
+
def handle_error(self) -> None:
|
97
|
+
raise NotImplementedError
|
98
|
+
|
99
|
+
#
|
100
|
+
|
35
101
|
@abc.abstractmethod
|
36
102
|
def readable(self) -> bool:
|
37
103
|
raise NotImplementedError
|
@@ -40,26 +106,25 @@ class Dispatcher(abc.ABC):
|
|
40
106
|
def writable(self) -> bool:
|
41
107
|
raise NotImplementedError
|
42
108
|
|
109
|
+
#
|
110
|
+
|
43
111
|
def handle_read_event(self) -> None:
|
44
112
|
raise TypeError
|
45
113
|
|
46
114
|
def handle_write_event(self) -> None:
|
47
115
|
raise TypeError
|
48
116
|
|
117
|
+
|
118
|
+
class OutputDispatcher(Dispatcher, abc.ABC):
|
49
119
|
@abc.abstractmethod
|
50
|
-
def
|
120
|
+
def remove_logs(self) -> None:
|
51
121
|
raise NotImplementedError
|
52
122
|
|
53
|
-
@property
|
54
123
|
@abc.abstractmethod
|
55
|
-
def
|
124
|
+
def reopen_logs(self) -> None:
|
56
125
|
raise NotImplementedError
|
57
126
|
|
58
127
|
|
59
|
-
class OutputDispatcher(Dispatcher, abc.ABC):
|
60
|
-
pass
|
61
|
-
|
62
|
-
|
63
128
|
class InputDispatcher(Dispatcher, abc.ABC):
|
64
129
|
@abc.abstractmethod
|
65
130
|
def write(self, chars: ta.Union[bytes, str]) -> None:
|
@@ -70,11 +135,13 @@ class InputDispatcher(Dispatcher, abc.ABC):
|
|
70
135
|
raise NotImplementedError
|
71
136
|
|
72
137
|
|
73
|
-
|
74
|
-
|
138
|
+
##
|
139
|
+
|
140
|
+
|
141
|
+
class Process(ConfigPriorityOrdered, abc.ABC):
|
75
142
|
@property
|
76
143
|
@abc.abstractmethod
|
77
|
-
def
|
144
|
+
def name(self) -> str:
|
78
145
|
raise NotImplementedError
|
79
146
|
|
80
147
|
@property
|
@@ -82,27 +149,25 @@ class Process(abc.ABC):
|
|
82
149
|
def config(self) -> ProcessConfig:
|
83
150
|
raise NotImplementedError
|
84
151
|
|
85
|
-
def __lt__(self, other):
|
86
|
-
return self.config.priority < other.config.priority
|
87
|
-
|
88
|
-
def __eq__(self, other):
|
89
|
-
return self.config.priority == other.config.priority
|
90
|
-
|
91
152
|
@property
|
92
153
|
@abc.abstractmethod
|
93
|
-
def
|
154
|
+
def group(self) -> 'ProcessGroup':
|
94
155
|
raise NotImplementedError
|
95
156
|
|
157
|
+
@property
|
96
158
|
@abc.abstractmethod
|
97
|
-
def
|
159
|
+
def pid(self) -> int:
|
98
160
|
raise NotImplementedError
|
99
161
|
|
162
|
+
#
|
163
|
+
|
164
|
+
@property
|
100
165
|
@abc.abstractmethod
|
101
|
-
def
|
166
|
+
def context(self) -> ServerContext:
|
102
167
|
raise NotImplementedError
|
103
168
|
|
104
169
|
@abc.abstractmethod
|
105
|
-
def
|
170
|
+
def finish(self, sts: int) -> None:
|
106
171
|
raise NotImplementedError
|
107
172
|
|
108
173
|
@abc.abstractmethod
|
@@ -122,50 +187,41 @@ class Process(abc.ABC):
|
|
122
187
|
raise NotImplementedError
|
123
188
|
|
124
189
|
@abc.abstractmethod
|
125
|
-
def
|
126
|
-
raise NotImplementedError
|
127
|
-
|
128
|
-
@abc.abstractmethod
|
129
|
-
def get_dispatchers(self) -> ta.Mapping[int, Dispatcher]:
|
190
|
+
def after_setuid(self) -> None:
|
130
191
|
raise NotImplementedError
|
131
192
|
|
132
|
-
|
133
|
-
@functools.total_ordering
|
134
|
-
class ProcessGroup(abc.ABC):
|
135
|
-
@property
|
136
193
|
@abc.abstractmethod
|
137
|
-
def
|
194
|
+
def get_dispatchers(self) -> 'Dispatchers':
|
138
195
|
raise NotImplementedError
|
139
196
|
|
140
|
-
def __lt__(self, other):
|
141
|
-
return self.config.priority < other.config.priority
|
142
197
|
|
143
|
-
|
144
|
-
return self.config.priority == other.config.priority
|
198
|
+
##
|
145
199
|
|
146
|
-
@abc.abstractmethod
|
147
|
-
def transition(self) -> None:
|
148
|
-
raise NotImplementedError
|
149
|
-
|
150
|
-
@abc.abstractmethod
|
151
|
-
def stop_all(self) -> None:
|
152
|
-
raise NotImplementedError
|
153
200
|
|
201
|
+
class ProcessGroup(
|
202
|
+
ConfigPriorityOrdered,
|
203
|
+
KeyedCollectionAccessors[str, Process],
|
204
|
+
abc.ABC,
|
205
|
+
):
|
154
206
|
@property
|
155
207
|
@abc.abstractmethod
|
156
208
|
def name(self) -> str:
|
157
209
|
raise NotImplementedError
|
158
210
|
|
211
|
+
@property
|
159
212
|
@abc.abstractmethod
|
160
|
-
def
|
213
|
+
def config(self) -> ProcessGroupConfig:
|
161
214
|
raise NotImplementedError
|
162
215
|
|
216
|
+
@property
|
163
217
|
@abc.abstractmethod
|
164
|
-
def
|
218
|
+
def by_name(self) -> ta.Mapping[str, Process]:
|
165
219
|
raise NotImplementedError
|
166
220
|
|
221
|
+
#
|
222
|
+
|
167
223
|
@abc.abstractmethod
|
168
|
-
def
|
224
|
+
def stop_all(self) -> None:
|
169
225
|
raise NotImplementedError
|
170
226
|
|
171
227
|
@abc.abstractmethod
|
@@ -173,5 +229,5 @@ class ProcessGroup(abc.ABC):
|
|
173
229
|
raise NotImplementedError
|
174
230
|
|
175
231
|
@abc.abstractmethod
|
176
|
-
def
|
232
|
+
def before_remove(self) -> None:
|
177
233
|
raise NotImplementedError
|
@@ -0,0 +1,64 @@
|
|
1
|
+
# ruff: noqa: UP007
|
2
|
+
import dataclasses as dc
|
3
|
+
import grp
|
4
|
+
import pwd
|
5
|
+
|
6
|
+
|
7
|
+
##
|
8
|
+
|
9
|
+
|
10
|
+
def name_to_uid(name: str) -> int:
|
11
|
+
try:
|
12
|
+
uid = int(name)
|
13
|
+
except ValueError:
|
14
|
+
try:
|
15
|
+
pwdrec = pwd.getpwnam(name)
|
16
|
+
except KeyError:
|
17
|
+
raise ValueError(f'Invalid user name {name}') # noqa
|
18
|
+
uid = pwdrec[2]
|
19
|
+
else:
|
20
|
+
try:
|
21
|
+
pwd.getpwuid(uid) # check if uid is valid
|
22
|
+
except KeyError:
|
23
|
+
raise ValueError(f'Invalid user id {name}') # noqa
|
24
|
+
return uid
|
25
|
+
|
26
|
+
|
27
|
+
def name_to_gid(name: str) -> int:
|
28
|
+
try:
|
29
|
+
gid = int(name)
|
30
|
+
except ValueError:
|
31
|
+
try:
|
32
|
+
grprec = grp.getgrnam(name)
|
33
|
+
except KeyError:
|
34
|
+
raise ValueError(f'Invalid group name {name}') # noqa
|
35
|
+
gid = grprec[2]
|
36
|
+
else:
|
37
|
+
try:
|
38
|
+
grp.getgrgid(gid) # check if gid is valid
|
39
|
+
except KeyError:
|
40
|
+
raise ValueError(f'Invalid group id {name}') # noqa
|
41
|
+
return gid
|
42
|
+
|
43
|
+
|
44
|
+
def gid_for_uid(uid: int) -> int:
|
45
|
+
pwrec = pwd.getpwuid(uid)
|
46
|
+
return pwrec[3]
|
47
|
+
|
48
|
+
|
49
|
+
##
|
50
|
+
|
51
|
+
|
52
|
+
@dc.dataclass(frozen=True)
|
53
|
+
class User:
|
54
|
+
name: str
|
55
|
+
uid: int
|
56
|
+
gid: int
|
57
|
+
|
58
|
+
|
59
|
+
def get_user(name: str) -> User:
|
60
|
+
return User(
|
61
|
+
name=name,
|
62
|
+
uid=(uid := name_to_uid(name)),
|
63
|
+
gid=gid_for_uid(uid),
|
64
|
+
)
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.1
|
2
2
|
Name: ominfra
|
3
|
-
Version: 0.0.0.
|
3
|
+
Version: 0.0.0.dev127
|
4
4
|
Summary: ominfra
|
5
5
|
Author: wrmsr
|
6
6
|
License: BSD-3-Clause
|
@@ -12,8 +12,8 @@ Classifier: Operating System :: OS Independent
|
|
12
12
|
Classifier: Operating System :: POSIX
|
13
13
|
Requires-Python: >=3.12
|
14
14
|
License-File: LICENSE
|
15
|
-
Requires-Dist: omdev==0.0.0.
|
16
|
-
Requires-Dist: omlish==0.0.0.
|
15
|
+
Requires-Dist: omdev==0.0.0.dev127
|
16
|
+
Requires-Dist: omlish==0.0.0.dev127
|
17
17
|
Provides-Extra: all
|
18
18
|
Requires-Dist: paramiko~=3.5; extra == "all"
|
19
19
|
Requires-Dist: asyncssh~=2.18; extra == "all"
|