ominfra 0.0.0.dev118__py3-none-any.whl → 0.0.0.dev120__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.
@@ -1,4 +1,4 @@
1
1
  if __name__ == '__main__':
2
- from .supervisor import main
2
+ from .main import main
3
3
 
4
4
  main()
@@ -2,7 +2,6 @@
2
2
  import errno
3
3
  import fcntl
4
4
  import grp
5
- import logging
6
5
  import os
7
6
  import pwd
8
7
  import re
@@ -12,6 +11,8 @@ import stat
12
11
  import typing as ta
13
12
  import warnings
14
13
 
14
+ from omlish.lite.logs import log
15
+
15
16
  from .compat import SignalReceiver
16
17
  from .compat import close_fd
17
18
  from .compat import mktempfile
@@ -23,6 +24,7 @@ from .datatypes import name_to_uid
23
24
  from .exceptions import NoPermissionError
24
25
  from .exceptions import NotExecutableError
25
26
  from .exceptions import NotFoundError
27
+ from .poller import BasePoller
26
28
  from .poller import Poller
27
29
  from .states import SupervisorState
28
30
  from .states import SupervisorStates
@@ -30,41 +32,47 @@ from .types import AbstractServerContext
30
32
  from .types import AbstractSubprocess
31
33
 
32
34
 
33
- log = logging.getLogger(__name__)
34
-
35
-
36
35
  class ServerContext(AbstractServerContext):
37
- first = False
38
- test = False
39
-
40
- ##
41
-
42
- def __init__(self, config: ServerConfig) -> None:
36
+ def __init__(
37
+ self,
38
+ config: ServerConfig,
39
+ *,
40
+ epoch: int = 0,
41
+ ) -> None:
43
42
  super().__init__()
44
43
 
45
44
  self._config = config
45
+ self._epoch = epoch
46
46
 
47
47
  self._pid_history: ta.Dict[int, AbstractSubprocess] = {}
48
48
  self._state: SupervisorState = SupervisorStates.RUNNING
49
49
 
50
- self.signal_receiver = SignalReceiver()
50
+ self._signal_receiver = SignalReceiver()
51
51
 
52
- self.poller = Poller()
52
+ self._poller: BasePoller = Poller()
53
53
 
54
- if self.config.user is not None:
55
- uid = name_to_uid(self.config.user)
56
- self.uid = uid
57
- self.gid = gid_for_uid(uid)
54
+ if config.user is not None:
55
+ uid = name_to_uid(config.user)
56
+ self._uid: ta.Optional[int] = uid
57
+ self._gid: ta.Optional[int] = gid_for_uid(uid)
58
58
  else:
59
- self.uid = None
60
- self.gid = None
59
+ self._uid = None
60
+ self._gid = None
61
61
 
62
- self.unlink_pidfile = False
62
+ self._unlink_pidfile = False
63
63
 
64
64
  @property
65
65
  def config(self) -> ServerConfig:
66
66
  return self._config
67
67
 
68
+ @property
69
+ def epoch(self) -> int:
70
+ return self._epoch
71
+
72
+ @property
73
+ def first(self) -> bool:
74
+ return not self._epoch
75
+
68
76
  @property
69
77
  def state(self) -> SupervisorState:
70
78
  return self._state
@@ -72,17 +80,26 @@ class ServerContext(AbstractServerContext):
72
80
  def set_state(self, state: SupervisorState) -> None:
73
81
  self._state = state
74
82
 
83
+ @property
84
+ def poller(self) -> BasePoller:
85
+ return self._poller
86
+
75
87
  @property
76
88
  def pid_history(self) -> ta.Dict[int, AbstractSubprocess]:
77
89
  return self._pid_history
78
90
 
79
- uid: ta.Optional[int]
80
- gid: ta.Optional[int]
91
+ @property
92
+ def uid(self) -> ta.Optional[int]:
93
+ return self._uid
94
+
95
+ @property
96
+ def gid(self) -> ta.Optional[int]:
97
+ return self._gid
81
98
 
82
99
  ##
83
100
 
84
101
  def set_signals(self) -> None:
85
- self.signal_receiver.install(
102
+ self._signal_receiver.install(
86
103
  signal.SIGTERM,
87
104
  signal.SIGINT,
88
105
  signal.SIGQUIT,
@@ -193,7 +210,7 @@ class ServerContext(AbstractServerContext):
193
210
  ))
194
211
 
195
212
  def cleanup(self) -> None:
196
- if self.unlink_pidfile:
213
+ if self._unlink_pidfile:
197
214
  try_unlink(self.config.pidfile)
198
215
  self.poller.close()
199
216
 
