ominfra 0.0.0.dev158__tar.gz → 0.0.0.dev160__tar.gz
Sign up to get free protection for your applications and to get access to all the features.
- {ominfra-0.0.0.dev158/ominfra.egg-info → ominfra-0.0.0.dev160}/PKG-INFO +3 -3
- {ominfra-0.0.0.dev158 → ominfra-0.0.0.dev160}/ominfra/manage/deploy/apps.py +13 -11
- ominfra-0.0.0.dev160/ominfra/manage/deploy/atomics.py +207 -0
- {ominfra-0.0.0.dev158 → ominfra-0.0.0.dev160}/ominfra/manage/deploy/commands.py +10 -1
- {ominfra-0.0.0.dev158 → ominfra-0.0.0.dev160}/ominfra/manage/deploy/git.py +32 -29
- {ominfra-0.0.0.dev158 → ominfra-0.0.0.dev160}/ominfra/manage/deploy/inject.py +11 -0
- {ominfra-0.0.0.dev158 → ominfra-0.0.0.dev160}/ominfra/manage/deploy/paths.py +46 -37
- ominfra-0.0.0.dev160/ominfra/manage/deploy/specs.py +56 -0
- ominfra-0.0.0.dev160/ominfra/manage/deploy/tmp.py +46 -0
- {ominfra-0.0.0.dev158 → ominfra-0.0.0.dev160}/ominfra/manage/deploy/types.py +1 -0
- {ominfra-0.0.0.dev158 → ominfra-0.0.0.dev160}/ominfra/manage/deploy/venvs.py +6 -1
- {ominfra-0.0.0.dev158 → ominfra-0.0.0.dev160}/ominfra/scripts/journald2aws.py +32 -18
- {ominfra-0.0.0.dev158 → ominfra-0.0.0.dev160}/ominfra/scripts/manage.py +417 -109
- {ominfra-0.0.0.dev158 → ominfra-0.0.0.dev160}/ominfra/scripts/supervisor.py +32 -18
- {ominfra-0.0.0.dev158 → ominfra-0.0.0.dev160/ominfra.egg-info}/PKG-INFO +3 -3
- {ominfra-0.0.0.dev158 → ominfra-0.0.0.dev160}/ominfra.egg-info/SOURCES.txt +2 -0
- {ominfra-0.0.0.dev158 → ominfra-0.0.0.dev160}/ominfra.egg-info/requires.txt +2 -2
- {ominfra-0.0.0.dev158 → ominfra-0.0.0.dev160}/pyproject.toml +3 -3
- ominfra-0.0.0.dev158/ominfra/manage/deploy/specs.py +0 -32
- {ominfra-0.0.0.dev158 → ominfra-0.0.0.dev160}/LICENSE +0 -0
- {ominfra-0.0.0.dev158 → ominfra-0.0.0.dev160}/MANIFEST.in +0 -0
- {ominfra-0.0.0.dev158 → ominfra-0.0.0.dev160}/README.rst +0 -0
- {ominfra-0.0.0.dev158 → ominfra-0.0.0.dev160}/ominfra/.manifests.json +0 -0
- {ominfra-0.0.0.dev158 → ominfra-0.0.0.dev160}/ominfra/__about__.py +0 -0
- {ominfra-0.0.0.dev158 → ominfra-0.0.0.dev160}/ominfra/__init__.py +0 -0
- {ominfra-0.0.0.dev158 → ominfra-0.0.0.dev160}/ominfra/clouds/__init__.py +0 -0
- {ominfra-0.0.0.dev158 → ominfra-0.0.0.dev160}/ominfra/clouds/aws/__init__.py +0 -0
- {ominfra-0.0.0.dev158 → ominfra-0.0.0.dev160}/ominfra/clouds/aws/__main__.py +0 -0
- {ominfra-0.0.0.dev158 → ominfra-0.0.0.dev160}/ominfra/clouds/aws/auth.py +0 -0
- {ominfra-0.0.0.dev158 → ominfra-0.0.0.dev160}/ominfra/clouds/aws/cli.py +0 -0
- {ominfra-0.0.0.dev158 → ominfra-0.0.0.dev160}/ominfra/clouds/aws/dataclasses.py +0 -0
- {ominfra-0.0.0.dev158 → ominfra-0.0.0.dev160}/ominfra/clouds/aws/journald2aws/__init__.py +0 -0
- {ominfra-0.0.0.dev158 → ominfra-0.0.0.dev160}/ominfra/clouds/aws/journald2aws/__main__.py +0 -0
- {ominfra-0.0.0.dev158 → ominfra-0.0.0.dev160}/ominfra/clouds/aws/journald2aws/cursor.py +0 -0
- {ominfra-0.0.0.dev158 → ominfra-0.0.0.dev160}/ominfra/clouds/aws/journald2aws/driver.py +0 -0
- {ominfra-0.0.0.dev158 → ominfra-0.0.0.dev160}/ominfra/clouds/aws/journald2aws/main.py +0 -0
- {ominfra-0.0.0.dev158 → ominfra-0.0.0.dev160}/ominfra/clouds/aws/journald2aws/poster.py +0 -0
- {ominfra-0.0.0.dev158 → ominfra-0.0.0.dev160}/ominfra/clouds/aws/logs.py +0 -0
- {ominfra-0.0.0.dev158 → ominfra-0.0.0.dev160}/ominfra/clouds/aws/metadata.py +0 -0
- {ominfra-0.0.0.dev158 → ominfra-0.0.0.dev160}/ominfra/clouds/gcp/__init__.py +0 -0
- {ominfra-0.0.0.dev158 → ominfra-0.0.0.dev160}/ominfra/clouds/gcp/auth.py +0 -0
- {ominfra-0.0.0.dev158 → ominfra-0.0.0.dev160}/ominfra/cmds.py +0 -0
- {ominfra-0.0.0.dev158 → ominfra-0.0.0.dev160}/ominfra/configs.py +0 -0
- {ominfra-0.0.0.dev158 → ominfra-0.0.0.dev160}/ominfra/journald/__init__.py +0 -0
- {ominfra-0.0.0.dev158 → ominfra-0.0.0.dev160}/ominfra/journald/fields.py +0 -0
- {ominfra-0.0.0.dev158 → ominfra-0.0.0.dev160}/ominfra/journald/genmessages.py +0 -0
- {ominfra-0.0.0.dev158 → ominfra-0.0.0.dev160}/ominfra/journald/messages.py +0 -0
- {ominfra-0.0.0.dev158 → ominfra-0.0.0.dev160}/ominfra/journald/tailer.py +0 -0
- {ominfra-0.0.0.dev158 → ominfra-0.0.0.dev160}/ominfra/manage/__init__.py +0 -0
- {ominfra-0.0.0.dev158 → ominfra-0.0.0.dev160}/ominfra/manage/__main__.py +0 -0
- {ominfra-0.0.0.dev158 → ominfra-0.0.0.dev160}/ominfra/manage/bootstrap.py +0 -0
- {ominfra-0.0.0.dev158 → ominfra-0.0.0.dev160}/ominfra/manage/bootstrap_.py +0 -0
- {ominfra-0.0.0.dev158 → ominfra-0.0.0.dev160}/ominfra/manage/commands/__init__.py +0 -0
- {ominfra-0.0.0.dev158 → ominfra-0.0.0.dev160}/ominfra/manage/commands/base.py +0 -0
- {ominfra-0.0.0.dev158 → ominfra-0.0.0.dev160}/ominfra/manage/commands/inject.py +0 -0
- {ominfra-0.0.0.dev158 → ominfra-0.0.0.dev160}/ominfra/manage/commands/local.py +0 -0
- {ominfra-0.0.0.dev158 → ominfra-0.0.0.dev160}/ominfra/manage/commands/marshal.py +0 -0
- {ominfra-0.0.0.dev158 → ominfra-0.0.0.dev160}/ominfra/manage/commands/ping.py +0 -0
- {ominfra-0.0.0.dev158 → ominfra-0.0.0.dev160}/ominfra/manage/commands/subprocess.py +0 -0
- {ominfra-0.0.0.dev158 → ominfra-0.0.0.dev160}/ominfra/manage/commands/types.py +0 -0
- {ominfra-0.0.0.dev158 → ominfra-0.0.0.dev160}/ominfra/manage/config.py +0 -0
- {ominfra-0.0.0.dev158 → ominfra-0.0.0.dev160}/ominfra/manage/deploy/__init__.py +0 -0
- {ominfra-0.0.0.dev158 → ominfra-0.0.0.dev160}/ominfra/manage/deploy/config.py +0 -0
- {ominfra-0.0.0.dev158 → ominfra-0.0.0.dev160}/ominfra/manage/deploy/interp.py +0 -0
- {ominfra-0.0.0.dev158 → ominfra-0.0.0.dev160}/ominfra/manage/inject.py +0 -0
- {ominfra-0.0.0.dev158 → ominfra-0.0.0.dev160}/ominfra/manage/main.py +0 -0
- {ominfra-0.0.0.dev158 → ominfra-0.0.0.dev160}/ominfra/manage/marshal.py +0 -0
- {ominfra-0.0.0.dev158 → ominfra-0.0.0.dev160}/ominfra/manage/remote/__init__.py +0 -0
- {ominfra-0.0.0.dev158 → ominfra-0.0.0.dev160}/ominfra/manage/remote/_main.py +0 -0
- {ominfra-0.0.0.dev158 → ominfra-0.0.0.dev160}/ominfra/manage/remote/channel.py +0 -0
- {ominfra-0.0.0.dev158 → ominfra-0.0.0.dev160}/ominfra/manage/remote/config.py +0 -0
- {ominfra-0.0.0.dev158 → ominfra-0.0.0.dev160}/ominfra/manage/remote/connection.py +0 -0
- {ominfra-0.0.0.dev158 → ominfra-0.0.0.dev160}/ominfra/manage/remote/execution.py +0 -0
- {ominfra-0.0.0.dev158 → ominfra-0.0.0.dev160}/ominfra/manage/remote/inject.py +0 -0
- {ominfra-0.0.0.dev158 → ominfra-0.0.0.dev160}/ominfra/manage/remote/payload.py +0 -0
- {ominfra-0.0.0.dev158 → ominfra-0.0.0.dev160}/ominfra/manage/remote/spawning.py +0 -0
- {ominfra-0.0.0.dev158 → ominfra-0.0.0.dev160}/ominfra/manage/system/__init__.py +0 -0
- {ominfra-0.0.0.dev158 → ominfra-0.0.0.dev160}/ominfra/manage/system/commands.py +0 -0
- {ominfra-0.0.0.dev158 → ominfra-0.0.0.dev160}/ominfra/manage/system/config.py +0 -0
- {ominfra-0.0.0.dev158 → ominfra-0.0.0.dev160}/ominfra/manage/system/inject.py +0 -0
- {ominfra-0.0.0.dev158 → ominfra-0.0.0.dev160}/ominfra/manage/system/packages.py +0 -0
- {ominfra-0.0.0.dev158 → ominfra-0.0.0.dev160}/ominfra/manage/system/platforms.py +0 -0
- {ominfra-0.0.0.dev158 → ominfra-0.0.0.dev160}/ominfra/manage/targets/__init__.py +0 -0
- {ominfra-0.0.0.dev158 → ominfra-0.0.0.dev160}/ominfra/manage/targets/connection.py +0 -0
- {ominfra-0.0.0.dev158 → ominfra-0.0.0.dev160}/ominfra/manage/targets/inject.py +0 -0
- {ominfra-0.0.0.dev158 → ominfra-0.0.0.dev160}/ominfra/manage/targets/targets.py +0 -0
- {ominfra-0.0.0.dev158 → ominfra-0.0.0.dev160}/ominfra/pyremote.py +0 -0
- {ominfra-0.0.0.dev158 → ominfra-0.0.0.dev160}/ominfra/scripts/__init__.py +0 -0
- {ominfra-0.0.0.dev158 → ominfra-0.0.0.dev160}/ominfra/ssh.py +0 -0
- {ominfra-0.0.0.dev158 → ominfra-0.0.0.dev160}/ominfra/supervisor/LICENSE.txt +0 -0
- {ominfra-0.0.0.dev158 → ominfra-0.0.0.dev160}/ominfra/supervisor/__init__.py +0 -0
- {ominfra-0.0.0.dev158 → ominfra-0.0.0.dev160}/ominfra/supervisor/__main__.py +0 -0
- {ominfra-0.0.0.dev158 → ominfra-0.0.0.dev160}/ominfra/supervisor/configs.py +0 -0
- {ominfra-0.0.0.dev158 → ominfra-0.0.0.dev160}/ominfra/supervisor/dispatchers.py +0 -0
- {ominfra-0.0.0.dev158 → ominfra-0.0.0.dev160}/ominfra/supervisor/dispatchersimpl.py +0 -0
- {ominfra-0.0.0.dev158 → ominfra-0.0.0.dev160}/ominfra/supervisor/events.py +0 -0
- {ominfra-0.0.0.dev158 → ominfra-0.0.0.dev160}/ominfra/supervisor/exceptions.py +0 -0
- {ominfra-0.0.0.dev158 → ominfra-0.0.0.dev160}/ominfra/supervisor/groups.py +0 -0
- {ominfra-0.0.0.dev158 → ominfra-0.0.0.dev160}/ominfra/supervisor/groupsimpl.py +0 -0
- {ominfra-0.0.0.dev158 → ominfra-0.0.0.dev160}/ominfra/supervisor/http.py +0 -0
- {ominfra-0.0.0.dev158 → ominfra-0.0.0.dev160}/ominfra/supervisor/inject.py +0 -0
- {ominfra-0.0.0.dev158 → ominfra-0.0.0.dev160}/ominfra/supervisor/io.py +0 -0
- {ominfra-0.0.0.dev158 → ominfra-0.0.0.dev160}/ominfra/supervisor/main.py +0 -0
- {ominfra-0.0.0.dev158 → ominfra-0.0.0.dev160}/ominfra/supervisor/pipes.py +0 -0
- {ominfra-0.0.0.dev158 → ominfra-0.0.0.dev160}/ominfra/supervisor/privileges.py +0 -0
- {ominfra-0.0.0.dev158 → ominfra-0.0.0.dev160}/ominfra/supervisor/process.py +0 -0
- {ominfra-0.0.0.dev158 → ominfra-0.0.0.dev160}/ominfra/supervisor/processimpl.py +0 -0
- {ominfra-0.0.0.dev158 → ominfra-0.0.0.dev160}/ominfra/supervisor/setup.py +0 -0
- {ominfra-0.0.0.dev158 → ominfra-0.0.0.dev160}/ominfra/supervisor/setupimpl.py +0 -0
- {ominfra-0.0.0.dev158 → ominfra-0.0.0.dev160}/ominfra/supervisor/signals.py +0 -0
- {ominfra-0.0.0.dev158 → ominfra-0.0.0.dev160}/ominfra/supervisor/spawning.py +0 -0
- {ominfra-0.0.0.dev158 → ominfra-0.0.0.dev160}/ominfra/supervisor/spawningimpl.py +0 -0
- {ominfra-0.0.0.dev158 → ominfra-0.0.0.dev160}/ominfra/supervisor/states.py +0 -0
- {ominfra-0.0.0.dev158 → ominfra-0.0.0.dev160}/ominfra/supervisor/supervisor.py +0 -0
- {ominfra-0.0.0.dev158 → ominfra-0.0.0.dev160}/ominfra/supervisor/types.py +0 -0
- {ominfra-0.0.0.dev158 → ominfra-0.0.0.dev160}/ominfra/supervisor/utils/__init__.py +0 -0
- {ominfra-0.0.0.dev158 → ominfra-0.0.0.dev160}/ominfra/supervisor/utils/collections.py +0 -0
- {ominfra-0.0.0.dev158 → ominfra-0.0.0.dev160}/ominfra/supervisor/utils/diag.py +0 -0
- {ominfra-0.0.0.dev158 → ominfra-0.0.0.dev160}/ominfra/supervisor/utils/fds.py +0 -0
- {ominfra-0.0.0.dev158 → ominfra-0.0.0.dev160}/ominfra/supervisor/utils/fs.py +0 -0
- {ominfra-0.0.0.dev158 → ominfra-0.0.0.dev160}/ominfra/supervisor/utils/os.py +0 -0
- {ominfra-0.0.0.dev158 → ominfra-0.0.0.dev160}/ominfra/supervisor/utils/ostypes.py +0 -0
- {ominfra-0.0.0.dev158 → ominfra-0.0.0.dev160}/ominfra/supervisor/utils/signals.py +0 -0
- {ominfra-0.0.0.dev158 → ominfra-0.0.0.dev160}/ominfra/supervisor/utils/strings.py +0 -0
- {ominfra-0.0.0.dev158 → ominfra-0.0.0.dev160}/ominfra/supervisor/utils/users.py +0 -0
- {ominfra-0.0.0.dev158 → ominfra-0.0.0.dev160}/ominfra/tailscale/__init__.py +0 -0
- {ominfra-0.0.0.dev158 → ominfra-0.0.0.dev160}/ominfra/tailscale/api.py +0 -0
- {ominfra-0.0.0.dev158 → ominfra-0.0.0.dev160}/ominfra/tailscale/cli.py +0 -0
- {ominfra-0.0.0.dev158 → ominfra-0.0.0.dev160}/ominfra/threadworkers.py +0 -0
- {ominfra-0.0.0.dev158 → ominfra-0.0.0.dev160}/ominfra/tools/__init__.py +0 -0
- {ominfra-0.0.0.dev158 → ominfra-0.0.0.dev160}/ominfra/tools/listresources.py +0 -0
- {ominfra-0.0.0.dev158 → ominfra-0.0.0.dev160}/ominfra.egg-info/dependency_links.txt +0 -0
- {ominfra-0.0.0.dev158 → ominfra-0.0.0.dev160}/ominfra.egg-info/entry_points.txt +0 -0
- {ominfra-0.0.0.dev158 → ominfra-0.0.0.dev160}/ominfra.egg-info/top_level.txt +0 -0
- {ominfra-0.0.0.dev158 → ominfra-0.0.0.dev160}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.1
|
2
2
|
Name: ominfra
|
3
|
-
Version: 0.0.0.
|
3
|
+
Version: 0.0.0.dev160
|
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.
|
16
|
-
Requires-Dist: omlish==0.0.0.
|
15
|
+
Requires-Dist: omdev==0.0.0.dev160
|
16
|
+
Requires-Dist: omlish==0.0.0.dev160
|
17
17
|
Provides-Extra: all
|
18
18
|
Requires-Dist: paramiko~=3.5; extra == "all"
|
19
19
|
Requires-Dist: asyncssh~=2.18; extra == "all"
|
@@ -12,6 +12,7 @@ from .paths import DeployPathOwner
|
|
12
12
|
from .specs import DeploySpec
|
13
13
|
from .types import DeployAppTag
|
14
14
|
from .types import DeployHome
|
15
|
+
from .types import DeployKey
|
15
16
|
from .types import DeployRev
|
16
17
|
from .types import DeployTag
|
17
18
|
from .venvs import DeployVenvManager
|
@@ -19,13 +20,15 @@ from .venvs import DeployVenvManager
|
|
19
20
|
|
20
21
|
def make_deploy_tag(
|
21
22
|
rev: DeployRev,
|
22
|
-
|
23
|
+
key: DeployKey,
|
24
|
+
*,
|
25
|
+
utcnow: ta.Optional[datetime.datetime] = None,
|
23
26
|
) -> DeployTag:
|
24
|
-
if
|
25
|
-
|
26
|
-
now_fmt = '%Y%m%dT%H%M%
|
27
|
-
now_str =
|
28
|
-
return DeployTag('-'.join([now_str, rev]))
|
27
|
+
if utcnow is None:
|
28
|
+
utcnow = datetime.datetime.now(tz=datetime.timezone.utc) # noqa
|
29
|
+
now_fmt = '%Y%m%dT%H%M%SZ'
|
30
|
+
now_str = utcnow.strftime(now_fmt)
|
31
|
+
return DeployTag('-'.join([now_str, rev, key]))
|
29
32
|
|
30
33
|
|
31
34
|
class DeployAppManager(DeployPathOwner):
|
@@ -46,7 +49,7 @@ class DeployAppManager(DeployPathOwner):
|
|
46
49
|
def _dir(self) -> str:
|
47
50
|
return os.path.join(check.non_empty_str(self._deploy_home), 'apps')
|
48
51
|
|
49
|
-
def
|
52
|
+
def get_owned_deploy_paths(self) -> ta.AbstractSet[DeployPath]:
|
50
53
|
return {
|
51
54
|
DeployPath.parse('apps/@app/@tag'),
|
52
55
|
}
|
@@ -54,15 +57,14 @@ class DeployAppManager(DeployPathOwner):
|
|
54
57
|
async def prepare_app(
|
55
58
|
self,
|
56
59
|
spec: DeploySpec,
|
57
|
-
):
|
58
|
-
app_tag = DeployAppTag(spec.app, make_deploy_tag(spec.rev))
|
60
|
+
) -> None:
|
61
|
+
app_tag = DeployAppTag(spec.app, make_deploy_tag(spec.checkout.rev, spec.key()))
|
59
62
|
app_dir = os.path.join(self._dir(), spec.app, app_tag.tag)
|
60
63
|
|
61
64
|
#
|
62
65
|
|
63
66
|
await self._git.checkout(
|
64
|
-
spec.
|
65
|
-
spec.rev,
|
67
|
+
spec.checkout,
|
66
68
|
app_dir,
|
67
69
|
)
|
68
70
|
|
@@ -0,0 +1,207 @@
|
|
1
|
+
# ruff: noqa: UP006 UP007
|
2
|
+
import abc
|
3
|
+
import os
|
4
|
+
import shutil
|
5
|
+
import tempfile
|
6
|
+
import typing as ta
|
7
|
+
|
8
|
+
from omlish.lite.check import check
|
9
|
+
from omlish.lite.strings import attr_repr
|
10
|
+
|
11
|
+
|
12
|
+
DeployAtomicPathSwapKind = ta.Literal['dir', 'file']
|
13
|
+
DeployAtomicPathSwapState = ta.Literal['open', 'committed', 'aborted'] # ta.TypeAlias
|
14
|
+
|
15
|
+
|
16
|
+
##
|
17
|
+
|
18
|
+
|
19
|
+
class DeployAtomicPathSwap(abc.ABC):
|
20
|
+
def __init__(
|
21
|
+
self,
|
22
|
+
kind: DeployAtomicPathSwapKind,
|
23
|
+
dst_path: str,
|
24
|
+
*,
|
25
|
+
auto_commit: bool = False,
|
26
|
+
) -> None:
|
27
|
+
super().__init__()
|
28
|
+
|
29
|
+
self._kind = kind
|
30
|
+
self._dst_path = dst_path
|
31
|
+
self._auto_commit = auto_commit
|
32
|
+
|
33
|
+
self._state: DeployAtomicPathSwapState = 'open'
|
34
|
+
|
35
|
+
def __repr__(self) -> str:
|
36
|
+
return attr_repr(self, 'kind', 'dst_path', 'tmp_path')
|
37
|
+
|
38
|
+
@property
|
39
|
+
def kind(self) -> DeployAtomicPathSwapKind:
|
40
|
+
return self._kind
|
41
|
+
|
42
|
+
@property
|
43
|
+
def dst_path(self) -> str:
|
44
|
+
return self._dst_path
|
45
|
+
|
46
|
+
@property
|
47
|
+
@abc.abstractmethod
|
48
|
+
def tmp_path(self) -> str:
|
49
|
+
raise NotImplementedError
|
50
|
+
|
51
|
+
#
|
52
|
+
|
53
|
+
@property
|
54
|
+
def state(self) -> DeployAtomicPathSwapState:
|
55
|
+
return self._state
|
56
|
+
|
57
|
+
def _check_state(self, *states: DeployAtomicPathSwapState) -> None:
|
58
|
+
if self._state not in states:
|
59
|
+
raise RuntimeError(f'Atomic path swap not in correct state: {self._state}, {states}')
|
60
|
+
|
61
|
+
#
|
62
|
+
|
63
|
+
@abc.abstractmethod
|
64
|
+
def _commit(self) -> None:
|
65
|
+
raise NotImplementedError
|
66
|
+
|
67
|
+
def commit(self) -> None:
|
68
|
+
if self._state == 'committed':
|
69
|
+
return
|
70
|
+
self._check_state('open')
|
71
|
+
try:
|
72
|
+
self._commit()
|
73
|
+
except Exception: # noqa
|
74
|
+
self._abort()
|
75
|
+
raise
|
76
|
+
else:
|
77
|
+
self._state = 'committed'
|
78
|
+
|
79
|
+
#
|
80
|
+
|
81
|
+
@abc.abstractmethod
|
82
|
+
def _abort(self) -> None:
|
83
|
+
raise NotImplementedError
|
84
|
+
|
85
|
+
def abort(self) -> None:
|
86
|
+
if self._state == 'aborted':
|
87
|
+
return
|
88
|
+
self._abort()
|
89
|
+
self._state = 'aborted'
|
90
|
+
|
91
|
+
#
|
92
|
+
|
93
|
+
def __enter__(self) -> 'DeployAtomicPathSwap':
|
94
|
+
return self
|
95
|
+
|
96
|
+
def __exit__(self, exc_type, exc_val, exc_tb):
|
97
|
+
if (
|
98
|
+
exc_type is None and
|
99
|
+
self._auto_commit and
|
100
|
+
self._state == 'open'
|
101
|
+
):
|
102
|
+
self.commit()
|
103
|
+
else:
|
104
|
+
self.abort()
|
105
|
+
|
106
|
+
|
107
|
+
#
|
108
|
+
|
109
|
+
|
110
|
+
class DeployAtomicPathSwapping(abc.ABC):
|
111
|
+
@abc.abstractmethod
|
112
|
+
def begin_atomic_path_swap(
|
113
|
+
self,
|
114
|
+
kind: DeployAtomicPathSwapKind,
|
115
|
+
dst_path: str,
|
116
|
+
*,
|
117
|
+
name_hint: ta.Optional[str] = None,
|
118
|
+
make_dirs: bool = False,
|
119
|
+
**kwargs: ta.Any,
|
120
|
+
) -> DeployAtomicPathSwap:
|
121
|
+
raise NotImplementedError
|
122
|
+
|
123
|
+
|
124
|
+
##
|
125
|
+
|
126
|
+
|
127
|
+
class OsRenameDeployAtomicPathSwap(DeployAtomicPathSwap):
|
128
|
+
def __init__(
|
129
|
+
self,
|
130
|
+
kind: DeployAtomicPathSwapKind,
|
131
|
+
dst_path: str,
|
132
|
+
tmp_path: str,
|
133
|
+
**kwargs: ta.Any,
|
134
|
+
) -> None:
|
135
|
+
if kind == 'dir':
|
136
|
+
check.state(os.path.isdir(tmp_path))
|
137
|
+
elif kind == 'file':
|
138
|
+
check.state(os.path.isfile(tmp_path))
|
139
|
+
else:
|
140
|
+
raise TypeError(kind)
|
141
|
+
|
142
|
+
super().__init__(
|
143
|
+
kind,
|
144
|
+
dst_path,
|
145
|
+
**kwargs,
|
146
|
+
)
|
147
|
+
|
148
|
+
self._tmp_path = tmp_path
|
149
|
+
|
150
|
+
@property
|
151
|
+
def tmp_path(self) -> str:
|
152
|
+
return self._tmp_path
|
153
|
+
|
154
|
+
def _commit(self) -> None:
|
155
|
+
os.rename(self._tmp_path, self._dst_path)
|
156
|
+
|
157
|
+
def _abort(self) -> None:
|
158
|
+
shutil.rmtree(self._tmp_path, ignore_errors=True)
|
159
|
+
|
160
|
+
|
161
|
+
class TempDirDeployAtomicPathSwapping(DeployAtomicPathSwapping):
|
162
|
+
def __init__(
|
163
|
+
self,
|
164
|
+
*,
|
165
|
+
temp_dir: ta.Optional[str] = None,
|
166
|
+
root_dir: ta.Optional[str] = None,
|
167
|
+
) -> None:
|
168
|
+
super().__init__()
|
169
|
+
|
170
|
+
if root_dir is not None:
|
171
|
+
root_dir = os.path.abspath(root_dir)
|
172
|
+
self._root_dir = root_dir
|
173
|
+
self._temp_dir = temp_dir
|
174
|
+
|
175
|
+
def begin_atomic_path_swap(
|
176
|
+
self,
|
177
|
+
kind: DeployAtomicPathSwapKind,
|
178
|
+
dst_path: str,
|
179
|
+
*,
|
180
|
+
name_hint: ta.Optional[str] = None,
|
181
|
+
make_dirs: bool = False,
|
182
|
+
**kwargs: ta.Any,
|
183
|
+
) -> DeployAtomicPathSwap:
|
184
|
+
dst_path = os.path.abspath(dst_path)
|
185
|
+
if self._root_dir is not None and not dst_path.startswith(check.non_empty_str(self._root_dir)):
|
186
|
+
raise RuntimeError(f'Atomic path swap dst must be in root dir: {dst_path}, {self._root_dir}')
|
187
|
+
|
188
|
+
dst_dir = os.path.dirname(dst_path)
|
189
|
+
if make_dirs:
|
190
|
+
os.makedirs(dst_dir, exist_ok=True)
|
191
|
+
if not os.path.isdir(dst_dir):
|
192
|
+
raise RuntimeError(f'Atomic path swap dst dir does not exist: {dst_dir}')
|
193
|
+
|
194
|
+
if kind == 'dir':
|
195
|
+
tmp_path = tempfile.mkdtemp(prefix=name_hint, dir=self._temp_dir)
|
196
|
+
elif kind == 'file':
|
197
|
+
fd, tmp_path = tempfile.mkstemp(prefix=name_hint, dir=self._temp_dir)
|
198
|
+
os.close(fd)
|
199
|
+
else:
|
200
|
+
raise TypeError(kind)
|
201
|
+
|
202
|
+
return OsRenameDeployAtomicPathSwap(
|
203
|
+
kind,
|
204
|
+
dst_path,
|
205
|
+
tmp_path,
|
206
|
+
**kwargs,
|
207
|
+
)
|
@@ -5,6 +5,8 @@ from omlish.lite.logs import log
|
|
5
5
|
|
6
6
|
from ..commands.base import Command
|
7
7
|
from ..commands.base import CommandExecutor
|
8
|
+
from .apps import DeployAppManager
|
9
|
+
from .specs import DeploySpec
|
8
10
|
|
9
11
|
|
10
12
|
##
|
@@ -12,13 +14,20 @@ from ..commands.base import CommandExecutor
|
|
12
14
|
|
13
15
|
@dc.dataclass(frozen=True)
|
14
16
|
class DeployCommand(Command['DeployCommand.Output']):
|
17
|
+
spec: DeploySpec
|
18
|
+
|
15
19
|
@dc.dataclass(frozen=True)
|
16
20
|
class Output(Command.Output):
|
17
21
|
pass
|
18
22
|
|
19
23
|
|
24
|
+
@dc.dataclass(frozen=True)
|
20
25
|
class DeployCommandExecutor(CommandExecutor[DeployCommand, DeployCommand.Output]):
|
26
|
+
_apps: DeployAppManager
|
27
|
+
|
21
28
|
async def execute(self, cmd: DeployCommand) -> DeployCommand.Output:
|
22
|
-
log.info('Deploying!')
|
29
|
+
log.info('Deploying! %r', cmd.spec)
|
30
|
+
|
31
|
+
await self._apps.prepare_app(cmd.spec)
|
23
32
|
|
24
33
|
return DeployCommand.Output()
|
@@ -14,11 +14,11 @@ import typing as ta
|
|
14
14
|
|
15
15
|
from omlish.asyncs.asyncio.subprocesses import asyncio_subprocesses
|
16
16
|
from omlish.lite.cached import async_cached_nullary
|
17
|
-
from omlish.lite.cached import cached_nullary
|
18
17
|
from omlish.lite.check import check
|
19
18
|
|
20
|
-
from .
|
21
|
-
from .paths import
|
19
|
+
from .atomics import DeployAtomicPathSwapping
|
20
|
+
from .paths import SingleDirDeployPathOwner
|
21
|
+
from .specs import DeployGitCheckout
|
22
22
|
from .specs import DeployGitRepo
|
23
23
|
from .types import DeployHome
|
24
24
|
from .types import DeployRev
|
@@ -27,27 +27,22 @@ from .types import DeployRev
|
|
27
27
|
##
|
28
28
|
|
29
29
|
|
30
|
-
class DeployGitManager(
|
30
|
+
class DeployGitManager(SingleDirDeployPathOwner):
|
31
31
|
def __init__(
|
32
32
|
self,
|
33
33
|
*,
|
34
34
|
deploy_home: ta.Optional[DeployHome] = None,
|
35
|
+
atomics: DeployAtomicPathSwapping,
|
35
36
|
) -> None:
|
36
|
-
super().__init__(
|
37
|
+
super().__init__(
|
38
|
+
owned_dir='git',
|
39
|
+
deploy_home=deploy_home,
|
40
|
+
)
|
37
41
|
|
38
|
-
self.
|
42
|
+
self._atomics = atomics
|
39
43
|
|
40
44
|
self._repo_dirs: ta.Dict[DeployGitRepo, DeployGitManager.RepoDir] = {}
|
41
45
|
|
42
|
-
@cached_nullary
|
43
|
-
def _dir(self) -> str:
|
44
|
-
return os.path.join(check.non_empty_str(self._deploy_home), 'git')
|
45
|
-
|
46
|
-
def get_deploy_paths(self) -> ta.AbstractSet[DeployPath]:
|
47
|
-
return {
|
48
|
-
DeployPath.parse('git'),
|
49
|
-
}
|
50
|
-
|
51
46
|
class RepoDir:
|
52
47
|
def __init__(
|
53
48
|
self,
|
@@ -59,7 +54,7 @@ class DeployGitManager(DeployPathOwner):
|
|
59
54
|
self._git = git
|
60
55
|
self._repo = repo
|
61
56
|
self._dir = os.path.join(
|
62
|
-
self._git.
|
57
|
+
self._git._make_dir(), # noqa
|
63
58
|
check.non_empty_str(repo.host),
|
64
59
|
check.non_empty_str(repo.path),
|
65
60
|
)
|
@@ -75,12 +70,16 @@ class DeployGitManager(DeployPathOwner):
|
|
75
70
|
else:
|
76
71
|
return f'https://{self._repo.host}/{self._repo.path}'
|
77
72
|
|
73
|
+
#
|
74
|
+
|
78
75
|
async def _call(self, *cmd: str) -> None:
|
79
76
|
await asyncio_subprocesses.check_call(
|
80
77
|
*cmd,
|
81
78
|
cwd=self._dir,
|
82
79
|
)
|
83
80
|
|
81
|
+
#
|
82
|
+
|
84
83
|
@async_cached_nullary
|
85
84
|
async def init(self) -> None:
|
86
85
|
os.makedirs(self._dir, exist_ok=True)
|
@@ -94,20 +93,24 @@ class DeployGitManager(DeployPathOwner):
|
|
94
93
|
await self.init()
|
95
94
|
await self._call('git', 'fetch', '--depth=1', 'origin', rev)
|
96
95
|
|
97
|
-
|
98
|
-
check.state(not os.path.exists(dst_dir))
|
99
|
-
|
100
|
-
await self.fetch(rev)
|
96
|
+
#
|
101
97
|
|
102
|
-
|
103
|
-
os.
|
98
|
+
async def checkout(self, checkout: DeployGitCheckout, dst_dir: str) -> None:
|
99
|
+
check.state(not os.path.exists(dst_dir))
|
100
|
+
with self._git._atomics.begin_atomic_path_swap( # noqa
|
101
|
+
'dir',
|
102
|
+
dst_dir,
|
103
|
+
auto_commit=True,
|
104
|
+
make_dirs=True,
|
105
|
+
) as dst_swap:
|
106
|
+
await self.fetch(checkout.rev)
|
104
107
|
|
105
|
-
|
106
|
-
|
108
|
+
dst_call = functools.partial(asyncio_subprocesses.check_call, cwd=dst_swap.tmp_path)
|
109
|
+
await dst_call('git', 'init')
|
107
110
|
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
+
await dst_call('git', 'remote', 'add', 'local', self._dir)
|
112
|
+
await dst_call('git', 'fetch', '--depth=1', 'local', checkout.rev)
|
113
|
+
await dst_call('git', 'checkout', checkout.rev, *(checkout.subtrees or []))
|
111
114
|
|
112
115
|
def get_repo_dir(self, repo: DeployGitRepo) -> RepoDir:
|
113
116
|
try:
|
@@ -116,5 +119,5 @@ class DeployGitManager(DeployPathOwner):
|
|
116
119
|
repo_dir = self._repo_dirs[repo] = DeployGitManager.RepoDir(self, repo)
|
117
120
|
return repo_dir
|
118
121
|
|
119
|
-
async def checkout(self,
|
120
|
-
await self.get_repo_dir(repo).checkout(
|
122
|
+
async def checkout(self, checkout: DeployGitCheckout, dst_dir: str) -> None:
|
123
|
+
await self.get_repo_dir(checkout.repo).checkout(checkout, dst_dir)
|
@@ -8,12 +8,14 @@ from omlish.lite.inject import inj
|
|
8
8
|
|
9
9
|
from ..commands.inject import bind_command
|
10
10
|
from .apps import DeployAppManager
|
11
|
+
from .atomics import DeployAtomicPathSwapping
|
11
12
|
from .commands import DeployCommand
|
12
13
|
from .commands import DeployCommandExecutor
|
13
14
|
from .config import DeployConfig
|
14
15
|
from .git import DeployGitManager
|
15
16
|
from .interp import InterpCommand
|
16
17
|
from .interp import InterpCommandExecutor
|
18
|
+
from .tmp import DeployTmpManager
|
17
19
|
from .types import DeployHome
|
18
20
|
from .venvs import DeployVenvManager
|
19
21
|
|
@@ -25,10 +27,19 @@ def bind_deploy(
|
|
25
27
|
lst: ta.List[InjectorBindingOrBindings] = [
|
26
28
|
inj.bind(deploy_config),
|
27
29
|
|
30
|
+
#
|
31
|
+
|
28
32
|
inj.bind(DeployAppManager, singleton=True),
|
33
|
+
|
29
34
|
inj.bind(DeployGitManager, singleton=True),
|
35
|
+
|
36
|
+
inj.bind(DeployTmpManager, singleton=True),
|
37
|
+
inj.bind(DeployAtomicPathSwapping, to_key=DeployTmpManager),
|
38
|
+
|
30
39
|
inj.bind(DeployVenvManager, singleton=True),
|
31
40
|
|
41
|
+
#
|
42
|
+
|
32
43
|
bind_command(DeployCommand, DeployCommandExecutor),
|
33
44
|
bind_command(InterpCommand, InterpCommandExecutor),
|
34
45
|
]
|
@@ -1,46 +1,22 @@
|
|
1
1
|
# ruff: noqa: UP006 UP007
|
2
2
|
"""
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
<appplaceholder>.env
|
10
|
-
/nginx
|
11
|
-
<appplaceholder>.conf
|
12
|
-
/supervisor
|
13
|
-
<appplaceholder>.conf
|
14
|
-
/venv
|
15
|
-
/<appplaceholder>
|
16
|
-
|
17
|
-
?
|
18
|
-
/logs
|
19
|
-
/wrmsr--omlish--<placeholder>
|
20
|
-
|
21
|
-
placeholder = <name>--<rev>--<when>
|
22
|
-
|
23
|
-
==
|
24
|
-
|
25
|
-
for dn in [
|
26
|
-
'app',
|
27
|
-
'conf',
|
28
|
-
'conf/env',
|
29
|
-
'conf/nginx',
|
30
|
-
'conf/supervisor',
|
31
|
-
'venv',
|
32
|
-
]:
|
33
|
-
|
34
|
-
==
|
35
|
-
|
3
|
+
TODO:
|
4
|
+
- run/pidfile
|
5
|
+
- logs/...
|
6
|
+
- current symlink
|
7
|
+
- conf/{nginx,supervisor}
|
8
|
+
- env/?
|
36
9
|
"""
|
37
10
|
import abc
|
38
11
|
import dataclasses as dc
|
39
12
|
import os.path
|
40
13
|
import typing as ta
|
41
14
|
|
15
|
+
from omlish.lite.cached import cached_nullary
|
42
16
|
from omlish.lite.check import check
|
43
17
|
|
18
|
+
from .types import DeployHome
|
19
|
+
|
44
20
|
|
45
21
|
DeployPathKind = ta.Literal['dir', 'file'] # ta.TypeAlias
|
46
22
|
DeployPathPlaceholder = ta.Literal['app', 'tag'] # ta.TypeAlias
|
@@ -54,7 +30,7 @@ DEPLOY_PATH_PLACEHOLDER_SEPARATORS = '-.'
|
|
54
30
|
|
55
31
|
DEPLOY_PATH_PLACEHOLDERS: ta.FrozenSet[str] = frozenset([
|
56
32
|
'app',
|
57
|
-
'tag',
|
33
|
+
'tag',
|
58
34
|
])
|
59
35
|
|
60
36
|
|
@@ -181,6 +157,8 @@ class DeployPath:
|
|
181
157
|
parts: ta.Sequence[DeployPathPart]
|
182
158
|
|
183
159
|
def __post_init__(self) -> None:
|
160
|
+
hash(self)
|
161
|
+
|
184
162
|
check.not_empty(self.parts)
|
185
163
|
for p in self.parts[:-1]:
|
186
164
|
check.equal(p.kind, 'dir')
|
@@ -215,10 +193,10 @@ class DeployPath:
|
|
215
193
|
else:
|
216
194
|
tail_parse = FileDeployPathPart.parse
|
217
195
|
ps = check.non_empty_str(s).split('/')
|
218
|
-
return cls(
|
196
|
+
return cls((
|
219
197
|
*([DirDeployPathPart.parse(p) for p in ps[:-1]] if len(ps) > 1 else []),
|
220
198
|
tail_parse(ps[-1]),
|
221
|
-
|
199
|
+
))
|
222
200
|
|
223
201
|
|
224
202
|
##
|
@@ -226,5 +204,36 @@ class DeployPath:
|
|
226
204
|
|
227
205
|
class DeployPathOwner(abc.ABC):
|
228
206
|
@abc.abstractmethod
|
229
|
-
def
|
207
|
+
def get_owned_deploy_paths(self) -> ta.AbstractSet[DeployPath]:
|
230
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
|
@@ -0,0 +1,56 @@
|
|
1
|
+
# ruff: noqa: UP006 UP007
|
2
|
+
import dataclasses as dc
|
3
|
+
import hashlib
|
4
|
+
import typing as ta
|
5
|
+
|
6
|
+
from omlish.lite.cached import cached_nullary
|
7
|
+
from omlish.lite.check import check
|
8
|
+
|
9
|
+
from .types import DeployApp
|
10
|
+
from .types import DeployKey
|
11
|
+
from .types import DeployRev
|
12
|
+
|
13
|
+
|
14
|
+
##
|
15
|
+
|
16
|
+
|
17
|
+
@dc.dataclass(frozen=True)
|
18
|
+
class DeployGitRepo:
|
19
|
+
host: ta.Optional[str] = None
|
20
|
+
username: ta.Optional[str] = None
|
21
|
+
path: ta.Optional[str] = None
|
22
|
+
|
23
|
+
def __post_init__(self) -> None:
|
24
|
+
check.not_in('..', check.non_empty_str(self.host))
|
25
|
+
check.not_in('.', check.non_empty_str(self.path))
|
26
|
+
|
27
|
+
|
28
|
+
@dc.dataclass(frozen=True)
|
29
|
+
class DeployGitCheckout:
|
30
|
+
repo: DeployGitRepo
|
31
|
+
rev: DeployRev
|
32
|
+
|
33
|
+
subtrees: ta.Optional[ta.Sequence[str]] = None
|
34
|
+
|
35
|
+
def __post_init__(self) -> None:
|
36
|
+
hash(self)
|
37
|
+
check.non_empty_str(self.rev)
|
38
|
+
if self.subtrees is not None:
|
39
|
+
for st in self.subtrees:
|
40
|
+
check.non_empty_str(st)
|
41
|
+
|
42
|
+
|
43
|
+
##
|
44
|
+
|
45
|
+
|
46
|
+
@dc.dataclass(frozen=True)
|
47
|
+
class DeploySpec:
|
48
|
+
app: DeployApp
|
49
|
+
checkout: DeployGitCheckout
|
50
|
+
|
51
|
+
def __post_init__(self) -> None:
|
52
|
+
hash(self)
|
53
|
+
|
54
|
+
@cached_nullary
|
55
|
+
def key(self) -> DeployKey:
|
56
|
+
return DeployKey(hashlib.sha256(repr(self).encode('utf-8')).hexdigest()[:8])
|
@@ -0,0 +1,46 @@
|
|
1
|
+
# ruff: noqa: UP006 UP007
|
2
|
+
import typing as ta
|
3
|
+
|
4
|
+
from omlish.lite.cached import cached_nullary
|
5
|
+
from omlish.lite.check import check
|
6
|
+
|
7
|
+
from .atomics import DeployAtomicPathSwap
|
8
|
+
from .atomics import DeployAtomicPathSwapKind
|
9
|
+
from .atomics import DeployAtomicPathSwapping
|
10
|
+
from .atomics import TempDirDeployAtomicPathSwapping
|
11
|
+
from .paths import SingleDirDeployPathOwner
|
12
|
+
from .types import DeployHome
|
13
|
+
|
14
|
+
|
15
|
+
class DeployTmpManager(
|
16
|
+
SingleDirDeployPathOwner,
|
17
|
+
DeployAtomicPathSwapping,
|
18
|
+
):
|
19
|
+
def __init__(
|
20
|
+
self,
|
21
|
+
*,
|
22
|
+
deploy_home: ta.Optional[DeployHome] = None,
|
23
|
+
) -> None:
|
24
|
+
super().__init__(
|
25
|
+
owned_dir='tmp',
|
26
|
+
deploy_home=deploy_home,
|
27
|
+
)
|
28
|
+
|
29
|
+
@cached_nullary
|
30
|
+
def _swapping(self) -> DeployAtomicPathSwapping:
|
31
|
+
return TempDirDeployAtomicPathSwapping(
|
32
|
+
temp_dir=self._make_dir(),
|
33
|
+
root_dir=check.non_empty_str(self._deploy_home),
|
34
|
+
)
|
35
|
+
|
36
|
+
def begin_atomic_path_swap(
|
37
|
+
self,
|
38
|
+
kind: DeployAtomicPathSwapKind,
|
39
|
+
dst_path: str,
|
40
|
+
**kwargs: ta.Any,
|
41
|
+
) -> DeployAtomicPathSwap:
|
42
|
+
return self._swapping().begin_atomic_path_swap(
|
43
|
+
kind,
|
44
|
+
dst_path,
|
45
|
+
**kwargs,
|
46
|
+
)
|