ominfra 0.0.0.dev167__py3-none-any.whl → 0.0.0.dev168__py3-none-any.whl

Sign up to get free protection for your applications and to get access to all the features.
@@ -9,8 +9,8 @@ from omlish.os.paths import relative_symlink
9
9
 
10
10
  from .conf import DeployConfManager
11
11
  from .git import DeployGitManager
12
- from .paths import DeployPath
13
- from .paths import DeployPathOwner
12
+ from .paths.owners import DeployPathOwner
13
+ from .paths.paths import DeployPath
14
14
  from .specs import DeploySpec
15
15
  from .types import DeployAppTag
16
16
  from .types import DeployHome
@@ -51,20 +51,44 @@ class DeployAppManager(DeployPathOwner):
51
51
  self._git = git
52
52
  self._venvs = venvs
53
53
 
54
- @cached_nullary
55
- def _dir(self) -> str:
56
- return os.path.join(check.non_empty_str(self._deploy_home), 'apps')
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')
57
67
 
68
+ @cached_nullary
58
69
  def get_owned_deploy_paths(self) -> ta.AbstractSet[DeployPath]:
59
70
  return {
60
- DeployPath.parse('apps/@app/current'),
61
- DeployPath.parse('apps/@app/deploying'),
71
+ self._APP_TAG_DIR,
72
+
73
+ self._CONF_TAG_DIR,
74
+
75
+ self._DEPLOY_DIR,
62
76
 
63
- DeployPath.parse('apps/@app/tags/@tag/conf/'),
64
- DeployPath.parse('apps/@app/tags/@tag/git/'),
65
- DeployPath.parse('apps/@app/tags/@tag/venv/'),
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
+ ],
66
88
  }
67
89
 
90
+ #
91
+
68
92
  async def prepare_app(
69
93
  self,
70
94
  spec: DeploySpec,
@@ -73,26 +97,52 @@ class DeployAppManager(DeployPathOwner):
73
97
 
74
98
  #
75
99
 
76
- app_dir = os.path.join(self._dir(), spec.app)
77
- os.makedirs(app_dir, exist_ok=True)
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)
78
110
 
79
111
  #
80
112
 
81
- tag_dir = os.path.join(app_dir, 'tags', app_tag.tag)
82
- os.makedirs(tag_dir)
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
+ )
83
122
 
84
123
  #
85
124
 
86
- deploying_file = os.path.join(app_dir, 'deploying')
87
- current_file = os.path.join(app_dir, 'current')
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
+ #
88
134
 
89
- if os.path.exists(deploying_file):
90
- os.unlink(deploying_file)
91
- relative_symlink(tag_dir, deploying_file, target_is_directory=True)
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
+ )
92
142
 
93
143
  #
94
144
 
