ominfra 0.0.0.dev102__py3-none-any.whl → 0.0.0.dev104__py3-none-any.whl

Sign up to get free protection for your applications and to get access to all the features.
@@ -62,7 +62,7 @@ class AwsPutLogEventsResponse(AwsDataclass):
62
62
  ##
63
63
 
64
64
 
65
- class AwsLogMessagePoster:
65
+ class AwsLogMessageBuilder:
66
66
  """
67
67
  TODO:
68
68
  - max_items
@@ -88,7 +88,7 @@ class AwsLogMessagePoster:
88
88
  log_group_name: str,
89
89
  log_stream_name: str,
90
90
  region_name: str,
91
- credentials: AwsSigner.Credentials,
91
+ credentials: ta.Optional[AwsSigner.Credentials],
92
92
 
93
93
  url: ta.Optional[str] = None,
94
94
  service_name: str = DEFAULT_SERVICE_NAME,
@@ -110,11 +110,16 @@ class AwsLogMessagePoster:
110
110
  headers = {**headers, **extra_headers}
111
111
  self._headers = {k: [v] for k, v in headers.items()}
112
112
 
113
- self._signer = V4AwsSigner(
114
- credentials,
115
- region_name,
116
- service_name,
117
- )
113
+ signer: ta.Optional[V4AwsSigner]
114
+ if credentials is not None:
115
+ signer = V4AwsSigner(
116
+ credentials,
117
+ region_name,
118
+ service_name,
119
+ )
120
+ else:
121
+ signer = None
122
+ self._signer = signer
118
123
 
119
124
  #
120
125
 
@@ -158,13 +163,14 @@ class AwsLogMessagePoster:
158
163
  payload=body,
159
164
  )
160
165
 
161
- sig_headers = self._signer.sign(
162
- sig_req,
163
- sign_payload=False,
164
- )
165
- sig_req = dc.replace(sig_req, headers={**sig_req.headers, **sig_headers})
166
+ if (signer := self._signer) is not None:
167
+ sig_headers = signer.sign(
168
+ sig_req,
169
+ sign_payload=False,
170
+ )
171
+ sig_req = dc.replace(sig_req, headers={**sig_req.headers, **sig_headers})
166
172
 
167
- post = AwsLogMessagePoster.Post(
173
+ post = AwsLogMessageBuilder.Post(
168
174
  url=self._url,
169
175
  headers={k: check_single(v) for k, v in sig_req.headers.items()},
170
176
  data=sig_req.payload,
File without changes
@@ -0,0 +1,48 @@
1
+ import json
2
+ import time
3
+ import typing as ta
4
+
5
+ from omlish import check
6
+ from omlish import http
7
+ from omlish.http import jwt
8
+
9
+
10
+ DEFAULT_JWT_SCOPE = 'https://www.googleapis.com/auth/cloud-platform'
11
+
12
+
13
+ def generate_gcp_jwt(
14
+ creds_dct: ta.Mapping[str, ta.Any],
15
+ *,
16
+ issued_at: int | None = None,
17
+ lifetime_s: int = 3600,
18
+ scope: str = DEFAULT_JWT_SCOPE,
19
+ ) -> str:
20
+ return jwt.generate_jwt(
21
+ issuer=creds_dct['client_email'],
22
+ subject=creds_dct['client_email'],
23
+ audience=creds_dct['token_uri'],
24
+ issued_at=(issued_at := int(issued_at if issued_at is not None else time.time())),
25
+ expires_at=issued_at + lifetime_s,
26
+ scope=scope,
27
+ key=creds_dct['private_key'],
28
+ algorithm='RS256',
29
+ )
30
+
31
+
32
+ def get_gcp_access_token(
33
+ creds_dct: ta.Mapping[str, ta.Any],
34
+ *,
35
+ client: http.HttpClient | None = None,
36
+ ) -> str:
37
+ signed_jwt = generate_gcp_jwt(creds_dct)
38
+ resp = http.request(
39
+ creds_dct['token_uri'],
40
+ 'POST',
41
+ data=jwt.build_get_token_body(signed_jwt).encode('utf-8'),
42
+ headers={
43
+ http.consts.HEADER_CONTENT_TYPE: http.consts.CONTENT_TYPE_FORM_URLENCODED,
44
+ },
45
+ client=client,
46
+ )
47
+ resp_dct = json.loads(check.not_none(resp.data).decode('utf-8'))
48
+ return resp_dct['access_token']
@@ -82,7 +82,7 @@ if sys.version_info < (3, 8):
82
82
  ########################################
83
83
 
84
84
 
85
- # ../../../../omlish/lite/check.py
85
+ # ../../../../omlish/lite/cached.py
86
86
  T = ta.TypeVar('T')
87
87
 
88
88
 
@@ -112,7 +112,7 @@ class HostConfig:
112
112
  # ../../../../omlish/lite/cached.py
113
113
 
114
114
 
115
- class cached_nullary: # noqa
115
+ class _cached_nullary: # noqa
116
116
  def __init__(self, fn):
117
117
  super().__init__()
118
118
  self._fn = fn
@@ -129,6 +129,10 @@ class cached_nullary: # noqa
129
129
  return bound
130
130
 
131
131
 
132
+ def cached_nullary(fn: ta.Callable[..., T]) -> ta.Callable[..., T]:
133
+ return _cached_nullary(fn)
134
+
135
+
132
136
  ########################################
133
137
  # ../../../../omlish/lite/check.py
134
138
 
@@ -661,7 +665,7 @@ class DataclassObjMarshaler(ObjMarshaler):
661
665
  return {k: m.marshal(getattr(o, k)) for k, m in self.fs.items()}
662
666
 
663
667
  def unmarshal(self, o: ta.Any) -> ta.Any:
664
- return self.ty(**{k: self.fs[k].unmarshal(v) for k, v in o.items() if self.nonstrict or k in self.fs})
668
+ return self.ty(**{k: self.fs[k].unmarshal(v) for k, v in o.items() if not self.nonstrict or k in self.fs})
665
669
 
666
670
 
667
671
  @dc.dataclass(frozen=True)
@@ -721,7 +725,10 @@ class UuidObjMarshaler(ObjMarshaler):
721
725
  return uuid.UUID(o)
722
726
 
723
727
 
724
- _OBJ_MARSHALERS: ta.Dict[ta.Any, ObjMarshaler] = {
728
+ ##
729
+
730
+
731
+ _DEFAULT_OBJ_MARSHALERS: ta.Dict[ta.Any, ObjMarshaler] = {
725
732
  **{t: NopObjMarshaler() for t in (type(None),)},
726
733
  **{t: CastObjMarshaler(t) for t in (int, float, str, bool)},
727
734
  **{t: Base64ObjMarshaler(t) for t in (bytes, bytearray)},
@@ -750,20 +757,19 @@ _OBJ_MARSHALER_GENERIC_ITERABLE_TYPES: ta.Dict[ta.Any, type] = {
750
757
  }
751
758
 
752
759
 
753
- def register_opj_marshaler(ty: ta.Any, m: ObjMarshaler) -> None:
754
- if ty in _OBJ_MARSHALERS:
755
- raise KeyError(ty)
756
- _OBJ_MARSHALERS[ty] = m
757
-
758
-
759
- def _make_obj_marshaler(ty: ta.Any) -> ObjMarshaler:
760
+ def _make_obj_marshaler(
761
+ ty: ta.Any,
762
+ rec: ta.Callable[[ta.Any], ObjMarshaler],
763
+ *,
764
+ nonstrict_dataclasses: bool = False,
765
+ ) -> ObjMarshaler:
760
766
  if isinstance(ty, type):
761
767
  if abc.ABC in ty.__bases__:
762
768
  impls = [ # type: ignore
763
769
  PolymorphicObjMarshaler.Impl(
764
770
  ity,
765
771
  ity.__qualname__,
766
- get_obj_marshaler(ity),
772
+ rec(ity),
767
773
  )
768
774
  for ity in deep_subclasses(ty)
769
775
  if abc.ABC not in ity.__bases__
@@ -779,7 +785,8 @@ def _make_obj_marshaler(ty: ta.Any) -> ObjMarshaler:
779
785
  if dc.is_dataclass(ty):
780
786
  return DataclassObjMarshaler(
781
787
  ty,
782
- {f.name: get_obj_marshaler(f.type) for f in dc.fields(ty)},
788
+ {f.name: rec(f.type) for f in dc.fields(ty)},
789
+ nonstrict=nonstrict_dataclasses,
783
790
  )
784
791
 
785
792
  if is_generic_alias(ty):
@@ -789,7 +796,7 @@ def _make_obj_marshaler(ty: ta.Any) -> ObjMarshaler:
789
796
  pass
790
797
  else:
791
798
  k, v = ta.get_args(ty)
792
- return MappingObjMarshaler(mt, get_obj_marshaler(k), get_obj_marshaler(v))
799
+ return MappingObjMarshaler(mt, rec(k), rec(v))
793
800
 
794
801
  try:
795
802
  st = _OBJ_MARSHALER_GENERIC_ITERABLE_TYPES[ta.get_origin(ty)]
@@ -797,33 +804,71 @@ def _make_obj_marshaler(ty: ta.Any) -> ObjMarshaler:
797
804
  pass
798
805
  else:
799
806
  [e] = ta.get_args(ty)
800
- return IterableObjMarshaler(st, get_obj_marshaler(e))
807
+ return IterableObjMarshaler(st, rec(e))
801
808
 
802
809
  if is_union_alias(ty):
803
- return OptionalObjMarshaler(get_obj_marshaler(get_optional_alias_arg(ty)))
810
+ return OptionalObjMarshaler(rec(get_optional_alias_arg(ty)))
804
811
 
805
812
  raise TypeError(ty)
806
813
 
807
814
 
808
- def get_obj_marshaler(ty: ta.Any) -> ObjMarshaler:
809
- try:
810
- return _OBJ_MARSHALERS[ty]
811
- except KeyError:
812
- pass
815
+ ##
813
816
 
814
- p = ProxyObjMarshaler()
815
- _OBJ_MARSHALERS[ty] = p
816
- try:
817
- m = _make_obj_marshaler(ty)
818
- except Exception:
819
- del _OBJ_MARSHALERS[ty]
820
- raise
821
- else:
822
- p.m = m
817
+
818
+ _OBJ_MARSHALERS_LOCK = threading.RLock()
819
+
820
+ _OBJ_MARSHALERS: ta.Dict[ta.Any, ObjMarshaler] = dict(_DEFAULT_OBJ_MARSHALERS)
821
+
822
+ _OBJ_MARSHALER_PROXIES: ta.Dict[ta.Any, ProxyObjMarshaler] = {}
823
+
824
+
825
+ def register_opj_marshaler(ty: ta.Any, m: ObjMarshaler) -> None:
826
+ with _OBJ_MARSHALERS_LOCK:
827
+ if ty in _OBJ_MARSHALERS:
828
+ raise KeyError(ty)
823
829
  _OBJ_MARSHALERS[ty] = m
830
+
831
+
832
+ def get_obj_marshaler(
833
+ ty: ta.Any,
834
+ *,
835
+ no_cache: bool = False,
836
+ **kwargs: ta.Any,
837
+ ) -> ObjMarshaler:
838
+ with _OBJ_MARSHALERS_LOCK:
839
+ if not no_cache:
840
+ try:
841
+ return _OBJ_MARSHALERS[ty]
842
+ except KeyError:
843
+ pass
844
+
845
+ try:
846
+ return _OBJ_MARSHALER_PROXIES[ty]
847
+ except KeyError:
848
+ pass
849
+
850
+ rec = functools.partial(
851
+ get_obj_marshaler,
852
+ no_cache=no_cache,
853
+ **kwargs,
854
+ )
855
+
856
+ p = ProxyObjMarshaler()
857
+ _OBJ_MARSHALER_PROXIES[ty] = p
858
+ try:
859
+ m = _make_obj_marshaler(ty, rec, **kwargs)
860
+ finally:
861
+ del _OBJ_MARSHALER_PROXIES[ty]
862
+ p.m = m
863
+
864
+ if not no_cache:
865
+ _OBJ_MARSHALERS[ty] = m
824
866
  return m
825
867
 
826
868
 
869
+ ##
870
+
871
+
827
872
  def marshal_obj(o: ta.Any, ty: ta.Any = None) -> ta.Any:
828
873
  return get_obj_marshaler(ty if ty is not None else type(o)).marshal(o)
829
874
 
@@ -956,6 +1001,24 @@ def subprocess_try_output_str(*args: str, **kwargs: ta.Any) -> ta.Optional[str]:
956
1001
  return out.decode().strip() if out is not None else None
957
1002
 
958
1003
 
1004
+ ##
1005
+
1006
+
1007
+ def subprocess_close(
1008
+ proc: subprocess.Popen,
1009
+ timeout: ta.Optional[float] = None,
1010
+ ) -> None:
1011
+ # TODO: terminate, sleep, kill
1012
+ if proc.stdout:
1013
+ proc.stdout.close()
1014
+ if proc.stderr:
1015
+ proc.stderr.close()
1016
+ if proc.stdin:
1017
+ proc.stdin.close()
1018
+
1019
+ proc.wait(timeout)
1020
+
1021
+
959
1022
  ########################################
960
1023
  # ../base.py
961
1024
 
@@ -34,8 +34,10 @@ if sys.version_info < (3, 8):
34
34
  ########################################
35
35
 
36
36
 
37
- # ../base.py
37
+ # ../../../../omlish/lite/cached.py
38
38
  T = ta.TypeVar('T')
39
+
40
+ # ../base.py
39
41
  ConcernT = ta.TypeVar('ConcernT')
40
42
  ConfigT = ta.TypeVar('ConfigT')
41
43
  SiteConcernConfigT = ta.TypeVar('SiteConcernConfigT', bound='SiteConcernConfig')
@@ -84,7 +86,7 @@ class DeployConfig:
84
86
  # ../../../../omlish/lite/cached.py
85
87
 
86
88
 
87
- class cached_nullary: # noqa
89
+ class _cached_nullary: # noqa
88
90
  def __init__(self, fn):
89
91
  super().__init__()
90
92
  self._fn = fn
@@ -101,6 +103,10 @@ class cached_nullary: # noqa
101
103
  return bound
102
104
 
103
105
 
106
+ def cached_nullary(fn: ta.Callable[..., T]) -> ta.Callable[..., T]:
107
+ return _cached_nullary(fn)
108
+
109
+
104
110
  ########################################
105
111
  # ../../../../omlish/lite/json.py
106
112
 
@@ -809,6 +815,24 @@ def subprocess_try_output_str(*args: str, **kwargs: ta.Any) -> ta.Optional[str]:
809
815
  return out.decode().strip() if out is not None else None
810
816
 
811
817
 
818
+ ##
819
+
820
+
821
+ def subprocess_close(
822
+ proc: subprocess.Popen,
823
+ timeout: ta.Optional[float] = None,
824
+ ) -> None:
825
+ # TODO: terminate, sleep, kill
826
+ if proc.stdout:
827
+ proc.stdout.close()
828
+ if proc.stderr:
829
+ proc.stderr.close()
830
+ if proc.stdin:
831
+ proc.stdin.close()
832
+
833
+ proc.wait(timeout)
834
+
835
+
812
836
  ########################################
813
837
  # ../runtime.py
814
838
 
@@ -3,6 +3,7 @@
3
3
  import argparse
4
4
  import datetime
5
5
  import json
6
+ import os
6
7
  import re
7
8
  import sys
8
9
  import time
@@ -34,6 +35,11 @@ def _main() -> None:
34
35
  else:
35
36
  start = 0
36
37
 
38
+ stdout_fd = sys.stdout.fileno()
39
+ out_fd = os.dup(stdout_fd)
40
+ null_fd = os.open('/dev/null', os.O_WRONLY)
41
+ os.dup2(null_fd, stdout_fd)
42
+
37
43
  for i in range(start, args.n):
38
44
  if args.sleep_s:
39
45
  if not args.sleep_n or (i and i % args.sleep_n == 0):
@@ -46,8 +52,14 @@ def _main() -> None:
46
52
  '__CURSOR': f'cursor:{i}',
47
53
  '_SOURCE_REALTIME_TIMESTAMP': str(int(ts_us)),
48
54
  }
49
- print(json.dumps(dct, indent=None, separators=(',', ':')))
50
- sys.stdout.flush()
55
+
56
+ buf = json.dumps(dct, indent=None, separators=(',', ':')).encode()
57
+
58
+ try:
59
+ os.write(out_fd, buf)
60
+ os.write(out_fd, b'\n')
61
+ except BrokenPipeError:
62
+ break
51
63
 
52
64
 
53
65
  if __name__ == '__main__':
@@ -347,9 +347,10 @@ import typing as ta
347
347
  from omlish.lite.cached import cached_nullary
348
348
  from omlish.lite.check import check_not_none
349
349
  from omlish.lite.logs import log
350
+ from omlish.lite.subprocesses import subprocess_close
350
351
  from omlish.lite.subprocesses import subprocess_shell_wrap_exec
351
352
 
352
- from ..threadworker import ThreadWorker
353
+ from ..threadworkers import ThreadWorker
353
354
  from .messages import JournalctlMessage # noqa
354
355
  from .messages import JournalctlMessageBuilder
355
356
 
@@ -385,7 +386,7 @@ class JournalctlTailerWorker(ThreadWorker):
385
386
  self._read_size = read_size
386
387
  self._sleep_s = sleep_s
387
388
 
388
- self._mb = JournalctlMessageBuilder()
389
+ self._builder = JournalctlMessageBuilder()
389
390
 
390
391
  self._proc: ta.Optional[subprocess.Popen] = None
391
392
 
@@ -409,45 +410,50 @@ class JournalctlTailerWorker(ThreadWorker):
409
410
 
410
411
  return cmd
411
412
 
413
+ def _read_loop(self, stdout: ta.IO) -> None:
414
+ while stdout.readable():
415
+ self._heartbeat()
416
+
417
+ buf = stdout.read(self._read_size)
418
+ if not buf:
419
+ log.debug('Journalctl empty read')
420
+ break
421
+
422
+ log.debug('Journalctl read buffer: %r', buf)
423
+ msgs = self._builder.feed(buf)
424
+ if msgs:
425
+ while True:
426
+ try:
427
+ self._output.put(msgs, timeout=1.)
428
+ except queue.Full:
429
+ self._heartbeat()
430
+ else:
431
+ break
432
+
412
433
  def _run(self) -> None:
413
434
  with subprocess.Popen(
414
435
  self._full_cmd(),
415
436
  stdout=subprocess.PIPE,
416
437
  ) as self._proc:
417
- stdout = check_not_none(self._proc.stdout)
438
+ try:
439
+ stdout = check_not_none(self._proc.stdout)
440
+
441
+ fd = stdout.fileno()
442
+ fl = fcntl.fcntl(fd, fcntl.F_GETFL)
443
+ fcntl.fcntl(fd, fcntl.F_SETFL, fl | os.O_NONBLOCK)
418
444
 
419
- fd = stdout.fileno()
420
- fl = fcntl.fcntl(fd, fcntl.F_GETFL)
421
- fcntl.fcntl(fd, fcntl.F_SETFL, fl | os.O_NONBLOCK)
445
+ while True:
446
+ self._heartbeat()
422
447
 
423
- while True:
424
- if not self._heartbeat():
425
- return
448
+ self._read_loop(stdout)
426
449
 
427
- while stdout.readable():
428
- if not self._heartbeat():
450
+ log.debug('Journalctl not readable')
451
+
452
+ if self._proc.poll() is not None:
453
+ log.critical('Journalctl process terminated')
429
454
  return
430
455
 
431
- buf = stdout.read(self._read_size)
432
- if not buf:
433
- log.debug('Journalctl empty read')
434
- break
456
+ time.sleep(self._sleep_s)
435
457
 
436
- log.debug('Journalctl read buffer: %r', buf)
437
- msgs = self._mb.feed(buf)
438
- if msgs:
439
- while True:
440
- try:
441
- self._output.put(msgs, timeout=1.)
442
- except queue.Full:
443
- if not self._heartbeat():
444
- return
445
- else:
446
- break
447
-
448
- if self._proc.poll() is not None:
449
- log.critical('Journalctl process terminated')
450
- return
451
-
452
- log.debug('Journalctl readable')
453
- time.sleep(self._sleep_s)
458
+ finally:
459
+ subprocess_close(self._proc)