@@ -246,6 +263,7 @@ class ServerContext(AbstractServerContext):
246
263
  # Parent
247
264
  log.debug('supervisord forked; parent exiting')
248
265
  real_exit(0)
266
+
249
267
  # Child
250
268
  log.info('daemonizing the supervisord process')
251
269
  if self.config.directory:
@@ -255,11 +273,15 @@ class ServerContext(AbstractServerContext):
255
273
  log.critical("can't chdir into %r: %s", self.config.directory, err)
256
274
  else:
257
275
  log.info('set current directory: %r', self.config.directory)
276
+
258
277
  os.dup2(0, os.open('/dev/null', os.O_RDONLY))
259
278
  os.dup2(1, os.open('/dev/null', os.O_WRONLY))
260
279
  os.dup2(2, os.open('/dev/null', os.O_WRONLY))
280
+
261
281
  os.setsid()
282
+
262
283
  os.umask(self.config.umask)
284
+
263
285
  # XXX Stevens, in his Advanced Unix book, section 13.3 (page 417) recommends calling umask(0) and closing unused
264
286
  # file descriptors. In his Network Programming book, he additionally recommends ignoring SIGHUP and forking
265
287
  # again after the setsid() call, for obscure SVR4 reasons.
@@ -274,7 +296,7 @@ class ServerContext(AbstractServerContext):
274
296
  return logfile
275
297
 
276
298
  def get_signal(self) -> ta.Optional[int]:
277
- return self.signal_receiver.get_signal()
299
+ return self._signal_receiver.get_signal()
278
300
 
279
301
  def write_pidfile(self) -> None:
280
302
  pid = os.getpid()
@@ -284,7 +306,7 @@ class ServerContext(AbstractServerContext):
284
306
  except OSError:
285
307
  log.critical('could not write pidfile %s', self.config.pidfile)
286
308
  else:
287
- self.unlink_pidfile = True
309
+ self._unlink_pidfile = True
288
310
  log.info('supervisord started with pid %s', pid)
289
311
 
290
312
 
@@ -337,11 +359,14 @@ def drop_privileges(user: ta.Union[int, str, None]) -> ta.Optional[str]:
337
359
  os.setgroups(groups)
338
360
  except OSError:
339
361
  return 'Could not set groups of effective user'
362
+
340
363
  try:
341
364
  os.setgid(gid)
342
365
  except OSError:
343
366
  return 'Could not set group id of effective user'
367
+
344
368
  os.setuid(uid)
369
+
345
370
  return None
346
371
 
347
372
 
@@ -5,21 +5,20 @@ import logging
5
5
  import os
6
6
  import typing as ta
7
7
 
8
+ from omlish.lite.logs import log
9
+
8
10
  from .compat import as_bytes
9
11
  from .compat import compact_traceback
10
12
  from .compat import find_prefix_at_end
11
13
  from .compat import readfd
12
14
  from .compat import strip_escapes
13
15
  from .configs import ProcessConfig
16
+ from .events import EVENT_CALLBACKS
14
17
  from .events import ProcessLogStderrEvent
15
18
  from .events import ProcessLogStdoutEvent
16
- from .events import notify_event
17
19
  from .types import AbstractSubprocess
18
20
 
19
21
 
20
- log = logging.getLogger(__name__)
21
-
22
-
23
22
  class Dispatcher(abc.ABC):
24
23
 
25
24
  def __init__(self, process: AbstractSubprocess, channel: str, fd: int) -> None:
@@ -176,16 +175,16 @@ class OutputDispatcher(Dispatcher):
176
175
  # )
177
176
 
178
177
  def remove_logs(self):
179
- for log in (self._normal_log, self._capture_log):
180
- if log is not None:
181
- for handler in log.handlers:
178
+ for l in (self._normal_log, self._capture_log):
179
+ if l is not None:
180
+ for handler in l.handlers:
182
181
  handler.remove() # type: ignore
183
182
  handler.reopen() # type: ignore
184
183
 
185
184
  def reopen_logs(self):
186
- for log in (self._normal_log, self._capture_log):
187
- if log is not None:
188
- for handler in log.handlers:
185
+ for l in (self._normal_log, self._capture_log):
186
+ if l is not None:
187
+ for handler in l.handlers:
189
188
  handler.reopen() # type: ignore
190
189
 
191
190
  def _log(self, data):
@@ -208,10 +207,10 @@ class OutputDispatcher(Dispatcher):
208
207
 
209
208
  if self._channel == 'stdout':
210
209
  if self._stdout_events_enabled:
211
- notify_event(ProcessLogStdoutEvent(self._process, self._process.pid, data))
210
+ EVENT_CALLBACKS.notify(ProcessLogStdoutEvent(self._process, self._process.pid, data))
212
211
 
