ominfra 0.0.0.dev150__py3-none-any.whl → 0.0.0.dev152__py3-none-any.whl

Sign up to get free protection for your applications and to get access to all the features.
@@ -41,8 +41,8 @@ from omlish.lite.cached import cached_nullary
41
41
  from omlish.lite.check import check
42
42
  from omlish.lite.contextmanagers import ExitStacked
43
43
  from omlish.lite.logs import log
44
- from omlish.lite.pidfile import Pidfile
45
44
  from omlish.lite.runtime import is_debugger_attached
45
+ from omlish.os.pidfile import Pidfile
46
46
 
47
47
  from ....journald.messages import JournalctlMessage # noqa
48
48
  from ....journald.tailer import JournalctlTailerWorker
ominfra/manage/main.py CHANGED
@@ -8,6 +8,7 @@ manage.py -s 'ssh -i /foo/bar.pem foo@bar.baz' -q --python=python3.8
8
8
  import asyncio
9
9
  import contextlib
10
10
  import json
11
+ import sys
11
12
  import typing as ta
12
13
 
13
14
  from omlish.argparse.cli import ArgparseCli
@@ -31,7 +32,7 @@ from .remote.spawning import RemoteSpawning
31
32
 
32
33
  class MainCli(ArgparseCli):
33
34
  @argparse_command(
34
- argparse_arg('--payload-file'),
35
+ argparse_arg('--_payload-file'),
35
36
 
36
37
  argparse_arg('-s', '--shell'),
37
38
  argparse_arg('-q', '--shell-quote', action='store_true'),
@@ -49,10 +50,7 @@ class MainCli(ArgparseCli):
49
50
 
50
51
  argparse_arg('command', nargs='+'),
51
52
  )
