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/scripts/manage.py CHANGED
@@ -6635,6 +6635,7 @@ class AtomicPathSwapping(abc.ABC):
6635
6635
  *,
6636
6636
  name_hint: ta.Optional[str] = None,
6637
6637
  make_dirs: bool = False,
6638
+ skip_root_dir_check: bool = False,
6638
6639
  **kwargs: ta.Any,
6639
6640
  ) -> AtomicPathSwap:
6640
6641
  raise NotImplementedError
@@ -6698,10 +6699,15 @@ class TempDirAtomicPathSwapping(AtomicPathSwapping):
6698
6699
  *,
6699
6700
  name_hint: ta.Optional[str] = None,
6700
6701
  make_dirs: bool = False,
6702
+ skip_root_dir_check: bool = False,
6701
6703
  **kwargs: ta.Any,
6702
6704
  ) -> AtomicPathSwap:
6703
6705
  dst_path = os.path.abspath(dst_path)
6704
- if self._root_dir is not None and not dst_path.startswith(check.non_empty_str(self._root_dir)):
6706
+ if (
6707
+ not skip_root_dir_check and
6708
+ self._root_dir is not None and
6709
+ not dst_path.startswith(check.non_empty_str(self._root_dir))
6710
+ ):
6705
6711
  raise RuntimeError(f'Atomic path swap dst must be in root dir: {dst_path}, {self._root_dir}')
6706
6712
 
6707
6713
  dst_dir = os.path.dirname(dst_path)
@@ -6726,6 +6732,54 @@ class TempDirAtomicPathSwapping(AtomicPathSwapping):
6726
6732
  )
6727
6733
 
6728
6734
 
6735
+ ########################################
6736
+ # ../../../omlish/text/indent.py
6737
+
6738
+
6739
+ class IndentWriter:
6740
+ DEFAULT_INDENT = ' ' * 4
6741
+
6742
+ def __init__(
6743
+ self,
6744
+ *,
6745
+ buf: ta.Optional[io.StringIO] = None,
6746
+ indent: ta.Optional[str] = None,
6747
+ ) -> None:
6748
+ super().__init__()
6749
+
6750
+ self._buf = buf if buf is not None else io.StringIO()
6751
+ self._indent = check.isinstance(indent, str) if indent is not None else self.DEFAULT_INDENT
6752
+ self._level = 0
6753
+ self._has_indented = False
6754
+
6755
+ @contextlib.contextmanager
6756
+ def indent(self, num: int = 1) -> ta.Iterator[None]:
6757
+ self._level += num
6758
+ try:
6759
+ yield
6760
+ finally:
6761
+ self._level -= num
6762
+
6763
+ def write(self, s: str) -> None:
6764
+ indent = self._indent * self._level
6765
+ i = 0
6766
+ while i < len(s):
6767
+ if not self._has_indented:
6768
+ self._buf.write(indent)
6769
+ self._has_indented = True
6770
+ try:
6771
+ n = s.index('\n', i)
6772
+ except ValueError:
6773
+ self._buf.write(s[i:])
6774
+ break
6775
+ self._buf.write(s[i:n + 1])
6776
+ self._has_indented = False
6777
+ i = n + 2
6778
+
6779
+ def getvalue(self) -> str:
6780
+ return self._buf.getvalue()
6781
+
6782
+
6729
6783
  ########################################
6730
6784
  # ../../../omdev/interp/types.py
6731
6785
 
@@ -7784,6 +7838,78 @@ class AbstractAsyncSubprocesses(BaseSubprocesses):
7784
7838
  return ret.decode().strip()
7785
7839
 
7786
7840
 
