ominfra 0.0.0.dev166__py3-none-any.whl → 0.0.0.dev168__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.
@@ -1726,6 +1726,8 @@ def async_cached_nullary(fn): # ta.Callable[..., T]) -> ta.Callable[..., T]:
1726
1726
  """
1727
1727
  TODO:
1728
1728
  - def maybe(v: lang.Maybe[T])
1729
+ - def not_ ?
1730
+ - ** class @dataclass Raise - user message should be able to be an exception type or instance or factory
1729
1731
  """
1730
1732
 
1731
1733
 
@@ -2406,6 +2408,37 @@ def snake_case(name: str) -> str:
2406
2408
  ##
2407
2409
 
2408
2410
 
2411
+ def strip_with_newline(s: str) -> str:
2412
+ if not s:
2413
+ return ''
2414
+ return s.strip() + '\n'
2415
+
2416
+
2417
+ @ta.overload
2418
+ def split_keep_delimiter(s: str, d: str) -> str:
2419
+ ...
2420
+
2421
+
2422
+ @ta.overload
2423
+ def split_keep_delimiter(s: bytes, d: bytes) -> bytes:
2424
+ ...
2425
+
2426
+
2427
+ def split_keep_delimiter(s, d):
2428
+ ps = []
2429
+ i = 0
2430
+ while i < len(s):
2431
+ if (n := s.find(d, i)) < i:
2432
+ ps.append(s[i:])
2433
+ break
2434
+ ps.append(s[i:n + 1])
2435
+ i = n + 1
2436
+ return ps
2437
+
2438
+
2439
+ ##
2440
+
2441
+
2409
2442
  def is_dunder(name: str) -> bool:
