ominfra 0.0.0.dev118__py3-none-any.whl → 0.0.0.dev120__py3-none-any.whl

Sign up to get free protection for your applications and to get access to all the features.
@@ -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]: