ominfra 0.0.0.dev166__py3-none-any.whl → 0.0.0.dev168__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- ominfra/manage/deploy/apps.py +112 -11
- ominfra/manage/deploy/commands.py +3 -3
- ominfra/manage/deploy/conf.py +183 -0
- ominfra/manage/deploy/deploy.py +27 -0
- ominfra/manage/deploy/git.py +12 -8
- ominfra/manage/deploy/inject.py +32 -7
- ominfra/manage/deploy/paths/__init__.py +0 -0
- ominfra/manage/deploy/paths/inject.py +21 -0
- ominfra/manage/deploy/paths/manager.py +36 -0
- ominfra/manage/deploy/paths/owners.py +50 -0
- ominfra/manage/deploy/paths/paths.py +216 -0
- ominfra/manage/deploy/specs.py +71 -6
- ominfra/manage/deploy/tmp.py +1 -1
- ominfra/manage/deploy/types.py +26 -1
- ominfra/manage/deploy/venvs.py +4 -33
- ominfra/scripts/journald2aws.py +33 -0
- ominfra/scripts/manage.py +1038 -518
- ominfra/scripts/supervisor.py +34 -0
- ominfra/supervisor/configs.py +1 -0
- {ominfra-0.0.0.dev166.dist-info → ominfra-0.0.0.dev168.dist-info}/METADATA +3 -3
- {ominfra-0.0.0.dev166.dist-info → ominfra-0.0.0.dev168.dist-info}/RECORD +25 -19
- ominfra/manage/deploy/paths.py +0 -239
- {ominfra-0.0.0.dev166.dist-info → ominfra-0.0.0.dev168.dist-info}/LICENSE +0 -0
- {ominfra-0.0.0.dev166.dist-info → ominfra-0.0.0.dev168.dist-info}/WHEEL +0 -0
- {ominfra-0.0.0.dev166.dist-info → ominfra-0.0.0.dev168.dist-info}/entry_points.txt +0 -0
- {ominfra-0.0.0.dev166.dist-info → ominfra-0.0.0.dev168.dist-info}/top_level.txt +0 -0
ominfra/manage/deploy/apps.py
CHANGED
@@ -5,10 +5,12 @@ import typing as ta
|
|
5
5
|
|
6
6
|
from omlish.lite.cached import cached_nullary
|
7
7
|
from omlish.lite.check import check
|
8
|
+
from omlish.os.paths import relative_symlink
|
8
9
|
|
10
|
+
from .conf import DeployConfManager
|
9
11
|
from .git import DeployGitManager
|
10
|
-
from .paths import
|
11
|
-
from .paths import
|
12
|
+
from .paths.owners import DeployPathOwner
|
13
|
+
from .paths.paths import DeployPath
|
12
14
|
from .specs import DeploySpec
|
13
15
|
from .types import DeployAppTag
|
14
16
|
from .types import DeployHome
|
@@ -36,39 +38,138 @@ class DeployAppManager(DeployPathOwner):
|
|
36
38
|
self,
|
37
39
|
*,
|
38
40
|
deploy_home: ta.Optional[DeployHome] = None,
|
41
|
+
|
42
|
+
conf: DeployConfManager,
|
39
43
|
git: DeployGitManager,
|
40
44
|
venvs: DeployVenvManager,
|
41
45
|
) -> None:
|
42
46
|
super().__init__()
|
43
47
|
|
44
48
|
self._deploy_home = deploy_home
|
49
|
+
|
50
|
+
self._conf = conf
|
45
51
|
self._git = git
|
46
52
|
self._venvs = venvs
|
47
53
|
|
48
|
-
|
49
|
-
|
50
|
-
|
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)
|
51
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
|
52
69
|
def get_owned_deploy_paths(self) -> ta.AbstractSet[DeployPath]:
|
53
70
|
return {
|
54
|
-
|
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
|
+
],
|
55
88
|
}
|
56
89
|
|
90
|
+
#
|
91
|
+
|
57
92
|
async def prepare_app(
|
58
93
|
self,
|
59
94
|
spec: DeploySpec,
|
60
95
|
) -> None:
|
61
|
-
app_tag = DeployAppTag(spec.app, make_deploy_tag(spec.
|
62
|
-
app_dir = os.path.join(self._dir(), spec.app, app_tag.tag)
|
96
|
+
app_tag = DeployAppTag(spec.app, make_deploy_tag(spec.git.rev, spec.key()))
|
63
97
|
|
64
98
|
#
|
65
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')
|
66
146
|
await self._git.checkout(
|
67
|
-
spec.
|
68
|
-
|
147
|
+
spec.git,
|
148
|
+
git_dir,
|
69
149
|
)
|
70
150
|
|
71
151
|
#
|
72
152
|
|
73
153
|
if spec.venv is not None:
|
74
|
-
|
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)
|
ominfra/manage/deploy/git.py
CHANGED
@@ -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)
|
ominfra/manage/deploy/inject.py
CHANGED
@@ -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))
|
File without changes
|
@@ -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
|