ominfra 0.0.0.dev190__py3-none-any.whl → 0.0.0.dev192__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 +82 -3
- ominfra/manage/deploy/conf/manager.py +70 -7
- ominfra/manage/deploy/deploy.py +127 -32
- ominfra/manage/deploy/git.py +3 -1
- ominfra/manage/deploy/specs.py +11 -0
- ominfra/manage/deploy/systemd.py +49 -27
- ominfra/manage/deploy/tags.py +1 -1
- ominfra/manage/deploy/venvs.py +8 -4
- ominfra/scripts/manage.py +337 -74
- ominfra/scripts/supervisor.py +18 -9
- ominfra/supervisor/configs.py +6 -0
- ominfra/supervisor/http.py +1 -1
- ominfra/supervisor/inject.py +11 -8
- ominfra/systemd.py +49 -0
- {ominfra-0.0.0.dev190.dist-info → ominfra-0.0.0.dev192.dist-info}/METADATA +4 -4
- {ominfra-0.0.0.dev190.dist-info → ominfra-0.0.0.dev192.dist-info}/RECORD +20 -19
- {ominfra-0.0.0.dev190.dist-info → ominfra-0.0.0.dev192.dist-info}/LICENSE +0 -0
- {ominfra-0.0.0.dev190.dist-info → ominfra-0.0.0.dev192.dist-info}/WHEEL +0 -0
- {ominfra-0.0.0.dev190.dist-info → ominfra-0.0.0.dev192.dist-info}/entry_points.txt +0 -0
- {ominfra-0.0.0.dev190.dist-info → ominfra-0.0.0.dev192.dist-info}/top_level.txt +0 -0
ominfra/manage/deploy/venvs.py
CHANGED
@@ -5,6 +5,7 @@ TODO:
|
|
5
5
|
- share more code with pyproject?
|
6
6
|
"""
|
7
7
|
import os.path
|
8
|
+
import shutil
|
8
9
|
|
9
10
|
from omdev.interp.default import get_default_interp_resolver
|
10
11
|
from omdev.interp.types import InterpSpecifier
|
@@ -44,9 +45,12 @@ class DeployVenvManager:
|
|
44
45
|
|
45
46
|
if os.path.isfile(reqs_txt):
|
46
47
|
if spec.use_uv:
|
47
|
-
|
48
|
-
|
48
|
+
if shutil.which('uv') is not None:
|
49
|
+
pip_cmd = ['uv', 'pip']
|
50
|
+
else:
|
51
|
+
await asyncio_subprocesses.check_call(venv_exe, '-m', 'pip', 'install', 'uv')
|
52
|
+
pip_cmd = [venv_exe, '-m', 'uv', 'pip']
|
49
53
|
else:
|
50
|
-
pip_cmd = ['-m', 'pip']
|
54
|
+
pip_cmd = [venv_exe, '-m', 'pip']
|
51
55
|
|
52
|
-
await asyncio_subprocesses.check_call(
|
56
|
+
await asyncio_subprocesses.check_call(*pip_cmd, 'install', '-r', reqs_txt, cwd=venv_dir)
|
ominfra/scripts/manage.py
CHANGED
@@ -7089,7 +7089,7 @@ DEPLOY_TAG_ILLEGAL_STRS: ta.AbstractSet[str] = frozenset([
|
|
7089
7089
|
##
|
7090
7090
|
|
7091
7091
|
|
7092
|
-
@dc.dataclass(frozen=True)
|
7092
|
+
@dc.dataclass(frozen=True, order=True)
|
7093
7093
|
class DeployTag(abc.ABC): # noqa
|
7094
7094
|
s: str
|
7095
7095
|
|
@@ -9268,20 +9268,63 @@ TODO:
|
|
9268
9268
|
"""
|
9269
9269
|
|
9270
9270
|
|
9271
|
+
##
|
9272
|
+
|
9273
|
+
|
9271
9274
|
class DeployConfManager:
|
9272
|
-
def
|
9275
|
+
def _process_conf_content(
|
9276
|
+
self,
|
9277
|
+
content: T,
|
9278
|
+
*,
|
9279
|
+
str_processor: ta.Optional[ta.Callable[[str], str]] = None,
|
9280
|
+
) -> T:
|
9281
|
+
def rec(o):
|
9282
|
+
if isinstance(o, str):
|
9283
|
+
if str_processor is not None:
|
9284
|
+
return type(o)(str_processor(o))
|
9285
|
+
|
9286
|
+
elif isinstance(o, collections.abc.Mapping):
|
9287
|
+
return type(o)([ # type: ignore
|
9288
|
+
(rec(k), rec(v))
|
9289
|
+
for k, v in o.items()
|
9290
|
+
])
|
9291
|
+
|
9292
|
+
elif isinstance(o, collections.abc.Iterable):
|
9293
|
+
return type(o)([ # type: ignore
|
9294
|
+
rec(e) for e in o
|
9295
|
+
])
|
9296
|
+
|
9297
|
+
return o
|
9298
|
+
|
9299
|
+
return rec(content)
|
9300
|
+
|
9301
|
+
#
|
9302
|
+
|
9303
|
+
def _render_app_conf_content(
|
9304
|
+
self,
|
9305
|
+
ac: DeployAppConfContent,
|
9306
|
+
*,
|
9307
|
+
str_processor: ta.Optional[ta.Callable[[str], str]] = None,
|
9308
|
+
) -> str:
|
9309
|
+
pcc = functools.partial(
|
9310
|
+
self._process_conf_content,
|
9311
|
+
str_processor=str_processor,
|
9312
|
+
)
|
9313
|
+
|
9273
9314
|
if isinstance(ac, RawDeployAppConfContent):
|
9274
|
-
return ac.body
|
9315
|
+
return pcc(ac.body)
|
9275
9316
|
|
9276
9317
|
elif isinstance(ac, JsonDeployAppConfContent):
|
9277
|
-
|
9318
|
+
json_obj = pcc(ac.obj)
|
9319
|
+
return strip_with_newline(json_dumps_pretty(json_obj))
|
9278
9320
|
|
9279
9321
|
elif isinstance(ac, IniDeployAppConfContent):
|
9280
|
-
|
9322
|
+
ini_sections = pcc(ac.sections)
|
9323
|
+
return strip_with_newline(render_ini_config(ini_sections))
|
9281
9324
|
|
9282
9325
|
elif isinstance(ac, NginxDeployAppConfContent):
|
9283
|
-
|
9284
|
-
return strip_with_newline(render_nginx_config_str(
|
9326
|
+
nginx_items = NginxConfigItems.of(pcc(ac.items))
|
9327
|
+
return strip_with_newline(render_nginx_config_str(nginx_items))
|
9285
9328
|
|
9286
9329
|
else:
|
9287
9330
|
raise TypeError(ac)
|
@@ -9290,11 +9333,16 @@ class DeployConfManager:
|
|
9290
9333
|
self,
|
9291
9334
|
acf: DeployAppConfFile,
|
9292
9335
|
app_conf_dir: str,
|
9336
|
+
*,
|
9337
|
+
str_processor: ta.Optional[ta.Callable[[str], str]] = None,
|
9293
9338
|
) -> None:
|
9294
9339
|
conf_file = os.path.join(app_conf_dir, acf.path)
|
9295
9340
|
check.arg(is_path_in_dir(app_conf_dir, conf_file))
|
9296
9341
|
|
9297
|
-
body = self._render_app_conf_content(
|
9342
|
+
body = self._render_app_conf_content(
|
9343
|
+
acf.content,
|
9344
|
+
str_processor=str_processor,
|
9345
|
+
)
|
9298
9346
|
|
9299
9347
|
os.makedirs(os.path.dirname(conf_file), exist_ok=True)
|
9300
9348
|
|
@@ -9305,11 +9353,21 @@ class DeployConfManager:
|
|
9305
9353
|
self,
|
9306
9354
|
spec: DeployAppConfSpec,
|
9307
9355
|
app_conf_dir: str,
|
9356
|
+
*,
|
9357
|
+
string_ns: ta.Optional[ta.Mapping[str, ta.Any]] = None,
|
9308
9358
|
) -> None:
|
9359
|
+
process_str: ta.Any
|
9360
|
+
if string_ns is not None:
|
9361
|
+
def process_str(s: str) -> str:
|
9362
|
+
return s.format(**string_ns)
|
9363
|
+
else:
|
9364
|
+
process_str = None
|
9365
|
+
|
9309
9366
|
for acf in spec.files or []:
|
9310
9367
|
await self._write_app_conf_file(
|
9311
9368
|
acf,
|
9312
9369
|
app_conf_dir,
|
9370
|
+
str_processor=process_str,
|
9313
9371
|
)
|
9314
9372
|
|
9315
9373
|
#
|
@@ -9552,6 +9610,15 @@ class DeployAppSpec(DeploySpecKeyed[DeployAppKey]):
|
|
9552
9610
|
return DeployAppKey(self._key_str())
|
9553
9611
|
|
9554
9612
|
|
9613
|
+
@dc.dataclass(frozen=True)
|
9614
|
+
class DeployAppLinksSpec:
|
9615
|
+
apps: ta.Sequence[DeployApp] = ()
|
9616
|
+
|
9617
|
+
removed_apps: ta.Sequence[DeployApp] = ()
|
9618
|
+
|
9619
|
+
exclude_unspecified: bool = False
|
9620
|
+
|
9621
|
+
|
9555
9622
|
##
|
9556
9623
|
|
9557
9624
|
|
@@ -9570,6 +9637,8 @@ class DeploySpec(DeploySpecKeyed[DeployKey]):
|
|
9570
9637
|
|
9571
9638
|
apps: ta.Sequence[DeployAppSpec] = ()
|
9572
9639
|
|
9640
|
+
app_links: DeployAppLinksSpec = DeployAppLinksSpec()
|
9641
|
+
|
9573
9642
|
systemd: ta.Optional[DeploySystemdSpec] = None
|
9574
9643
|
|
9575
9644
|
def __post_init__(self) -> None:
|
@@ -10906,7 +10975,9 @@ class DeployGitManager(SingleDirDeployPathOwner):
|
|
10906
10975
|
|
10907
10976
|
async def fetch(self, rev: DeployRev) -> None:
|
10908
10977
|
await self.init()
|
10909
|
-
|
10978
|
+
|
10979
|
+
# This fetch shouldn't be depth=1 - git doesn't reuse local data with shallow fetches.
|
10980
|
+
await self._call('git', 'fetch', 'origin', rev)
|
10910
10981
|
|
10911
10982
|
#
|
10912
10983
|
|
@@ -10975,31 +11046,12 @@ def bind_deploy_paths() -> InjectorBindings:
|
|
10975
11046
|
########################################
|
10976
11047
|
# ../deploy/systemd.py
|
10977
11048
|
"""
|
10978
|
-
|
10979
|
-
|
10980
|
-
|
10981
|
-
|
10982
|
-
|
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
|
11049
|
+
TODO:
|
11050
|
+
- verify - systemd-analyze
|
11051
|
+
- sudo loginctl enable-linger "$USER"
|
11052
|
+
- idemp kill services that shouldn't be running, start ones that should
|
11053
|
+
- ideally only those defined by links to deploy home
|
11054
|
+
- ominfra.systemd / x.sd_orphans
|
11003
11055
|
"""
|
11004
11056
|
|
11005
11057
|
|
@@ -11038,6 +11090,8 @@ class DeploySystemdManager:
|
|
11038
11090
|
if not spec:
|
11039
11091
|
return
|
11040
11092
|
|
11093
|
+
#
|
11094
|
+
|
11041
11095
|
if not (ud := spec.unit_dir):
|
11042
11096
|
return
|
11043
11097
|
|
@@ -11045,6 +11099,8 @@ class DeploySystemdManager:
|
|
11045
11099
|
|
11046
11100
|
os.makedirs(ud, exist_ok=True)
|
11047
11101
|
|
11102
|
+
#
|
11103
|
+
|
11048
11104
|
uld = {
|
11049
11105
|
n: p
|
11050
11106
|
for n, p in self._scan_link_dir(ud).items()
|
@@ -11056,8 +11112,11 @@ class DeploySystemdManager:
|
|
11056
11112
|
else:
|
11057
11113
|
cld = {}
|
11058
11114
|
|
11059
|
-
|
11060
|
-
|
11115
|
+
#
|
11116
|
+
|
11117
|
+
ns = sorted(set(uld) | set(cld))
|
11118
|
+
|
11119
|
+
for n in ns:
|
11061
11120
|
cl = cld.get(n)
|
11062
11121
|
if cl is None:
|
11063
11122
|
os.unlink(os.path.join(ud, n))
|
@@ -11074,6 +11133,37 @@ class DeploySystemdManager:
|
|
11074
11133
|
dst_swap.tmp_path,
|
11075
11134
|
)
|
11076
11135
|
|
11136
|
+
#
|
11137
|
+
|
11138
|
+
if sys.platform == 'linux' and shutil.which('systemctl') is not None:
|
11139
|
+
async def reload() -> None:
|
11140
|
+
await asyncio_subprocesses.check_call('systemctl', '--user', 'daemon-reload')
|
11141
|
+
|
11142
|
+
await reload()
|
11143
|
+
|
11144
|
+
num_deleted = 0
|
11145
|
+
for n in ns:
|
11146
|
+
if n.endswith('.service'):
|
11147
|
+
cl = cld.get(n)
|
11148
|
+
ul = uld.get(n)
|
11149
|
+
if cl is not None:
|
11150
|
+
if ul is None:
|
11151
|
+
cs = ['enable', 'start']
|
11152
|
+
else:
|
11153
|
+
cs = ['restart']
|
11154
|
+
else: # noqa
|
11155
|
+
if ul is not None:
|
11156
|
+
cs = ['stop']
|
11157
|
+
num_deleted += 1
|
11158
|
+
else:
|
11159
|
+
cs = []
|
11160
|
+
|
11161
|
+
for c in cs:
|
11162
|
+
await asyncio_subprocesses.check_call('systemctl', '--user', c, n)
|
11163
|
+
|
11164
|
+
if num_deleted:
|
11165
|
+
await reload()
|
11166
|
+
|
11077
11167
|
|
11078
11168
|
########################################
|
11079
11169
|
# ../remote/inject.py
|
@@ -11455,12 +11545,15 @@ class DeployVenvManager:
|
|
11455
11545
|
|
11456
11546
|
if os.path.isfile(reqs_txt):
|
11457
11547
|
if spec.use_uv:
|
11458
|
-
|
11459
|
-
|
11548
|
+
if shutil.which('uv') is not None:
|
11549
|
+
pip_cmd = ['uv', 'pip']
|
11550
|
+
else:
|
11551
|
+
await asyncio_subprocesses.check_call(venv_exe, '-m', 'pip', 'install', 'uv')
|
11552
|
+
pip_cmd = [venv_exe, '-m', 'uv', 'pip']
|
11460
11553
|
else:
|
11461
|
-
pip_cmd = ['-m', 'pip']
|
11554
|
+
pip_cmd = [venv_exe, '-m', 'pip']
|
11462
11555
|
|
11463
|
-
await asyncio_subprocesses.check_call(
|
11556
|
+
await asyncio_subprocesses.check_call(*pip_cmd, 'install', '-r', reqs_txt, cwd=venv_dir)
|
11464
11557
|
|
11465
11558
|
|
11466
11559
|
########################################
|
@@ -11506,9 +11599,21 @@ class DeployAppManager(DeployPathOwner):
|
|
11506
11599
|
|
11507
11600
|
#
|
11508
11601
|
|
11602
|
+
def _make_tags(self, spec: DeployAppSpec) -> DeployTagMap:
|
11603
|
+
return DeployTagMap(
|
11604
|
+
spec.app,
|
11605
|
+
spec.key(),
|
11606
|
+
DeployAppRev(spec.git.rev),
|
11607
|
+
)
|
11608
|
+
|
11609
|
+
#
|
11610
|
+
|
11509
11611
|
@dc.dataclass(frozen=True)
|
11510
11612
|
class PreparedApp:
|
11511
|
-
|
11613
|
+
spec: DeployAppSpec
|
11614
|
+
tags: DeployTagMap
|
11615
|
+
|
11616
|
+
dir: str
|
11512
11617
|
|
11513
11618
|
git_dir: ta.Optional[str] = None
|
11514
11619
|
venv_dir: ta.Optional[str] = None
|
@@ -11519,21 +11624,30 @@ class DeployAppManager(DeployPathOwner):
|
|
11519
11624
|
spec: DeployAppSpec,
|
11520
11625
|
home: DeployHome,
|
11521
11626
|
tags: DeployTagMap,
|
11627
|
+
*,
|
11628
|
+
conf_string_ns: ta.Optional[ta.Mapping[str, ta.Any]] = None,
|
11522
11629
|
) -> PreparedApp:
|
11523
11630
|
spec_json = json_dumps_pretty(self._msh.marshal_obj(spec))
|
11524
11631
|
|
11525
11632
|
#
|
11526
11633
|
|
11634
|
+
app_tags = tags.add(*self._make_tags(spec))
|
11635
|
+
|
11636
|
+
#
|
11637
|
+
|
11527
11638
|
check.non_empty_str(home)
|
11528
11639
|
|
11529
|
-
app_dir = os.path.join(home, self.APP_DIR.render(
|
11640
|
+
app_dir = os.path.join(home, self.APP_DIR.render(app_tags))
|
11530
11641
|
|
11531
11642
|
os.makedirs(app_dir, exist_ok=True)
|
11532
11643
|
|
11533
11644
|
#
|
11534
11645
|
|
11535
11646
|
rkw: ta.Dict[str, ta.Any] = dict(
|
11536
|
-
|
11647
|
+
spec=spec,
|
11648
|
+
tags=app_tags,
|
11649
|
+
|
11650
|
+
dir=app_dir,
|
11537
11651
|
)
|
11538
11652
|
|
11539
11653
|
#
|
@@ -11568,15 +11682,71 @@ class DeployAppManager(DeployPathOwner):
|
|
11568
11682
|
if spec.conf is not None:
|
11569
11683
|
conf_dir = os.path.join(app_dir, 'conf')
|
11570
11684
|
rkw.update(conf_dir=conf_dir)
|
11685
|
+
|
11686
|
+
conf_ns: ta.Dict[str, ta.Any] = dict(
|
11687
|
+
**(conf_string_ns or {}),
|
11688
|
+
app=spec.app.s,
|
11689
|
+
app_dir=app_dir.rstrip('/'),
|
11690
|
+
)
|
11691
|
+
|
11571
11692
|
await self._conf.write_app_conf(
|
11572
11693
|
spec.conf,
|
11573
11694
|
conf_dir,
|
11695
|
+
string_ns=conf_ns,
|
11574
11696
|
)
|
11575
11697
|
|
11576
11698
|
#
|
11577
11699
|
|
11578
11700
|
return DeployAppManager.PreparedApp(**rkw)
|
11579
11701
|
|
11702
|
+
async def prepare_app_link(
|
11703
|
+
self,
|
11704
|
+
tags: DeployTagMap,
|
11705
|
+
app_dir: str,
|
11706
|
+
) -> PreparedApp:
|
11707
|
+
spec_file = os.path.join(app_dir, 'spec.json')
|
11708
|
+
with open(spec_file) as f: # noqa
|
11709
|
+
spec_json = f.read()
|
11710
|
+
|
11711
|
+
spec: DeployAppSpec = self._msh.unmarshal_obj(json.loads(spec_json), DeployAppSpec)
|
11712
|
+
|
11713
|
+
#
|
11714
|
+
|
11715
|
+
app_tags = tags.add(*self._make_tags(spec))
|
11716
|
+
|
11717
|
+
#
|
11718
|
+
|
11719
|
+
rkw: ta.Dict[str, ta.Any] = dict(
|
11720
|
+
spec=spec,
|
11721
|
+
tags=app_tags,
|
11722
|
+
|
11723
|
+
dir=app_dir,
|
11724
|
+
)
|
11725
|
+
|
11726
|
+
#
|
11727
|
+
|
11728
|
+
git_dir = os.path.join(app_dir, 'git')
|
11729
|
+
check.state(os.path.isdir(git_dir))
|
11730
|
+
rkw.update(git_dir=git_dir)
|
11731
|
+
|
11732
|
+
#
|
11733
|
+
|
11734
|
+
if spec.venv is not None:
|
11735
|
+
venv_dir = os.path.join(app_dir, 'venv')
|
11736
|
+
check.state(os.path.isdir(venv_dir))
|
11737
|
+
rkw.update(venv_dir=venv_dir)
|
11738
|
+
|
11739
|
+
#
|
11740
|
+
|
11741
|
+
if spec.conf is not None:
|
11742
|
+
conf_dir = os.path.join(app_dir, 'conf')
|
11743
|
+
check.state(os.path.isdir(conf_dir))
|
11744
|
+
rkw.update(conf_dir=conf_dir)
|
11745
|
+
|
11746
|
+
#
|
11747
|
+
|
11748
|
+
return DeployAppManager.PreparedApp(**rkw)
|
11749
|
+
|
11580
11750
|
|
11581
11751
|
########################################
|
11582
11752
|
# ../deploy/deploy.py
|
@@ -11585,7 +11755,7 @@ class DeployAppManager(DeployPathOwner):
|
|
11585
11755
|
##
|
11586
11756
|
|
11587
11757
|
|
11588
|
-
DEPLOY_TAG_DATETIME_FMT = '%Y
|
11758
|
+
DEPLOY_TAG_DATETIME_FMT = '%Y-%m-%d-T-%H-%M-%S-%f-Z'
|
11589
11759
|
|
11590
11760
|
|
11591
11761
|
DeployManagerUtcClock = ta.NewType('DeployManagerUtcClock', Func0[datetime.datetime])
|
@@ -11595,16 +11765,24 @@ class DeployManager(DeployPathOwner):
|
|
11595
11765
|
def __init__(
|
11596
11766
|
self,
|
11597
11767
|
*,
|
11768
|
+
atomics: DeployHomeAtomics,
|
11769
|
+
|
11598
11770
|
utc_clock: ta.Optional[DeployManagerUtcClock] = None,
|
11599
11771
|
):
|
11600
11772
|
super().__init__()
|
11601
11773
|
|
11774
|
+
self._atomics = atomics
|
11775
|
+
|
11602
11776
|
self._utc_clock = utc_clock
|
11603
11777
|
|
11604
11778
|
#
|
11605
11779
|
|
11780
|
+
# Home current link just points to CURRENT_DEPLOY_LINK, and is intended for user convenience.
|
11781
|
+
HOME_CURRENT_LINK = DeployPath.parse('current')
|
11782
|
+
|
11606
11783
|
DEPLOYS_DIR = DeployPath.parse('deploys/')
|
11607
11784
|
|
11785
|
+
# Authoritative current symlink is not in deploy-home, just to prevent accidental corruption.
|
11608
11786
|
CURRENT_DEPLOY_LINK = DeployPath.parse(f'{DEPLOYS_DIR}current')
|
11609
11787
|
DEPLOYING_DEPLOY_LINK = DeployPath.parse(f'{DEPLOYS_DIR}deploying')
|
11610
11788
|
|
@@ -11637,6 +11815,11 @@ class DeployManager(DeployPathOwner):
|
|
11637
11815
|
|
11638
11816
|
#
|
11639
11817
|
|
11818
|
+
def render_path(self, home: DeployHome, pth: DeployPath, tags: ta.Optional[DeployTagMap] = None) -> str:
|
11819
|
+
return os.path.join(check.non_empty_str(home), pth.render(tags))
|
11820
|
+
|
11821
|
+
#
|
11822
|
+
|
11640
11823
|
def _utc_now(self) -> datetime.datetime:
|
11641
11824
|
if self._utc_clock is not None:
|
11642
11825
|
return self._utc_clock() # noqa
|
@@ -11646,6 +11829,22 @@ class DeployManager(DeployPathOwner):
|
|
11646
11829
|
def make_deploy_time(self) -> DeployTime:
|
11647
11830
|
return DeployTime(self._utc_now().strftime(DEPLOY_TAG_DATETIME_FMT))
|
11648
11831
|
|
11832
|
+
#
|
11833
|
+
|
11834
|
+
def make_home_current_link(self, home: DeployHome) -> None:
|
11835
|
+
home_current_link = os.path.join(check.non_empty_str(home), self.HOME_CURRENT_LINK.render())
|
11836
|
+
current_deploy_link = os.path.join(check.non_empty_str(home), self.CURRENT_DEPLOY_LINK.render())
|
11837
|
+
with self._atomics(home).begin_atomic_path_swap( # noqa
|
11838
|
+
'file',
|
11839
|
+
home_current_link,
|
11840
|
+
auto_commit=True,
|
11841
|
+
) as dst_swap:
|
11842
|
+
os.unlink(dst_swap.tmp_path)
|
11843
|
+
os.symlink(
|
11844
|
+
os.path.relpath(current_deploy_link, os.path.dirname(dst_swap.dst_path)),
|
11845
|
+
dst_swap.tmp_path,
|
11846
|
+
)
|
11847
|
+
|
11649
11848
|
|
11650
11849
|
##
|
11651
11850
|
|
@@ -11687,18 +11886,18 @@ class DeployDriver:
|
|
11687
11886
|
#
|
11688
11887
|
|
11689
11888
|
@property
|
11690
|
-
def
|
11889
|
+
def tags(self) -> DeployTagMap:
|
11691
11890
|
return DeployTagMap(
|
11692
11891
|
self._time,
|
11693
11892
|
self._spec.key(),
|
11694
11893
|
)
|
11695
11894
|
|
11696
|
-
def
|
11697
|
-
return os.path.join(self._home, pth.render(tags if tags is not None else self.
|
11895
|
+
def render_path(self, pth: DeployPath, tags: ta.Optional[DeployTagMap] = None) -> str:
|
11896
|
+
return os.path.join(self._home, pth.render(tags if tags is not None else self.tags))
|
11698
11897
|
|
11699
11898
|
@property
|
11700
|
-
def
|
11701
|
-
return self.
|
11899
|
+
def dir(self) -> str:
|
11900
|
+
return self.render_path(self._deploys.DEPLOY_DIR)
|
11702
11901
|
|
11703
11902
|
#
|
11704
11903
|
|
@@ -11707,25 +11906,37 @@ class DeployDriver:
|
|
11707
11906
|
|
11708
11907
|
#
|
11709
11908
|
|
11909
|
+
das: ta.Set[DeployApp] = {a.app for a in self._spec.apps}
|
11910
|
+
las: ta.Set[DeployApp] = set(self._spec.app_links.apps)
|
11911
|
+
ras: ta.Set[DeployApp] = set(self._spec.app_links.removed_apps)
|
11912
|
+
check.empty(das & (las | ras))
|
11913
|
+
check.empty(las & ras)
|
11914
|
+
|
11915
|
+
#
|
11916
|
+
|
11710
11917
|
self._paths.validate_deploy_paths()
|
11711
11918
|
|
11712
11919
|
#
|
11713
11920
|
|
11714
|
-
os.makedirs(self.
|
11921
|
+
os.makedirs(self.dir)
|
11715
11922
|
|
11716
11923
|
#
|
11717
11924
|
|
11718
|
-
spec_file = self.
|
11925
|
+
spec_file = self.render_path(self._deploys.DEPLOY_SPEC_FILE)
|
11719
11926
|
with open(spec_file, 'w') as f: # noqa
|
11720
11927
|
f.write(spec_json)
|
11721
11928
|
|
11722
11929
|
#
|
11723
11930
|
|
11724
|
-
deploying_link = self.
|
11931
|
+
deploying_link = self.render_path(self._deploys.DEPLOYING_DEPLOY_LINK)
|
11932
|
+
current_link = self.render_path(self._deploys.CURRENT_DEPLOY_LINK)
|
11933
|
+
|
11934
|
+
#
|
11935
|
+
|
11725
11936
|
if os.path.exists(deploying_link):
|
11726
11937
|
os.unlink(deploying_link)
|
11727
11938
|
relative_symlink(
|
11728
|
-
self.
|
11939
|
+
self.dir,
|
11729
11940
|
deploying_link,
|
11730
11941
|
target_is_directory=True,
|
11731
11942
|
make_dirs=True,
|
@@ -11737,16 +11948,30 @@ class DeployDriver:
|
|
11737
11948
|
self._deploys.APPS_DEPLOY_DIR,
|
11738
11949
|
self._deploys.CONFS_DEPLOY_DIR,
|
11739
11950
|
]:
|
11740
|
-
os.makedirs(self.
|
11951
|
+
os.makedirs(self.render_path(md))
|
11741
11952
|
|
11742
11953
|
#
|
11743
11954
|
|
11955
|
+
if not self._spec.app_links.exclude_unspecified:
|
11956
|
+
cad = abs_real_path(os.path.join(current_link, 'apps'))
|
11957
|
+
if os.path.exists(cad):
|
11958
|
+
for d in os.listdir(cad):
|
11959
|
+
if (da := DeployApp(d)) not in das and da not in ras:
|
11960
|
+
las.add(da)
|
11961
|
+
|
11962
|
+
for la in las:
|
11963
|
+
await self._drive_app_link(
|
11964
|
+
la,
|
11965
|
+
current_link,
|
11966
|
+
)
|
11967
|
+
|
11744
11968
|
for app in self._spec.apps:
|
11745
|
-
await self.
|
11969
|
+
await self._drive_app_deploy(
|
11970
|
+
app,
|
11971
|
+
)
|
11746
11972
|
|
11747
11973
|
#
|
11748
11974
|
|
11749
|
-
current_link = self.render_deploy_path(self._deploys.CURRENT_DEPLOY_LINK)
|
11750
11975
|
os.replace(deploying_link, current_link)
|
11751
11976
|
|
11752
11977
|
#
|
@@ -11754,31 +11979,33 @@ class DeployDriver:
|
|
11754
11979
|
await self._systemd.sync_systemd(
|
11755
11980
|
self._spec.systemd,
|
11756
11981
|
self._home,
|
11757
|
-
os.path.join(self.
|
11982
|
+
os.path.join(self.dir, 'conf', 'systemd'), # FIXME
|
11758
11983
|
)
|
11759
11984
|
|
11760
|
-
|
11985
|
+
#
|
11761
11986
|
|
11762
|
-
|
11763
|
-
app_tags = self.deploy_tags.add(
|
11764
|
-
app.app,
|
11765
|
-
app.key(),
|
11766
|
-
DeployAppRev(app.git.rev),
|
11767
|
-
)
|
11987
|
+
self._deploys.make_home_current_link(self._home)
|
11768
11988
|
|
11769
|
-
|
11989
|
+
#
|
11990
|
+
|
11991
|
+
async def _drive_app_deploy(self, app: DeployAppSpec) -> None:
|
11992
|
+
current_deploy_link = os.path.join(self._home, self._deploys.CURRENT_DEPLOY_LINK.render())
|
11770
11993
|
|
11771
|
-
|
11994
|
+
pa = await self._apps.prepare_app(
|
11772
11995
|
app,
|
11773
11996
|
self._home,
|
11774
|
-
|
11997
|
+
self.tags,
|
11998
|
+
conf_string_ns=dict(
|
11999
|
+
deploy_home=self._home,
|
12000
|
+
current_deploy_link=current_deploy_link,
|
12001
|
+
),
|
11775
12002
|
)
|
11776
12003
|
|
11777
12004
|
#
|
11778
12005
|
|
11779
|
-
app_link = self.
|
12006
|
+
app_link = self.render_path(self._deploys.APP_DEPLOY_LINK, pa.tags)
|
11780
12007
|
relative_symlink(
|
11781
|
-
|
12008
|
+
pa.dir,
|
11782
12009
|
app_link,
|
11783
12010
|
target_is_directory=True,
|
11784
12011
|
make_dirs=True,
|
@@ -11786,12 +12013,48 @@ class DeployDriver:
|
|
11786
12013
|
|
11787
12014
|
#
|
11788
12015
|
|
11789
|
-
|
11790
|
-
|
12016
|
+
await self._drive_app_configure(pa)
|
12017
|
+
|
12018
|
+
async def _drive_app_link(
|
12019
|
+
self,
|
12020
|
+
app: DeployApp,
|
12021
|
+
current_link: str,
|
12022
|
+
) -> None:
|
12023
|
+
app_link = os.path.join(abs_real_path(current_link), 'apps', app.s)
|
12024
|
+
check.state(os.path.islink(app_link))
|
12025
|
+
|
12026
|
+
app_dir = abs_real_path(app_link)
|
12027
|
+
check.state(os.path.isdir(app_dir))
|
12028
|
+
|
12029
|
+
#
|
12030
|
+
|
12031
|
+
pa = await self._apps.prepare_app_link(
|
12032
|
+
self.tags,
|
12033
|
+
app_dir,
|
12034
|
+
)
|
12035
|
+
|
12036
|
+
#
|
12037
|
+
|
12038
|
+
relative_symlink(
|
12039
|
+
app_dir,
|
12040
|
+
os.path.join(self.dir, 'apps', app.s),
|
12041
|
+
target_is_directory=True,
|
12042
|
+
)
|
12043
|
+
|
12044
|
+
#
|
12045
|
+
|
12046
|
+
await self._drive_app_configure(pa)
|
12047
|
+
|
12048
|
+
async def _drive_app_configure(
|
12049
|
+
self,
|
12050
|
+
pa: DeployAppManager.PreparedApp,
|
12051
|
+
) -> None:
|
12052
|
+
deploy_conf_dir = self.render_path(self._deploys.CONFS_DEPLOY_DIR)
|
12053
|
+
if pa.spec.conf is not None:
|
11791
12054
|
await self._conf.link_app_conf(
|
11792
|
-
|
11793
|
-
|
11794
|
-
check.non_empty_str(
|
12055
|
+
pa.spec.conf,
|
12056
|
+
pa.tags,
|
12057
|
+
check.non_empty_str(pa.conf_dir),
|
11795
12058
|
deploy_conf_dir,
|
11796
12059
|
)
|
11797
12060
|
|