ominfra 0.0.0.dev166__py3-none-any.whl → 0.0.0.dev168__py3-none-any.whl

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