ominfra 0.0.0.dev167__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
 
@@ -2412,6 +2414,28 @@ def strip_with_newline(s: str) -> str:
2412
2414
  return s.strip() + '\n'
2413
2415
 
2414
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
+
2415
2439
  ##
2416
2440
 
2417
2441
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: ominfra
3
- Version: 0.0.0.dev167
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.dev167
16
- Requires-Dist: omlish==0.0.0.dev167
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,18 +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=jLvam6sVcWfRmyyLeJyTxz0IErWTn98rf1-8lIEEYlM,3292
48
- ominfra/manage/deploy/commands.py,sha256=N9qVntnRgJ_IneI7rEQB2Za0oU7gouPfm-sl2MCwW1E,764
49
- ominfra/manage/deploy/conf.py,sha256=HxKLWIT95StavJ0uHhMhuvWio1gTYBlDJv6N1_XdxEo,4417
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
50
50
  ominfra/manage/deploy/config.py,sha256=aR6ubMEWqkTI55XtcG1Cczn6YhCVN6eSL8DT5EHQJN0,166
51
- ominfra/manage/deploy/git.py,sha256=BGht0UmY1d76kSGHESWuG8qxNKLuOGrwey5V9M4pfCY,3746
52
- ominfra/manage/deploy/inject.py,sha256=o-bgWvziUuE5XzsE9_rcPSdWqPO9AAf2SbXamEpir2k,1978
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
53
54
  ominfra/manage/deploy/interp.py,sha256=OKkenH8YKEW_mEDR6X7_ZLxK9a1Ox6KHSwFPTHT6OzA,1029
54
- ominfra/manage/deploy/paths.py,sha256=Rq9OsK3MpporH6tfuumzLO51e4Yz_wi6RiXEg82iyLY,6947
55
- ominfra/manage/deploy/specs.py,sha256=Xv1OQ3yQxOTR39Gj5ACgAn7vpgL-zVuGk1CHk_xmt5o,2883
56
- ominfra/manage/deploy/tmp.py,sha256=L0pIfQuxQ7_6gC_AAv7eubI37_IPzCVR29hkn1MHL2Q,1230
57
- ominfra/manage/deploy/types.py,sha256=o95wqvTGNRq8Cxx7VpqeX-9x1tI8k8BpqPFvJZkJYBA,305
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
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
59
64
  ominfra/manage/remote/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
60
65
  ominfra/manage/remote/_main.py,sha256=p5KoiS2WMw6QAqlDl_Zun-JybmCsy8awIfpBMLBjGMY,4356
61
66
  ominfra/manage/remote/channel.py,sha256=36xR9Ti9ZA8TUBtxmY0u7_3Lv7E6wzQTxlZl7gLR5GE,2224
@@ -76,9 +81,9 @@ ominfra/manage/targets/connection.py,sha256=5e8h9Miej2DKJxZfLyxpGe8y-Y0V_b_AuUW1
76
81
  ominfra/manage/targets/inject.py,sha256=P4597xWM-V3I_gCt2O71OLhYQkkXtuJvkYRsIbhhMcE,1561
77
82
  ominfra/manage/targets/targets.py,sha256=CFl8Uirgn3gfowO1Fn-LBK-6qYqEMFJ9snPUl0gCRuM,1753
78
83
  ominfra/scripts/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
79
- ominfra/scripts/journald2aws.py,sha256=Cfxnq9N2PaRLBrGSPq4qi39SOGldKZXT_Ve2_uPWcpY,155191
80
- ominfra/scripts/manage.py,sha256=Yb_Ok9rB8zMycxED0fjzHQVNmVQ2qc14pASuzpves68,301283
81
- ominfra/scripts/supervisor.py,sha256=y9o5NyKKb7ugwk3ZEywzlqVFd7QDhsSO2_C8oSqO_28,273957
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
82
87
  ominfra/supervisor/LICENSE.txt,sha256=yvqaMNsDhWxziHa9ien6qCW1SkZv-DQlAg96XjfSee8,1746