95
- git_dir = os.path.join(tag_dir, 'git')
145
+ git_dir = os.path.join(app_tag_dir, 'git')
96
146
  await self._git.checkout(
97
147
  spec.git,
98
148
  git_dir,
@@ -101,7 +151,7 @@ class DeployAppManager(DeployPathOwner):
101
151
  #
102
152
 
103
153
  if spec.venv is not None:
104
- venv_dir = os.path.join(tag_dir, 'venv')
154
+ venv_dir = os.path.join(app_tag_dir, 'venv')
105
155
  await self._venvs.setup_venv(
106
156
  spec.venv,
107
157
  git_dir,
@@ -111,15 +161,15 @@ class DeployAppManager(DeployPathOwner):
111
161
  #
112
162
 
113
163
  if spec.conf is not None:
114
- conf_dir = os.path.join(tag_dir, 'conf')
115
- conf_link_dir = os.path.join(current_file, 'conf')
164
+ conf_dir = os.path.join(app_tag_dir, 'conf')
116
165
  await self._conf.write_conf(
117
166
  spec.conf,
118
- conf_dir,
119
167
  app_tag,
120
- conf_link_dir,
168
+ conf_dir,
169
+ conf_tag_dir,
121
170
  )
122
171
 
123
172
  #
124
173
 
125
- os.replace(deploying_file, current_file)
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 .apps import DeployAppManager
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
- _apps: DeployAppManager
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._apps.prepare_app(cmd.spec)
31
+ await self._deploy.run_deploy(cmd.spec)
32
32
 
33
33
  return DeployCommand.Output()
@@ -23,7 +23,7 @@ from omlish.lite.check import check
23
23
  from omlish.os.paths import is_path_in_dir
24
24
  from omlish.os.paths import relative_symlink
25
25
 
26
- from .paths import SingleDirDeployPathOwner
26
+ from .paths.paths import DEPLOY_PATH_PLACEHOLDER_SEPARATOR
27
27
  from .specs import AppDeployConfLink
28
28
  from .specs import DeployConfFile
29
29
  from .specs import DeployConfLink
@@ -33,16 +33,17 @@ from .types import DeployAppTag
33
33
  from .types import DeployHome
34
34
 
35
35
 
36
- class DeployConfManager(SingleDirDeployPathOwner):
36
+ class DeployConfManager:
37
37
  def __init__(
38
38
  self,
39
39
  *,
40
40
  deploy_home: ta.Optional[DeployHome] = None,
41
41
  ) -> None:
42
- super().__init__(
43
- owned_dir='conf',
44
- deploy_home=deploy_home,
45
- )
42
+ super().__init__()
43
+
44
+ self._deploy_home = deploy_home
45
+
46
+ #
46
47
 
47
48
  async def _write_conf_file(
48
49
  self,
@@ -57,32 +58,40 @@ class DeployConfManager(SingleDirDeployPathOwner):
57
58
  with open(conf_file, 'w') as f: # noqa
58
59
  f.write(cf.body)
59
60
 
60
- async def _make_conf_link(
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(
61
69
  self,
62
70
  link: DeployConfLink,
63
- conf_dir: str,
64
71
  app_tag: DeployAppTag,
72
+ conf_dir: str,
65
73
  link_dir: str,
66
- ) -> None:
74
+ ) -> _ComputedConfLink:
67
75
  link_src = os.path.join(conf_dir, link.src)
68
76
  check.arg(is_path_in_dir(conf_dir, link_src))
69
77
 
70
- is_link_dir = link.src.endswith('/')
71
- if is_link_dir:
78
+ #
79
+
80
+ if (is_dir := link.src.endswith('/')):
81
+ # @conf/ - links a directory in root of app conf dir to conf/@conf/@dst/
72
82
  check.arg(link.src.count('/') == 1)
73
- check.arg(os.path.isdir(link_src))
74
83
  link_dst_pfx = link.src
75
84
  link_dst_sfx = ''
76
85
 
77
86
  elif '/' in link.src:
78
- check.arg(os.path.isfile(link_src))
87
+ # @conf/file - links a single file in a single subdir to conf/@conf/@dst--file
79
88
  d, f = os.path.split(link.src)
80
89
  # TODO: check filename :|
81
90
  link_dst_pfx = d + '/'
82
- link_dst_sfx = '-' + f
91
+ link_dst_sfx = DEPLOY_PATH_PLACEHOLDER_SEPARATOR + f
83
92
 
84
- else:
85
- check.arg(os.path.isfile(link_src))
93
+ else: # noqa
94
+ # @conf(.ext)* - links a single file in root of app conf dir to conf/@conf/@dst(.ext)*
86
95
  if '.' in link.src:
87
96
  l, _, r = link.src.partition('.')
88
97
  link_dst_pfx = l + '/'
@@ -91,41 +100,72 @@ class DeployConfManager(SingleDirDeployPathOwner):
91
100
  link_dst_pfx = link.src + '/'
92
101
  link_dst_sfx = ''
93
102
 
103
+ #
104
+
94
105
  if isinstance(link, AppDeployConfLink):
95
106
  link_dst_mid = str(app_tag.app)
96
- sym_root = link_dir
97
107
  elif isinstance(link, TagDeployConfLink):
98
- link_dst_mid = '-'.join([app_tag.app, app_tag.tag])
99
- sym_root = conf_dir
108
+ link_dst_mid = DEPLOY_PATH_PLACEHOLDER_SEPARATOR.join([app_tag.app, app_tag.tag])
100
109
  else:
101
110
  raise TypeError(link)
102
111
 
103
- link_dst = ''.join([
112
+ #
113
+
114
+ link_dst_name = ''.join([
104
115
  link_dst_pfx,
105
116
  link_dst_mid,
106
117
  link_dst_sfx,
107
118
  ])
119
+ link_dst = os.path.join(link_dir, link_dst_name)
108
120
 
109
- root_conf_dir = self._make_dir()
110
- sym_src = os.path.join(sym_root, link.src)
111
- sym_dst = os.path.join(root_conf_dir, link_dst)
112
- check.arg(is_path_in_dir(root_conf_dir, sym_dst))
113
-
114
- os.makedirs(os.path.dirname(sym_dst), exist_ok=True)
115
- relative_symlink(sym_src, sym_dst, target_is_directory=is_link_dir)
121
+ return DeployConfManager._ComputedConfLink(
122
+ is_dir=is_dir,
123
+ link_src=link_src,
124
+ link_dst=link_dst,
125
+ )
116
126
 
117
- async def write_conf(
127
+ async def _make_conf_link(
118
128
  self,
119
- spec: DeployConfSpec,
120
- conf_dir: str,
129
+ link: DeployConfLink,
121
130
  app_tag: DeployAppTag,
131
+ conf_dir: str,
122
132
  link_dir: str,
123
133
  ) -> None:
124
- conf_dir = os.path.abspath(conf_dir)
125
- os.makedirs(conf_dir)
134
+ comp = self._compute_conf_link_dst(
135
+ link,
136
+ app_tag,
137
+ conf_dir,
138
+ link_dir,
139
+ )
126
140
 
127
141
  #
128
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:
129
169
  for cf in spec.files or []:
130
170
  await self._write_conf_file(
131
171
  cf,
@@ -137,7 +177,7 @@ class DeployConfManager(SingleDirDeployPathOwner):
137
177
  for link in spec.links or []:
138
178
  await self._make_conf_link(
139
179
  link,
140
- conf_dir,
141
180
  app_tag,
181
+ conf_dir,
142
182
  link_dir,
143
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,7 +17,7 @@ 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
20
+ from .paths.owners import SingleDirDeployPathOwner
21
21
  from .specs import DeployGitRepo
22
22
  from .specs import DeployGitSpec
23
23
  from .types import DeployHome
@@ -13,11 +13,12 @@ from .commands import DeployCommand
13
13
  from .commands import DeployCommandExecutor
14
14
  from .conf import DeployConfManager
15
15
  from .config import DeployConfig
16
+ from .deploy import DeployManager
16
17
  from .git import DeployGitManager
17
18
  from .interp import InterpCommand
18
19
  from .interp import InterpCommandExecutor
19
- from .paths import DeployPathOwner
20
- from .paths import DeployPathOwners
20
+ from .paths.inject import bind_deploy_paths
21
+ from .paths.owners import DeployPathOwner
21
22
  from .tmp import DeployTmpManager
22
23
  from .types import DeployHome
23
24
  from .venvs import DeployVenvManager
@@ -29,6 +30,8 @@ def bind_deploy(
29
30
  ) -> InjectorBindings:
30
31
  lst: ta.List[InjectorBindingOrBindings] = [
31
32
  inj.bind(deploy_config),
33
+
34
+ bind_deploy_paths(),
32
35
  ]
33
36
 
34
37
  #
@@ -40,11 +43,6 @@ def bind_deploy(
40
43
  *([inj.bind(DeployPathOwner, to_key=cls, array=True)] if issubclass(cls, DeployPathOwner) else []),
41
44
  )
42
45
 
43
- lst.extend([
44
- inj.bind_array(DeployPathOwner),
45
- inj.bind_array_type(DeployPathOwner, DeployPathOwners),
46
- ])
47
-
48
46
  #
49
47
 
50
48
  lst.extend([
@@ -54,6 +52,8 @@ def bind_deploy(
54
52
 
55
53
  bind_manager(DeployGitManager),
56
54
 
55
+ bind_manager(DeployManager),
56
+
57
57
  bind_manager(DeployTmpManager),
58
58
  inj.bind(AtomicPathSwapping, to_key=DeployTmpManager),
59
59
 
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