7841
+ ########################################
7842
+ # ../../../omserv/nginx/configs.py
7843
+ """
7844
+ TODO:
7845
+ - omnibus/jmespath
7846
+
7847
+ https://nginx.org/en/docs/dev/development_guide.html
7848
+ https://nginx.org/en/docs/dev/development_guide.html#config_directives
7849
+ https://nginx.org/en/docs/example.html
7850
+
7851
+ https://github.com/yandex/gixy
7852
+ """
7853
+
7854
+
7855
+ @dc.dataclass()
7856
+ class NginxConfigItems:
7857
+ lst: ta.List['NginxConfigItem']
7858
+
7859
+ @classmethod
7860
+ def of(cls, obj: ta.Any) -> 'NginxConfigItems':
7861
+ if isinstance(obj, NginxConfigItems):
7862
+ return obj
7863
+ return cls([NginxConfigItem.of(e) for e in check.isinstance(obj, list)])
7864
+
7865
+
7866
+ @dc.dataclass()
7867
+ class NginxConfigItem:
7868
+ name: str
7869
+ args: ta.Optional[ta.List[str]] = None
7870
+ block: ta.Optional[NginxConfigItems] = None
7871
+
7872
+ @classmethod
7873
+ def of(cls, obj: ta.Any) -> 'NginxConfigItem':
7874
+ if isinstance(obj, NginxConfigItem):
7875
+ return obj
7876
+ args = check.isinstance(check.not_isinstance(obj, str), collections.abc.Sequence)
7877
+ name, args = check.isinstance(args[0], str), args[1:]
7878
+ if args and not isinstance(args[-1], str):
7879
+ block, args = NginxConfigItems.of(args[-1]), args[:-1]
7880
+ else:
7881
+ block = None
7882
+ return NginxConfigItem(name, [check.isinstance(e, str) for e in args], block=block)
7883
+
7884
+
7885
+ def render_nginx_config(wr: IndentWriter, obj: ta.Any) -> None:
7886
+ if isinstance(obj, NginxConfigItem):
7887
+ wr.write(obj.name)
7888
+ for e in obj.args or ():
7889
+ wr.write(' ')
7890
+ wr.write(e)
7891
+ if obj.block:
7892
+ wr.write(' {\n')
7893
+ with wr.indent():
7894
+ render_nginx_config(wr, obj.block)
7895
+ wr.write('}\n')
7896
+ else:
7897
+ wr.write(';\n')
7898
+
7899
+ elif isinstance(obj, NginxConfigItems):
7900
+ for e2 in obj.lst:
7901
+ render_nginx_config(wr, e2)
7902
+
7903
+ else:
7904
+ raise TypeError(obj)
7905
+
7906
+
7907
+ def render_nginx_config_str(obj: ta.Any) -> str:
7908
+ iw = IndentWriter()
7909
+ render_nginx_config(iw, obj)
7910
+ return iw.getvalue()
7911
+
7912
+
7787
7913
  ########################################
7788
7914
  # ../../../omdev/interp/providers/base.py
7789
7915
  """
@@ -7900,6 +8026,15 @@ class IniDeployAppConfContent(DeployAppConfContent):
7900
8026
  sections: IniConfigSectionSettingsMap
7901
8027
 
7902
8028
 
8029
+ #
8030
+
8031
+
8032
+ @register_single_field_type_obj_marshaler('items')
8033
+ @dc.dataclass(frozen=True)
8034
+ class NginxDeployAppConfContent(DeployAppConfContent):
8035
+ items: ta.Any
8036
+
8037
+
7903
8038
  ##
7904
8039
 
7905
8040
 
@@ -7951,37 +8086,6 @@ class DeployAppConfSpec:
7951
8086
  seen.add(f.path)
7952
8087
 
7953
8088
 
7954
- ########################################
7955
- # ../deploy/deploy.py
7956
-
7957
-
7958
- DEPLOY_TAG_DATETIME_FMT = '%Y%m%dT%H%M%SZ'
7959
-
7960
-
7961
- DeployManagerUtcClock = ta.NewType('DeployManagerUtcClock', Func0[datetime.datetime])
7962
-
7963
-
7964
- class DeployManager:
7965
- def __init__(
7966
- self,
7967
- *,
7968
-
7969
- utc_clock: ta.Optional[DeployManagerUtcClock] = None,
7970
- ):
7971
- super().__init__()
7972
-
7973
- self._utc_clock = utc_clock
7974
-
7975
- def _utc_now(self) -> datetime.datetime:
7976
- if self._utc_clock is not None:
7977
- return self._utc_clock() # noqa
7978
- else:
7979
- return datetime.datetime.now(tz=datetime.timezone.utc) # noqa
7980
-
7981
- def make_deploy_time(self) -> DeployTime:
7982
- return DeployTime(self._utc_now().strftime(DEPLOY_TAG_DATETIME_FMT))
7983
-
7984
-
7985
8089
  ########################################
7986
8090
  # ../deploy/paths/paths.py
7987
8091
  """