83
88
  ominfra/supervisor/__init__.py,sha256=Y3l4WY4JRi2uLG6kgbGp93fuGfkxkKwZDvhsa0Rwgtk,15
84
89
  ominfra/supervisor/__main__.py,sha256=I0yFw-C08OOiZ3BF6lF1Oiv789EQXu-_j6whDhQUTEA,66
@@ -120,9 +125,9 @@ ominfra/tailscale/api.py,sha256=C5-t_b6jZXUWcy5k8bXm7CFnk73pSdrlMOgGDeGVrpw,1370
120
125
  ominfra/tailscale/cli.py,sha256=h6akQJMl0KuWLHS7Ur6WcBZ2JwF0DJQhsPTnFBdGyNk,3571
121
126
  ominfra/tools/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
122
127
  ominfra/tools/listresources.py,sha256=4qVg5txsb10EHhvqXXeM6gJ2jx9LbroEnPydDv1uXs0,6176
123
- ominfra-0.0.0.dev167.dist-info/LICENSE,sha256=B_hVtavaA8zCYDW99DYdcpDLKz1n3BBRjZrcbv8uG8c,1451
124
- ominfra-0.0.0.dev167.dist-info/METADATA,sha256=JFB_UaK98rzNZ6vQWBEVI9f7zjJ4DeSf00RpqMQrczs,731
125
- ominfra-0.0.0.dev167.dist-info/WHEEL,sha256=PZUExdf71Ui_so67QXpySuHtCi3-J3wvF4ORK6k_S8U,91
126
- ominfra-0.0.0.dev167.dist-info/entry_points.txt,sha256=kgecQ2MgGrM9qK744BoKS3tMesaC3yjLnl9pa5CRczg,37
127
- ominfra-0.0.0.dev167.dist-info/top_level.txt,sha256=E-b2OHkk_AOBLXHYZQ2EOFKl-_6uOGd8EjeG-Zy6h_w,8
128
- ominfra-0.0.0.dev167.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,243 +0,0 @@
1
- # ruff: noqa: UP006 UP007
2
- """
3
- TODO:
4
- - run/{.pid,.sock}
5
- - logs/...
6
- - current symlink
7
- - conf/{nginx,supervisor}
8
- - env/?
9
- - apps/<app>/shared
10
- """
11
- import abc
12
- import dataclasses as dc
13
- import os.path
14
- import typing as ta
15
-
16
- from omlish.lite.cached import cached_nullary
17
- from omlish.lite.check import check
18
-
19
- from .types import DeployHome
20
-
21
-
22
- DeployPathKind = ta.Literal['dir', 'file'] # ta.TypeAlias
23
- DeployPathPlaceholder = ta.Literal['app', 'tag'] # ta.TypeAlias
24
-
25
-
26
- ##
27
-
28
-
29
- DEPLOY_PATH_PLACEHOLDER_PLACEHOLDER = '@'
30
- DEPLOY_PATH_PLACEHOLDER_SEPARATORS = '-.'
31
-
32
- DEPLOY_PATH_PLACEHOLDERS: ta.FrozenSet[str] = frozenset([
33
- 'app',
34
- 'tag',
35
- ])
36
-
37
-
38
- class DeployPathError(Exception):
39
- pass
40
-
41
-
42
- @dc.dataclass(frozen=True)
43
- class DeployPathPart(abc.ABC): # noqa
44
- @property
45
- @abc.abstractmethod
46
- def kind(self) -> DeployPathKind:
47
- raise NotImplementedError
48
-
49
- @abc.abstractmethod
50
- def render(self, placeholders: ta.Optional[ta.Mapping[DeployPathPlaceholder, str]] = None) -> str:
51
- raise NotImplementedError
52
-
53
-
54
- #
55
-
56
-
57
- class DirDeployPathPart(DeployPathPart, abc.ABC):
58
- @property
59
- def kind(self) -> DeployPathKind:
60
- return 'dir'
61
-
62
- @classmethod
63
- def parse(cls, s: str) -> 'DirDeployPathPart':
64
- if DEPLOY_PATH_PLACEHOLDER_PLACEHOLDER in s:
65
- check.equal(s[0], DEPLOY_PATH_PLACEHOLDER_PLACEHOLDER)
66
- return PlaceholderDirDeployPathPart(s[1:])
67
- else:
68
- return ConstDirDeployPathPart(s)
69
-
70
-
71
- class FileDeployPathPart(DeployPathPart, abc.ABC):
72
- @property
73
- def kind(self) -> DeployPathKind:
74
- return 'file'
75
-
76
- @classmethod
77
- def parse(cls, s: str) -> 'FileDeployPathPart':
78
- if DEPLOY_PATH_PLACEHOLDER_PLACEHOLDER in s:
79
- check.equal(s[0], DEPLOY_PATH_PLACEHOLDER_PLACEHOLDER)
80
- if not any(c in s for c in DEPLOY_PATH_PLACEHOLDER_SEPARATORS):
81
- return PlaceholderFileDeployPathPart(s[1:], '')
82
- else:
83
- p = min(f for c in DEPLOY_PATH_PLACEHOLDER_SEPARATORS if (f := s.find(c)) > 0)
84
- return PlaceholderFileDeployPathPart(s[1:p], s[p:])
85
- else:
86
- return ConstFileDeployPathPart(s)
87
-
88
-
89
- #
90
-
91
-
92
- @dc.dataclass(frozen=True)
93
- class ConstDeployPathPart(DeployPathPart, abc.ABC):
94
- name: str
95
-
96
- def __post_init__(self) -> None:
97
- check.non_empty_str(self.name)
98
- check.not_in('/', self.name)
99
- check.not_in(DEPLOY_PATH_PLACEHOLDER_PLACEHOLDER, self.name)
100
-
101
- def render(self, placeholders: ta.Optional[ta.Mapping[DeployPathPlaceholder, str]] = None) -> str:
102
- return self.name
103
-
104
-
105
- class ConstDirDeployPathPart(ConstDeployPathPart, DirDeployPathPart):
106
- pass
107
-
108
-
109
- class ConstFileDeployPathPart(ConstDeployPathPart, FileDeployPathPart):
110
- pass
111
-
112
-
113
- #
114
-
115
-
116
- @dc.dataclass(frozen=True)
117
- class PlaceholderDeployPathPart(DeployPathPart, abc.ABC):
118
- placeholder: str # DeployPathPlaceholder
119
-
120
- def __post_init__(self) -> None:
121
- check.non_empty_str(self.placeholder)
122
- for c in [*DEPLOY_PATH_PLACEHOLDER_SEPARATORS, DEPLOY_PATH_PLACEHOLDER_PLACEHOLDER, '/']:
123
- check.not_in(c, self.placeholder)
124
- check.in_(self.placeholder, DEPLOY_PATH_PLACEHOLDERS)
125
-
126
- def _render_placeholder(self, placeholders: ta.Optional[ta.Mapping[DeployPathPlaceholder, str]] = None) -> str:
127
- if placeholders is not None:
128
- return placeholders[self.placeholder] # type: ignore
129
- else:
130
- return DEPLOY_PATH_PLACEHOLDER_PLACEHOLDER + self.placeholder
131
-
132
-
133
- @dc.dataclass(frozen=True)
134
- class PlaceholderDirDeployPathPart(PlaceholderDeployPathPart, DirDeployPathPart):
135
- def render(self, placeholders: ta.Optional[ta.Mapping[DeployPathPlaceholder, str]] = None) -> str:
136
- return self._render_placeholder(placeholders)
137
-
138
-
139
- @dc.dataclass(frozen=True)
140
- class PlaceholderFileDeployPathPart(PlaceholderDeployPathPart, FileDeployPathPart):
141
- suffix: str
142
-
143
- def __post_init__(self) -> None:
144
- super().__post_init__()
145
- if self.suffix:
146
- for c in [DEPLOY_PATH_PLACEHOLDER_PLACEHOLDER, '/']:
147
- check.not_in(c, self.suffix)
148
-
149
- def render(self, placeholders: ta.Optional[ta.Mapping[DeployPathPlaceholder, str]] = None) -> str:
150
- return self._render_placeholder(placeholders) + self.suffix
151
-
152
-
153
- ##
154
-
155
-
156
- @dc.dataclass(frozen=True)
157
- class DeployPath:
158
- parts: ta.Sequence[DeployPathPart]
159
-
160
- def __post_init__(self) -> None:
161
- hash(self)
162
-
163
- check.not_empty(self.parts)
164
- for p in self.parts[:-1]:
165
- check.equal(p.kind, 'dir')
166
-
167
- pd = {}
168
- for i, p in enumerate(self.parts):
169
- if isinstance(p, PlaceholderDeployPathPart):
170
- if p.placeholder in pd:
171
- raise DeployPathError('Duplicate placeholders in path', self)
172
- pd[p.placeholder] = i
173
-
174
- if 'tag' in pd:
175
- if 'app' not in pd or pd['app'] >= pd['tag']:
176
- raise DeployPathError('Tag placeholder in path without preceding app', self)
177
-
178
- @property
179
- def kind(self) -> ta.Literal['file', 'dir']:
180
- return self.parts[-1].kind
181
-
182
- def render(self, placeholders: ta.Optional[ta.Mapping[DeployPathPlaceholder, str]] = None) -> str:
183
- return os.path.join( # noqa
184
- *[p.render(placeholders) for p in self.parts],
185
- *([''] if self.kind == 'dir' else []),
186
- )
187
-
188
- @classmethod
189
- def parse(cls, s: str) -> 'DeployPath':
190
- tail_parse: ta.Callable[[str], DeployPathPart]
191
- if s.endswith('/'):
192
- tail_parse = DirDeployPathPart.parse
193
- s = s[:-1]
194
- else:
195
- tail_parse = FileDeployPathPart.parse
196
- ps = check.non_empty_str(s).split('/')
197
- return cls((
198
- *([DirDeployPathPart.parse(p) for p in ps[:-1]] if len(ps) > 1 else []),
199
- tail_parse(ps[-1]),
200
- ))
201
-
202
-
203
- ##
204
-
205
-
206
- class DeployPathOwner(abc.ABC):
207
- @abc.abstractmethod
208
- def get_owned_deploy_paths(self) -> ta.AbstractSet[DeployPath]:
209
- raise NotImplementedError
210
-
211
-
212
- DeployPathOwners = ta.NewType('DeployPathOwners', ta.Sequence[DeployPathOwner])
213
-
214
-
215
- class SingleDirDeployPathOwner(DeployPathOwner, abc.ABC):
216
- def __init__(
217
- self,
218
- *args: ta.Any,
219
- owned_dir: str,
220
- deploy_home: ta.Optional[DeployHome],
221
- **kwargs: ta.Any,
222
- ) -> None:
223
- super().__init__(*args, **kwargs)
224
-
225
- check.not_in('/', owned_dir)
226
- self._owned_dir: str = check.non_empty_str(owned_dir)
227
-
228
- self._deploy_home = deploy_home
229
-
230
- self._owned_deploy_paths = frozenset([DeployPath.parse(self._owned_dir + '/')])
231
-
232
- @cached_nullary
233
- def _dir(self) -> str:
234
- return os.path.join(check.non_empty_str(self._deploy_home), self._owned_dir)
235
-
236
- @cached_nullary
237
- def _make_dir(self) -> str:
238
- if not os.path.isdir(d := self._dir()):
239
- os.makedirs(d, exist_ok=True)
240
- return d
241
-
242
- def get_owned_deploy_paths(self) -> ta.AbstractSet[DeployPath]:
243
- return self._owned_deploy_paths