213
212
  elif self._stderr_events_enabled:
214
- notify_event(ProcessLogStderrEvent(self._process, self._process.pid, data))
213
+ EVENT_CALLBACKS.notify(ProcessLogStderrEvent(self._process, self._process.pid, data))
215
214
 
216
215
  def record_output(self):
217
216
  if self._capture_log is None:
@@ -262,7 +261,7 @@ class OutputDispatcher(Dispatcher):
262
261
  channel = self._channel
263
262
  procname = self._process.config.name
264
263
  event = self.event_type(self._process, self._process.pid, data)
265
- notify_event(event)
264
+ EVENT_CALLBACKS.notify(event)
266
265
 
267
266
  log.debug('%r %s emitted a comm event', procname, channel)
268
267
  for handler in self._capture_log.handlers:
@@ -29,12 +29,9 @@ class EventCallbacks:
29
29
 
30
30
  EVENT_CALLBACKS = EventCallbacks()
31
31
 
32
- notify_event = EVENT_CALLBACKS.notify
33
- clear_events = EVENT_CALLBACKS.clear
34
-
35
32
 
36
33
  class Event(abc.ABC): # noqa
37
- """Abstract event type """
34
+ """Abstract event type."""
38
35
 
39
36
 
40
37
  class ProcessLogEvent(Event, abc.ABC):
@@ -114,7 +111,7 @@ class RemoteCommunicationEvent(Event):
114
111
 
115
112
 
116
113
  class SupervisorStateChangeEvent(Event):
117
- """ Abstract class """
114
+ """Abstract class."""
118
115
 
119
116
  def payload(self):
120
117
  return ''
@@ -136,7 +133,7 @@ class EventRejectedEvent: # purposely does not subclass Event
136
133
 
137
134
 
138
135
  class ProcessStateEvent(Event):
139
- """ Abstract class, never raised directly """
136
+ """Abstract class, never raised directly."""
140
137
  frm = None
141
138
  to = None
142
139
 
@@ -225,7 +222,7 @@ class ProcessGroupRemovedEvent(ProcessGroupEvent):
225
222
 
226
223
 
227
224
  class TickEvent(Event):
228
- """ Abstract """
225
+ """Abstract."""
229
226
 
230
227
  def __init__(self, when, supervisord):
231
228
  super().__init__()
@@ -0,0 +1,68 @@
1
+ #!/usr/bin/env python3
2
+ # ruff: noqa: UP006 UP007
3
+ # @omlish-amalg ../scripts/supervisor.py
4
+ import itertools
5
+ import json
6
+ import typing as ta
7
+
8
+ from omlish.lite.journald import journald_log_handler_factory
9
+ from omlish.lite.logs import configure_standard_logging
10
+ from omlish.lite.marshal import unmarshal_obj
11
+
12
+ from .compat import ExitNow
13
+ from .configs import ServerConfig
14
+ from .context import ServerContext
15
+ from .states import SupervisorStates
16
+ from .supervisor import Supervisor
17
+
18
+
19
+ def main(
20
+ argv: ta.Optional[ta.Sequence[str]] = None,
21
+ *,
22
+ no_logging: bool = False,
23
+ ) -> None:
24
+ import argparse
25
+
26
+ parser = argparse.ArgumentParser()
27
+ parser.add_argument('config_file', metavar='config-file')
28
+ parser.add_argument('--no-journald', action='store_true')
29
+ args = parser.parse_args(argv)
30
+
31
+ #
32
+
33
+ if not (cf := args.config_file):
34
+ raise RuntimeError('No config file specified')
35
+
36
+ if not no_logging:
37
+ configure_standard_logging(
38
+ 'INFO',
39
+ handler_factory=journald_log_handler_factory if not args.no_journald else None,
40
+ )
41
+
42
+ #
43
+
44
+ # if we hup, restart by making a new Supervisor()
45
+ for epoch in itertools.count():
46
+ with open(cf) as f:
47
+ config_src = f.read()
48
+
49
+ config_dct = json.loads(config_src)
50
+ config: ServerConfig = unmarshal_obj(config_dct, ServerConfig)
51
+
52
+ context = ServerContext(
53
+ config,
54
+ epoch=epoch,
55
+ )
56
+
57
+ supervisor = Supervisor(context)
58
+ try:
59
+ supervisor.main()
60
+ except ExitNow:
61
+ pass
62
+
63
+ if context.state < SupervisorStates.RESTARTING:
64
+ break
65
+
66
+
67
+ if __name__ == '__main__':
68
+ main()
@@ -1,13 +1,11 @@
1
1
  # ruff: noqa: UP006 UP007