2410
2443
  return (
2411
2444
  name[:2] == name[-2:] == '__' and
@@ -6110,6 +6143,7 @@ class ServerConfig:
6110
6143
 
6111
6144
  groups: ta.Optional[ta.Sequence[ProcessGroupConfig]] = None
6112
6145
 
6146
+ # TODO: implement - make sure to accept broken symlinks
6113
6147
  group_config_dirs: ta.Optional[ta.Sequence[str]] = None
6114
6148
 
6115
6149
  @classmethod
@@ -245,6 +245,7 @@ class ServerConfig:
245
245
 
246
246
  groups: ta.Optional[ta.Sequence[ProcessGroupConfig]] = None
247
247
 
248
+ # TODO: implement - make sure to accept broken symlinks
248
249
  group_config_dirs: ta.Optional[ta.Sequence[str]] = None
249
250
 
250
251
  @classmethod
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: ominfra
3
- Version: 0.0.0.dev166
3
+ Version: 0.0.0.dev168
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.dev166
16
- Requires-Dist: omlish==0.0.0.dev166
15
+ Requires-Dist: omdev==0.0.0.dev168
16
+ Requires-Dist: omlish==0.0.0.dev168
17
17
  Provides-Extra: all
18
18
  Requires-Dist: paramiko~=3.5; extra == "all"
19
19
  Requires-Dist: asyncssh~=2.18; extra == "all"
@@ -44,17 +44,23 @@ ominfra/manage/commands/ping.py,sha256=DVZFzL1Z_f-Bq53vxMrL3xOi0iK_nMonJE4KvQf9w
44
44
  ominfra/manage/commands/subprocess.py,sha256=yHGMbAI-xKe_9BUs5IZ3Yav8qRE-I9aGnBtTwW15Pnw,2440
45
45
  ominfra/manage/commands/types.py,sha256=XFZPeqeIBAaIIQF3pdPbGxLlb-LCrz6WtlDWO2q_vz0,210
46
46
  ominfra/manage/deploy/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
47
- ominfra/manage/deploy/apps.py,sha256=Nv3eXVPxEbSz4wyTI8chJ-C-EEC721vFssvvg0YDRXo,1937
48
- ominfra/manage/deploy/commands.py,sha256=N9qVntnRgJ_IneI7rEQB2Za0oU7gouPfm-sl2MCwW1E,764
47
+ ominfra/manage/deploy/apps.py,sha256=HE2-ThrWR4wplT5hpPSQx0ERAkV-f58hj38al_FaZ7o,4457
48
+ ominfra/manage/deploy/commands.py,sha256=fKFKhFwqIqC_PsgA-W66qIJ5S32xRgBBaRt3lbPX5Zg,763
49
+ ominfra/manage/deploy/conf.py,sha256=AvIZFNuURpS0mKZazr36oBMKi4kJDeTlt74JZmDrpcA,5227
49
50
  ominfra/manage/deploy/config.py,sha256=aR6ubMEWqkTI55XtcG1Cczn6YhCVN6eSL8DT5EHQJN0,166
50
- ominfra/manage/deploy/git.py,sha256=6CGLvGH8uYkuT8gyZHybJb7sgUPTtFgy7grj1YHkI9g,3747
51
- ominfra/manage/deploy/inject.py,sha256=8wuIgdzkDCHbc69nD1meLjwfCOMdWOIfDT5yijL-du8,1441
51
+ ominfra/manage/deploy/deploy.py,sha256=APg8Pbni48ycw83JdsWZRlc1utVDCWlj4q8RCEDXPDg,564
52
+ ominfra/manage/deploy/git.py,sha256=cfTCx1qD-FQPFkbYW28tkU8nVxQbnfnWxpuJuGQHtBw,3753
53
+ ominfra/manage/deploy/inject.py,sha256=kzGl2N2jhijUw4-PYUK1LNG8_MJD7BMgCbi6nDViMWg,1965
52
54
  ominfra/manage/deploy/interp.py,sha256=OKkenH8YKEW_mEDR6X7_ZLxK9a1Ox6KHSwFPTHT6OzA,1029
53
- ominfra/manage/deploy/paths.py,sha256=tK8zZFWOHDRdTN5AlTe-3MpgZqovhWrljGosQmeEYvo,6839
54
- ominfra/manage/deploy/specs.py,sha256=qQSMA2ns0dalbizYwOp14i154mCOkjyvvh4_cN0gT3k,1574
55
- ominfra/manage/deploy/tmp.py,sha256=L0pIfQuxQ7_6gC_AAv7eubI37_IPzCVR29hkn1MHL2Q,1230
56
- ominfra/manage/deploy/types.py,sha256=o95wqvTGNRq8Cxx7VpqeX-9x1tI8k8BpqPFvJZkJYBA,305
57
- ominfra/manage/deploy/venvs.py,sha256=u8ds1TVyipFVSfm7sgEHGPw4JPG_hXY2j1LnTCLjxqw,2337
55
+ ominfra/manage/deploy/specs.py,sha256=3VenKZyX_PmAFcqmvNQqRO-EaMuvFwWWaVF8Y_X9fHU,2865
56
+ ominfra/manage/deploy/tmp.py,sha256=dFJuqGfSf5otCxSaCI01a5UOSaArMlU4MzzYcyr74-s,1237
57
+ ominfra/manage/deploy/types.py,sha256=HzOTDmj9HcHWA_X1GGiwYZ98CGG7X_hdAGMvit_a_n4,829
58
+ ominfra/manage/deploy/venvs.py,sha256=1co8l3eSxl2WccrF-XOTqeltoMccRH63o69NblV3yok,1366
59
+ ominfra/manage/deploy/paths/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
60
+ ominfra/manage/deploy/paths/inject.py,sha256=X81C-Qhef1LQ7tILWvkomBwFTvgooLVmWRnKL7TeVoI,596
61
+ ominfra/manage/deploy/paths/manager.py,sha256=gxr_CsjLmjxXx8w3J8ookJk9OGltCpyBFYBnxXaw5lg,1050
62
+ ominfra/manage/deploy/paths/owners.py,sha256=GmLy0E70C8CF3eYIdkAhBtYaZXW4QWmSzvgts5l1i_4,1379
63
+ ominfra/manage/deploy/paths/paths.py,sha256=gluJ0I-OPfig0YbjlqE1Tepv48oKIIv6lN6kcQLtUg0,6076
58
64
  ominfra/manage/remote/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
59
65
  ominfra/manage/remote/_main.py,sha256=p5KoiS2WMw6QAqlDl_Zun-JybmCsy8awIfpBMLBjGMY,4356
60
66
  ominfra/manage/remote/channel.py,sha256=36xR9Ti9ZA8TUBtxmY0u7_3Lv7E6wzQTxlZl7gLR5GE,2224
@@ -75,13 +81,13 @@ ominfra/manage/targets/connection.py,sha256=5e8h9Miej2DKJxZfLyxpGe8y-Y0V_b_AuUW1
75
81
  ominfra/manage/targets/inject.py,sha256=P4597xWM-V3I_gCt2O71OLhYQkkXtuJvkYRsIbhhMcE,1561
76
82
  ominfra/manage/targets/targets.py,sha256=CFl8Uirgn3gfowO1Fn-LBK-6qYqEMFJ9snPUl0gCRuM,1753
77
83
  ominfra/scripts/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
78
- ominfra/scripts/journald2aws.py,sha256=EC8tSKW3hztBV_Kr_ykK72AmcvnWivUxcz6Sfg3M_hI,155085
79
- ominfra/scripts/manage.py,sha256=M8k4XmOrq2R3rjUNU3SAHeqdjvWZptpXvFAl4A0RmGM,294060
80
- ominfra/scripts/supervisor.py,sha256=uPcw4o8gt8xvQ97jXK-WBAaZj3D81Lq9tqDoKxGvLCU,273791
84
+ ominfra/scripts/journald2aws.py,sha256=OjbemVx-zyCBuWZVgTky66n2-MfyyS7ups4A9hsvHfA,155684
85
+ ominfra/scripts/manage.py,sha256=Xq28GDDorMsopZqYqZ240lcn1IFCHZi5-YOjZAEXVFY,306243
86
+ ominfra/scripts/supervisor.py,sha256=viYrsafRW5fLN2KUS1MtxCOm9CO4iOgIu60cmN3joSg,274450
81
87
  ominfra/supervisor/LICENSE.txt,sha256=yvqaMNsDhWxziHa9ien6qCW1SkZv-DQlAg96XjfSee8,1746
82
88
  ominfra/supervisor/__init__.py,sha256=Y3l4WY4JRi2uLG6kgbGp93fuGfkxkKwZDvhsa0Rwgtk,15
83
89
  ominfra/supervisor/__main__.py,sha256=I0yFw-C08OOiZ3BF6lF1Oiv789EQXu-_j6whDhQUTEA,66
84
- ominfra/supervisor/configs.py,sha256=InaLW0T93dbAuzKyc6H8wGIFR2oM1GROSiQX9hZY_ko,13711
90
+ ominfra/supervisor/configs.py,sha256=UxcZ1yaw7CiwiSb7A1AegwcWKFXqtswaKaHlIN8Bt_Q,13771
85
91
  ominfra/supervisor/dispatchers.py,sha256=zXLwQS4Vc6dWw5o9QOL04UMDt7w6CKu9wf19CjUiS2Q,1005
86
92
  ominfra/supervisor/dispatchersimpl.py,sha256=q3dEyOHWTPKm28nmAGisjgIW1BX6O3-SzbYa7nWuTEs,11349
87
93
  ominfra/supervisor/events.py,sha256=XGrtzHr1xm0dwjz329fn9eR0_Ap-LQL6Sk8LJ8eVDEo,6692
@@ -119,9 +125,9 @@ ominfra/tailscale/api.py,sha256=C5-t_b6jZXUWcy5k8bXm7CFnk73pSdrlMOgGDeGVrpw,1370
119
125
  ominfra/tailscale/cli.py,sha256=h6akQJMl0KuWLHS7Ur6WcBZ2JwF0DJQhsPTnFBdGyNk,3571
120
126
  ominfra/tools/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
121
127
  ominfra/tools/listresources.py,sha256=4qVg5txsb10EHhvqXXeM6gJ2jx9LbroEnPydDv1uXs0,6176
122
- ominfra-0.0.0.dev166.dist-info/LICENSE,sha256=B_hVtavaA8zCYDW99DYdcpDLKz1n3BBRjZrcbv8uG8c,1451
123
- ominfra-0.0.0.dev166.dist-info/METADATA,sha256=K1Yk7mm_LiNl0FtGC69NQqG23xD68-dpAwbNiBgfn90,731
124
- ominfra-0.0.0.dev166.dist-info/WHEEL,sha256=PZUExdf71Ui_so67QXpySuHtCi3-J3wvF4ORK6k_S8U,91
125
- ominfra-0.0.0.dev166.dist-info/entry_points.txt,sha256=kgecQ2MgGrM9qK744BoKS3tMesaC3yjLnl9pa5CRczg,37
126
- ominfra-0.0.0.dev166.dist-info/top_level.txt,sha256=E-b2OHkk_AOBLXHYZQ2EOFKl-_6uOGd8EjeG-Zy6h_w,8
127
- ominfra-0.0.0.dev166.dist-info/RECORD,,
128
+ ominfra-0.0.0.dev168.dist-info/LICENSE,sha256=B_hVtavaA8zCYDW99DYdcpDLKz1n3BBRjZrcbv8uG8c,1451
129
+ ominfra-0.0.0.dev168.dist-info/METADATA,sha256=x8GQTcbUEVciriMjOnk4XA2OdGaQu6r2XcaOmwfSo1w,731
130
+ ominfra-0.0.0.dev168.dist-info/WHEEL,sha256=PZUExdf71Ui_so67QXpySuHtCi3-J3wvF4ORK6k_S8U,91
131
+ ominfra-0.0.0.dev168.dist-info/entry_points.txt,sha256=kgecQ2MgGrM9qK744BoKS3tMesaC3yjLnl9pa5CRczg,37
132
+ ominfra-0.0.0.dev168.dist-info/top_level.txt,sha256=E-b2OHkk_AOBLXHYZQ2EOFKl-_6uOGd8EjeG-Zy6h_w,8
133
+ ominfra-0.0.0.dev168.dist-info/RECORD,,
@@ -1,239 +0,0 @@
1
- # ruff: noqa: UP006 UP007
2
- """
3
- TODO:
4
- - run/pidfile
5
- - logs/...
6
- - current symlink
7
- - conf/{nginx,supervisor}
8
- - env/?
9
- """
10
- import abc
11
- import dataclasses as dc
12
- import os.path
13
- import typing as ta
14
-
15
- from omlish.lite.cached import cached_nullary
16
- from omlish.lite.check import check
17
-
18
- from .types import DeployHome
19
-
20
-
21
- DeployPathKind = ta.Literal['dir', 'file'] # ta.TypeAlias
22
- DeployPathPlaceholder = ta.Literal['app', 'tag'] # ta.TypeAlias
23
-
24
-
25
- ##
26
-
27
-
28
- DEPLOY_PATH_PLACEHOLDER_PLACEHOLDER = '@'
29
- DEPLOY_PATH_PLACEHOLDER_SEPARATORS = '-.'
30
-
31
- DEPLOY_PATH_PLACEHOLDERS: ta.FrozenSet[str] = frozenset([
32
- 'app',
33
- 'tag',
34
- ])
35
-
36
-
37
- class DeployPathError(Exception):
38
- pass
39
-
40
-
41
- @dc.dataclass(frozen=True)
42
- class DeployPathPart(abc.ABC): # noqa
43
- @property
44
- @abc.abstractmethod
45
- def kind(self) -> DeployPathKind:
46
- raise NotImplementedError
47
-
48
- @abc.abstractmethod
49
- def render(self, placeholders: ta.Optional[ta.Mapping[DeployPathPlaceholder, str]] = None) -> str:
50
- raise NotImplementedError
51
-
52
-
53
- #
54
-
55
-
56
- class DirDeployPathPart(DeployPathPart, abc.ABC):
57
- @property
58
- def kind(self) -> DeployPathKind:
59
- return 'dir'
60
-
61
- @classmethod
62
- def parse(cls, s: str) -> 'DirDeployPathPart':
63
- if DEPLOY_PATH_PLACEHOLDER_PLACEHOLDER in s:
64
- check.equal(s[0], DEPLOY_PATH_PLACEHOLDER_PLACEHOLDER)
65
- return PlaceholderDirDeployPathPart(s[1:])
66
- else:
67
- return ConstDirDeployPathPart(s)
68
-
69
-
70
- class FileDeployPathPart(DeployPathPart, abc.ABC):
71
- @property
72
- def kind(self) -> DeployPathKind:
73
- return 'file'
74
-
75
- @classmethod
76
- def parse(cls, s: str) -> 'FileDeployPathPart':
77
- if DEPLOY_PATH_PLACEHOLDER_PLACEHOLDER in s:
78
- check.equal(s[0], DEPLOY_PATH_PLACEHOLDER_PLACEHOLDER)
79
- if not any(c in s for c in DEPLOY_PATH_PLACEHOLDER_SEPARATORS):
80
- return PlaceholderFileDeployPathPart(s[1:], '')
81
- else:
82
- p = min(f for c in DEPLOY_PATH_PLACEHOLDER_SEPARATORS if (f := s.find(c)) > 0)
83
- return PlaceholderFileDeployPathPart(s[1:p], s[p:])
84
- else:
85
- return ConstFileDeployPathPart(s)
86
-
87
-
88
- #
89
-
90
-
91
- @dc.dataclass(frozen=True)
92
- class ConstDeployPathPart(DeployPathPart, abc.ABC):
93
- name: str
94
-
95
- def __post_init__(self) -> None:
96
- check.non_empty_str(self.name)
97
- check.not_in('/', self.name)
98
- check.not_in(DEPLOY_PATH_PLACEHOLDER_PLACEHOLDER, self.name)
99
-
100
- def render(self, placeholders: ta.Optional[ta.Mapping[DeployPathPlaceholder, str]] = None) -> str:
101
- return self.name
102
-
103
-
104
- class ConstDirDeployPathPart(ConstDeployPathPart, DirDeployPathPart):
105
- pass
106
-
107
-
108
- class ConstFileDeployPathPart(ConstDeployPathPart, FileDeployPathPart):
109
- pass
110
-
111
-
112
- #
113
-
114
-
115
- @dc.dataclass(frozen=True)
116
- class PlaceholderDeployPathPart(DeployPathPart, abc.ABC):
117
- placeholder: str # DeployPathPlaceholder
118
-
119
- def __post_init__(self) -> None:
120
- check.non_empty_str(self.placeholder)
121
- for c in [*DEPLOY_PATH_PLACEHOLDER_SEPARATORS, DEPLOY_PATH_PLACEHOLDER_PLACEHOLDER, '/']:
122
- check.not_in(c, self.placeholder)
123
- check.in_(self.placeholder, DEPLOY_PATH_PLACEHOLDERS)
124
-
125
- def _render_placeholder(self, placeholders: ta.Optional[ta.Mapping[DeployPathPlaceholder, str]] = None) -> str:
126
- if placeholders is not None:
127
- return placeholders[self.placeholder] # type: ignore
128
- else:
129
- return DEPLOY_PATH_PLACEHOLDER_PLACEHOLDER + self.placeholder
130
-
131
-
132
- @dc.dataclass(frozen=True)
133
- class PlaceholderDirDeployPathPart(PlaceholderDeployPathPart, DirDeployPathPart):
134
- def render(self, placeholders: ta.Optional[ta.Mapping[DeployPathPlaceholder, str]] = None) -> str:
135
- return self._render_placeholder(placeholders)
136
-
137
-
138
- @dc.dataclass(frozen=True)
139
- class PlaceholderFileDeployPathPart(PlaceholderDeployPathPart, FileDeployPathPart):
140
- suffix: str
141
-
142
- def __post_init__(self) -> None:
143
- super().__post_init__()
144
- if self.suffix:
145
- for c in [DEPLOY_PATH_PLACEHOLDER_PLACEHOLDER, '/']:
146
- check.not_in(c, self.suffix)
147
-
148
- def render(self, placeholders: ta.Optional[ta.Mapping[DeployPathPlaceholder, str]] = None) -> str:
149
- return self._render_placeholder(placeholders) + self.suffix
150
-
151
-
152
- ##
153
-
154
-
155
- @dc.dataclass(frozen=True)
156
- class DeployPath:
157
- parts: ta.Sequence[DeployPathPart]
158
-
159
- def __post_init__(self) -> None:
160
- hash(self)
161
-
162
- check.not_empty(self.parts)
163
- for p in self.parts[:-1]:
164
- check.equal(p.kind, 'dir')
165
-
166
- pd = {}
167
- for i, p in enumerate(self.parts):
168
- if isinstance(p, PlaceholderDeployPathPart):
169
- if p.placeholder in pd:
170
- raise DeployPathError('Duplicate placeholders in path', self)
171
- pd[p.placeholder] = i
172
-
173
- if 'tag' in pd:
174
- if 'app' not in pd or pd['app'] >= pd['tag']:
175
- raise DeployPathError('Tag placeholder in path without preceding app', self)
176
-
177
- @property
178
- def kind(self) -> ta.Literal['file', 'dir']:
179
- return self.parts[-1].kind
180
-
181
- def render(self, placeholders: ta.Optional[ta.Mapping[DeployPathPlaceholder, str]] = None) -> str:
182
- return os.path.join( # noqa
183
- *[p.render(placeholders) for p in self.parts],
184
- *([''] if self.kind == 'dir' else []),
185
- )
186
-
187
- @classmethod
188
- def parse(cls, s: str) -> 'DeployPath':
189
- tail_parse: ta.Callable[[str], DeployPathPart]
190
- if s.endswith('/'):
191
- tail_parse = DirDeployPathPart.parse
192
- s = s[:-1]
193
- else:
194
- tail_parse = FileDeployPathPart.parse
195
- ps = check.non_empty_str(s).split('/')
196
- return cls((
197
- *([DirDeployPathPart.parse(p) for p in ps[:-1]] if len(ps) > 1 else []),
198
- tail_parse(ps[-1]),
199
- ))
200
-
201
-
202
- ##
203
-
204
-
205
- class DeployPathOwner(abc.ABC):
206
- @abc.abstractmethod
207
- def get_owned_deploy_paths(self) -> ta.AbstractSet[DeployPath]:
208
- raise NotImplementedError
209
-
210
-
211
- class SingleDirDeployPathOwner(DeployPathOwner, abc.ABC):
212
- def __init__(
213
- self,
214
- *args: ta.Any,
215
- owned_dir: str,
216
- deploy_home: ta.Optional[DeployHome],
217
- **kwargs: ta.Any,
218
- ) -> None:
219
- super().__init__(*args, **kwargs)
220
-
221
- check.not_in('/', owned_dir)
222
- self._owned_dir: str = check.non_empty_str(owned_dir)
223
-
224
- self._deploy_home = deploy_home
225
-
226
- self._owned_deploy_paths = frozenset([DeployPath.parse(self._owned_dir + '/')])
227
-
228
- @cached_nullary
229
- def _dir(self) -> str:
230
- return os.path.join(check.non_empty_str(self._deploy_home), self._owned_dir)
231
-
232
- @cached_nullary
233
- def _make_dir(self) -> str:
234
- if not os.path.isdir(d := self._dir()):
235
- os.makedirs(d, exist_ok=True)
236
- return d
237
-
238
- def get_owned_deploy_paths(self) -> ta.AbstractSet[DeployPath]:
239
- return self._owned_deploy_paths