ominfra 0.0.0.dev150__py3-none-any.whl → 0.0.0.dev152__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.
@@ -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,,