ominfra 0.0.0.dev188__py3-none-any.whl → 0.0.0.dev190__py3-none-any.whl

Sign up to get free protection for your applications and to get access to all the features.
ominfra/__about__.py CHANGED
@@ -10,6 +10,7 @@ class Project(ProjectBase):
10
10
  dependencies = [
11
11
  f'omdev == {__version__}',
12
12
  f'omlish == {__version__}',
13
+ f'omserv == {__version__}',
13
14
  ]
14
15
 
15
16
  optional_dependencies = {
@@ -1,10 +1,12 @@
1
1
  # ruff: noqa: UP006 UP007
2
+ import dataclasses as dc
2
3
  import os.path
3
4
  import typing as ta
4
5
 
5
6
  from omlish.lite.cached import cached_nullary
6
7
  from omlish.lite.check import check
7
- from omlish.os.paths import relative_symlink
8
+ from omlish.lite.json import json_dumps_pretty
9
+ from omlish.lite.marshal import ObjMarshalerManager
8
10
 
9
11
  from .conf.manager import DeployConfManager
10
12
  from .git import DeployGitManager
@@ -20,39 +22,31 @@ class DeployAppManager(DeployPathOwner):
20
22
  def __init__(
21
23
  self,
22
24
  *,
23
- conf: DeployConfManager,
24
25
  git: DeployGitManager,
25
26
  venvs: DeployVenvManager,
27
+ conf: DeployConfManager,
28
+
29
+ msh: ObjMarshalerManager,
26
30
  ) -> None:
27
31
  super().__init__()
28
32
 
29
- self._conf = conf
30
33
  self._git = git
31
34
  self._venvs = venvs
35
+ self._conf = conf
32
36
 
33
- #
34
-
35
- _APP_DIR_STR = 'apps/@app/@time--@app-rev--@app-key/'
36
- _APP_DIR = DeployPath.parse(_APP_DIR_STR)
37
+ self._msh = msh
37
38
 
38
- _DEPLOY_DIR_STR = 'deploys/@time--@deploy-key/'
39
- _DEPLOY_DIR = DeployPath.parse(_DEPLOY_DIR_STR)
39
+ #
40
40
 
41
- _APP_DEPLOY_LINK = DeployPath.parse(f'{_DEPLOY_DIR_STR}apps/@app')
42
- _CONF_DEPLOY_DIR = DeployPath.parse(f'{_DEPLOY_DIR_STR}conf/@conf/')
41
+ APP_DIR = DeployPath.parse('apps/@app/@time--@app-rev--@app-key/')
43
42
 
44
43
  @cached_nullary
45
44
  def get_owned_deploy_paths(self) -> ta.AbstractSet[DeployPath]:
46
45
  return {
47
- self._APP_DIR,
48
-
49
- self._DEPLOY_DIR,
50
-
51
- self._APP_DEPLOY_LINK,
52
- self._CONF_DEPLOY_DIR,
46
+ self.APP_DIR,
53
47
 
54
48
  *[
55
- DeployPath.parse(f'{self._APP_DIR_STR}{sfx}/')
49
+ DeployPath.parse(f'{self.APP_DIR}{sfx}/')
56
50
  for sfx in [
57
51
  'conf',
58
52
  'git',
@@ -63,115 +57,73 @@ class DeployAppManager(DeployPathOwner):
63
57
 
64
58
  #
65
59
 
60
+ @dc.dataclass(frozen=True)
61
+ class PreparedApp:
62
+ app_dir: str
63
+
64
+ git_dir: ta.Optional[str] = None
65
+ venv_dir: ta.Optional[str] = None
66
+ conf_dir: ta.Optional[str] = None
67
+
66
68
  async def prepare_app(
67
69
  self,
68
70
  spec: DeployAppSpec,
69
71
  home: DeployHome,
70
72
  tags: DeployTagMap,
71
- ) -> None:
72
- check.non_empty_str(home)
73
-
74
- def build_path(pth: DeployPath) -> str:
75
- return os.path.join(home, pth.render(tags))
76
-
77
- app_dir = build_path(self._APP_DIR)
78
- deploy_dir = build_path(self._DEPLOY_DIR)
79
- app_deploy_link = build_path(self._APP_DEPLOY_LINK)
73
+ ) -> PreparedApp:
74
+ spec_json = json_dumps_pretty(self._msh.marshal_obj(spec))
80
75
 
81
76
  #
82
77
 
83
- os.makedirs(deploy_dir, exist_ok=True)
84
-
85
- deploying_link = os.path.join(home, 'deploys/deploying')
86
- if os.path.exists(deploying_link):
87
- os.unlink(deploying_link)
88
- relative_symlink(
89
- deploy_dir,
90
- deploying_link,
91
- target_is_directory=True,
92
- make_dirs=True,
93
- )
78
+ check.non_empty_str(home)
94
79
 
95
- #
80
+ app_dir = os.path.join(home, self.APP_DIR.render(tags))
96
81
 
97
- os.makedirs(app_dir)
98
- relative_symlink(
99
- app_dir,
100
- app_deploy_link,
101
- target_is_directory=True,
102
- make_dirs=True,
103
- )
82
+ os.makedirs(app_dir, exist_ok=True)
104
83
 
105
84
  #
106
85
 
107
- deploy_conf_dir = os.path.join(deploy_dir, 'conf')
108
- os.makedirs(deploy_conf_dir, exist_ok=True)
86
+ rkw: ta.Dict[str, ta.Any] = dict(
87
+ app_dir=app_dir,
88
+ )
109
89
 
110
90
  #
111
91
 
112
- # def mirror_symlinks(src: str, dst: str) -> None:
113
- # def mirror_link(lp: str) -> None:
114
- # check.state(os.path.islink(lp))
115
- # shutil.copy2(
116
- # lp,
117
- # os.path.join(dst, os.path.relpath(lp, src)),
118
- # follow_symlinks=False,
119
- # )
120
- #
121
- # for dp, dns, fns in os.walk(src, followlinks=False):
122
- # for fn in fns:
123
- # mirror_link(os.path.join(dp, fn))
124
- #
125
- # for dn in dns:
126
- # dp2 = os.path.join(dp, dn)
127
- # if os.path.islink(dp2):
128
- # mirror_link(dp2)
129
- # else:
130
- # os.makedirs(os.path.join(dst, os.path.relpath(dp2, src)))
131
-
132
- current_link = os.path.join(home, 'deploys/current')
133
-
134
- # if os.path.exists(current_link):
135
- # mirror_symlinks(
136
- # os.path.join(current_link, 'conf'),
137
- # conf_tag_dir,
138
- # )
139
- # mirror_symlinks(
140
- # os.path.join(current_link, 'apps'),
141
- # os.path.join(deploy_dir, 'apps'),
142
- # )
92
+ spec_file = os.path.join(app_dir, 'spec.json')
93
+ with open(spec_file, 'w') as f: # noqa
94
+ f.write(spec_json)
143
95
 
144
96
  #
145
97
 
146
- app_git_dir = os.path.join(app_dir, 'git')
98
+ git_dir = os.path.join(app_dir, 'git')
99
+ rkw.update(git_dir=git_dir)
147
100
  await self._git.checkout(
148
101
  spec.git,
149
102
  home,
150
- app_git_dir,
103
+ git_dir,
151
104
  )
152
105
 
153
106
  #
154
107
 
155
108
  if spec.venv is not None:
156
- app_venv_dir = os.path.join(app_dir, 'venv')
109
+ venv_dir = os.path.join(app_dir, 'venv')
110
+ rkw.update(venv_dir=venv_dir)
157
111
  await self._venvs.setup_venv(
158
112
  spec.venv,
159
- home,
160
- app_git_dir,
161
- app_venv_dir,
113
+ git_dir,
114
+ venv_dir,
162
115
  )
163
116
 
164
117
  #
165
118
 
166
119
  if spec.conf is not None:
167
- app_conf_dir = os.path.join(app_dir, 'conf')
120
+ conf_dir = os.path.join(app_dir, 'conf')
121
+ rkw.update(conf_dir=conf_dir)
168
122
  await self._conf.write_app_conf(
169
123
  spec.conf,
170
- tags,
171
- app_conf_dir,
172
- deploy_conf_dir,
124
+ conf_dir,
173
125
  )
174
126
 
175
127
  #
176
128
 
177
- os.replace(deploying_link, current_link)
129
+ return DeployAppManager.PreparedApp(**rkw)
@@ -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 .driver import DeployDriverFactory
8
+ from .deploy import DeployDriverFactory
9
9
  from .specs import DeploySpec
10
10
 
11
11
 
@@ -24,6 +24,8 @@ from omlish.lite.json import json_dumps_pretty
24
24
  from omlish.lite.strings import strip_with_newline
25
25
  from omlish.os.paths import is_path_in_dir
26
26
  from omlish.os.paths import relative_symlink
27
+ from omserv.nginx.configs import NginxConfigItems
28
+ from omserv.nginx.configs import render_nginx_config_str
27
29
 
28
30
  from ....configs import render_ini_config
29
31
  from ..paths.paths import DeployPath
@@ -37,6 +39,7 @@ from .specs import DeployAppConfLink
37
39
  from .specs import DeployAppConfSpec
38
40
  from .specs import IniDeployAppConfContent
39
41
  from .specs import JsonDeployAppConfContent
42
+ from .specs import NginxDeployAppConfContent
40
43
  from .specs import RawDeployAppConfContent
41
44
 
42
45
 
@@ -51,6 +54,10 @@ class DeployConfManager:
51
54
  elif isinstance(ac, IniDeployAppConfContent):
52
55
  return strip_with_newline(render_ini_config(ac.sections))
53
56
 
57
+ elif isinstance(ac, NginxDeployAppConfContent):
58
+ ni = NginxConfigItems.of(ac.items)
59
+ return strip_with_newline(render_nginx_config_str(ni))
60
+
54
61
  else:
55
62
  raise TypeError(ac)
56
63
 
@@ -69,6 +76,17 @@ class DeployConfManager:
69
76
  with open(conf_file, 'w') as f: # noqa
70
77
  f.write(body)
71
78
 
79
+ async def write_app_conf(
80
+ self,
81
+ spec: DeployAppConfSpec,
82
+ app_conf_dir: str,
83
+ ) -> None:
84
+ for acf in spec.files or []:
85
+ await self._write_app_conf_file(
86
+ acf,
87
+ app_conf_dir,
88
+ )
89
+
72
90
  #
73
91
 
74
92
  class _ComputedConfLink(ta.NamedTuple):
@@ -178,23 +196,13 @@ class DeployConfManager:
178
196
  make_dirs=True,
179
197
  )
180
198
 
181
- #
182
-
183
- async def write_app_conf(
199
+ async def link_app_conf(
184
200
  self,
185
201
  spec: DeployAppConfSpec,
186
202
  tags: DeployTagMap,
187
203
  app_conf_dir: str,
188
204
  conf_link_dir: str,
189
- ) -> None:
190
- for acf in spec.files or []:
191
- await self._write_app_conf_file(
192
- acf,
193
- app_conf_dir,
194
- )
195
-
196
- #
197
-
205
+ ):
198
206
  for link in spec.links or []:
199
207
  await self._make_app_conf_link(
200
208
  link,
@@ -44,6 +44,15 @@ class IniDeployAppConfContent(DeployAppConfContent):
44
44
  sections: IniConfigSectionSettingsMap
45
45
 
46
46
 
47
+ #
48
+
49
+
50
+ @register_single_field_type_obj_marshaler('items')
51
+ @dc.dataclass(frozen=True)
52
+ class NginxDeployAppConfContent(DeployAppConfContent):
53
+ items: ta.Any
54
+
55
+
47
56
  ##
48
57
 
49
58
 
@@ -1,10 +1,31 @@
1
1
  # ruff: noqa: UP006 UP007
2
2
  import datetime
3
+ import os.path
3
4
  import typing as ta
4
5
 
6
+ from omlish.lite.cached import cached_nullary
7
+ from omlish.lite.check import check
8
+ from omlish.lite.json import json_dumps_pretty
9
+ from omlish.lite.marshal import ObjMarshalerManager
5
10
  from omlish.lite.typing import Func0
11
+ from omlish.lite.typing import Func1
12
+ from omlish.os.paths import relative_symlink
6
13
 
14
+ from .apps import DeployAppManager
15
+ from .conf.manager import DeployConfManager
16
+ from .paths.manager import DeployPathsManager
17
+ from .paths.owners import DeployPathOwner
18
+ from .paths.paths import DeployPath
19
+ from .specs import DeployAppSpec
20
+ from .specs import DeploySpec
21
+ from .systemd import DeploySystemdManager
22
+ from .tags import DeployAppRev
23
+ from .tags import DeployTagMap
7
24
  from .tags import DeployTime
25
+ from .types import DeployHome
26
+
27
+
28
+ ##
8
29
 
9
30
 
10
31
  DEPLOY_TAG_DATETIME_FMT = '%Y%m%dT%H%M%SZ'
@@ -13,17 +34,52 @@ DEPLOY_TAG_DATETIME_FMT = '%Y%m%dT%H%M%SZ'
13
34
  DeployManagerUtcClock = ta.NewType('DeployManagerUtcClock', Func0[datetime.datetime])
14
35
 
15
36
 
16
- class DeployManager:
37
+ class DeployManager(DeployPathOwner):
17
38
  def __init__(
18
39
  self,
19
40
  *,
20
-
21
41
  utc_clock: ta.Optional[DeployManagerUtcClock] = None,
22
42
  ):
23
43
  super().__init__()
24
44
 
25
45
  self._utc_clock = utc_clock
26
46
 
47
+ #
48
+
49
+ DEPLOYS_DIR = DeployPath.parse('deploys/')
50
+
51
+ CURRENT_DEPLOY_LINK = DeployPath.parse(f'{DEPLOYS_DIR}current')
52
+ DEPLOYING_DEPLOY_LINK = DeployPath.parse(f'{DEPLOYS_DIR}deploying')
53
+
54
+ DEPLOY_DIR = DeployPath.parse(f'{DEPLOYS_DIR}@time--@deploy-key/')
55
+ DEPLOY_SPEC_FILE = DeployPath.parse(f'{DEPLOY_DIR}spec.json')
56
+
57
+ APPS_DEPLOY_DIR = DeployPath.parse(f'{DEPLOY_DIR}apps/')
58
+ APP_DEPLOY_LINK = DeployPath.parse(f'{APPS_DEPLOY_DIR}@app')
59
+
60
+ CONFS_DEPLOY_DIR = DeployPath.parse(f'{DEPLOY_DIR}conf/')
61
+ CONF_DEPLOY_DIR = DeployPath.parse(f'{CONFS_DEPLOY_DIR}@conf/')
62
+
63
+ @cached_nullary
64
+ def get_owned_deploy_paths(self) -> ta.AbstractSet[DeployPath]:
65
+ return {
66
+ self.DEPLOYS_DIR,
67
+
68
+ self.CURRENT_DEPLOY_LINK,
69
+ self.DEPLOYING_DEPLOY_LINK,
70
+
71
+ self.DEPLOY_DIR,
72
+ self.DEPLOY_SPEC_FILE,
73
+
74
+ self.APPS_DEPLOY_DIR,
75
+ self.APP_DEPLOY_LINK,
76
+
77
+ self.CONFS_DEPLOY_DIR,
78
+ self.CONF_DEPLOY_DIR,
79
+ }
80
+
81
+ #
82
+
27
83
  def _utc_now(self) -> datetime.datetime:
28
84
  if self._utc_clock is not None:
29
85
  return self._utc_clock() # noqa
@@ -32,3 +88,152 @@ class DeployManager:
32
88
 
33
89
  def make_deploy_time(self) -> DeployTime:
34
90
  return DeployTime(self._utc_now().strftime(DEPLOY_TAG_DATETIME_FMT))
91
+
92
+
93
+ ##
94
+
95
+
96
+ class DeployDriverFactory(Func1[DeploySpec, ta.ContextManager['DeployDriver']]):
97
+ pass
98
+
99
+
100
+ class DeployDriver:
101
+ def __init__(
102
+ self,
103
+ *,
104
+ spec: DeploySpec,
105
+ home: DeployHome,
106
+ time: DeployTime,
107
+
108
+ deploys: DeployManager,
109
+ paths: DeployPathsManager,
110
+ apps: DeployAppManager,
111
+ conf: DeployConfManager,
112
+ systemd: DeploySystemdManager,
113
+
114
+ msh: ObjMarshalerManager,
115
+ ) -> None:
116
+ super().__init__()
117
+
118
+ self._spec = spec
119
+ self._home = home
120
+ self._time = time
121
+
122
+ self._deploys = deploys
123
+ self._paths = paths
124
+ self._apps = apps
125
+ self._conf = conf
126
+ self._systemd = systemd
127
+
128
+ self._msh = msh
129
+
130
+ #
131
+
132
+ @property
133
+ def deploy_tags(self) -> DeployTagMap:
134
+ return DeployTagMap(
135
+ self._time,
136
+ self._spec.key(),
137
+ )
138
+
139
+ def render_deploy_path(self, pth: DeployPath, tags: ta.Optional[DeployTagMap] = None) -> str:
140
+ return os.path.join(self._home, pth.render(tags if tags is not None else self.deploy_tags))
141
+
142
+ @property
143
+ def deploy_dir(self) -> str:
144
+ return self.render_deploy_path(self._deploys.DEPLOY_DIR)
145
+
146
+ #
147
+
148
+ async def drive_deploy(self) -> None:
149
+ spec_json = json_dumps_pretty(self._msh.marshal_obj(self._spec))
150
+
151
+ #
152
+
153
+ self._paths.validate_deploy_paths()
154
+
155
+ #
156
+
157
+ os.makedirs(self.deploy_dir)
158
+
159
+ #
160
+
161
+ spec_file = self.render_deploy_path(self._deploys.DEPLOY_SPEC_FILE)
162
+ with open(spec_file, 'w') as f: # noqa
163
+ f.write(spec_json)
164
+
165
+ #
166
+
167
+ deploying_link = self.render_deploy_path(self._deploys.DEPLOYING_DEPLOY_LINK)
168
+ if os.path.exists(deploying_link):
169
+ os.unlink(deploying_link)
170
+ relative_symlink(
171
+ self.deploy_dir,
172
+ deploying_link,
173
+ target_is_directory=True,
174
+ make_dirs=True,
175
+ )
176
+
177
+ #
178
+
179
+ for md in [
180
+ self._deploys.APPS_DEPLOY_DIR,
181
+ self._deploys.CONFS_DEPLOY_DIR,
182
+ ]:
183
+ os.makedirs(self.render_deploy_path(md))
184
+
185
+ #
186
+
187
+ for app in self._spec.apps:
188
+ await self.drive_app_deploy(app)
189
+
190
+ #
191
+
192
+ current_link = self.render_deploy_path(self._deploys.CURRENT_DEPLOY_LINK)
193
+ os.replace(deploying_link, current_link)
194
+
195
+ #
196
+
197
+ await self._systemd.sync_systemd(
198
+ self._spec.systemd,
199
+ self._home,
200
+ os.path.join(self.deploy_dir, 'conf', 'systemd'), # FIXME
201
+ )
202
+
203
+ #
204
+
205
+ async def drive_app_deploy(self, app: DeployAppSpec) -> None:
206
+ app_tags = self.deploy_tags.add(
207
+ app.app,
208
+ app.key(),
209
+ DeployAppRev(app.git.rev),
210
+ )
211
+
212
+ #
213
+
214
+ da = await self._apps.prepare_app(
215
+ app,
216
+ self._home,
217
+ app_tags,
218
+ )
219
+
220
+ #
221
+
222
+ app_link = self.render_deploy_path(self._deploys.APP_DEPLOY_LINK, app_tags)
223
+ relative_symlink(
224
+ da.app_dir,
225
+ app_link,
226
+ target_is_directory=True,
227
+ make_dirs=True,
228
+ )
229
+
230
+ #
231
+
232
+ deploy_conf_dir = self.render_deploy_path(self._deploys.CONFS_DEPLOY_DIR)
233
+ if app.conf is not None:
234
+ await self._conf.link_app_conf(
235
+ app.conf,
236
+ app_tags,
237
+ check.non_empty_str(da.conf_dir),
238
+ deploy_conf_dir,
239
+ )
@@ -16,15 +16,16 @@ from .commands import DeployCommand
16
16
  from .commands import DeployCommandExecutor
17
17
  from .conf.inject import bind_deploy_conf
18
18
  from .config import DeployConfig
19
+ from .deploy import DeployDriver
20
+ from .deploy import DeployDriverFactory
19
21
  from .deploy import DeployManager
20
- from .driver import DeployDriver
21
- from .driver import DeployDriverFactory
22
22
  from .git import DeployGitManager
23
23
  from .inject_ import bind_deploy_manager
24
24
  from .interp import InterpCommand
25
25
  from .interp import InterpCommandExecutor
26
26
  from .paths.inject import bind_deploy_paths
27
27
  from .specs import DeploySpec
28
+ from .systemd import DeploySystemdManager
28
29
  from .tags import DeployTime
29
30
  from .tmp import DeployHomeAtomics
30
31
  from .tmp import DeployTmpManager
@@ -101,13 +102,10 @@ def bind_deploy(
101
102
 
102
103
  lst.extend([
103
104
  bind_deploy_manager(DeployAppManager),
104
-
105
105
  bind_deploy_manager(DeployGitManager),
106
-
107
106
  bind_deploy_manager(DeployManager),
108
-
107
+ bind_deploy_manager(DeploySystemdManager),
109
108
  bind_deploy_manager(DeployTmpManager),
110
-
111
109
  bind_deploy_manager(DeployVenvManager),
112
110
  ])
113
111
 
@@ -0,0 +1,8 @@
1
+ # ruff: noqa: UP006 UP007
2
+ """
3
+ verify - nginx -t
4
+ """
5
+
6
+
7
+ class DeployNginxManager:
8
+ pass
@@ -33,6 +33,10 @@ class DeployPathError(Exception):
33
33
 
34
34
 
35
35
  class DeployPathRenderable(abc.ABC):
36
+ @cached_nullary
37
+ def __str__(self) -> str:
38
+ return self.render(None)
39
+
36
40
  @abc.abstractmethod
37
41
  def render(self, tags: ta.Optional[DeployTagMap] = None) -> str:
38
42
  raise NotImplementedError
@@ -174,7 +178,7 @@ class FileDeployPathPart(DeployPathPart):
174
178
 
175
179
 
176
180
  @dc.dataclass(frozen=True)
177
- class DeployPath:
181
+ class DeployPath(DeployPathRenderable):
178
182
  parts: ta.Sequence[DeployPathPart]
179
183
 
180
184
  @property
@@ -94,11 +94,22 @@ class DeployAppSpec(DeploySpecKeyed[DeployAppKey]):
94
94
  ##
95
95
 
96
96
 
97
+ @dc.dataclass(frozen=True)
98
+ class DeploySystemdSpec:
99
+ # ~/.config/systemd/user/
100
+ unit_dir: ta.Optional[str] = None
101
+
102
+
103
+ ##
104
+
105
+
97
106
  @dc.dataclass(frozen=True)
98
107
  class DeploySpec(DeploySpecKeyed[DeployKey]):
99
108
  home: DeployHome
100
109
 
101
- apps: ta.Sequence[DeployAppSpec]
110
+ apps: ta.Sequence[DeployAppSpec] = ()
111
+
112
+ systemd: ta.Optional[DeploySystemdSpec] = None
102
113
 
103
114
  def __post_init__(self) -> None:
104
115
  check.non_empty_str(self.home)