ominfra 0.0.0.dev166__tar.gz → 0.0.0.dev168__tar.gz
Sign up to get free protection for your applications and to get access to all the features.
- {ominfra-0.0.0.dev166/ominfra.egg-info → ominfra-0.0.0.dev168}/PKG-INFO +3 -3
- ominfra-0.0.0.dev168/ominfra/manage/deploy/apps.py +175 -0
- {ominfra-0.0.0.dev166 → ominfra-0.0.0.dev168}/ominfra/manage/deploy/commands.py +3 -3
- ominfra-0.0.0.dev168/ominfra/manage/deploy/conf.py +183 -0
- ominfra-0.0.0.dev168/ominfra/manage/deploy/deploy.py +27 -0
- {ominfra-0.0.0.dev166 → ominfra-0.0.0.dev168}/ominfra/manage/deploy/git.py +12 -8
- {ominfra-0.0.0.dev166 → ominfra-0.0.0.dev168}/ominfra/manage/deploy/inject.py +32 -7
- ominfra-0.0.0.dev168/ominfra/manage/deploy/paths/inject.py +21 -0
- ominfra-0.0.0.dev168/ominfra/manage/deploy/paths/manager.py +36 -0
- ominfra-0.0.0.dev168/ominfra/manage/deploy/paths/owners.py +50 -0
- ominfra-0.0.0.dev168/ominfra/manage/deploy/paths/paths.py +216 -0
- {ominfra-0.0.0.dev166 → ominfra-0.0.0.dev168}/ominfra/manage/deploy/specs.py +71 -6
- {ominfra-0.0.0.dev166 → ominfra-0.0.0.dev168}/ominfra/manage/deploy/tmp.py +1 -1
- ominfra-0.0.0.dev168/ominfra/manage/deploy/types.py +39 -0
- {ominfra-0.0.0.dev166 → ominfra-0.0.0.dev168}/ominfra/manage/deploy/venvs.py +4 -33
- {ominfra-0.0.0.dev166 → ominfra-0.0.0.dev168}/ominfra/scripts/journald2aws.py +33 -0
- {ominfra-0.0.0.dev166 → ominfra-0.0.0.dev168}/ominfra/scripts/manage.py +1038 -518
- {ominfra-0.0.0.dev166 → ominfra-0.0.0.dev168}/ominfra/scripts/supervisor.py +34 -0
- {ominfra-0.0.0.dev166 → ominfra-0.0.0.dev168}/ominfra/supervisor/configs.py +1 -0
- ominfra-0.0.0.dev168/ominfra/tools/__init__.py +0 -0
- {ominfra-0.0.0.dev166 → ominfra-0.0.0.dev168/ominfra.egg-info}/PKG-INFO +3 -3
- {ominfra-0.0.0.dev166 → ominfra-0.0.0.dev168}/ominfra.egg-info/SOURCES.txt +7 -1
- {ominfra-0.0.0.dev166 → ominfra-0.0.0.dev168}/ominfra.egg-info/requires.txt +2 -2
- {ominfra-0.0.0.dev166 → ominfra-0.0.0.dev168}/pyproject.toml +3 -3
- ominfra-0.0.0.dev166/ominfra/manage/deploy/apps.py +0 -74
- ominfra-0.0.0.dev166/ominfra/manage/deploy/paths.py +0 -239
- ominfra-0.0.0.dev166/ominfra/manage/deploy/types.py +0 -14
- {ominfra-0.0.0.dev166 → ominfra-0.0.0.dev168}/LICENSE +0 -0
- {ominfra-0.0.0.dev166 → ominfra-0.0.0.dev168}/MANIFEST.in +0 -0
- {ominfra-0.0.0.dev166 → ominfra-0.0.0.dev168}/README.rst +0 -0
- {ominfra-0.0.0.dev166 → ominfra-0.0.0.dev168}/ominfra/.manifests.json +0 -0
- {ominfra-0.0.0.dev166 → ominfra-0.0.0.dev168}/ominfra/__about__.py +0 -0
- {ominfra-0.0.0.dev166 → ominfra-0.0.0.dev168}/ominfra/__init__.py +0 -0
- {ominfra-0.0.0.dev166 → ominfra-0.0.0.dev168}/ominfra/clouds/__init__.py +0 -0
- {ominfra-0.0.0.dev166 → ominfra-0.0.0.dev168}/ominfra/clouds/aws/__init__.py +0 -0
- {ominfra-0.0.0.dev166 → ominfra-0.0.0.dev168}/ominfra/clouds/aws/__main__.py +0 -0
- {ominfra-0.0.0.dev166 → ominfra-0.0.0.dev168}/ominfra/clouds/aws/auth.py +0 -0
- {ominfra-0.0.0.dev166 → ominfra-0.0.0.dev168}/ominfra/clouds/aws/cli.py +0 -0
- {ominfra-0.0.0.dev166 → ominfra-0.0.0.dev168}/ominfra/clouds/aws/dataclasses.py +0 -0
- {ominfra-0.0.0.dev166 → ominfra-0.0.0.dev168}/ominfra/clouds/aws/journald2aws/__init__.py +0 -0
- {ominfra-0.0.0.dev166 → ominfra-0.0.0.dev168}/ominfra/clouds/aws/journald2aws/__main__.py +0 -0
- {ominfra-0.0.0.dev166 → ominfra-0.0.0.dev168}/ominfra/clouds/aws/journald2aws/cursor.py +0 -0
- {ominfra-0.0.0.dev166 → ominfra-0.0.0.dev168}/ominfra/clouds/aws/journald2aws/driver.py +0 -0
- {ominfra-0.0.0.dev166 → ominfra-0.0.0.dev168}/ominfra/clouds/aws/journald2aws/main.py +0 -0
- {ominfra-0.0.0.dev166 → ominfra-0.0.0.dev168}/ominfra/clouds/aws/journald2aws/poster.py +0 -0
- {ominfra-0.0.0.dev166 → ominfra-0.0.0.dev168}/ominfra/clouds/aws/logs.py +0 -0
- {ominfra-0.0.0.dev166 → ominfra-0.0.0.dev168}/ominfra/clouds/aws/metadata.py +0 -0
- {ominfra-0.0.0.dev166 → ominfra-0.0.0.dev168}/ominfra/clouds/gcp/__init__.py +0 -0
- {ominfra-0.0.0.dev166 → ominfra-0.0.0.dev168}/ominfra/clouds/gcp/auth.py +0 -0
- {ominfra-0.0.0.dev166 → ominfra-0.0.0.dev168}/ominfra/cmds.py +0 -0
- {ominfra-0.0.0.dev166 → ominfra-0.0.0.dev168}/ominfra/configs.py +0 -0
- {ominfra-0.0.0.dev166 → ominfra-0.0.0.dev168}/ominfra/journald/__init__.py +0 -0
- {ominfra-0.0.0.dev166 → ominfra-0.0.0.dev168}/ominfra/journald/fields.py +0 -0
- {ominfra-0.0.0.dev166 → ominfra-0.0.0.dev168}/ominfra/journald/genmessages.py +0 -0
- {ominfra-0.0.0.dev166 → ominfra-0.0.0.dev168}/ominfra/journald/messages.py +0 -0
- {ominfra-0.0.0.dev166 → ominfra-0.0.0.dev168}/ominfra/journald/tailer.py +0 -0
- {ominfra-0.0.0.dev166 → ominfra-0.0.0.dev168}/ominfra/manage/__init__.py +0 -0
- {ominfra-0.0.0.dev166 → ominfra-0.0.0.dev168}/ominfra/manage/__main__.py +0 -0
- {ominfra-0.0.0.dev166 → ominfra-0.0.0.dev168}/ominfra/manage/bootstrap.py +0 -0
- {ominfra-0.0.0.dev166 → ominfra-0.0.0.dev168}/ominfra/manage/bootstrap_.py +0 -0
- {ominfra-0.0.0.dev166 → ominfra-0.0.0.dev168}/ominfra/manage/commands/__init__.py +0 -0
- {ominfra-0.0.0.dev166 → ominfra-0.0.0.dev168}/ominfra/manage/commands/base.py +0 -0
- {ominfra-0.0.0.dev166 → ominfra-0.0.0.dev168}/ominfra/manage/commands/inject.py +0 -0
- {ominfra-0.0.0.dev166 → ominfra-0.0.0.dev168}/ominfra/manage/commands/local.py +0 -0
- {ominfra-0.0.0.dev166 → ominfra-0.0.0.dev168}/ominfra/manage/commands/marshal.py +0 -0
- {ominfra-0.0.0.dev166 → ominfra-0.0.0.dev168}/ominfra/manage/commands/ping.py +0 -0
- {ominfra-0.0.0.dev166 → ominfra-0.0.0.dev168}/ominfra/manage/commands/subprocess.py +0 -0
- {ominfra-0.0.0.dev166 → ominfra-0.0.0.dev168}/ominfra/manage/commands/types.py +0 -0
- {ominfra-0.0.0.dev166 → ominfra-0.0.0.dev168}/ominfra/manage/config.py +0 -0
- {ominfra-0.0.0.dev166 → ominfra-0.0.0.dev168}/ominfra/manage/deploy/__init__.py +0 -0
- {ominfra-0.0.0.dev166 → ominfra-0.0.0.dev168}/ominfra/manage/deploy/config.py +0 -0
- {ominfra-0.0.0.dev166 → ominfra-0.0.0.dev168}/ominfra/manage/deploy/interp.py +0 -0
- {ominfra-0.0.0.dev166/ominfra/manage/remote → ominfra-0.0.0.dev168/ominfra/manage/deploy/paths}/__init__.py +0 -0
- {ominfra-0.0.0.dev166 → ominfra-0.0.0.dev168}/ominfra/manage/inject.py +0 -0
- {ominfra-0.0.0.dev166 → ominfra-0.0.0.dev168}/ominfra/manage/main.py +0 -0
- {ominfra-0.0.0.dev166 → ominfra-0.0.0.dev168}/ominfra/manage/marshal.py +0 -0
- {ominfra-0.0.0.dev166/ominfra/manage/system → ominfra-0.0.0.dev168/ominfra/manage/remote}/__init__.py +0 -0
- {ominfra-0.0.0.dev166 → ominfra-0.0.0.dev168}/ominfra/manage/remote/_main.py +0 -0
- {ominfra-0.0.0.dev166 → ominfra-0.0.0.dev168}/ominfra/manage/remote/channel.py +0 -0
- {ominfra-0.0.0.dev166 → ominfra-0.0.0.dev168}/ominfra/manage/remote/config.py +0 -0
- {ominfra-0.0.0.dev166 → ominfra-0.0.0.dev168}/ominfra/manage/remote/connection.py +0 -0
- {ominfra-0.0.0.dev166 → ominfra-0.0.0.dev168}/ominfra/manage/remote/execution.py +0 -0
- {ominfra-0.0.0.dev166 → ominfra-0.0.0.dev168}/ominfra/manage/remote/inject.py +0 -0
- {ominfra-0.0.0.dev166 → ominfra-0.0.0.dev168}/ominfra/manage/remote/payload.py +0 -0
- {ominfra-0.0.0.dev166 → ominfra-0.0.0.dev168}/ominfra/manage/remote/spawning.py +0 -0
- {ominfra-0.0.0.dev166/ominfra/manage/targets → ominfra-0.0.0.dev168/ominfra/manage/system}/__init__.py +0 -0
- {ominfra-0.0.0.dev166 → ominfra-0.0.0.dev168}/ominfra/manage/system/commands.py +0 -0
- {ominfra-0.0.0.dev166 → ominfra-0.0.0.dev168}/ominfra/manage/system/config.py +0 -0
- {ominfra-0.0.0.dev166 → ominfra-0.0.0.dev168}/ominfra/manage/system/inject.py +0 -0
- {ominfra-0.0.0.dev166 → ominfra-0.0.0.dev168}/ominfra/manage/system/packages.py +0 -0
- {ominfra-0.0.0.dev166 → ominfra-0.0.0.dev168}/ominfra/manage/system/platforms.py +0 -0
- {ominfra-0.0.0.dev166/ominfra/scripts → ominfra-0.0.0.dev168/ominfra/manage/targets}/__init__.py +0 -0
- {ominfra-0.0.0.dev166 → ominfra-0.0.0.dev168}/ominfra/manage/targets/connection.py +0 -0
- {ominfra-0.0.0.dev166 → ominfra-0.0.0.dev168}/ominfra/manage/targets/inject.py +0 -0
- {ominfra-0.0.0.dev166 → ominfra-0.0.0.dev168}/ominfra/manage/targets/targets.py +0 -0
- {ominfra-0.0.0.dev166 → ominfra-0.0.0.dev168}/ominfra/pyremote.py +0 -0
- {ominfra-0.0.0.dev166/ominfra/supervisor/utils → ominfra-0.0.0.dev168/ominfra/scripts}/__init__.py +0 -0
- {ominfra-0.0.0.dev166 → ominfra-0.0.0.dev168}/ominfra/ssh.py +0 -0
- {ominfra-0.0.0.dev166 → ominfra-0.0.0.dev168}/ominfra/supervisor/LICENSE.txt +0 -0
- {ominfra-0.0.0.dev166 → ominfra-0.0.0.dev168}/ominfra/supervisor/__init__.py +0 -0
- {ominfra-0.0.0.dev166 → ominfra-0.0.0.dev168}/ominfra/supervisor/__main__.py +0 -0
- {ominfra-0.0.0.dev166 → ominfra-0.0.0.dev168}/ominfra/supervisor/dispatchers.py +0 -0
- {ominfra-0.0.0.dev166 → ominfra-0.0.0.dev168}/ominfra/supervisor/dispatchersimpl.py +0 -0
- {ominfra-0.0.0.dev166 → ominfra-0.0.0.dev168}/ominfra/supervisor/events.py +0 -0
- {ominfra-0.0.0.dev166 → ominfra-0.0.0.dev168}/ominfra/supervisor/exceptions.py +0 -0
- {ominfra-0.0.0.dev166 → ominfra-0.0.0.dev168}/ominfra/supervisor/groups.py +0 -0
- {ominfra-0.0.0.dev166 → ominfra-0.0.0.dev168}/ominfra/supervisor/groupsimpl.py +0 -0
- {ominfra-0.0.0.dev166 → ominfra-0.0.0.dev168}/ominfra/supervisor/http.py +0 -0
- {ominfra-0.0.0.dev166 → ominfra-0.0.0.dev168}/ominfra/supervisor/inject.py +0 -0
- {ominfra-0.0.0.dev166 → ominfra-0.0.0.dev168}/ominfra/supervisor/io.py +0 -0
- {ominfra-0.0.0.dev166 → ominfra-0.0.0.dev168}/ominfra/supervisor/main.py +0 -0
- {ominfra-0.0.0.dev166 → ominfra-0.0.0.dev168}/ominfra/supervisor/pipes.py +0 -0
- {ominfra-0.0.0.dev166 → ominfra-0.0.0.dev168}/ominfra/supervisor/privileges.py +0 -0
- {ominfra-0.0.0.dev166 → ominfra-0.0.0.dev168}/ominfra/supervisor/process.py +0 -0
- {ominfra-0.0.0.dev166 → ominfra-0.0.0.dev168}/ominfra/supervisor/processimpl.py +0 -0
- {ominfra-0.0.0.dev166 → ominfra-0.0.0.dev168}/ominfra/supervisor/setup.py +0 -0
- {ominfra-0.0.0.dev166 → ominfra-0.0.0.dev168}/ominfra/supervisor/setupimpl.py +0 -0
- {ominfra-0.0.0.dev166 → ominfra-0.0.0.dev168}/ominfra/supervisor/signals.py +0 -0
- {ominfra-0.0.0.dev166 → ominfra-0.0.0.dev168}/ominfra/supervisor/spawning.py +0 -0
- {ominfra-0.0.0.dev166 → ominfra-0.0.0.dev168}/ominfra/supervisor/spawningimpl.py +0 -0
- {ominfra-0.0.0.dev166 → ominfra-0.0.0.dev168}/ominfra/supervisor/states.py +0 -0
- {ominfra-0.0.0.dev166 → ominfra-0.0.0.dev168}/ominfra/supervisor/supervisor.py +0 -0
- {ominfra-0.0.0.dev166 → ominfra-0.0.0.dev168}/ominfra/supervisor/types.py +0 -0
- {ominfra-0.0.0.dev166/ominfra/tailscale → ominfra-0.0.0.dev168/ominfra/supervisor/utils}/__init__.py +0 -0
- {ominfra-0.0.0.dev166 → ominfra-0.0.0.dev168}/ominfra/supervisor/utils/collections.py +0 -0
- {ominfra-0.0.0.dev166 → ominfra-0.0.0.dev168}/ominfra/supervisor/utils/diag.py +0 -0
- {ominfra-0.0.0.dev166 → ominfra-0.0.0.dev168}/ominfra/supervisor/utils/fds.py +0 -0
- {ominfra-0.0.0.dev166 → ominfra-0.0.0.dev168}/ominfra/supervisor/utils/fs.py +0 -0
- {ominfra-0.0.0.dev166 → ominfra-0.0.0.dev168}/ominfra/supervisor/utils/os.py +0 -0
- {ominfra-0.0.0.dev166 → ominfra-0.0.0.dev168}/ominfra/supervisor/utils/ostypes.py +0 -0
- {ominfra-0.0.0.dev166 → ominfra-0.0.0.dev168}/ominfra/supervisor/utils/signals.py +0 -0
- {ominfra-0.0.0.dev166 → ominfra-0.0.0.dev168}/ominfra/supervisor/utils/strings.py +0 -0
- {ominfra-0.0.0.dev166 → ominfra-0.0.0.dev168}/ominfra/supervisor/utils/users.py +0 -0
- {ominfra-0.0.0.dev166/ominfra/tools → ominfra-0.0.0.dev168/ominfra/tailscale}/__init__.py +0 -0
- {ominfra-0.0.0.dev166 → ominfra-0.0.0.dev168}/ominfra/tailscale/api.py +0 -0
- {ominfra-0.0.0.dev166 → ominfra-0.0.0.dev168}/ominfra/tailscale/cli.py +0 -0
- {ominfra-0.0.0.dev166 → ominfra-0.0.0.dev168}/ominfra/threadworkers.py +0 -0
- {ominfra-0.0.0.dev166 → ominfra-0.0.0.dev168}/ominfra/tools/listresources.py +0 -0
- {ominfra-0.0.0.dev166 → ominfra-0.0.0.dev168}/ominfra.egg-info/dependency_links.txt +0 -0
- {ominfra-0.0.0.dev166 → ominfra-0.0.0.dev168}/ominfra.egg-info/entry_points.txt +0 -0
- {ominfra-0.0.0.dev166 → ominfra-0.0.0.dev168}/ominfra.egg-info/top_level.txt +0 -0
- {ominfra-0.0.0.dev166 → ominfra-0.0.0.dev168}/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.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.
|
16
|
-
Requires-Dist: omlish==0.0.0.
|
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"
|
@@ -0,0 +1,175 @@
|
|
1
|
+
# ruff: noqa: UP006 UP007
|
2
|
+
import datetime
|
3
|
+
import os.path
|
4
|
+
import typing as ta
|
5
|
+
|
6
|
+
from omlish.lite.cached import cached_nullary
|
7
|
+
from omlish.lite.check import check
|
8
|
+
from omlish.os.paths import relative_symlink
|
9
|
+
|
10
|
+
from .conf import DeployConfManager
|
11
|
+
from .git import DeployGitManager
|
12
|
+
from .paths.owners import DeployPathOwner
|
13
|
+
from .paths.paths import DeployPath
|
14
|
+
from .specs import DeploySpec
|
15
|
+
from .types import DeployAppTag
|
16
|
+
from .types import DeployHome
|
17
|
+
from .types import DeployKey
|
18
|
+
from .types import DeployRev
|
19
|
+
from .types import DeployTag
|
20
|
+
from .venvs import DeployVenvManager
|
21
|
+
|
22
|
+
|
23
|
+
def make_deploy_tag(
|
24
|
+
rev: DeployRev,
|
25
|
+
key: DeployKey,
|
26
|
+
*,
|
27
|
+
utcnow: ta.Optional[datetime.datetime] = None,
|
28
|
+
) -> DeployTag:
|
29
|
+
if utcnow is None:
|
30
|
+
utcnow = datetime.datetime.now(tz=datetime.timezone.utc) # noqa
|
31
|
+
now_fmt = '%Y%m%dT%H%M%SZ'
|
32
|
+
now_str = utcnow.strftime(now_fmt)
|
33
|
+
return DeployTag('-'.join([now_str, rev, key]))
|
34
|
+
|
35
|
+
|
36
|
+
class DeployAppManager(DeployPathOwner):
|
37
|
+
def __init__(
|
38
|
+
self,
|
39
|
+
*,
|
40
|
+
deploy_home: ta.Optional[DeployHome] = None,
|
41
|
+
|
42
|
+
conf: DeployConfManager,
|
43
|
+
git: DeployGitManager,
|
44
|
+
venvs: DeployVenvManager,
|
45
|
+
) -> None:
|
46
|
+
super().__init__()
|
47
|
+
|
48
|
+
self._deploy_home = deploy_home
|
49
|
+
|
50
|
+
self._conf = conf
|
51
|
+
self._git = git
|
52
|
+
self._venvs = venvs
|
53
|
+
|
54
|
+
#
|
55
|
+
|
56
|
+
_APP_TAG_DIR_STR = 'tags/apps/@app/@tag/'
|
57
|
+
_APP_TAG_DIR = DeployPath.parse(_APP_TAG_DIR_STR)
|
58
|
+
|
59
|
+
_CONF_TAG_DIR_STR = 'tags/conf/@tag--@app/'
|
60
|
+
_CONF_TAG_DIR = DeployPath.parse(_CONF_TAG_DIR_STR)
|
61
|
+
|
62
|
+
_DEPLOY_DIR_STR = 'deploys/@tag--@app/'
|
63
|
+
_DEPLOY_DIR = DeployPath.parse(_DEPLOY_DIR_STR)
|
64
|
+
|
65
|
+
_APP_DEPLOY_LINK = DeployPath.parse(f'{_DEPLOY_DIR_STR}apps/@app')
|
66
|
+
_CONF_DEPLOY_LINK = DeployPath.parse(f'{_DEPLOY_DIR_STR}conf')
|
67
|
+
|
68
|
+
@cached_nullary
|
69
|
+
def get_owned_deploy_paths(self) -> ta.AbstractSet[DeployPath]:
|
70
|
+
return {
|
71
|
+
self._APP_TAG_DIR,
|
72
|
+
|
73
|
+
self._CONF_TAG_DIR,
|
74
|
+
|
75
|
+
self._DEPLOY_DIR,
|
76
|
+
|
77
|
+
self._APP_DEPLOY_LINK,
|
78
|
+
self._CONF_DEPLOY_LINK,
|
79
|
+
|
80
|
+
*[
|
81
|
+
DeployPath.parse(f'{self._APP_TAG_DIR_STR}{sfx}/')
|
82
|
+
for sfx in [
|
83
|
+
'conf',
|
84
|
+
'git',
|
85
|
+
'venv',
|
86
|
+
]
|
87
|
+
],
|
88
|
+
}
|
89
|
+
|
90
|
+
#
|
91
|
+
|
92
|
+
async def prepare_app(
|
93
|
+
self,
|
94
|
+
spec: DeploySpec,
|
95
|
+
) -> None:
|
96
|
+
app_tag = DeployAppTag(spec.app, make_deploy_tag(spec.git.rev, spec.key()))
|
97
|
+
|
98
|
+
#
|
99
|
+
|
100
|
+
deploy_home = check.non_empty_str(self._deploy_home)
|
101
|
+
|
102
|
+
def build_path(pth: DeployPath) -> str:
|
103
|
+
return os.path.join(deploy_home, pth.render(app_tag.placeholders()))
|
104
|
+
|
105
|
+
app_tag_dir = build_path(self._APP_TAG_DIR)
|
106
|
+
conf_tag_dir = build_path(self._CONF_TAG_DIR)
|
107
|
+
deploy_dir = build_path(self._DEPLOY_DIR)
|
108
|
+
app_deploy_link = build_path(self._APP_DEPLOY_LINK)
|
109
|
+
conf_deploy_link_file = build_path(self._CONF_DEPLOY_LINK)
|
110
|
+
|
111
|
+
#
|
112
|
+
|
113
|
+
os.makedirs(deploy_dir)
|
114
|
+
|
115
|
+
deploying_link = os.path.join(deploy_home, 'deploys/deploying')
|
116
|
+
relative_symlink(
|
117
|
+
deploy_dir,
|
118
|
+
deploying_link,
|
119
|
+
target_is_directory=True,
|
120
|
+
make_dirs=True,
|
121
|
+
)
|
122
|
+
|
123
|
+
#
|
124
|
+
|
125
|
+
os.makedirs(app_tag_dir)
|
126
|
+
relative_symlink(
|
127
|
+
app_tag_dir,
|
128
|
+
app_deploy_link,
|
129
|
+
target_is_directory=True,
|
130
|
+
make_dirs=True,
|
131
|
+
)
|
132
|
+
|
133
|
+
#
|
134
|
+
|
135
|
+
os.makedirs(conf_tag_dir)
|
136
|
+
relative_symlink(
|
137
|
+
conf_tag_dir,
|
138
|
+
conf_deploy_link_file,
|
139
|
+
target_is_directory=True,
|
140
|
+
make_dirs=True,
|
141
|
+
)
|
142
|
+
|
143
|
+
#
|
144
|
+
|
145
|
+
git_dir = os.path.join(app_tag_dir, 'git')
|
146
|
+
await self._git.checkout(
|
147
|
+
spec.git,
|
148
|
+
git_dir,
|
149
|
+
)
|
150
|
+
|
151
|
+
#
|
152
|
+
|
153
|
+
if spec.venv is not None:
|
154
|
+
venv_dir = os.path.join(app_tag_dir, 'venv')
|
155
|
+
await self._venvs.setup_venv(
|
156
|
+
spec.venv,
|
157
|
+
git_dir,
|
158
|
+
venv_dir,
|
159
|
+
)
|
160
|
+
|
161
|
+
#
|
162
|
+
|
163
|
+
if spec.conf is not None:
|
164
|
+
conf_dir = os.path.join(app_tag_dir, 'conf')
|
165
|
+
await self._conf.write_conf(
|
166
|
+
spec.conf,
|
167
|
+
app_tag,
|
168
|
+
conf_dir,
|
169
|
+
conf_tag_dir,
|
170
|
+
)
|
171
|
+
|
172
|
+
#
|
173
|
+
|
174
|
+
current_link = os.path.join(deploy_home, 'deploys/current')
|
175
|
+
os.replace(deploying_link, current_link)
|
@@ -5,7 +5,7 @@ from omlish.lite.logs import log
|
|
5
5
|
|
6
6
|
from ..commands.base import Command
|
7
7
|
from ..commands.base import CommandExecutor
|
8
|
-
from .
|
8
|
+
from .deploy import DeployManager
|
9
9
|
from .specs import DeploySpec
|
10
10
|
|
11
11
|
|
@@ -23,11 +23,11 @@ class DeployCommand(Command['DeployCommand.Output']):
|
|
23
23
|
|
24
24
|
@dc.dataclass(frozen=True)
|
25
25
|
class DeployCommandExecutor(CommandExecutor[DeployCommand, DeployCommand.Output]):
|
26
|
-
|
26
|
+
_deploy: DeployManager
|
27
27
|
|
28
28
|
async def execute(self, cmd: DeployCommand) -> DeployCommand.Output:
|
29
29
|
log.info('Deploying! %r', cmd.spec)
|
30
30
|
|
31
|
-
await self.
|
31
|
+
await self._deploy.run_deploy(cmd.spec)
|
32
32
|
|
33
33
|
return DeployCommand.Output()
|
@@ -0,0 +1,183 @@
|
|
1
|
+
# ruff: noqa: UP006 UP007
|
2
|
+
"""
|
3
|
+
TODO:
|
4
|
+
- @conf DeployPathPlaceholder? :|
|
5
|
+
- post-deploy: remove any dir_links not present in new spec
|
6
|
+
- * only if succeeded * - otherwise, remove any dir_links present in new spec but not previously present?
|
7
|
+
- no such thing as 'previously present'.. build a 'deploy state' and pass it back?
|
8
|
+
- ** whole thing can be atomic **
|
9
|
+
- 1) new atomic temp dir
|
10
|
+
- 2) for each subdir not needing modification, hardlink into temp dir
|
11
|
+
- 3) for each subdir needing modification, new subdir, hardlink all files not needing modification
|
12
|
+
- 4) write (or if deleting, omit) new files
|
13
|
+
- 5) swap top level
|
14
|
+
- ** whole deploy can be atomic(-ish) - do this for everything **
|
15
|
+
- just a '/deploy/current' dir
|
16
|
+
- some things (venvs) cannot be moved, thus the /deploy/venvs dir
|
17
|
+
- ** ensure (enforce) equivalent relpath nesting
|
18
|
+
"""
|
19
|
+
import os.path
|
20
|
+
import typing as ta
|
21
|
+
|
22
|
+
from omlish.lite.check import check
|
23
|
+
from omlish.os.paths import is_path_in_dir
|
24
|
+
from omlish.os.paths import relative_symlink
|
25
|
+
|
26
|
+
from .paths.paths import DEPLOY_PATH_PLACEHOLDER_SEPARATOR
|
27
|
+
from .specs import AppDeployConfLink
|
28
|
+
from .specs import DeployConfFile
|
29
|
+
from .specs import DeployConfLink
|
30
|
+
from .specs import DeployConfSpec
|
31
|
+
from .specs import TagDeployConfLink
|
32
|
+
from .types import DeployAppTag
|
33
|
+
from .types import DeployHome
|
34
|
+
|
35
|
+
|
36
|
+
class DeployConfManager:
|
37
|
+
def __init__(
|
38
|
+
self,
|
39
|
+
*,
|
40
|
+
deploy_home: ta.Optional[DeployHome] = None,
|
41
|
+
) -> None:
|
42
|
+
super().__init__()
|
43
|
+
|
44
|
+
self._deploy_home = deploy_home
|
45
|
+
|
46
|
+
#
|
47
|
+
|
48
|
+
async def _write_conf_file(
|
49
|
+
self,
|
50
|
+
cf: DeployConfFile,
|
51
|
+
conf_dir: str,
|
52
|
+
) -> None:
|
53
|
+
conf_file = os.path.join(conf_dir, cf.path)
|
54
|
+
check.arg(is_path_in_dir(conf_dir, conf_file))
|
55
|
+
|
56
|
+
os.makedirs(os.path.dirname(conf_file), exist_ok=True)
|
57
|
+
|
58
|
+
with open(conf_file, 'w') as f: # noqa
|
59
|
+
f.write(cf.body)
|
60
|
+
|
61
|
+
#
|
62
|
+
|
63
|
+
class _ComputedConfLink(ta.NamedTuple):
|
64
|
+
is_dir: bool
|
65
|
+
link_src: str
|
66
|
+
link_dst: str
|
67
|
+
|
68
|
+
def _compute_conf_link_dst(
|
69
|
+
self,
|
70
|
+
link: DeployConfLink,
|
71
|
+
app_tag: DeployAppTag,
|
72
|
+
conf_dir: str,
|
73
|
+
link_dir: str,
|
74
|
+
) -> _ComputedConfLink:
|
75
|
+
link_src = os.path.join(conf_dir, link.src)
|
76
|
+
check.arg(is_path_in_dir(conf_dir, link_src))
|
77
|
+
|
78
|
+
#
|
79
|
+
|
80
|
+
if (is_dir := link.src.endswith('/')):
|
81
|
+
# @conf/ - links a directory in root of app conf dir to conf/@conf/@dst/
|
82
|
+
check.arg(link.src.count('/') == 1)
|
83
|
+
link_dst_pfx = link.src
|
84
|
+
link_dst_sfx = ''
|
85
|
+
|
86
|
+
elif '/' in link.src:
|
87
|
+
# @conf/file - links a single file in a single subdir to conf/@conf/@dst--file
|
88
|
+
d, f = os.path.split(link.src)
|
89
|
+
# TODO: check filename :|
|
90
|
+
link_dst_pfx = d + '/'
|
91
|
+
link_dst_sfx = DEPLOY_PATH_PLACEHOLDER_SEPARATOR + f
|
92
|
+
|
93
|
+
else: # noqa
|
94
|
+
# @conf(.ext)* - links a single file in root of app conf dir to conf/@conf/@dst(.ext)*
|
95
|
+
if '.' in link.src:
|
96
|
+
l, _, r = link.src.partition('.')
|
97
|
+
link_dst_pfx = l + '/'
|
98
|
+
link_dst_sfx = '.' + r
|
99
|
+
else:
|
100
|
+
link_dst_pfx = link.src + '/'
|
101
|
+
link_dst_sfx = ''
|
102
|
+
|
103
|
+
#
|
104
|
+
|
105
|
+
if isinstance(link, AppDeployConfLink):
|
106
|
+
link_dst_mid = str(app_tag.app)
|
107
|
+
elif isinstance(link, TagDeployConfLink):
|
108
|
+
link_dst_mid = DEPLOY_PATH_PLACEHOLDER_SEPARATOR.join([app_tag.app, app_tag.tag])
|
109
|
+
else:
|
110
|
+
raise TypeError(link)
|
111
|
+
|
112
|
+
#
|
113
|
+
|
114
|
+
link_dst_name = ''.join([
|
115
|
+
link_dst_pfx,
|
116
|
+
link_dst_mid,
|
117
|
+
link_dst_sfx,
|
118
|
+
])
|
119
|
+
link_dst = os.path.join(link_dir, link_dst_name)
|
120
|
+
|
121
|
+
return DeployConfManager._ComputedConfLink(
|
122
|
+
is_dir=is_dir,
|
123
|
+
link_src=link_src,
|
124
|
+
link_dst=link_dst,
|
125
|
+
)
|
126
|
+
|
127
|
+
async def _make_conf_link(
|
128
|
+
self,
|
129
|
+
link: DeployConfLink,
|
130
|
+
app_tag: DeployAppTag,
|
131
|
+
conf_dir: str,
|
132
|
+
link_dir: str,
|
133
|
+
) -> None:
|
134
|
+
comp = self._compute_conf_link_dst(
|
135
|
+
link,
|
136
|
+
app_tag,
|
137
|
+
conf_dir,
|
138
|
+
link_dir,
|
139
|
+
)
|
140
|
+
|
141
|
+
#
|
142
|
+
|
143
|
+
check.arg(is_path_in_dir(conf_dir, comp.link_src))
|
144
|
+
check.arg(is_path_in_dir(link_dir, comp.link_dst))
|
145
|
+
|
146
|
+
if comp.is_dir:
|
147
|
+
check.arg(os.path.isdir(comp.link_src))
|
148
|
+
else:
|
149
|
+
check.arg(os.path.isfile(comp.link_src))
|
150
|
+
|
151
|
+
#
|
152
|
+
|
153
|
+
relative_symlink( # noqa
|
154
|
+
comp.link_src,
|
155
|
+
comp.link_dst,
|
156
|
+
target_is_directory=comp.is_dir,
|
157
|
+
make_dirs=True,
|
158
|
+
)
|
159
|
+
|
160
|
+
#
|
161
|
+
|
162
|
+
async def write_conf(
|
163
|
+
self,
|
164
|
+
spec: DeployConfSpec,
|
165
|
+
app_tag: DeployAppTag,
|
166
|
+
conf_dir: str,
|
167
|
+
link_dir: str,
|
168
|
+
) -> None:
|
169
|
+
for cf in spec.files or []:
|
170
|
+
await self._write_conf_file(
|
171
|
+
cf,
|
172
|
+
conf_dir,
|
173
|
+
)
|
174
|
+
|
175
|
+
#
|
176
|
+
|
177
|
+
for link in spec.links or []:
|
178
|
+
await self._make_conf_link(
|
179
|
+
link,
|
180
|
+
app_tag,
|
181
|
+
conf_dir,
|
182
|
+
link_dir,
|
183
|
+
)
|
@@ -0,0 +1,27 @@
|
|
1
|
+
# ruff: noqa: UP006 UP007
|
2
|
+
from .apps import DeployAppManager
|
3
|
+
from .paths.manager import DeployPathsManager
|
4
|
+
from .specs import DeploySpec
|
5
|
+
|
6
|
+
|
7
|
+
class DeployManager:
|
8
|
+
def __init__(
|
9
|
+
self,
|
10
|
+
*,
|
11
|
+
apps: DeployAppManager,
|
12
|
+
paths: DeployPathsManager,
|
13
|
+
):
|
14
|
+
super().__init__()
|
15
|
+
|
16
|
+
self._apps = apps
|
17
|
+
self._paths = paths
|
18
|
+
|
19
|
+
async def run_deploy(
|
20
|
+
self,
|
21
|
+
spec: DeploySpec,
|
22
|
+
) -> None:
|
23
|
+
self._paths.validate_deploy_paths()
|
24
|
+
|
25
|
+
#
|
26
|
+
|
27
|
+
await self._apps.prepare_app(spec)
|
@@ -17,9 +17,9 @@ from omlish.lite.cached import async_cached_nullary
|
|
17
17
|
from omlish.lite.check import check
|
18
18
|
from omlish.os.atomics import AtomicPathSwapping
|
19
19
|
|
20
|
-
from .paths import SingleDirDeployPathOwner
|
21
|
-
from .specs import DeployGitCheckout
|
20
|
+
from .paths.owners import SingleDirDeployPathOwner
|
22
21
|
from .specs import DeployGitRepo
|
22
|
+
from .specs import DeployGitSpec
|
23
23
|
from .types import DeployHome
|
24
24
|
from .types import DeployRev
|
25
25
|
|
@@ -95,7 +95,7 @@ class DeployGitManager(SingleDirDeployPathOwner):
|
|
95
95
|
|
96
96
|
#
|
97
97
|
|
98
|
-
async def checkout(self,
|
98
|
+
async def checkout(self, spec: DeployGitSpec, dst_dir: str) -> None:
|
99
99
|
check.state(not os.path.exists(dst_dir))
|
100
100
|
with self._git._atomics.begin_atomic_path_swap( # noqa
|
101
101
|
'dir',
|
@@ -103,14 +103,14 @@ class DeployGitManager(SingleDirDeployPathOwner):
|
|
103
103
|
auto_commit=True,
|
104
104
|
make_dirs=True,
|
105
105
|
) as dst_swap:
|
106
|
-
await self.fetch(
|
106
|
+
await self.fetch(spec.rev)
|
107
107
|
|
108
108
|
dst_call = functools.partial(asyncio_subprocesses.check_call, cwd=dst_swap.tmp_path)
|
109
109
|
await dst_call('git', 'init')
|
110
110
|
|
111
111
|
await dst_call('git', 'remote', 'add', 'local', self._dir)
|
112
|
-
await dst_call('git', 'fetch', '--depth=1', 'local',
|
113
|
-
await dst_call('git', 'checkout',
|
112
|
+
await dst_call('git', 'fetch', '--depth=1', 'local', spec.rev)
|
113
|
+
await dst_call('git', 'checkout', spec.rev, *(spec.subtrees or []))
|
114
114
|
|
115
115
|
def get_repo_dir(self, repo: DeployGitRepo) -> RepoDir:
|
116
116
|
try:
|
@@ -119,5 +119,9 @@ class DeployGitManager(SingleDirDeployPathOwner):
|
|
119
119
|
repo_dir = self._repo_dirs[repo] = DeployGitManager.RepoDir(self, repo)
|
120
120
|
return repo_dir
|
121
121
|
|
122
|
-
async def checkout(
|
123
|
-
|
122
|
+
async def checkout(
|
123
|
+
self,
|
124
|
+
spec: DeployGitSpec,
|
125
|
+
dst_dir: str,
|
126
|
+
) -> None:
|
127
|
+
await self.get_repo_dir(spec.repo).checkout(spec, dst_dir)
|
@@ -11,10 +11,14 @@ from ..commands.inject import bind_command
|
|
11
11
|
from .apps import DeployAppManager
|
12
12
|
from .commands import DeployCommand
|
13
13
|
from .commands import DeployCommandExecutor
|
14
|
+
from .conf import DeployConfManager
|
14
15
|
from .config import DeployConfig
|
16
|
+
from .deploy import DeployManager
|
15
17
|
from .git import DeployGitManager
|
16
18
|
from .interp import InterpCommand
|
17
19
|
from .interp import InterpCommandExecutor
|
20
|
+
from .paths.inject import bind_deploy_paths
|
21
|
+
from .paths.owners import DeployPathOwner
|
18
22
|
from .tmp import DeployTmpManager
|
19
23
|
from .types import DeployHome
|
20
24
|
from .venvs import DeployVenvManager
|
@@ -27,22 +31,43 @@ def bind_deploy(
|
|
27
31
|
lst: ta.List[InjectorBindingOrBindings] = [
|
28
32
|
inj.bind(deploy_config),
|
29
33
|
|
30
|
-
|
34
|
+
bind_deploy_paths(),
|
35
|
+
]
|
36
|
+
|
37
|
+
#
|
38
|
+
|
39
|
+
def bind_manager(cls: type) -> InjectorBindings:
|
40
|
+
return inj.as_bindings(
|
41
|
+
inj.bind(cls, singleton=True),
|
42
|
+
|
43
|
+
*([inj.bind(DeployPathOwner, to_key=cls, array=True)] if issubclass(cls, DeployPathOwner) else []),
|
44
|
+
)
|
45
|
+
|
46
|
+
#
|
31
47
|
|
32
|
-
|
48
|
+
lst.extend([
|
49
|
+
bind_manager(DeployAppManager),
|
33
50
|
|
34
|
-
|
51
|
+
bind_manager(DeployConfManager),
|
35
52
|
|
36
|
-
|
53
|
+
bind_manager(DeployGitManager),
|
54
|
+
|
55
|
+
bind_manager(DeployManager),
|
56
|
+
|
57
|
+
bind_manager(DeployTmpManager),
|
37
58
|
inj.bind(AtomicPathSwapping, to_key=DeployTmpManager),
|
38
59
|
|
39
|
-
|
60
|
+
bind_manager(DeployVenvManager),
|
61
|
+
])
|
40
62
|
|
41
|
-
|
63
|
+
#
|
42
64
|
|
65
|
+
lst.extend([
|
43
66
|
bind_command(DeployCommand, DeployCommandExecutor),
|
44
67
|
bind_command(InterpCommand, InterpCommandExecutor),
|
45
|
-
]
|
68
|
+
])
|
69
|
+
|
70
|
+
#
|
46
71
|
|
47
72
|
if (dh := deploy_config.deploy_home) is not None:
|
48
73
|
dh = os.path.abspath(os.path.expanduser(dh))
|
@@ -0,0 +1,21 @@
|
|
1
|
+
# ruff: noqa: UP006 UP007
|
2
|
+
import typing as ta
|
3
|
+
|
4
|
+
from omlish.lite.inject import InjectorBindingOrBindings
|
5
|
+
from omlish.lite.inject import InjectorBindings
|
6
|
+
from omlish.lite.inject import inj
|
7
|
+
|
8
|
+
from .manager import DeployPathsManager
|
9
|
+
from .owners import DeployPathOwner
|
10
|
+
from .owners import DeployPathOwners
|
11
|
+
|
12
|
+
|
13
|
+
def bind_deploy_paths() -> InjectorBindings:
|
14
|
+
lst: ta.List[InjectorBindingOrBindings] = [
|
15
|
+
inj.bind_array(DeployPathOwner),
|
16
|
+
inj.bind_array_type(DeployPathOwner, DeployPathOwners),
|
17
|
+
|
18
|
+
inj.bind(DeployPathsManager, singleton=True),
|
19
|
+
]
|
20
|
+
|
21
|
+
return inj.as_bindings(*lst)
|
@@ -0,0 +1,36 @@
|
|
1
|
+
# ruff: noqa: UP006 UP007
|
2
|
+
import typing as ta
|
3
|
+
|
4
|
+
from omlish.lite.cached import cached_nullary
|
5
|
+
|
6
|
+
from ..types import DeployHome
|
7
|
+
from .owners import DeployPathOwner
|
8
|
+
from .owners import DeployPathOwners
|
9
|
+
from .paths import DeployPath
|
10
|
+
from .paths import DeployPathError
|
11
|
+
|
12
|
+
|
13
|
+
class DeployPathsManager:
|
14
|
+
def __init__(
|
15
|
+
self,
|
16
|
+
*,
|
17
|
+
deploy_home: ta.Optional[DeployHome],
|
18
|
+
deploy_path_owners: DeployPathOwners,
|
19
|
+
) -> None:
|
20
|
+
super().__init__()
|
21
|
+
|
22
|
+
self._deploy_home = deploy_home
|
23
|
+
self._deploy_path_owners = deploy_path_owners
|
24
|
+
|
25
|
+
@cached_nullary
|
26
|
+
def owners_by_path(self) -> ta.Mapping[DeployPath, DeployPathOwner]:
|
27
|
+
dct: ta.Dict[DeployPath, DeployPathOwner] = {}
|
28
|
+
for o in self._deploy_path_owners:
|
29
|
+
for p in o.get_owned_deploy_paths():
|
30
|
+
if p in dct:
|
31
|
+
raise DeployPathError(f'Duplicate deploy path owner: {p}')
|
32
|
+
dct[p] = o
|
33
|
+
return dct
|
34
|
+
|
35
|
+
def validate_deploy_paths(self) -> None:
|
36
|
+
self.owners_by_path()
|
@@ -0,0 +1,50 @@
|
|
1
|
+
# ruff: noqa: UP006 UP007
|
2
|
+
import abc
|
3
|
+
import os.path
|
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 DeployHome
|
10
|
+
from .paths import DeployPath
|
11
|
+
|
12
|
+
|
13
|
+
class DeployPathOwner(abc.ABC):
|
14
|
+
@abc.abstractmethod
|
15
|
+
def get_owned_deploy_paths(self) -> ta.AbstractSet[DeployPath]:
|
16
|
+
raise NotImplementedError
|
17
|
+
|
18
|
+
|
19
|
+
DeployPathOwners = ta.NewType('DeployPathOwners', ta.Sequence[DeployPathOwner])
|
20
|
+
|
21
|
+
|
22
|
+
class SingleDirDeployPathOwner(DeployPathOwner, abc.ABC):
|
23
|
+
def __init__(
|
24
|
+
self,
|
25
|
+
*args: ta.Any,
|
26
|
+
owned_dir: str,
|
27
|
+
deploy_home: ta.Optional[DeployHome],
|
28
|
+
**kwargs: ta.Any,
|
29
|
+
) -> None:
|
30
|
+
super().__init__(*args, **kwargs)
|
31
|
+
|
32
|
+
check.not_in('/', owned_dir)
|
33
|
+
self._owned_dir: str = check.non_empty_str(owned_dir)
|
34
|
+
|
35
|
+
self._deploy_home = deploy_home
|
36
|
+
|
37
|
+
self._owned_deploy_paths = frozenset([DeployPath.parse(self._owned_dir + '/')])
|
38
|
+
|
39
|
+
@cached_nullary
|
40
|
+
def _dir(self) -> str:
|
41
|
+
return os.path.join(check.non_empty_str(self._deploy_home), self._owned_dir)
|
42
|
+
|
43
|
+
@cached_nullary
|
44
|
+
def _make_dir(self) -> str:
|
45
|
+
if not os.path.isdir(d := self._dir()):
|
46
|
+
os.makedirs(d, exist_ok=True)
|
47
|
+
return d
|
48
|
+
|
49
|
+
def get_owned_deploy_paths(self) -> ta.AbstractSet[DeployPath]:
|
50
|
+
return self._owned_deploy_paths
|