ominfra 0.0.0.dev167__py3-none-any.whl → 0.0.0.dev169__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.
@@ -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