52
- def run(self) -> None:
53
- asyncio.run(self._async_run())
54
-
55
- async def _async_run(self) -> None:
53
+ async def run(self) -> None:
56
54
  bs = MainBootstrap(
57
55
  main_config=MainConfig(
58
56
  log_level='DEBUG' if self.args.debug else 'INFO',
@@ -61,7 +59,7 @@ class MainCli(ArgparseCli):
61
59
  ),
62
60
 
63
61
  remote_config=RemoteConfig(
64
- payload_file=self.args.payload_file, # noqa
62
+ payload_file=self.args._payload_file, # noqa
65
63
 
66
64
  pycharm_remote_debug=PycharmRemoteDebug(
67
65
  port=self.args.pycharm_debug_port,
@@ -124,7 +122,7 @@ class MainCli(ArgparseCli):
124
122
 
125
123
 
126
124
  def _main() -> None:
127
- MainCli().call_and_exit()
125
+ sys.exit(asyncio.run(MainCli().async_cli_run()))
128
126
 
129
127
 
130
128
  if __name__ == '__main__':
@@ -12,11 +12,11 @@ from omlish.lite.asyncio.asyncio import asyncio_open_stream_reader
12
12
  from omlish.lite.asyncio.asyncio import asyncio_open_stream_writer
13
13
  from omlish.lite.cached import cached_nullary
14
14
  from omlish.lite.check import check
15
- from omlish.lite.deathsig import set_process_deathsig
16
15
  from omlish.lite.inject import Injector
17
16
  from omlish.lite.logs import log
18
17
  from omlish.lite.marshal import ObjMarshalerManager
19
18
  from omlish.lite.pycharm import pycharm_debug_connect
19
+ from omlish.os.deathsig import set_process_deathsig
20
20
 
21
21
  from ...pyremote import pyremote_bootstrap_finalize
22
22
  from ..bootstrap import MainBootstrap
@@ -64,7 +64,7 @@ CallableT = ta.TypeVar('CallableT', bound=ta.Callable)
64
64
  # ../../../../omlish/lite/check.py
65
65
  SizedT = ta.TypeVar('SizedT', bound=ta.Sized)
66
66
  CheckMessage = ta.Union[str, ta.Callable[..., ta.Optional[str]], None] # ta.TypeAlias
67
- CheckLateConfigureFn = ta.Callable[['Checks'], None]
67
+ CheckLateConfigureFn = ta.Callable[['Checks'], None] # ta.TypeAlias
68
68
  CheckOnRaiseFn = ta.Callable[[Exception], None] # ta.TypeAlias
69
69
  CheckExceptionFactory = ta.Callable[..., Exception] # ta.TypeAlias
70
70
  CheckArgsRenderer = ta.Callable[..., ta.Optional[str]] # ta.TypeAlias
@@ -79,7 +79,7 @@ ConfigMapping = ta.Mapping[str, ta.Any]
79
79
  ThreadWorkerT = ta.TypeVar('ThreadWorkerT', bound='ThreadWorker')
80
80
 
81
81
  # ../../../../omlish/lite/subprocesses.py
82
- SubprocessChannelOption = ta.Literal['pipe', 'stdout', 'devnull']
82
+ SubprocessChannelOption = ta.Literal['pipe', 'stdout', 'devnull'] # ta.TypeAlias
83
83
 
84
84
 
85
85
  ########################################
@@ -1439,73 +1439,6 @@ json_dump_compact: ta.Callable[..., bytes] = functools.partial(json.dump, **JSON
1439
1439
  json_dumps_compact: ta.Callable[..., str] = functools.partial(json.dumps, **JSON_COMPACT_KWARGS)
1440
1440
 
1441
1441
 
1442
- ########################################
1443
- # ../../../../../omlish/lite/pidfile.py
1444
-
1445
-
1446
- class Pidfile:
1447
- def __init__(self, path: str) -> None:
1448
- super().__init__()
1449
- self._path = path
1450
-
1451
- _f: ta.TextIO
1452
-
1453
- def __repr__(self) -> str:
1454
- return f'{self.__class__.__name__}({self._path!r})'
1455
-
1456
- def __enter__(self) -> 'Pidfile':
1457
- fd = os.open(self._path, os.O_RDWR | os.O_CREAT, 0o600)
1458
- try:
1459
- os.set_inheritable(fd, True)
1460
- f = os.fdopen(fd, 'r+')
1461
- except Exception:
1462
- try:
1463
- os.close(fd)
1464
- except Exception: # noqa
1465
- pass
1466
- raise
1467
- self._f = f
1468
- return self
1469
-
1470
- def __exit__(self, exc_type, exc_val, exc_tb):
1471
- if hasattr(self, '_f'):
1472
- self._f.close()
1473
- del self._f
1474
-
1475
- def try_lock(self) -> bool:
1476
- try:
1477
- fcntl.flock(self._f, fcntl.LOCK_EX | fcntl.LOCK_NB)
1478
- return True
1479
- except OSError:
1480
- return False
1481
-
1482
- def ensure_locked(self) -> None:
1483
- if not self.try_lock():
1484
- raise RuntimeError('Could not get lock')
1485
-
1486
- def write(self, pid: ta.Optional[int] = None) -> None:
1487
- self.ensure_locked()
1488
- if pid is None:
1489
- pid = os.getpid()
1490
- self._f.write(f'{pid}\n')
1491
- self._f.flush()
1492
-
1493
- def clear(self) -> None:
1494
- self.ensure_locked()
1495
- self._f.seek(0)
1496
- self._f.truncate()
1497
-
1498
- def read(self) -> int:
1499
- if self.try_lock():
1500
- raise RuntimeError('Got lock')
1501
- self._f.seek(0)
1502
- return int(self._f.read())
1503
-
1504
- def kill(self, sig: int = signal.SIGTERM) -> None:
1505
- pid = self.read()
1506
- os.kill(pid, sig) # FIXME: Still racy
1507
-
1508
-
1509
1442
  ########################################
1510
1443
  # ../../../../../omlish/lite/reflect.py
1511
1444
 
@@ -1628,6 +1561,73 @@ def format_num_bytes(num_bytes: int) -> str:
1628
1561
  return f'{num_bytes / 1024 ** (len(FORMAT_NUM_BYTES_SUFFIXES) - 1):.2f}{FORMAT_NUM_BYTES_SUFFIXES[-1]}'
1629
1562
 
1630
1563
 
1564
+ ########################################
1565
+ # ../../../../../omlish/os/pidfile.py
1566
+
1567
+
1568
+ class Pidfile:
1569
+ def __init__(self, path: str) -> None:
1570
+ super().__init__()
1571
+ self._path = path
1572
+
1573
+ _f: ta.TextIO
1574
+
1575
+ def __repr__(self) -> str:
1576
+ return f'{self.__class__.__name__}({self._path!r})'
1577
+
1578
+ def __enter__(self) -> 'Pidfile':
1579
+ fd = os.open(self._path, os.O_RDWR | os.O_CREAT, 0o600)
1580
+ try:
1581
+ os.set_inheritable(fd, True)
1582
+ f = os.fdopen(fd, 'r+')
1583
+ except Exception:
1584
+ try:
1585
+ os.close(fd)
1586
+ except Exception: # noqa
1587
+ pass
1588
+ raise
1589
+ self._f = f
1590
+ return self
1591
+
1592
+ def __exit__(self, exc_type, exc_val, exc_tb):
1593
+ if hasattr(self, '_f'):
1594
+ self._f.close()
1595
+ del self._f
1596
+
1597
+ def try_lock(self) -> bool:
1598
+ try:
1599
+ fcntl.flock(self._f, fcntl.LOCK_EX | fcntl.LOCK_NB)
1600
+ return True
1601
+ except OSError:
1602
+ return False
1603
+
1604
+ def ensure_locked(self) -> None:
1605
+ if not self.try_lock():
1606
+ raise RuntimeError('Could not get lock')
1607
+
1608
+ def write(self, pid: ta.Optional[int] = None) -> None:
1609
+ self.ensure_locked()
1610
+ if pid is None:
1611
+ pid = os.getpid()
1612
+ self._f.write(f'{pid}\n')
1613
+ self._f.flush()
1614
+
1615
+ def clear(self) -> None:
1616
+ self.ensure_locked()
1617
+ self._f.seek(0)
1618
+ self._f.truncate()
1619
+
1620
+ def read(self) -> int:
1621
+ if self.try_lock():
1622
+ raise RuntimeError('Got lock')
1623
+ self._f.seek(0)
1624
+ return int(self._f.read())
1625
+
1626
+ def kill(self, sig: int = signal.SIGTERM) -> None:
1627
+ pid = self.read()
1628
+ os.kill(pid, sig) # FIXME: Still racy
1629
+
1630
+
1631
1631
  ########################################
1632
1632
  # ../../auth.py
1633
1633
  """
ominfra/scripts/manage.py CHANGED
@@ -78,7 +78,7 @@ CallableT = ta.TypeVar('CallableT', bound=ta.Callable)
78
78
  # ../../omlish/lite/check.py
79
79
  SizedT = ta.TypeVar('SizedT', bound=ta.Sized)
80
80
  CheckMessage = ta.Union[str, ta.Callable[..., ta.Optional[str]], None] # ta.TypeAlias
81
- CheckLateConfigureFn = ta.Callable[['Checks'], None]
81
+ CheckLateConfigureFn = ta.Callable[['Checks'], None] # ta.TypeAlias
82
82
  CheckOnRaiseFn = ta.Callable[[Exception], None] # ta.TypeAlias
83
83
  CheckExceptionFactory = ta.Callable[..., Exception] # ta.TypeAlias
84
84
  CheckArgsRenderer = ta.Callable[..., ta.Optional[str]] # ta.TypeAlias
@@ -103,7 +103,7 @@ InjectorProviderFnMap = ta.Mapping['InjectorKey', 'InjectorProviderFn']
103
103
  InjectorBindingOrBindings = ta.Union['InjectorBinding', 'InjectorBindings']
104
104
 
105
105
  # ../../omlish/lite/subprocesses.py
106
- SubprocessChannelOption = ta.Literal['pipe', 'stdout', 'devnull']
106
+ SubprocessChannelOption = ta.Literal['pipe', 'stdout', 'devnull'] # ta.TypeAlias
107
107
 
108
108
 
109
109
  ########################################
@@ -1611,30 +1611,6 @@ class Checks:
1611
1611
  check = Checks()
1612
1612
 
1613
1613
 
1614
- ########################################
1615
- # ../../../omlish/lite/deathsig.py
1616
-
1617
-
1618
- LINUX_PR_SET_PDEATHSIG = 1 # Second arg is a signal
1619
- LINUX_PR_GET_PDEATHSIG = 2 # Second arg is a ptr to return the signal
1620
-
1621
-
1622
- def set_process_deathsig(sig: int) -> bool:
1623
- if sys.platform == 'linux':
1624
- libc = ct.CDLL('libc.so.6')
1625
-
1626
- # int prctl(int option, unsigned long arg2, unsigned long arg3, unsigned long arg4, unsigned long arg5);
1627
- libc.prctl.restype = ct.c_int
1628
- libc.prctl.argtypes = [ct.c_int, ct.c_ulong, ct.c_ulong, ct.c_ulong, ct.c_ulong]
1629
-
1630
- libc.prctl(LINUX_PR_SET_PDEATHSIG, sig, 0, 0, 0, 0)
1631
-
1632
- return True
1633
-
1634
- else:
1635
- return False
1636
-
1637
-
1638
1614
  ########################################
1639
1615
  # ../../../omlish/lite/json.py
1640
1616
 
@@ -1880,6 +1856,30 @@ def format_num_bytes(num_bytes: int) -> str:
1880
1856
  return f'{num_bytes / 1024 ** (len(FORMAT_NUM_BYTES_SUFFIXES) - 1):.2f}{FORMAT_NUM_BYTES_SUFFIXES[-1]}'
1881
1857
 
1882
1858
 
1859
+ ########################################
1860
+ # ../../../omlish/os/deathsig.py
1861
+
1862
+
1863
+ LINUX_PR_SET_PDEATHSIG = 1 # Second arg is a signal
1864
+ LINUX_PR_GET_PDEATHSIG = 2 # Second arg is a ptr to return the signal
1865
+
1866
+
1867
+ def set_process_deathsig(sig: int) -> bool:
1868
+ if sys.platform == 'linux':
1869
+ libc = ct.CDLL('libc.so.6')
1870
+
1871
+ # int prctl(int option, unsigned long arg2, unsigned long arg3, unsigned long arg4, unsigned long arg5);
1872
+ libc.prctl.restype = ct.c_int
1873
+ libc.prctl.argtypes = [ct.c_int, ct.c_ulong, ct.c_ulong, ct.c_ulong, ct.c_ulong]
1874
+
1875
+ libc.prctl(LINUX_PR_SET_PDEATHSIG, sig, 0, 0, 0, 0)
1876
+
1877
+ return True
1878
+
1879
+ else:
1880
+ return False
1881
+
1882
+
1883
1883
  ########################################
1884
1884
  # ../../../omdev/packaging/specifiers.py
1885
1885
  # Copyright (c) Donald Stufft and individual contributors.
@@ -2618,6 +2618,8 @@ def get_remote_payload_src(
2618
2618
  TODO:
2619
2619
  - default command
2620
2620
  - auto match all underscores to hyphens
2621
+ - pre-run, post-run hooks
2622
+ - exitstack?
2621
2623
  """
2622
2624
 
2623
2625
 
@@ -2737,11 +2739,12 @@ class ArgparseCli:
2737
2739
 
2738
2740
  self._args, self._unknown_args = self.get_parser().parse_known_args(self._argv)
2739
2741
 
2742
+ #
2743
+
2740
2744
  def __init_subclass__(cls, **kwargs: ta.Any) -> None:
2741
2745
  super().__init_subclass__(**kwargs)
2742
2746
 
2743
2747
  ns = cls.__dict__
2744
-
2745
2748
  objs = {}
2746
2749
  mro = cls.__mro__[::-1]
2747
2750
  for bns in [bcls.__dict__ for bcls in reversed(mro)] + [ns]:
@@ -2754,24 +2757,33 @@ class ArgparseCli:
2754
2757
  elif k in objs:
2755
2758
  del [k]
2756
2759
 
2760
+ #
2761
+
2757
2762
  anns = ta.get_type_hints(_ArgparseCliAnnotationBox({
2758
2763
  **{k: v for bcls in reversed(mro) for k, v in getattr(bcls, '__annotations__', {}).items()},
2759
2764
  **ns.get('__annotations__', {}),
2760
2765
  }), globalns=ns.get('__globals__', {}))
2761
2766
 
2767
+ #
2768
+
2762
2769
  if '_parser' in ns:
2763
2770
  parser = check.isinstance(ns['_parser'], argparse.ArgumentParser)
2764
2771
  else:
2765
2772
  parser = argparse.ArgumentParser()
2766
2773
  setattr(cls, '_parser', parser)
2767
2774
 
2775
+ #
2776
+
2768
2777
  subparsers = parser.add_subparsers()
2778
+
2769
2779
  for att, obj in objs.items():
2770
2780
  if isinstance(obj, ArgparseCommand):
2771
2781
  if obj.parent is not None:
2772
2782
  raise NotImplementedError
2783
+
2773
2784
  for cn in [obj.name, *(obj.aliases or [])]:
2774
- cparser = subparsers.add_parser(cn)
2785
+ subparser = subparsers.add_parser(cn)
2786
+
2775
2787
  for arg in (obj.args or []):
2776
2788
  if (
2777
2789
  len(arg.args) == 1 and
@@ -2779,29 +2791,34 @@ class ArgparseCli:
2779
2791
  not (n := check.isinstance(arg.args[0], str)).startswith('-') and
2780
2792
  'metavar' not in arg.kwargs
2781
2793
  ):
2782
- cparser.add_argument(
2794
+ subparser.add_argument(
2783
2795
  n.replace('-', '_'),
2784
2796
  **arg.kwargs,
2785
2797
  metavar=n,
2786
2798
  )
2787
2799
  else:
2788
- cparser.add_argument(*arg.args, **arg.kwargs)
2789
- cparser.set_defaults(_cmd=obj)
2800
+ subparser.add_argument(*arg.args, **arg.kwargs)
2801
+
2802
+ subparser.set_defaults(_cmd=obj)
2790
2803
 
2791
2804
  elif isinstance(obj, ArgparseArg):
2792
2805
  if att in anns:
2793
- akwargs = _get_argparse_arg_ann_kwargs(anns[att])
2794
- obj.kwargs = {**akwargs, **obj.kwargs}
2806
+ ann_kwargs = _get_argparse_arg_ann_kwargs(anns[att])
2807
+ obj.kwargs = {**ann_kwargs, **obj.kwargs}
2808
+
2795
2809
  if not obj.dest:
2796
2810
  if 'dest' in obj.kwargs:
2797
2811
  obj.dest = obj.kwargs['dest']
2798
2812
  else:
2799
2813
  obj.dest = obj.kwargs['dest'] = att # type: ignore
2814
+
2800
2815
  parser.add_argument(*obj.args, **obj.kwargs)
2801
2816
 
2802
2817
  else:
2803
2818
  raise TypeError(obj)
2804
2819
 
2820
+ #
2821
+
2805
2822
  _parser: ta.ClassVar[argparse.ArgumentParser]
2806
2823
 
2807
2824
  @classmethod
@@ -2820,10 +2837,12 @@ class ArgparseCli:
2820
2837
  def unknown_args(self) -> ta.Sequence[str]:
2821
2838
  return self._unknown_args
2822
2839
 
2823
- def _run_cmd(self, cmd: ArgparseCommand) -> ta.Optional[int]:
2824
- return cmd.__get__(self, type(self))()
2840
+ #
2841
+
2842
+ def _bind_cli_cmd(self, cmd: ArgparseCommand) -> ta.Callable:
2843
+ return cmd.__get__(self, type(self))
2825
2844
 
2826
- def __call__(self) -> ta.Optional[int]:
2845
+ def prepare_cli_run(self) -> ta.Optional[ta.Callable]:
2827
2846
  cmd = getattr(self.args, '_cmd', None)
2828
2847
 
2829
2848
  if self._unknown_args and not (cmd is not None and cmd.accepts_unknown):
@@ -2835,12 +2854,34 @@ class ArgparseCli:
2835
2854
 
2836
2855
  if cmd is None:
2837
2856
  self.get_parser().print_help()
2857
+ return None
2858
+
2859
+ return self._bind_cli_cmd(cmd)
2860
+
2861
+ #
2862
+
2863
+ def cli_run(self) -> ta.Optional[int]:
2864
+ if (fn := self.prepare_cli_run()) is None:
2838
2865
  return 0
2839
2866
 
2840
- return self._run_cmd(cmd)
2867
+ return fn()
2841
2868
 
2842
- def call_and_exit(self) -> ta.NoReturn:
2843
- sys.exit(rc if isinstance(rc := self(), int) else 0)
2869
+ def cli_run_and_exit(self) -> ta.NoReturn:
2870
+ sys.exit(rc if isinstance(rc := self.cli_run(), int) else 0)
2871
+
2872
+ def __call__(self, *, exit: bool = False) -> ta.Optional[int]: # noqa
2873
+ if exit:
2874
+ return self.cli_run_and_exit()
2875
+ else:
2876
+ return self.cli_run()
2877
+
2878
+ #
2879
+
2880
+ async def async_cli_run(self) -> ta.Optional[int]:
2881
+ if (fn := self.prepare_cli_run()) is None:
2882
+ return 0
2883
+
2884
+ return await fn()
2844
2885
 
2845
2886
 
2846
2887
  ########################################
@@ -6975,7 +7016,7 @@ def main_bootstrap(bs: MainBootstrap) -> Injector:
6975
7016
 
6976
7017
  class MainCli(ArgparseCli):
6977
7018
  @argparse_command(
6978
- argparse_arg('--payload-file'),
7019
+ argparse_arg('--_payload-file'),
6979
7020
 
6980
7021
  argparse_arg('-s', '--shell'),
6981
7022
  argparse_arg('-q', '--shell-quote', action='store_true'),
@@ -6993,10 +7034,7 @@ class MainCli(ArgparseCli):
6993
7034
 
6994
7035
  argparse_arg('command', nargs='+'),
6995
7036
  )
6996
- def run(self) -> None:
6997
- asyncio.run(self._async_run())
6998
-
6999
- async def _async_run(self) -> None:
7037
+ async def run(self) -> None:
7000
7038
  bs = MainBootstrap(
7001
7039
  main_config=MainConfig(
7002
7040
  log_level='DEBUG' if self.args.debug else 'INFO',
@@ -7005,7 +7043,7 @@ class MainCli(ArgparseCli):
7005
7043
  ),
7006
7044
 
7007
7045
  remote_config=RemoteConfig(
7008
- payload_file=self.args.payload_file, # noqa
7046
+ payload_file=self.args._payload_file, # noqa
7009
7047
 
7010
7048
  pycharm_remote_debug=PycharmRemoteDebug(
7011
7049
  port=self.args.pycharm_debug_port,
@@ -7068,7 +7106,7 @@ class MainCli(ArgparseCli):
7068
7106
 
7069
7107
 
7070
7108
  def _main() -> None:
7071
- MainCli().call_and_exit()
7109
+ sys.exit(asyncio.run(MainCli().async_cli_run()))
7072
7110
 
7073
7111
 
7074
7112
  if __name__ == '__main__':
@@ -107,7 +107,7 @@ CallableT = ta.TypeVar('CallableT', bound=ta.Callable)
107
107
  # ../../omlish/lite/check.py
108
108
  SizedT = ta.TypeVar('SizedT', bound=ta.Sized)
109
109
  CheckMessage = ta.Union[str, ta.Callable[..., ta.Optional[str]], None] # ta.TypeAlias
110
- CheckLateConfigureFn = ta.Callable[['Checks'], None]
110
+ CheckLateConfigureFn = ta.Callable[['Checks'], None] # ta.TypeAlias
111
111
  CheckOnRaiseFn = ta.Callable[[Exception], None] # ta.TypeAlias
112
112
  CheckExceptionFactory = ta.Callable[..., Exception] # ta.TypeAlias
113
113
  CheckArgsRenderer = ta.Callable[..., ta.Optional[str]] # ta.TypeAlias
@@ -4682,163 +4682,6 @@ class Injection:
4682
4682
  inj = Injection
4683
4683
 
4684
4684
 
4685
- ########################################
4686
- # ../../../omlish/lite/journald.py
4687
-
4688
-
4689
- ##
4690
-
4691
-
4692
- class sd_iovec(ct.Structure): # noqa
4693
- pass
4694
-
4695
-
4696
- sd_iovec._fields_ = [
4697
- ('iov_base', ct.c_void_p), # Pointer to data.
4698
- ('iov_len', ct.c_size_t), # Length of data.
4699
- ]
4700
-
4701
-
4702
- ##
4703
-
4704
-
4705
- @cached_nullary
4706
- def sd_libsystemd() -> ta.Any:
4707
- lib = ct.CDLL('libsystemd.so.0')
4708
-
4709
- lib.sd_journal_sendv.restype = ct.c_int
4710
- lib.sd_journal_sendv.argtypes = [ct.POINTER(sd_iovec), ct.c_int]
4711
-
4712
- return lib
4713
-
4714
-
4715
- @cached_nullary
4716
- def sd_try_libsystemd() -> ta.Optional[ta.Any]:
4717
- try:
4718
- return sd_libsystemd()
4719
- except OSError: # noqa
4720
- return None
4721
-
4722
-
4723
- ##
4724
-
4725
-
4726
- def sd_journald_send(**fields: str) -> int:
4727
- lib = sd_libsystemd()
4728
-
4729
- msgs = [
4730
- f'{k.upper()}={v}\0'.encode('utf-8')
4731
- for k, v in fields.items()
4732
- ]
4733
-
4734
- vec = (sd_iovec * len(msgs))()
4735
- cl = (ct.c_char_p * len(msgs))() # noqa
4736
- for i in range(len(msgs)):
4737
- vec[i].iov_base = ct.cast(ct.c_char_p(msgs[i]), ct.c_void_p)
4738
- vec[i].iov_len = len(msgs[i]) - 1
4739
-
4740
- return lib.sd_journal_sendv(vec, len(msgs))
4741
-
4742
-
4743
- ##
4744
-
4745
-
4746
- SD_LOG_LEVEL_MAP: ta.Mapping[int, int] = {
4747
- logging.FATAL: syslog.LOG_EMERG, # system is unusable
4748
- # LOG_ALERT ? # action must be taken immediately
4749
- logging.CRITICAL: syslog.LOG_CRIT,
4750
- logging.ERROR: syslog.LOG_ERR,
4751
- logging.WARNING: syslog.LOG_WARNING,
4752
- # LOG_NOTICE ? # normal but significant condition
4753
- logging.INFO: syslog.LOG_INFO,
4754
- logging.DEBUG: syslog.LOG_DEBUG,
4755
- }
4756
-
4757
-
4758
- class JournaldLogHandler(logging.Handler):
4759
- """
4760
- TODO:
4761
- - fallback handler for when this barfs
4762
- """
4763
-
4764
- def __init__(
4765
- self,
4766
- *,
4767
- use_formatter_output: bool = False,
4768
- ) -> None:
4769
- super().__init__()
4770
-
4771
- sd_libsystemd()
4772
-
4773
- self._use_formatter_output = use_formatter_output
4774
-
4775
- #
4776
-
4777
- EXTRA_RECORD_ATTRS_BY_JOURNALD_FIELD: ta.ClassVar[ta.Mapping[str, str]] = {
4778
- 'name': 'name',
4779
- 'module': 'module',
4780
- 'exception': 'exc_text',
4781
- 'thread_name': 'threadName',
4782
- 'task_name': 'taskName',
4783
- }
4784
-
4785
- def make_fields(self, record: logging.LogRecord) -> ta.Mapping[str, str]:
4786
- formatter_message = self.format(record)
4787
- if self._use_formatter_output:
4788
- message = formatter_message
4789
- else:
4790
- message = record.message
4791
-
4792
- fields: dict[str, str] = {
4793
- 'message': message,
4794
- 'priority': str(SD_LOG_LEVEL_MAP[record.levelno]),
4795
- 'tid': str(threading.get_ident()),
4796
- }
4797
-
4798
- if (pathname := record.pathname) is not None:
4799
- fields['code_file'] = pathname
4800
- if (lineno := record.lineno) is not None:
4801
- fields['code_lineno'] = str(lineno)
4802
- if (func_name := record.funcName) is not None:
4803
- fields['code_func'] = func_name
4804
-
4805
- for f, a in self.EXTRA_RECORD_ATTRS_BY_JOURNALD_FIELD.items():
4806
- if (v := getattr(record, a, None)) is not None:
4807
- fields[f] = str(v)
4808
-
4809
- return fields
4810
-
4811
- #
4812
-
4813
- def emit(self, record: logging.LogRecord) -> None:
4814
- try:
4815
- fields = self.make_fields(record)
4816
-
4817
- if rc := sd_journald_send(**fields):
4818
- raise RuntimeError(f'{self.__class__.__name__}.emit failed: {rc=}') # noqa
4819
-
4820
- except RecursionError: # See issue 36272
4821
- raise
4822
-
4823
- except Exception: # noqa
4824
- self.handleError(record)
4825
-
4826
-
4827
- def journald_log_handler_factory(
4828
- *,
4829
- no_tty_check: bool = False,
4830
- no_fallback: bool = False,
4831
- ) -> logging.Handler:
4832
- if (
4833
- sys.platform == 'linux' and
4834
- (no_tty_check or not sys.stderr.isatty()) and
4835
- (no_fallback or sd_try_libsystemd() is not None)
4836
- ):
4837
- return JournaldLogHandler()
4838
-
4839
- return logging.StreamHandler()
4840
-
4841
-
4842
4685
  ########################################
4843
4686
  # ../../../omlish/lite/logs.py
4844
4687
  """
@@ -5565,6 +5408,163 @@ def check_runtime_version() -> None:
5565
5408
  raise OSError(f'Requires python {REQUIRED_PYTHON_VERSION}, got {sys.version_info} from {sys.executable}') # noqa
5566
5409
 
5567
5410
 
5411
+ ########################################
5412
+ # ../../../omlish/os/journald.py
5413
+
5414
+
5415
+ ##
5416
+
5417
+
5418
+ class sd_iovec(ct.Structure): # noqa
5419
+ pass
5420
+
5421
+
5422
+ sd_iovec._fields_ = [
5423
+ ('iov_base', ct.c_void_p), # Pointer to data.
5424
+ ('iov_len', ct.c_size_t), # Length of data.
5425
+ ]
5426
+
5427
+
5428
+ ##
5429
+
5430
+
5431
+ @cached_nullary
5432
+ def sd_libsystemd() -> ta.Any:
5433
+ lib = ct.CDLL('libsystemd.so.0')
5434
+
5435
+ lib.sd_journal_sendv.restype = ct.c_int
5436
+ lib.sd_journal_sendv.argtypes = [ct.POINTER(sd_iovec), ct.c_int]
5437
+
5438
+ return lib
5439
+
5440
+
5441
+ @cached_nullary
5442
+ def sd_try_libsystemd() -> ta.Optional[ta.Any]:
5443
+ try:
5444
+ return sd_libsystemd()
5445
+ except OSError: # noqa
5446
+ return None
5447
+
5448
+
5449
+ ##
5450
+
5451
+
5452
+ def sd_journald_send(**fields: str) -> int:
5453
+ lib = sd_libsystemd()
5454
+
5455
+ msgs = [
5456
+ f'{k.upper()}={v}\0'.encode('utf-8')
5457
+ for k, v in fields.items()
5458
+ ]
5459
+
5460
+ vec = (sd_iovec * len(msgs))()
5461
+ cl = (ct.c_char_p * len(msgs))() # noqa
5462
+ for i in range(len(msgs)):
5463
+ vec[i].iov_base = ct.cast(ct.c_char_p(msgs[i]), ct.c_void_p)
5464
+ vec[i].iov_len = len(msgs[i]) - 1
5465
+
5466
+ return lib.sd_journal_sendv(vec, len(msgs))
5467
+
5468
+
5469
+ ##
5470
+
5471
+
5472
+ SD_LOG_LEVEL_MAP: ta.Mapping[int, int] = {
5473
+ logging.FATAL: syslog.LOG_EMERG, # system is unusable
5474
+ # LOG_ALERT ? # action must be taken immediately
5475
+ logging.CRITICAL: syslog.LOG_CRIT,
5476
+ logging.ERROR: syslog.LOG_ERR,
5477
+ logging.WARNING: syslog.LOG_WARNING,
5478
+ # LOG_NOTICE ? # normal but significant condition
5479
+ logging.INFO: syslog.LOG_INFO,
5480
+ logging.DEBUG: syslog.LOG_DEBUG,
5481
+ }
5482
+
5483
+
5484
+ class JournaldLogHandler(logging.Handler):
5485
+ """
5486
+ TODO:
5487
+ - fallback handler for when this barfs
5488
+ """
5489
+
5490
+ def __init__(
5491
+ self,
5492
+ *,
5493
+ use_formatter_output: bool = False,
5494
+ ) -> None:
5495
+ super().__init__()
5496
+
5497
+ sd_libsystemd()
5498
+
5499
+ self._use_formatter_output = use_formatter_output
5500
+
5501
+ #
5502
+
5503
+ EXTRA_RECORD_ATTRS_BY_JOURNALD_FIELD: ta.ClassVar[ta.Mapping[str, str]] = {
5504
+ 'name': 'name',
5505
+ 'module': 'module',
5506
+ 'exception': 'exc_text',
5507
+ 'thread_name': 'threadName',
5508
+ 'task_name': 'taskName',
5509
+ }
5510
+
5511
+ def make_fields(self, record: logging.LogRecord) -> ta.Mapping[str, str]:
5512
+ formatter_message = self.format(record)
5513
+ if self._use_formatter_output:
5514
+ message = formatter_message
5515
+ else:
5516
+ message = record.message
5517
+
5518
+ fields: dict[str, str] = {
5519
+ 'message': message,
5520
+ 'priority': str(SD_LOG_LEVEL_MAP[record.levelno]),
5521
+ 'tid': str(threading.get_ident()),
5522
+ }
5523
+
5524
+ if (pathname := record.pathname) is not None:
5525
+ fields['code_file'] = pathname
5526
+ if (lineno := record.lineno) is not None:
5527
+ fields['code_lineno'] = str(lineno)
5528
+ if (func_name := record.funcName) is not None:
5529
+ fields['code_func'] = func_name
5530
+
5531
+ for f, a in self.EXTRA_RECORD_ATTRS_BY_JOURNALD_FIELD.items():
5532
+ if (v := getattr(record, a, None)) is not None:
5533
+ fields[f] = str(v)
5534
+
5535
+ return fields
5536
+
5537
+ #
5538
+
5539
+ def emit(self, record: logging.LogRecord) -> None:
5540
+ try:
5541
+ fields = self.make_fields(record)
5542
+
5543
+ if rc := sd_journald_send(**fields):
5544
+ raise RuntimeError(f'{self.__class__.__name__}.emit failed: {rc=}') # noqa
5545
+
5546
+ except RecursionError: # See issue 36272
5547
+ raise
5548
+
5549
+ except Exception: # noqa
5550
+ self.handleError(record)
5551
+
5552
+
5553
+ def journald_log_handler_factory(
5554
+ *,
5555
+ no_tty_check: bool = False,
5556
+ no_fallback: bool = False,
5557
+ ) -> logging.Handler:
5558
+ if (
5559
+ sys.platform == 'linux' and
5560
+ (no_tty_check or not sys.stderr.isatty()) and
5561
+ (no_fallback or sd_try_libsystemd() is not None)
5562
+ ):
5563
+ return JournaldLogHandler()
5564
+
5565
+ return logging.StreamHandler()
5566
+
5567
+
5568
5568
  ########################################
5569
5569
  # ../../configs.py
5570
5570
 
@@ -36,9 +36,9 @@ import typing as ta
36
36
 
37
37
  from omlish.http.coroserver import CoroHttpServer
38
38
  from omlish.lite.inject import inj
39
- from omlish.lite.journald import journald_log_handler_factory
40
39
  from omlish.lite.logs import configure_standard_logging
41
40
  from omlish.lite.runtime import is_debugger_attached
41
+ from omlish.os.journald import journald_log_handler_factory
42
42
 
43
43
  from ..configs import read_config_file
44
44
  from .configs import ServerConfig
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: ominfra
3
- Version: 0.0.0.dev150
3
+ Version: 0.0.0.dev152
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.dev150
16
- Requires-Dist: omlish==0.0.0.dev150
15
+ Requires-Dist: omdev==0.0.0.dev152
16
+ Requires-Dist: omlish==0.0.0.dev152
17
17
  Provides-Extra: all
18
18
  Requires-Dist: paramiko~=3.5; extra == "all"
19
19
  Requires-Dist: asyncssh~=2.18; extra == "all"
@@ -17,7 +17,7 @@ ominfra/clouds/aws/metadata.py,sha256=XR1BuMdQheyeFjjA3MN8GCNWVAp5ahoPdbWXEmViut
17
17
  ominfra/clouds/aws/journald2aws/__init__.py,sha256=Y3l4WY4JRi2uLG6kgbGp93fuGfkxkKwZDvhsa0Rwgtk,15
18
18
  ominfra/clouds/aws/journald2aws/__main__.py,sha256=d23loR_cKfTYZwYiqpt_CmKI7dd5WcYFgIYzqMep75E,68
19
19
  ominfra/clouds/aws/journald2aws/cursor.py,sha256=tQ7O6BHlEdaalbiI_Rqagj0aHfdtTQ_ZJwdOSRUjNvQ,1173
20
- ominfra/clouds/aws/journald2aws/driver.py,sha256=xSK8rCgBNtSnuApBYWtVLdRrcKAtef87hgNqBcn8gGw,6113
20
+ ominfra/clouds/aws/journald2aws/driver.py,sha256=b3XfSlIBWAWJLImmyh1ZK9FpdNAYNZ2YZeCv_Q9GckM,6111
21
21
  ominfra/clouds/aws/journald2aws/main.py,sha256=RQJhk4aPtnp4EHzC-ST1Rs9BN6D7bqQQVjCRxGU7JuQ,2147
22
22
  ominfra/clouds/aws/journald2aws/poster.py,sha256=hz1XuctW8GtLmfjhRvCFY6py52D4BzXHYny5XKFpHSA,2833
23
23
  ominfra/clouds/gcp/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -33,7 +33,7 @@ ominfra/manage/bootstrap.py,sha256=puGpmK6b6ZCAX5E_HI9ucUuWlXB4U_4Xwc2x8GAKSVo,2
33
33
  ominfra/manage/bootstrap_.py,sha256=WVry3P5ViPpdEC_-EFBouZTECMxs4CwRbek4Oz_iRbc,517
34
34
  ominfra/manage/config.py,sha256=1y2N_8nXHBZc6YbW6BaRZoDDCTBmiHuWtTOQ7zdr5VE,184
35
35
  ominfra/manage/inject.py,sha256=jGjGqlecpxzpJR3-RqQhEBOmpsix5UwSsvrz5DbayC4,1395
36
- ominfra/manage/main.py,sha256=wXeMx_lo6KQLSxRWACgdmrVlWGLUbs4qPJIyPWhzIH4,3898
36
+ ominfra/manage/main.py,sha256=XtQNtFqTlm2hM225qBuhpri_B0Aeo1dezQhMJwfjotc,3860
37
37
  ominfra/manage/marshal.py,sha256=WKj7IU9bo4fBMSSzT6ZMm_WFalXIJZ-V7j8oi92fNhk,305
38
38
  ominfra/manage/commands/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
39
39
  ominfra/manage/commands/base.py,sha256=LtaI0AgnplttQK7gNncNItq8yuTZQimJTaprVpZktI8,3993
@@ -47,7 +47,7 @@ ominfra/manage/deploy/command.py,sha256=CBzsw31u86wxkY9Ms-Es70ijD_3naZLAcj3Gb6Of
47
47
  ominfra/manage/deploy/inject.py,sha256=j5LYK5y_nZgdaNLde8sV2w_---GvmibgO_ZBT8qzeFM,499
48
48
  ominfra/manage/deploy/paths.py,sha256=Ad7OGERMqGUWO-0os1PXSO3sh9uGqrxxlEyT_3fYkac,3703
49
49
  ominfra/manage/remote/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
50
- ominfra/manage/remote/_main.py,sha256=xxCKOu52b_a5BoH1mkHlof-PLn01QwKu928SKvlT9G0,4358
50
+ ominfra/manage/remote/_main.py,sha256=7grHKEE72a6IHijwLV3uWbqnrl_v27ZNJSI3WWwGs7o,4356
51
51
  ominfra/manage/remote/channel.py,sha256=36xR9Ti9ZA8TUBtxmY0u7_3Lv7E6wzQTxlZl7gLR5GE,2224
52
52
  ominfra/manage/remote/config.py,sha256=zRIC7Yhh3FMfoIgyqlbDTwpL_jS23lhQXjGB2_YG4Eg,473
53
53
  ominfra/manage/remote/connection.py,sha256=5h-JrMK07Ulqgol3ZXmcTpSUr6iMu1oZd-wCjnRqe_Y,2821
@@ -56,9 +56,9 @@ ominfra/manage/remote/inject.py,sha256=euI9YmLTmNw7tdkQq_5PtFg6n9pYz3mGukOOApdYw
56
56
  ominfra/manage/remote/payload.py,sha256=Rn-Yo26POpHEOOfUHX3jWkqcQVEAvkJ_5Bu13jwoob4,944
57
57
  ominfra/manage/remote/spawning.py,sha256=bL87ted_lLwLW6DhrYPaXLRaIq7f6VYg_u2LJoMEpoE,3205
58
58
  ominfra/scripts/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
59
- ominfra/scripts/journald2aws.py,sha256=G4ZFbopI_TEOgy6CQGnfYk_ibbH3CMYwfd500e8_dLM,149423
60
- ominfra/scripts/manage.py,sha256=ohVW5YEjiN-yfY4jfmsiCuQkOdM63XPQzZlS06B6ZJM,194823
61
- ominfra/scripts/supervisor.py,sha256=D8DGUFcdp1SYUia_xx3HV-xeVE1DpYnwmSymZjYpuRU,262192
59
+ ominfra/scripts/journald2aws.py,sha256=jLXI4iuMN1uRtRgCeyyczjwRl1pxKEgfbz1q8CJoUjc,149453
60
+ ominfra/scripts/manage.py,sha256=17S7oz99WAEb9GRguohf6uEofd5WdcYhvM1ulgOmU6w,195440
61
+ ominfra/scripts/supervisor.py,sha256=uPzXIfstv1J-LhDCKLx70RMUBQVA0zMkd7h4X18SAzQ,262206
62
62
  ominfra/supervisor/LICENSE.txt,sha256=yvqaMNsDhWxziHa9ien6qCW1SkZv-DQlAg96XjfSee8,1746
63
63
  ominfra/supervisor/__init__.py,sha256=Y3l4WY4JRi2uLG6kgbGp93fuGfkxkKwZDvhsa0Rwgtk,15
64
64
  ominfra/supervisor/__main__.py,sha256=I0yFw-C08OOiZ3BF6lF1Oiv789EQXu-_j6whDhQUTEA,66
@@ -72,7 +72,7 @@ ominfra/supervisor/groupsimpl.py,sha256=PCDyc_Wc-pnvIj56_aEyiA5exCpK6n9iErqnJzO2
72
72
  ominfra/supervisor/http.py,sha256=hysC2uFm5wWqi_MmOs1NrQ_UdwiXOawKEV-B37fwA-A,3445
73
73
  ominfra/supervisor/inject.py,sha256=6nBEnpE8VLjtYK12z5DGRP7WzgbwLAz5yf__1KnJl6g,4693
74
74
  ominfra/supervisor/io.py,sha256=moaGNaPuYXEAUzLg8Qjo05DEIcOUNYUj8SSr8eT0d24,3198
75
- ominfra/supervisor/main.py,sha256=BhAZJX_8KrH311URzz33i-XN4cChoqhKSbDgCcr4WSw,4248
75
+ ominfra/supervisor/main.py,sha256=Pad43J0c6jvWGJqn3BAtIubD6MYuj261z-AgTuchvAs,4246
76
76
  ominfra/supervisor/pipes.py,sha256=2ZihNTnRNjnIPOtPbm3_pyqO15f7BNs7WnNtO5V8ahM,2231
77
77
  ominfra/supervisor/privileges.py,sha256=kaRTHI7XjqzxEWCeHp3_0J0Vc4gSPugRbXEwxuw6MYE,2054
78
78
  ominfra/supervisor/process.py,sha256=UaubVxsxVqDnbuWVpTH0DTGbJGLO0vGJ9mNcvy2kCXM,217
@@ -100,9 +100,9 @@ ominfra/tailscale/api.py,sha256=C5-t_b6jZXUWcy5k8bXm7CFnk73pSdrlMOgGDeGVrpw,1370
100
100
  ominfra/tailscale/cli.py,sha256=h6akQJMl0KuWLHS7Ur6WcBZ2JwF0DJQhsPTnFBdGyNk,3571
101
101
  ominfra/tools/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
102
102
  ominfra/tools/listresources.py,sha256=4qVg5txsb10EHhvqXXeM6gJ2jx9LbroEnPydDv1uXs0,6176
103
- ominfra-0.0.0.dev150.dist-info/LICENSE,sha256=B_hVtavaA8zCYDW99DYdcpDLKz1n3BBRjZrcbv8uG8c,1451
104
- ominfra-0.0.0.dev150.dist-info/METADATA,sha256=FSLGLLhr0JDZ8cCgCbvNXg_oBIDz1xOt7VFEK-Nmsks,731
105
- ominfra-0.0.0.dev150.dist-info/WHEEL,sha256=PZUExdf71Ui_so67QXpySuHtCi3-J3wvF4ORK6k_S8U,91
106
- ominfra-0.0.0.dev150.dist-info/entry_points.txt,sha256=kgecQ2MgGrM9qK744BoKS3tMesaC3yjLnl9pa5CRczg,37
107
- ominfra-0.0.0.dev150.dist-info/top_level.txt,sha256=E-b2OHkk_AOBLXHYZQ2EOFKl-_6uOGd8EjeG-Zy6h_w,8
108
- ominfra-0.0.0.dev150.dist-info/RECORD,,
103
+ ominfra-0.0.0.dev152.dist-info/LICENSE,sha256=B_hVtavaA8zCYDW99DYdcpDLKz1n3BBRjZrcbv8uG8c,1451
104
+ ominfra-0.0.0.dev152.dist-info/METADATA,sha256=j1IeXSSy10gAqMN5hUB8Ab2oEDKYcYQyKEgvFkSnv3s,731
105
+ ominfra-0.0.0.dev152.dist-info/WHEEL,sha256=PZUExdf71Ui_so67QXpySuHtCi3-J3wvF4ORK6k_S8U,91
106
+ ominfra-0.0.0.dev152.dist-info/entry_points.txt,sha256=kgecQ2MgGrM9qK744BoKS3tMesaC3yjLnl9pa5CRczg,37
107
+ ominfra-0.0.0.dev152.dist-info/top_level.txt,sha256=E-b2OHkk_AOBLXHYZQ2EOFKl-_6uOGd8EjeG-Zy6h_w,8
108
+ ominfra-0.0.0.dev152.dist-info/RECORD,,