2
2
  import abc
3
3
  import errno
4
- import logging
5
4
  import select
6
5
  import sys
7
6
  import typing as ta
8
7
 
9
-
10
- log = logging.getLogger(__name__)
8
+ from omlish.lite.logs import log
11
9
 
12
10
 
13
11
  class BasePoller(abc.ABC):
@@ -1,14 +1,15 @@
1
1
  # ruff: noqa: UP006 UP007
2
2
  import errno
3
3
  import functools
4
- import logging
5
- import os
4
+ import os.path
6
5
  import shlex
7
6
  import signal
8
7
  import time
9
8
  import traceback
10
9
  import typing as ta
11
10
 
11
+ from omlish.lite.logs import log
12
+
12
13
  from .compat import as_bytes
13
14
  from .compat import as_string
14
15
  from .compat import close_fd
@@ -29,6 +30,7 @@ from .datatypes import RestartUnconditionally
29
30
  from .dispatchers import Dispatcher
30
31
  from .dispatchers import InputDispatcher
31
32
  from .dispatchers import OutputDispatcher
33
+ from .events import EVENT_CALLBACKS
32
34
  from .events import EventRejectedEvent
33
35
  from .events import ProcessCommunicationEvent
34
36
  from .events import ProcessCommunicationStderrEvent
@@ -42,7 +44,6 @@ from .events import ProcessStateStartingEvent
42
44
  from .events import ProcessStateStoppedEvent
43
45
  from .events import ProcessStateStoppingEvent
44
46
  from .events import ProcessStateUnknownEvent
45
- from .events import notify_event
46
47
  from .exceptions import BadCommandError
47
48
  from .exceptions import ProcessError
48
49
  from .states import STOPPED_STATES
@@ -54,9 +55,6 @@ from .types import AbstractServerContext
54
55
  from .types import AbstractSubprocess
55
56
 
56
57
 
57
- log = logging.getLogger(__name__)
58
-
59
-
60
58
  @functools.total_ordering
61
59
  class Subprocess(AbstractSubprocess):
62
60
  """A class to manage a subprocess."""
@@ -209,7 +207,7 @@ class Subprocess(AbstractSubprocess):
209
207
  event_class = self.event_map.get(new_state)
210
208
  if event_class is not None:
211
209
  event = event_class(self, old_state, expected)
212
- notify_event(event)
210
+ EVENT_CALLBACKS.notify(event)
213
211
 
214
212
  return True
215
213
 
@@ -326,6 +324,7 @@ class Subprocess(AbstractSubprocess):
326
324
  os.dup2(self._pipes['child_stdout'], 2)
327
325
  else:
328
326
  os.dup2(self._pipes['child_stderr'], 2)
327
+ # FIXME: leave debugger fds
329
328
  for i in range(3, self.context.config.minfds):
330
329
  close_fd(i)
331
330
 
@@ -361,7 +360,7 @@ class Subprocess(AbstractSubprocess):
361
360
  cwd = self.config.directory
362
361
  try:
363
362
  if cwd is not None:
364
- os.chdir(cwd)
363
+ os.chdir(os.path.expanduser(cwd))
365
364
  except OSError as why:
366
365
  code = errno.errorcode.get(why.args[0], why.args[0])
367
366
  msg = f"couldn't chdir to {cwd}: {code}\n"
@@ -417,7 +416,7 @@ class Subprocess(AbstractSubprocess):
417
416
  return self.kill(self.config.stopsignal)
418
417
 
419
418
  def stop_report(self) -> None:
420
- """ Log a 'waiting for x to stop' message with throttling. """
419
+ """Log a 'waiting for x to stop' message with throttling."""
421
420
  if self.state == ProcessStates.STOPPING:
422
421
  now = time.time()
423
422
 
@@ -547,7 +546,7 @@ class Subprocess(AbstractSubprocess):
547
546
  return None
548
547
 
549
548
  def finish(self, sts: int) -> None:
550
- """ The process was reaped and we need to report and manage its state """
549
+ """The process was reaped and we need to report and manage its state."""
551
550
 
552
551
  self.drain()
553
552
 
@@ -628,7 +627,7 @@ class Subprocess(AbstractSubprocess):
628
627
  # system that this event was rejected so it can be processed again.
629
628
  if self.event is not None:
630
629
  # Note: this should only be true if we were in the BUSY state when finish() was called.
631
- notify_event(EventRejectedEvent(self, self.event)) # type: ignore
630
+ EVENT_CALLBACKS.notify(EventRejectedEvent(self, self.event)) # type: ignore
632
631
  self.event = None
633
632
 
634
633
  def set_uid(self) -> ta.Optional[str]: