ominfra 0.0.0.dev190__py3-none-any.whl → 0.0.0.dev191__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 +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"
|