@@ -8003,6 +8107,10 @@ class DeployPathError(Exception):
8003
8107
 
8004
8108
 
8005
8109
  class DeployPathRenderable(abc.ABC):
8110
+ @cached_nullary
8111
+ def __str__(self) -> str:
8112
+ return self.render(None)
8113
+
8006
8114
  @abc.abstractmethod
8007
8115
  def render(self, tags: ta.Optional[DeployTagMap] = None) -> str:
8008
8116
  raise NotImplementedError
@@ -8144,7 +8252,7 @@ class FileDeployPathPart(DeployPathPart):
8144
8252
 
8145
8253
 
8146
8254
  @dc.dataclass(frozen=True)
8147
- class DeployPath:
8255
+ class DeployPath(DeployPathRenderable):
8148
8256
  parts: ta.Sequence[DeployPathPart]
8149
8257
 
8150
8258
  @property
@@ -9171,6 +9279,10 @@ class DeployConfManager:
9171
9279
  elif isinstance(ac, IniDeployAppConfContent):
9172
9280
  return strip_with_newline(render_ini_config(ac.sections))
9173
9281
 
9282
+ elif isinstance(ac, NginxDeployAppConfContent):
9283
+ ni = NginxConfigItems.of(ac.items)
9284
+ return strip_with_newline(render_nginx_config_str(ni))
9285
+
9174
9286
  else:
9175
9287
  raise TypeError(ac)
9176
9288
 
@@ -9189,6 +9301,17 @@ class DeployConfManager:
9189
9301
  with open(conf_file, 'w') as f: # noqa
9190
9302
  f.write(body)
9191
9303
 
9304
+ async def write_app_conf(
9305
+ self,
9306
+ spec: DeployAppConfSpec,
9307
+ app_conf_dir: str,
9308
+ ) -> None:
9309
+ for acf in spec.files or []:
9310
+ await self._write_app_conf_file(
9311
+ acf,
9312
+ app_conf_dir,
9313
+ )
9314
+
9192
9315
  #
9193
9316
 
9194
9317
  class _ComputedConfLink(ta.NamedTuple):
@@ -9298,23 +9421,13 @@ class DeployConfManager:
9298
9421
  make_dirs=True,
9299
9422
  )
9300
9423
 
9301
- #
9302
-
9303
- async def write_app_conf(
9424
+ async def link_app_conf(
9304
9425
  self,
9305
9426
  spec: DeployAppConfSpec,
9306
9427
  tags: DeployTagMap,
9307
9428
  app_conf_dir: str,
9308
9429
  conf_link_dir: str,
9309
- ) -> None:
9310
- for acf in spec.files or []:
9311
- await self._write_app_conf_file(
9312
- acf,
9313
- app_conf_dir,
9314
- )
9315
-
9316
- #
9317
-
9430
+ ):
9318
9431
  for link in spec.links or []:
