ominfra 0.0.0.dev190__py3-none-any.whl → 0.0.0.dev191__py3-none-any.whl
Sign up to get free protection for your applications and to get access to all the features.
- ominfra/manage/deploy/apps.py +72 -3
- ominfra/manage/deploy/conf/manager.py +64 -7
- ominfra/manage/deploy/deploy.py +119 -31
- ominfra/manage/deploy/specs.py +9 -0
- ominfra/manage/deploy/systemd.py +48 -27
- ominfra/manage/deploy/tags.py +1 -1
- ominfra/manage/deploy/venvs.py +8 -4
- ominfra/scripts/manage.py +308 -72
- ominfra/systemd.py +49 -0
- {ominfra-0.0.0.dev190.dist-info → ominfra-0.0.0.dev191.dist-info}/METADATA +4 -4
- {ominfra-0.0.0.dev190.dist-info → ominfra-0.0.0.dev191.dist-info}/RECORD +15 -14
- {ominfra-0.0.0.dev190.dist-info → ominfra-0.0.0.dev191.dist-info}/LICENSE +0 -0
- {ominfra-0.0.0.dev190.dist-info → ominfra-0.0.0.dev191.dist-info}/WHEEL +0 -0
- {ominfra-0.0.0.dev190.dist-info → ominfra-0.0.0.dev191.dist-info}/entry_points.txt +0 -0
- {ominfra-0.0.0.dev190.dist-info → ominfra-0.0.0.dev191.dist-info}/top_level.txt +0 -0
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
|
|
@@ -9306,10 +9354,14 @@ class DeployConfManager:
|
|
9306
9354
|
spec: DeployAppConfSpec,
|
9307
9355
|
app_conf_dir: str,
|
9308
9356
|
) -> None:
|
9357
|
+
def process_str(s: str) -> str:
|
9358
|
+
return s
|
9359
|
+
|
9309
9360
|
for acf in spec.files or []:
|
9310
9361
|
await self._write_app_conf_file(
|
9311
9362
|
acf,
|
9312
9363
|
app_conf_dir,
|
9364
|
+
str_processor=process_str,
|
9313
9365
|
)
|
9314
9366
|
|
9315
9367
|
#
|
@@ -9552,6 +9604,13 @@ class DeployAppSpec(DeploySpecKeyed[DeployAppKey]):
|
|
9552
9604
|
return DeployAppKey(self._key_str())
|
9553
9605
|
|
9554
9606
|
|
9607
|
+
@dc.dataclass(frozen=True)
|
9608
|
+
class DeployAppLinksSpec:
|
9609
|
+
apps: ta.Sequence[DeployApp] = ()
|
9610
|
+
|
9611
|
+
exclude_unspecified: bool = False
|
9612
|
+
|
9613
|
+
|
9555
9614
|
##
|
9556
9615
|
|
9557
9616
|
|
@@ -9570,6 +9629,8 @@ class DeploySpec(DeploySpecKeyed[DeployKey]):
|
|
9570
9629
|
|
9571
9630
|
apps: ta.Sequence[DeployAppSpec] = ()
|
9572
9631
|
|
9632
|
+
app_links: DeployAppLinksSpec = DeployAppLinksSpec()
|
9633
|
+
|
9573
9634
|
systemd: ta.Optional[DeploySystemdSpec] = None
|
9574
9635
|
|
9575
9636
|
def __post_init__(self) -> None:
|
@@ -10975,31 +11036,12 @@ def bind_deploy_paths() -> InjectorBindings:
|
|
10975
11036
|
########################################
|
10976
11037
|
# ../deploy/systemd.py
|
10977
11038
|
"""
|
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
|
11039
|
+
TODO:
|
11040
|
+
- verify - systemd-analyze
|
11041
|
+
- sudo loginctl enable-linger "$USER"
|
11042
|
+
- idemp kill services that shouldn't be running, start ones that should
|
11043
|
+
- ideally only those defined by links to deploy home
|
11044
|
+
- ominfra.systemd / x.sd_orphans
|
11003
11045
|
"""
|
11004
11046
|
|
11005
11047
|
|
@@ -11038,6 +11080,8 @@ class DeploySystemdManager:
|
|
11038
11080
|
if not spec:
|
11039
11081
|
return
|
11040
11082
|
|
11083
|
+
#
|
11084
|
+
|
11041
11085
|
if not (ud := spec.unit_dir):
|
11042
11086
|
return
|
11043
11087
|
|
@@ -11045,6 +11089,8 @@ class DeploySystemdManager:
|
|
11045
11089
|
|
11046
11090
|
os.makedirs(ud, exist_ok=True)
|
11047
11091
|
|
11092
|
+
#
|
11093
|
+
|
11048
11094
|
uld = {
|
11049
11095
|
n: p
|
11050
11096
|
for n, p in self._scan_link_dir(ud).items()
|
@@ -11056,8 +11102,11 @@ class DeploySystemdManager:
|
|
11056
11102
|
else:
|
11057
11103
|
cld = {}
|
11058
11104
|
|
11059
|
-
|
11060
|
-
|
11105
|
+
#
|
11106
|
+
|
11107
|
+
ns = sorted(set(uld) | set(cld))
|
11108
|
+
|
11109
|
+
for n in ns:
|
11061
11110
|
cl = cld.get(n)
|
11062
11111
|
if cl is None:
|
11063
11112
|
os.unlink(os.path.join(ud, n))
|
@@ -11074,6 +11123,37 @@ class DeploySystemdManager:
|
|
11074
11123
|
dst_swap.tmp_path,
|
11075
11124
|
)
|
11076
11125
|
|
11126
|
+
#
|
11127
|
+
|
11128
|
+
if sys.platform == 'linux':
|
11129
|
+
async def reload() -> None:
|
11130
|
+
await asyncio_subprocesses.check_call('systemctl', '--user', 'daemon-reload')
|
11131
|
+
|
11132
|
+
await reload()
|
11133
|
+
|
11134
|
+
num_deleted = 0
|
11135
|
+
for n in ns:
|
11136
|
+
if n.endswith('.service'):
|
11137
|
+
cl = cld.get(n)
|
11138
|
+
ul = uld.get(n)
|
11139
|
+
if cl is not None:
|
11140
|
+
if ul is None:
|
11141
|
+
cs = ['enable', 'start']
|
11142
|
+
else:
|
11143
|
+
cs = ['restart']
|
11144
|
+
else: # noqa
|
11145
|
+
if ul is not None:
|
11146
|
+
cs = ['stop']
|
11147
|
+
num_deleted += 1
|
11148
|
+
else:
|
11149
|
+
cs = []
|
11150
|
+
|
11151
|
+
for c in cs:
|
11152
|
+
await asyncio_subprocesses.check_call('systemctl', '--user', c, n)
|
11153
|
+
|
11154
|
+
if num_deleted:
|
11155
|
+
await reload()
|
11156
|
+
|
11077
11157
|
|
11078
11158
|
########################################
|
11079
11159
|
# ../remote/inject.py
|
@@ -11455,12 +11535,15 @@ class DeployVenvManager:
|
|
11455
11535
|
|
11456
11536
|
if os.path.isfile(reqs_txt):
|
11457
11537
|
if spec.use_uv:
|
11458
|
-
|
11459
|
-
|
11538
|
+
if shutil.which('uv') is not None:
|
11539
|
+
pip_cmd = ['uv', 'pip']
|
11540
|
+
else:
|
11541
|
+
await asyncio_subprocesses.check_call(venv_exe, '-m', 'pip', 'install', 'uv')
|
11542
|
+
pip_cmd = [venv_exe, '-m', 'uv', 'pip']
|
11460
11543
|
else:
|
11461
|
-
pip_cmd = ['-m', 'pip']
|
11544
|
+
pip_cmd = [venv_exe, '-m', 'pip']
|
11462
11545
|
|
11463
|
-
await asyncio_subprocesses.check_call(
|
11546
|
+
await asyncio_subprocesses.check_call(*pip_cmd, 'install', '-r', reqs_txt, cwd=venv_dir)
|
11464
11547
|
|
11465
11548
|
|
11466
11549
|
########################################
|
@@ -11506,9 +11589,21 @@ class DeployAppManager(DeployPathOwner):
|
|
11506
11589
|
|
11507
11590
|
#
|
11508
11591
|
|
11592
|
+
def _make_tags(self, spec: DeployAppSpec) -> DeployTagMap:
|
11593
|
+
return DeployTagMap(
|
11594
|
+
spec.app,
|
11595
|
+
spec.key(),
|
11596
|
+
DeployAppRev(spec.git.rev),
|
11597
|
+
)
|
11598
|
+
|
11599
|
+
#
|
11600
|
+
|
11509
11601
|
@dc.dataclass(frozen=True)
|
11510
11602
|
class PreparedApp:
|
11511
|
-
|
11603
|
+
spec: DeployAppSpec
|
11604
|
+
tags: DeployTagMap
|
11605
|
+
|
11606
|
+
dir: str
|
11512
11607
|
|
11513
11608
|
git_dir: ta.Optional[str] = None
|
11514
11609
|
venv_dir: ta.Optional[str] = None
|
@@ -11524,16 +11619,23 @@ class DeployAppManager(DeployPathOwner):
|
|
11524
11619
|
|
11525
11620
|
#
|
11526
11621
|
|
11622
|
+
app_tags = tags.add(*self._make_tags(spec))
|
11623
|
+
|
11624
|
+
#
|
11625
|
+
|
11527
11626
|
check.non_empty_str(home)
|
11528
11627
|
|
11529
|
-
app_dir = os.path.join(home, self.APP_DIR.render(
|
11628
|
+
app_dir = os.path.join(home, self.APP_DIR.render(app_tags))
|
11530
11629
|
|
11531
11630
|
os.makedirs(app_dir, exist_ok=True)
|
11532
11631
|
|
11533
11632
|
#
|
11534
11633
|
|
11535
11634
|
rkw: ta.Dict[str, ta.Any] = dict(
|
11536
|
-
|
11635
|
+
spec=spec,
|
11636
|
+
tags=app_tags,
|
11637
|
+
|
11638
|
+
dir=app_dir,
|
11537
11639
|
)
|
11538
11640
|
|
11539
11641
|
#
|
@@ -11577,6 +11679,54 @@ class DeployAppManager(DeployPathOwner):
|
|
11577
11679
|
|
11578
11680
|
return DeployAppManager.PreparedApp(**rkw)
|
11579
11681
|
|
11682
|
+
async def prepare_app_link(
|
11683
|
+
self,
|
11684
|
+
tags: DeployTagMap,
|
11685
|
+
app_dir: str,
|
11686
|
+
) -> PreparedApp:
|
11687
|
+
spec_file = os.path.join(app_dir, 'spec.json')
|
11688
|
+
with open(spec_file) as f: # noqa
|
11689
|
+
spec_json = f.read()
|
11690
|
+
|
11691
|
+
spec: DeployAppSpec = self._msh.unmarshal_obj(json.loads(spec_json), DeployAppSpec)
|
11692
|
+
|
11693
|
+
#
|
11694
|
+
|
11695
|
+
app_tags = tags.add(*self._make_tags(spec))
|
11696
|
+
|
11697
|
+
#
|
11698
|
+
|
11699
|
+
rkw: ta.Dict[str, ta.Any] = dict(
|
11700
|
+
spec=spec,
|
11701
|
+
tags=app_tags,
|
11702
|
+
|
11703
|
+
dir=app_dir,
|
11704
|
+
)
|
11705
|
+
|
11706
|
+
#
|
11707
|
+
|
11708
|
+
git_dir = os.path.join(app_dir, 'git')
|
11709
|
+
check.state(os.path.isdir(git_dir))
|
11710
|
+
rkw.update(git_dir=git_dir)
|
11711
|
+
|
11712
|
+
#
|
11713
|
+
|
11714
|
+
if spec.venv is not None:
|
11715
|
+
venv_dir = os.path.join(app_dir, 'venv')
|
11716
|
+
check.state(os.path.isdir(venv_dir))
|
11717
|
+
rkw.update(venv_dir=venv_dir)
|
11718
|
+
|
11719
|
+
#
|
11720
|
+
|
11721
|
+
if spec.conf is not None:
|
11722
|
+
conf_dir = os.path.join(app_dir, 'conf')
|
11723
|
+
check.state(os.path.isdir(conf_dir))
|
11724
|
+
rkw.update(conf_dir=conf_dir)
|
11725
|
+
|
11726
|
+
#
|
11727
|
+
|
11728
|
+
return DeployAppManager.PreparedApp(**rkw)
|
11729
|
+
|
11580
11730
|
|
11581
11731
|
########################################
|
11582
11732
|
# ../deploy/deploy.py
|
@@ -11595,16 +11745,24 @@ class DeployManager(DeployPathOwner):
|
|
11595
11745
|
def __init__(
|
11596
11746
|
self,
|
11597
11747
|
*,
|
11748
|
+
atomics: DeployHomeAtomics,
|
11749
|
+
|
11598
11750
|
utc_clock: ta.Optional[DeployManagerUtcClock] = None,
|
11599
11751
|
):
|
11600
11752
|
super().__init__()
|
11601
11753
|
|
11754
|
+
self._atomics = atomics
|
11755
|
+
|
11602
11756
|
self._utc_clock = utc_clock
|
11603
11757
|
|
11604
11758
|
#
|
11605
11759
|
|
11760
|
+
# Home current link just points to CURRENT_DEPLOY_LINK, and is intended for user convenience.
|
11761
|
+
HOME_CURRENT_LINK = DeployPath.parse('current')
|
11762
|
+
|
11606
11763
|
DEPLOYS_DIR = DeployPath.parse('deploys/')
|
11607
11764
|
|
11765
|
+
# Authoritative current symlink is not in deploy-home, just to prevent accidental corruption.
|
11608
11766
|
CURRENT_DEPLOY_LINK = DeployPath.parse(f'{DEPLOYS_DIR}current')
|
11609
11767
|
DEPLOYING_DEPLOY_LINK = DeployPath.parse(f'{DEPLOYS_DIR}deploying')
|
11610
11768
|
|
@@ -11637,6 +11795,11 @@ class DeployManager(DeployPathOwner):
|
|
11637
11795
|
|
11638
11796
|
#
|
11639
11797
|
|
11798
|
+
def render_path(self, home: DeployHome, pth: DeployPath, tags: ta.Optional[DeployTagMap] = None) -> str:
|
11799
|
+
return os.path.join(check.non_empty_str(home), pth.render(tags))
|
11800
|
+
|
11801
|
+
#
|
11802
|
+
|
11640
11803
|
def _utc_now(self) -> datetime.datetime:
|
11641
11804
|
if self._utc_clock is not None:
|
11642
11805
|
return self._utc_clock() # noqa
|
@@ -11646,6 +11809,22 @@ class DeployManager(DeployPathOwner):
|
|
11646
11809
|
def make_deploy_time(self) -> DeployTime:
|
11647
11810
|
return DeployTime(self._utc_now().strftime(DEPLOY_TAG_DATETIME_FMT))
|
11648
11811
|
|
11812
|
+
#
|
11813
|
+
|
11814
|
+
def make_home_current_link(self, home: DeployHome) -> None:
|
11815
|
+
home_current_link = os.path.join(check.non_empty_str(home), self.HOME_CURRENT_LINK.render())
|
11816
|
+
current_deploy_link = os.path.join(check.non_empty_str(home), self.CURRENT_DEPLOY_LINK.render())
|
11817
|
+
with self._atomics(home).begin_atomic_path_swap( # noqa
|
11818
|
+
'file',
|
11819
|
+
home_current_link,
|
11820
|
+
auto_commit=True,
|
11821
|
+
) as dst_swap:
|
11822
|
+
os.unlink(dst_swap.tmp_path)
|
11823
|
+
os.symlink(
|
11824
|
+
os.path.relpath(current_deploy_link, os.path.dirname(dst_swap.dst_path)),
|
11825
|
+
dst_swap.tmp_path,
|
11826
|
+
)
|
11827
|
+
|
11649
11828
|
|
11650
11829
|
##
|
11651
11830
|
|
@@ -11687,18 +11866,18 @@ class DeployDriver:
|
|
11687
11866
|
#
|
11688
11867
|
|
11689
11868
|
@property
|
11690
|
-
def
|
11869
|
+
def tags(self) -> DeployTagMap:
|
11691
11870
|
return DeployTagMap(
|
11692
11871
|
self._time,
|
11693
11872
|
self._spec.key(),
|
11694
11873
|
)
|
11695
11874
|
|
11696
|
-
def
|
11697
|
-
return os.path.join(self._home, pth.render(tags if tags is not None else self.
|
11875
|
+
def render_path(self, pth: DeployPath, tags: ta.Optional[DeployTagMap] = None) -> str:
|
11876
|
+
return os.path.join(self._home, pth.render(tags if tags is not None else self.tags))
|
11698
11877
|
|
11699
11878
|
@property
|
11700
|
-
def
|
11701
|
-
return self.
|
11879
|
+
def dir(self) -> str:
|
11880
|
+
return self.render_path(self._deploys.DEPLOY_DIR)
|
11702
11881
|
|
11703
11882
|
#
|
11704
11883
|
|
@@ -11707,25 +11886,36 @@ class DeployDriver:
|
|
11707
11886
|
|
11708
11887
|
#
|
11709
11888
|
|
11889
|
+
das: ta.Set[DeployApp] = {a.app for a in self._spec.apps}
|
11890
|
+
las: ta.Set[DeployApp] = set(self._spec.app_links.apps)
|
11891
|
+
if (ras := das & las):
|
11892
|
+
raise RuntimeError(f'Must not specify apps as both deploy and link: {sorted(a.s for a in ras)}')
|
11893
|
+
|
11894
|
+
#
|
11895
|
+
|
11710
11896
|
self._paths.validate_deploy_paths()
|
11711
11897
|
|
11712
11898
|
#
|
11713
11899
|
|
11714
|
-
os.makedirs(self.
|
11900
|
+
os.makedirs(self.dir)
|
11715
11901
|
|
11716
11902
|
#
|
11717
11903
|
|
11718
|
-
spec_file = self.
|
11904
|
+
spec_file = self.render_path(self._deploys.DEPLOY_SPEC_FILE)
|
11719
11905
|
with open(spec_file, 'w') as f: # noqa
|
11720
11906
|
f.write(spec_json)
|
11721
11907
|
|
11722
11908
|
#
|
11723
11909
|
|
11724
|
-
deploying_link = self.
|
11910
|
+
deploying_link = self.render_path(self._deploys.DEPLOYING_DEPLOY_LINK)
|
11911
|
+
current_link = self.render_path(self._deploys.CURRENT_DEPLOY_LINK)
|
11912
|
+
|
11913
|
+
#
|
11914
|
+
|
11725
11915
|
if os.path.exists(deploying_link):
|
11726
11916
|
os.unlink(deploying_link)
|
11727
11917
|
relative_symlink(
|
11728
|
-
self.
|
11918
|
+
self.dir,
|
11729
11919
|
deploying_link,
|
11730
11920
|
target_is_directory=True,
|
11731
11921
|
make_dirs=True,
|
@@ -11737,16 +11927,30 @@ class DeployDriver:
|
|
11737
11927
|
self._deploys.APPS_DEPLOY_DIR,
|
11738
11928
|
self._deploys.CONFS_DEPLOY_DIR,
|
11739
11929
|
]:
|
11740
|
-
os.makedirs(self.
|
11930
|
+
os.makedirs(self.render_path(md))
|
11741
11931
|
|
11742
11932
|
#
|
11743
11933
|
|
11934
|
+
if not self._spec.app_links.exclude_unspecified:
|
11935
|
+
cad = abs_real_path(os.path.join(current_link, 'apps'))
|
11936
|
+
if os.path.exists(cad):
|
11937
|
+
for d in os.listdir(cad):
|
11938
|
+
if (da := DeployApp(d)) not in das:
|
11939
|
+
las.add(da)
|
11940
|
+
|
11941
|
+
for la in self._spec.app_links.apps:
|
11942
|
+
await self._drive_app_link(
|
11943
|
+
la,
|
11944
|
+
current_link,
|
11945
|
+
)
|
11946
|
+
|
11744
11947
|
for app in self._spec.apps:
|
11745
|
-
await self.
|
11948
|
+
await self._drive_app_deploy(
|
11949
|
+
app,
|
11950
|
+
)
|
11746
11951
|
|
11747
11952
|
#
|
11748
11953
|
|
11749
|
-
current_link = self.render_deploy_path(self._deploys.CURRENT_DEPLOY_LINK)
|
11750
11954
|
os.replace(deploying_link, current_link)
|
11751
11955
|
|
11752
11956
|
#
|
@@ -11754,31 +11958,27 @@ class DeployDriver:
|
|
11754
11958
|
await self._systemd.sync_systemd(
|
11755
11959
|
self._spec.systemd,
|
11756
11960
|
self._home,
|
11757
|
-
os.path.join(self.
|
11961
|
+
os.path.join(self.dir, 'conf', 'systemd'), # FIXME
|
11758
11962
|
)
|
11759
11963
|
|
11760
|
-
|
11964
|
+
#
|
11761
11965
|
|
11762
|
-
|
11763
|
-
app_tags = self.deploy_tags.add(
|
11764
|
-
app.app,
|
11765
|
-
app.key(),
|
11766
|
-
DeployAppRev(app.git.rev),
|
11767
|
-
)
|
11966
|
+
self._deploys.make_home_current_link(self._home)
|
11768
11967
|
|
11769
|
-
|
11968
|
+
#
|
11770
11969
|
|
11771
|
-
|
11970
|
+
async def _drive_app_deploy(self, app: DeployAppSpec) -> None:
|
11971
|
+
pa = await self._apps.prepare_app(
|
11772
11972
|
app,
|
11773
11973
|
self._home,
|
11774
|
-
|
11974
|
+
self.tags,
|
11775
11975
|
)
|
11776
11976
|
|
11777
11977
|
#
|
11778
11978
|
|
11779
|
-
app_link = self.
|
11979
|
+
app_link = self.render_path(self._deploys.APP_DEPLOY_LINK, pa.tags)
|
11780
11980
|
relative_symlink(
|
11781
|
-
|
11981
|
+
pa.dir,
|
11782
11982
|
app_link,
|
11783
11983
|
target_is_directory=True,
|
11784
11984
|
make_dirs=True,
|
@@ -11786,12 +11986,48 @@ class DeployDriver:
|
|
11786
11986
|
|
11787
11987
|
#
|
11788
11988
|
|
11789
|
-
|
11790
|
-
|
11989
|
+
await self._drive_app_configure(pa)
|
11990
|
+
|
11991
|
+
async def _drive_app_link(
|
11992
|
+
self,
|
11993
|
+
app: DeployApp,
|
11994
|
+
current_link: str,
|
11995
|
+
) -> None:
|
11996
|
+
app_link = os.path.join(abs_real_path(current_link), 'apps', app.s)
|
11997
|
+
check.state(os.path.islink(app_link))
|
11998
|
+
|
11999
|
+
app_dir = abs_real_path(app_link)
|
12000
|
+
check.state(os.path.isdir(app_dir))
|
12001
|
+
|
12002
|
+
#
|
12003
|
+
|
12004
|
+
pa = await self._apps.prepare_app_link(
|
12005
|
+
self.tags,
|
12006
|
+
app_dir,
|
12007
|
+
)
|
12008
|
+
|
12009
|
+
#
|
12010
|
+
|
12011
|
+
relative_symlink(
|
12012
|
+
app_dir,
|
12013
|
+
os.path.join(self.dir, 'apps', app.s),
|
12014
|
+
target_is_directory=True,
|
12015
|
+
)
|
12016
|
+
|
12017
|
+
#
|
12018
|
+
|
12019
|
+
await self._drive_app_configure(pa)
|
12020
|
+
|
12021
|
+
async def _drive_app_configure(
|
12022
|
+
self,
|
12023
|
+
pa: DeployAppManager.PreparedApp,
|
12024
|
+
) -> None:
|
12025
|
+
deploy_conf_dir = self.render_path(self._deploys.CONFS_DEPLOY_DIR)
|
12026
|
+
if pa.spec.conf is not None:
|
11791
12027
|
await self._conf.link_app_conf(
|
11792
|
-
|
11793
|
-
|
11794
|
-
check.non_empty_str(
|
12028
|
+
pa.spec.conf,
|
12029
|
+
pa.tags,
|
12030
|
+
check.non_empty_str(pa.conf_dir),
|
11795
12031
|
deploy_conf_dir,
|
11796
12032
|
)
|
11797
12033
|
|
ominfra/systemd.py
ADDED
@@ -0,0 +1,49 @@
|
|
1
|
+
# ruff: noqa: UP006 UP007
|
2
|
+
# @omlish-lite
|
3
|
+
import dataclasses as dc
|
4
|
+
import typing as ta
|
5
|
+
|
6
|
+
|
7
|
+
##
|
8
|
+
|
9
|
+
|
10
|
+
@dc.dataclass(frozen=True)
|
11
|
+
class SystemdListUnit:
|
12
|
+
unit: str
|
13
|
+
load: str # loaded, not-found
|
14
|
+
active: str # active, inactive
|
15
|
+
sub: str # running, exited, dead
|
16
|
+
description: str
|
17
|
+
|
18
|
+
@classmethod
|
19
|
+
def parse(cls, s: str) -> 'SystemdListUnit':
|
20
|
+
return SystemdListUnit(*[p.strip() for p in s.strip().split(None, 4)])
|
21
|
+
|
22
|
+
@classmethod
|
23
|
+
def parse_all(cls, s: str) -> ta.List['SystemdListUnit']:
|
24
|
+
return [
|
25
|
+
cls.parse(sl)
|
26
|
+
for l in s.strip().splitlines()
|
27
|
+
if (sl := l.strip())
|
28
|
+
]
|
29
|
+
|
30
|
+
|
31
|
+
PARSABLE_SYSTEMD_LIST_UNIT_ARGS: ta.Sequence[str] = [
|
32
|
+
'--all',
|
33
|
+
'--no-legend',
|
34
|
+
'--no-pager',
|
35
|
+
'--plain',
|
36
|
+
]
|
37
|
+
|
38
|
+
|
39
|
+
##
|
40
|
+
|
41
|
+
|
42
|
+
def parse_systemd_show_output(s: str) -> ta.Mapping[str, str]:
|
43
|
+
d: ta.Dict[str, str] = {}
|
44
|
+
for l in s.strip().splitlines():
|
45
|
+
if not (l := l.strip()):
|
46
|
+
continue
|
47
|
+
k, _, v = l.partition('=')
|
48
|
+
d[k] = v
|
49
|
+
return d
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.1
|
2
2
|
Name: ominfra
|
3
|
-
Version: 0.0.0.
|
3
|
+
Version: 0.0.0.dev191
|
4
4
|
Summary: ominfra
|
5
5
|
Author: wrmsr
|
6
6
|
License: BSD-3-Clause
|
@@ -12,9 +12,9 @@ Classifier: Operating System :: OS Independent
|
|
12
12
|
Classifier: Operating System :: POSIX
|
13
13
|
Requires-Python: >=3.12
|
14
14
|
License-File: LICENSE
|
15
|
-
Requires-Dist: omdev==0.0.0.
|
16
|
-
Requires-Dist: omlish==0.0.0.
|
17
|
-
Requires-Dist: omserv==0.0.0.
|
15
|
+
Requires-Dist: omdev==0.0.0.dev191
|
16
|
+
Requires-Dist: omlish==0.0.0.dev191
|
17
|
+
Requires-Dist: omserv==0.0.0.dev191
|
18
18
|
Provides-Extra: all
|
19
19
|
Requires-Dist: paramiko~=3.5; extra == "all"
|
20
20
|
Requires-Dist: asyncssh~=2.18; extra == "all"
|