ominfra 0.0.0.dev125__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 +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"
|