9319
9432
  await self._make_app_conf_link(
9320
9433
  link,
@@ -9442,11 +9555,22 @@ class DeployAppSpec(DeploySpecKeyed[DeployAppKey]):
9442
9555
  ##
9443
9556
 
9444
9557
 
9558
+ @dc.dataclass(frozen=True)
9559
+ class DeploySystemdSpec:
9560
+ # ~/.config/systemd/user/
9561
+ unit_dir: ta.Optional[str] = None
9562
+
9563
+
9564
+ ##
9565
+
9566
+
9445
9567
  @dc.dataclass(frozen=True)
9446
9568
  class DeploySpec(DeploySpecKeyed[DeployKey]):
9447
9569
  home: DeployHome
9448
9570
 
9449
- apps: ta.Sequence[DeployAppSpec]
9571
+ apps: ta.Sequence[DeployAppSpec] = ()
9572
+
9573
+ systemd: ta.Optional[DeploySystemdSpec] = None
9450
9574
 
9451
9575
  def __post_init__(self) -> None:
9452
9576
  check.non_empty_str(self.home)
@@ -10848,6 +10972,109 @@ def bind_deploy_paths() -> InjectorBindings:
10848
10972
  return inj.as_bindings(*lst)
10849
10973
 
10850
10974
 
10975
+ ########################################
10976
+ # ../deploy/systemd.py
10977
+ """
10978
+ ~/.config/systemd/user/
10979
+
10980
+ verify - systemd-analyze
10981
+
10982
+ sudo loginctl enable-linger "$USER"
10983
+
10984
+ cat ~/.config/systemd/user/sleep-infinity.service
10985
+ [Unit]
10986
+ Description=User-specific service to run 'sleep infinity'
10987
+ After=default.target
10988
+
10989
+ [Service]
10990
+ ExecStart=/bin/sleep infinity
10991
+ Restart=always
10992
+ RestartSec=5
10993
+
10994
+ [Install]
10995
+ WantedBy=default.target
10996
+
10997
+ systemctl --user daemon-reload
10998
+
10999
+ systemctl --user enable sleep-infinity.service
11000
+ systemctl --user start sleep-infinity.service
11001
+
11002
+ systemctl --user status sleep-infinity.service
11003
+ """
11004
+
11005
+
11006
+ class DeploySystemdManager:
11007
+ def __init__(
11008
+ self,
11009
+ *,
11010
+ atomics: DeployHomeAtomics,
11011
+ ) -> None:
11012
+ super().__init__()
11013
+
11014
+ self._atomics = atomics
11015
+
11016
+ def _scan_link_dir(
11017
+ self,
11018
+ d: str,
11019
+ *,
11020
+ strict: bool = False,
11021
+ ) -> ta.Dict[str, str]:
11022
+ o: ta.Dict[str, str] = {}
11023
+ for f in os.listdir(d):
11024
+ fp = os.path.join(d, f)
11025
+ if strict:
11026
+ check.state(os.path.islink(fp))
11027
+ o[f] = abs_real_path(fp)
11028
+ return o
11029
+
11030
+ async def sync_systemd(
11031
+ self,
11032
+ spec: ta.Optional[DeploySystemdSpec],
11033
+ home: DeployHome,
11034
+ conf_dir: str,
11035
+ ) -> None:
11036
+ check.non_empty_str(home)
11037
+
11038
+ if not spec:
11039
+ return
11040
+
11041
+ if not (ud := spec.unit_dir):
11042
+ return
11043
+
11044
+ ud = abs_real_path(os.path.expanduser(ud))
11045
+
11046
+ os.makedirs(ud, exist_ok=True)
11047
+
11048
+ uld = {
11049
+ n: p
11050
+ for n, p in self._scan_link_dir(ud).items()
11051
+ if is_path_in_dir(home, p)
11052
+ }
11053
+
11054
+ if os.path.exists(conf_dir):
11055
+ cld = self._scan_link_dir(conf_dir, strict=True)
11056
+ else:
11057
+ cld = {}
11058
+
11059
+ for n in sorted(set(uld) | set(cld)):
11060
+ ul = uld.get(n) # noqa
11061
+ cl = cld.get(n)
11062
+ if cl is None:
11063
+ os.unlink(os.path.join(ud, n))
11064
+ else:
11065
+ with self._atomics(home).begin_atomic_path_swap( # noqa
11066
+ 'file',
11067
+ os.path.join(ud, n),
11068
+ auto_commit=True,
11069
+ skip_root_dir_check=True,
11070
+ ) as dst_swap:
11071
+ os.unlink(dst_swap.tmp_path)
11072
+ os.symlink(
11073
+ os.path.relpath(cl, os.path.dirname(dst_swap.dst_path)),
11074
+ dst_swap.tmp_path,
11075
+ )
11076
+
11077
+
10851
11078
  ########################################
10852
11079
  # ../remote/inject.py
10853
11080
 
@@ -11202,7 +11429,6 @@ class DeployVenvManager:
11202
11429
  async def setup_venv(
11203
11430
  self,
11204
11431
  spec: DeployVenvSpec,
11205
- home: DeployHome,
11206
11432
  git_dir: str,
11207
11433
  venv_dir: str,
11208
11434
  ) -> None:
@@ -11245,39 +11471,31 @@ class DeployAppManager(DeployPathOwner):
11245
11471
  def __init__(
11246
11472
  self,
11247
11473
  *,
11248
- conf: DeployConfManager,
11249
11474
  git: DeployGitManager,
11250
11475
  venvs: DeployVenvManager,
11476
+ conf: DeployConfManager,
11477
+
11478
+ msh: ObjMarshalerManager,
11251
11479
  ) -> None:
11252
11480
  super().__init__()
11253
11481
 
11254
- self._conf = conf
11255
11482
  self._git = git
11256
11483
  self._venvs = venvs
11484
+ self._conf = conf
11257
11485
 
11258
- #
11259
-
11260
- _APP_DIR_STR = 'apps/@app/@time--@app-rev--@app-key/'
11261
- _APP_DIR = DeployPath.parse(_APP_DIR_STR)
11486
+ self._msh = msh
11262
11487
 
11263
- _DEPLOY_DIR_STR = 'deploys/@time--@deploy-key/'
11264
- _DEPLOY_DIR = DeployPath.parse(_DEPLOY_DIR_STR)
11488
+ #
11265
11489
 
11266
- _APP_DEPLOY_LINK = DeployPath.parse(f'{_DEPLOY_DIR_STR}apps/@app')
11267
- _CONF_DEPLOY_DIR = DeployPath.parse(f'{_DEPLOY_DIR_STR}conf/@conf/')
11490
+ APP_DIR = DeployPath.parse('apps/@app/@time--@app-rev--@app-key/')
11268
11491
 
11269
11492
  @cached_nullary
11270
11493
  def get_owned_deploy_paths(self) -> ta.AbstractSet[DeployPath]:
11271
11494
  return {
11272
- self._APP_DIR,
11273
-
11274
- self._DEPLOY_DIR,
11275
-
11276
- self._APP_DEPLOY_LINK,
11277
- self._CONF_DEPLOY_DIR,
11495
+ self.APP_DIR,
11278
11496
 
11279
11497
  *[
11280
- DeployPath.parse(f'{self._APP_DIR_STR}{sfx}/')
11498
+ DeployPath.parse(f'{self.APP_DIR}{sfx}/')
11281
11499
  for sfx in [
11282
11500
  'conf',
11283
11501
  'git',
@@ -11288,122 +11506,148 @@ class DeployAppManager(DeployPathOwner):
11288
11506
 
11289
11507
  #
11290
11508
 
11509
+ @dc.dataclass(frozen=True)
11510
+ class PreparedApp:
11511
+ app_dir: str
11512
+
11513
+ git_dir: ta.Optional[str] = None
11514
+ venv_dir: ta.Optional[str] = None
11515
+ conf_dir: ta.Optional[str] = None
11516
+
11291
11517
  async def prepare_app(
11292
11518
  self,
11293
11519
  spec: DeployAppSpec,
11294
11520
  home: DeployHome,
11295
11521
  tags: DeployTagMap,
11296
- ) -> None:
11297
- check.non_empty_str(home)
11298
-
11299
- def build_path(pth: DeployPath) -> str:
11300
- return os.path.join(home, pth.render(tags))
11301
-
11302
- app_dir = build_path(self._APP_DIR)
11303
- deploy_dir = build_path(self._DEPLOY_DIR)
11304
- app_deploy_link = build_path(self._APP_DEPLOY_LINK)
11522
+ ) -> PreparedApp:
11523
+ spec_json = json_dumps_pretty(self._msh.marshal_obj(spec))
11305
11524
 
11306
11525
  #
11307
11526
 
11308
- os.makedirs(deploy_dir, exist_ok=True)
11527
+ check.non_empty_str(home)
11309
11528
 
11310
- deploying_link = os.path.join(home, 'deploys/deploying')
11311
- if os.path.exists(deploying_link):
11312
- os.unlink(deploying_link)
11313
- relative_symlink(
11314
- deploy_dir,
11315
- deploying_link,
11316
- target_is_directory=True,
11317
- make_dirs=True,
11318
- )
11529
+ app_dir = os.path.join(home, self.APP_DIR.render(tags))
11319
11530
 
11320
- #
11321
-
11322
- os.makedirs(app_dir)
11323
- relative_symlink(
11324
- app_dir,
11325
- app_deploy_link,
11326
- target_is_directory=True,
11327
- make_dirs=True,
11328
- )
11531
+ os.makedirs(app_dir, exist_ok=True)
11329
11532
 
11330
11533
  #
11331
11534
 
11332
- deploy_conf_dir = os.path.join(deploy_dir, 'conf')
11333
- os.makedirs(deploy_conf_dir, exist_ok=True)
11535
+ rkw: ta.Dict[str, ta.Any] = dict(
11536
+ app_dir=app_dir,
11537
+ )
11334
11538
 
11335
11539
  #
11336
11540
 
11337
- # def mirror_symlinks(src: str, dst: str) -> None:
11338
- # def mirror_link(lp: str) -> None:
11339
- # check.state(os.path.islink(lp))
11340
- # shutil.copy2(
11341
- # lp,
11342
- # os.path.join(dst, os.path.relpath(lp, src)),
11343
- # follow_symlinks=False,
11344
- # )
11345
- #
11346
- # for dp, dns, fns in os.walk(src, followlinks=False):
11347
- # for fn in fns:
11348
- # mirror_link(os.path.join(dp, fn))
11349
- #
11350
- # for dn in dns:
11351
- # dp2 = os.path.join(dp, dn)
11352
- # if os.path.islink(dp2):
11353
- # mirror_link(dp2)
11354
- # else:
11355
- # os.makedirs(os.path.join(dst, os.path.relpath(dp2, src)))
11356
-
11357
- current_link = os.path.join(home, 'deploys/current')
11358
-
11359
- # if os.path.exists(current_link):
11360
- # mirror_symlinks(
11361
- # os.path.join(current_link, 'conf'),
11362
- # conf_tag_dir,
11363
- # )
11364
- # mirror_symlinks(
11365
- # os.path.join(current_link, 'apps'),
11366
- # os.path.join(deploy_dir, 'apps'),
11367
- # )
11541
+ spec_file = os.path.join(app_dir, 'spec.json')
11542
+ with open(spec_file, 'w') as f: # noqa
11543
+ f.write(spec_json)
11368
11544
 
11369
11545
  #
11370
11546
 
11371
- app_git_dir = os.path.join(app_dir, 'git')
11547
+ git_dir = os.path.join(app_dir, 'git')
11548
+ rkw.update(git_dir=git_dir)
11372
11549
  await self._git.checkout(
11373
11550
  spec.git,
11374
11551
  home,
11375
- app_git_dir,
11552
+ git_dir,
11376
11553
  )
11377
11554
 
11378
11555
  #
11379
11556
 
11380
11557
  if spec.venv is not None:
11381
- app_venv_dir = os.path.join(app_dir, 'venv')
11558
+ venv_dir = os.path.join(app_dir, 'venv')
11559
+ rkw.update(venv_dir=venv_dir)
11382
11560
  await self._venvs.setup_venv(
11383
11561
  spec.venv,
11384
- home,
11385
- app_git_dir,
11386
- app_venv_dir,
11562
+ git_dir,
11563
+ venv_dir,
11387
11564
  )
11388
11565
 
11389
11566
  #
11390
11567
 
11391
11568
  if spec.conf is not None:
11392
- app_conf_dir = os.path.join(app_dir, 'conf')
11569
+ conf_dir = os.path.join(app_dir, 'conf')
11570
+ rkw.update(conf_dir=conf_dir)
11393
11571
  await self._conf.write_app_conf(
11394
11572
  spec.conf,
11395
- tags,
11396
- app_conf_dir,
11397
- deploy_conf_dir,
11573
+ conf_dir,
11398
11574
  )
11399
11575
 
11400
11576
  #
11401
11577
 
11402
- os.replace(deploying_link, current_link)
11578
+ return DeployAppManager.PreparedApp(**rkw)
11403
11579
 
11404
11580
 
11405
11581
  ########################################
11406
- # ../deploy/driver.py
11582
+ # ../deploy/deploy.py
11583
+
11584
+
11585
+ ##
11586
+
11587
+
11588
+ DEPLOY_TAG_DATETIME_FMT = '%Y%m%dT%H%M%SZ'
11589
+
11590
+
11591
+ DeployManagerUtcClock = ta.NewType('DeployManagerUtcClock', Func0[datetime.datetime])
11592
+
11593
+
11594
+ class DeployManager(DeployPathOwner):
11595
+ def __init__(
11596
+ self,
11597
+ *,
11598
+ utc_clock: ta.Optional[DeployManagerUtcClock] = None,
11599
+ ):
11600
+ super().__init__()
11601
+
11602
+ self._utc_clock = utc_clock
11603
+
11604
+ #
11605
+
11606
+ DEPLOYS_DIR = DeployPath.parse('deploys/')
11607
+
11608
+ CURRENT_DEPLOY_LINK = DeployPath.parse(f'{DEPLOYS_DIR}current')
11609
+ DEPLOYING_DEPLOY_LINK = DeployPath.parse(f'{DEPLOYS_DIR}deploying')
11610
+
11611
+ DEPLOY_DIR = DeployPath.parse(f'{DEPLOYS_DIR}@time--@deploy-key/')
11612
+ DEPLOY_SPEC_FILE = DeployPath.parse(f'{DEPLOY_DIR}spec.json')
11613
+
11614
+ APPS_DEPLOY_DIR = DeployPath.parse(f'{DEPLOY_DIR}apps/')
11615
+ APP_DEPLOY_LINK = DeployPath.parse(f'{APPS_DEPLOY_DIR}@app')
11616
+
11617
+ CONFS_DEPLOY_DIR = DeployPath.parse(f'{DEPLOY_DIR}conf/')
11618
+ CONF_DEPLOY_DIR = DeployPath.parse(f'{CONFS_DEPLOY_DIR}@conf/')
11619
+
11620
+ @cached_nullary
11621
+ def get_owned_deploy_paths(self) -> ta.AbstractSet[DeployPath]:
11622
+ return {
11623
+ self.DEPLOYS_DIR,
11624
+
11625
+ self.CURRENT_DEPLOY_LINK,
11626
+ self.DEPLOYING_DEPLOY_LINK,
11627
+
11628
+ self.DEPLOY_DIR,
11629
+ self.DEPLOY_SPEC_FILE,
11630
+
11631
+ self.APPS_DEPLOY_DIR,
11632
+ self.APP_DEPLOY_LINK,
11633
+
11634
+ self.CONFS_DEPLOY_DIR,
11635
+ self.CONF_DEPLOY_DIR,
11636
+ }
11637
+
11638
+ #
11639
+
11640
+ def _utc_now(self) -> datetime.datetime:
11641
+ if self._utc_clock is not None:
11642
+ return self._utc_clock() # noqa
11643
+ else:
11644
+ return datetime.datetime.now(tz=datetime.timezone.utc) # noqa
11645
+
11646
+ def make_deploy_time(self) -> DeployTime:
11647
+ return DeployTime(self._utc_now().strftime(DEPLOY_TAG_DATETIME_FMT))
11648
+
11649
+
11650
+ ##
11407
11651
 
11408
11652
 
11409
11653
  class DeployDriverFactory(Func1[DeploySpec, ta.ContextManager['DeployDriver']]):
@@ -11418,8 +11662,13 @@ class DeployDriver:
11418
11662
  home: DeployHome,
11419
11663
  time: DeployTime,
11420
11664
 
11665
+ deploys: DeployManager,
11421
11666
  paths: DeployPathsManager,
11422
11667
  apps: DeployAppManager,
11668
+ conf: DeployConfManager,
11669
+ systemd: DeploySystemdManager,
11670
+
11671
+ msh: ObjMarshalerManager,
11423
11672
  ) -> None:
11424
11673
  super().__init__()
11425
11674
 
@@ -11427,32 +11676,123 @@ class DeployDriver:
11427
11676
  self._home = home
11428
11677
  self._time = time
11429
11678
 
11679
+ self._deploys = deploys
11430
11680
  self._paths = paths
11431
11681
  self._apps = apps
11682
+ self._conf = conf
11683
+ self._systemd = systemd
11684
+
11685
+ self._msh = msh
11686
+
11687
+ #
11688
+
11689
+ @property
11690
+ def deploy_tags(self) -> DeployTagMap:
11691
+ return DeployTagMap(
11692
+ self._time,
11693
+ self._spec.key(),
11694
+ )
11695
+
11696
+ def render_deploy_path(self, pth: DeployPath, tags: ta.Optional[DeployTagMap] = None) -> str:
11697
+ return os.path.join(self._home, pth.render(tags if tags is not None else self.deploy_tags))
11698
+
11699
+ @property
11700
+ def deploy_dir(self) -> str:
11701
+ return self.render_deploy_path(self._deploys.DEPLOY_DIR)
11702
+
11703
+ #
11432
11704
 
11433
11705
  async def drive_deploy(self) -> None:
11706
+ spec_json = json_dumps_pretty(self._msh.marshal_obj(self._spec))
11707
+
11708
+ #
11709
+
11434
11710
  self._paths.validate_deploy_paths()
11435
11711
 
11436
11712
  #
11437
11713
 
11438
- deploy_tags = DeployTagMap(
11439
- self._time,
11440
- self._spec.key(),
11714
+ os.makedirs(self.deploy_dir)
11715
+
11716
+ #
11717
+
11718
+ spec_file = self.render_deploy_path(self._deploys.DEPLOY_SPEC_FILE)
11719
+ with open(spec_file, 'w') as f: # noqa
11720
+ f.write(spec_json)
11721
+
11722
+ #
11723
+
11724
+ deploying_link = self.render_deploy_path(self._deploys.DEPLOYING_DEPLOY_LINK)
11725
+ if os.path.exists(deploying_link):
11726
+ os.unlink(deploying_link)
11727
+ relative_symlink(
11728
+ self.deploy_dir,
11729
+ deploying_link,
11730
+ target_is_directory=True,
11731
+ make_dirs=True,
11441
11732
  )
11442
11733
 
11443
11734
  #
11444
11735
 
11736
+ for md in [
11737
+ self._deploys.APPS_DEPLOY_DIR,
11738
+ self._deploys.CONFS_DEPLOY_DIR,
11739
+ ]:
11740
+ os.makedirs(self.render_deploy_path(md))
11741
+
11742
+ #
11743
+
11445
11744
  for app in self._spec.apps:
11446
- app_tags = deploy_tags.add(
11447
- app.app,
11448
- app.key(),
11449
- DeployAppRev(app.git.rev),
11450
- )
11745
+ await self.drive_app_deploy(app)
11746
+
11747
+ #
11748
+
11749
+ current_link = self.render_deploy_path(self._deploys.CURRENT_DEPLOY_LINK)
11750
+ os.replace(deploying_link, current_link)
11751
+
11752
+ #
11753
+
11754
+ await self._systemd.sync_systemd(
11755
+ self._spec.systemd,
11756
+ self._home,
11757
+ os.path.join(self.deploy_dir, 'conf', 'systemd'), # FIXME
11758
+ )
11759
+
11760
+ #
11451
11761
 
11452
- await self._apps.prepare_app(
11453
- app,
11454
- self._home,
11762
+ async def drive_app_deploy(self, app: DeployAppSpec) -> None:
11763
+ app_tags = self.deploy_tags.add(
11764
+ app.app,
11765
+ app.key(),
11766
+ DeployAppRev(app.git.rev),
11767
+ )
11768
+
11769
+ #
11770
+
11771
+ da = await self._apps.prepare_app(
11772
+ app,
11773
+ self._home,
11774
+ app_tags,
11775
+ )
11776
+
11777
+ #
11778
+
11779
+ app_link = self.render_deploy_path(self._deploys.APP_DEPLOY_LINK, app_tags)
11780
+ relative_symlink(
11781
+ da.app_dir,
11782
+ app_link,
11783
+ target_is_directory=True,
11784
+ make_dirs=True,
11785
+ )
11786
+
11787
+ #
11788
+
11789
+ deploy_conf_dir = self.render_deploy_path(self._deploys.CONFS_DEPLOY_DIR)
11790
+ if app.conf is not None:
11791
+ await self._conf.link_app_conf(
11792
+ app.conf,
11455
11793
  app_tags,
11794
+ check.non_empty_str(da.conf_dir),
11795
+ deploy_conf_dir,
11456
11796
  )
11457
11797
 
11458
11798
 
@@ -11558,13 +11898,10 @@ def bind_deploy(
11558
11898
 
11559
11899
  lst.extend([
11560
11900
  bind_deploy_manager(DeployAppManager),
11561
-
11562
11901
  bind_deploy_manager(DeployGitManager),
11563
-
11564
11902
  bind_deploy_manager(DeployManager),
11565
-
11903
+ bind_deploy_manager(DeploySystemdManager),
11566
11904
  bind_deploy_manager(DeployTmpManager),
11567
-
11568
11905
  bind_deploy_manager(DeployVenvManager),
11569
11906
  ])
11570
11907