ominfra 0.0.0.dev158__py3-none-any.whl → 0.0.0.dev160__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 +13 -11
- ominfra/manage/deploy/atomics.py +207 -0
- ominfra/manage/deploy/commands.py +10 -1
- ominfra/manage/deploy/git.py +32 -29
- ominfra/manage/deploy/inject.py +11 -0
- ominfra/manage/deploy/paths.py +46 -37
- ominfra/manage/deploy/specs.py +26 -2
- ominfra/manage/deploy/tmp.py +46 -0
- ominfra/manage/deploy/types.py +1 -0
- ominfra/manage/deploy/venvs.py +6 -1
- ominfra/scripts/journald2aws.py +32 -18
- ominfra/scripts/manage.py +417 -109
- ominfra/scripts/supervisor.py +32 -18
- {ominfra-0.0.0.dev158.dist-info → ominfra-0.0.0.dev160.dist-info}/METADATA +3 -3
- {ominfra-0.0.0.dev158.dist-info → ominfra-0.0.0.dev160.dist-info}/RECORD +19 -17
- {ominfra-0.0.0.dev158.dist-info → ominfra-0.0.0.dev160.dist-info}/LICENSE +0 -0
- {ominfra-0.0.0.dev158.dist-info → ominfra-0.0.0.dev160.dist-info}/WHEEL +0 -0
- {ominfra-0.0.0.dev158.dist-info → ominfra-0.0.0.dev160.dist-info}/entry_points.txt +0 -0
- {ominfra-0.0.0.dev158.dist-info → ominfra-0.0.0.dev160.dist-info}/top_level.txt +0 -0
ominfra/manage/deploy/apps.py
CHANGED
@@ -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()
|
ominfra/manage/deploy/git.py
CHANGED
@@ -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)
|
ominfra/manage/deploy/inject.py
CHANGED
@@ -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
|
]
|
ominfra/manage/deploy/paths.py
CHANGED
@@ -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
|
ominfra/manage/deploy/specs.py
CHANGED
@@ -1,10 +1,13 @@
|
|
1
1
|
# ruff: noqa: UP006 UP007
|
2
2
|
import dataclasses as dc
|
3
|
+
import hashlib
|
3
4
|
import typing as ta
|
4
5
|
|
6
|
+
from omlish.lite.cached import cached_nullary
|
5
7
|
from omlish.lite.check import check
|
6
8
|
|
7
9
|
from .types import DeployApp
|
10
|
+
from .types import DeployKey
|
8
11
|
from .types import DeployRev
|
9
12
|
|
10
13
|
|
@@ -22,11 +25,32 @@ class DeployGitRepo:
|
|
22
25
|
check.not_in('.', check.non_empty_str(self.path))
|
23
26
|
|
24
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
|
+
|
25
43
|
##
|
26
44
|
|
27
45
|
|
28
46
|
@dc.dataclass(frozen=True)
|
29
47
|
class DeploySpec:
|
30
48
|
app: DeployApp
|
31
|
-
|
32
|
-
|
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
|
+
)
|
ominfra/manage/deploy/types.py
CHANGED