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