ominfra 0.0.0.dev189__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/__about__.py +1 -0
- ominfra/manage/deploy/apps.py +109 -88
- ominfra/manage/deploy/commands.py +1 -1
- ominfra/manage/deploy/conf/manager.py +82 -17
- ominfra/manage/deploy/conf/specs.py +9 -0
- ominfra/manage/deploy/deploy.py +294 -1
- ominfra/manage/deploy/inject.py +4 -6
- ominfra/manage/deploy/nginx.py +8 -0
- ominfra/manage/deploy/paths/paths.py +5 -1
- ominfra/manage/deploy/specs.py +21 -1
- ominfra/manage/deploy/systemd.py +131 -0
- ominfra/manage/deploy/tags.py +1 -1
- ominfra/manage/deploy/venvs.py +8 -6
- ominfra/scripts/manage.py +733 -160
- ominfra/systemd.py +49 -0
- {ominfra-0.0.0.dev189.dist-info → ominfra-0.0.0.dev191.dist-info}/METADATA +4 -3
- {ominfra-0.0.0.dev189.dist-info → ominfra-0.0.0.dev191.dist-info}/RECORD +21 -19
- ominfra/manage/deploy/driver.py +0 -62
- {ominfra-0.0.0.dev189.dist-info → ominfra-0.0.0.dev191.dist-info}/LICENSE +0 -0
- {ominfra-0.0.0.dev189.dist-info → ominfra-0.0.0.dev191.dist-info}/WHEEL +0 -0
- {ominfra-0.0.0.dev189.dist-info → ominfra-0.0.0.dev191.dist-info}/entry_points.txt +0 -0
- {ominfra-0.0.0.dev189.dist-info → ominfra-0.0.0.dev191.dist-info}/top_level.txt +0 -0
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
|
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
|
|
@@ -7035,7 +7089,7 @@ DEPLOY_TAG_ILLEGAL_STRS: ta.AbstractSet[str] = frozenset([
|
|
7035
7089
|
##
|
7036
7090
|
|
7037
7091
|
|
7038
|
-
@dc.dataclass(frozen=True)
|
7092
|
+
@dc.dataclass(frozen=True, order=True)
|
7039
7093
|
class DeployTag(abc.ABC): # noqa
|
7040
7094
|
s: str
|
7041
7095
|
|
@@ -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
|
@@ -9160,16 +9268,63 @@ TODO:
|
|
9160
9268
|
"""
|
9161
9269
|
|
9162
9270
|
|
9271
|
+
##
|
9272
|
+
|
9273
|
+
|
9163
9274
|
class DeployConfManager:
|
9164
|
-
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
|
+
|
9165
9314
|
if isinstance(ac, RawDeployAppConfContent):
|
9166
|
-
return ac.body
|
9315
|
+
return pcc(ac.body)
|
9167
9316
|
|
9168
9317
|
elif isinstance(ac, JsonDeployAppConfContent):
|
9169
|
-
|
9318
|
+
json_obj = pcc(ac.obj)
|
9319
|
+
return strip_with_newline(json_dumps_pretty(json_obj))
|
9170
9320
|
|
9171
9321
|
elif isinstance(ac, IniDeployAppConfContent):
|
9172
|
-
|
9322
|
+
ini_sections = pcc(ac.sections)
|
9323
|
+
return strip_with_newline(render_ini_config(ini_sections))
|
9324
|
+
|
9325
|
+
elif isinstance(ac, NginxDeployAppConfContent):
|
9326
|
+
nginx_items = NginxConfigItems.of(pcc(ac.items))
|
9327
|
+
return strip_with_newline(render_nginx_config_str(nginx_items))
|
9173
9328
|
|
9174
9329
|
else:
|
9175
9330
|
raise TypeError(ac)
|
@@ -9178,17 +9333,37 @@ class DeployConfManager:
|
|
9178
9333
|
self,
|
9179
9334
|
acf: DeployAppConfFile,
|
9180
9335
|
app_conf_dir: str,
|
9336
|
+
*,
|
9337
|
+
str_processor: ta.Optional[ta.Callable[[str], str]] = None,
|
9181
9338
|
) -> None:
|
9182
9339
|
conf_file = os.path.join(app_conf_dir, acf.path)
|
9183
9340
|
check.arg(is_path_in_dir(app_conf_dir, conf_file))
|
9184
9341
|
|
9185
|
-
body = self._render_app_conf_content(
|
9342
|
+
body = self._render_app_conf_content(
|
9343
|
+
acf.content,
|
9344
|
+
str_processor=str_processor,
|
9345
|
+
)
|
9186
9346
|
|
9187
9347
|
os.makedirs(os.path.dirname(conf_file), exist_ok=True)
|
9188
9348
|
|
9189
9349
|
with open(conf_file, 'w') as f: # noqa
|
9190
9350
|
f.write(body)
|
9191
9351
|
|
9352
|
+
async def write_app_conf(
|
9353
|
+
self,
|
9354
|
+
spec: DeployAppConfSpec,
|
9355
|
+
app_conf_dir: str,
|
9356
|
+
) -> None:
|
9357
|
+
def process_str(s: str) -> str:
|
9358
|
+
return s
|
9359
|
+
|
9360
|
+
for acf in spec.files or []:
|
9361
|
+
await self._write_app_conf_file(
|
9362
|
+
acf,
|
9363
|
+
app_conf_dir,
|
9364
|
+
str_processor=process_str,
|
9365
|
+
)
|
9366
|
+
|
9192
9367
|
#
|
9193
9368
|
|
9194
9369
|
class _ComputedConfLink(ta.NamedTuple):
|
@@ -9298,23 +9473,13 @@ class DeployConfManager:
|
|
9298
9473
|
make_dirs=True,
|
9299
9474
|
)
|
9300
9475
|
|
9301
|
-
|
9302
|
-
|
9303
|
-
async def write_app_conf(
|
9476
|
+
async def link_app_conf(
|
9304
9477
|
self,
|
9305
9478
|
spec: DeployAppConfSpec,
|
9306
9479
|
tags: DeployTagMap,
|
9307
9480
|
app_conf_dir: str,
|
9308
9481
|
conf_link_dir: str,
|
9309
|
-
)
|
9310
|
-
for acf in spec.files or []:
|
9311
|
-
await self._write_app_conf_file(
|
9312
|
-
acf,
|
9313
|
-
app_conf_dir,
|
9314
|
-
)
|
9315
|
-
|
9316
|
-
#
|
9317
|
-
|
9482
|
+
):
|
9318
9483
|
for link in spec.links or []:
|
9319
9484
|
await self._make_app_conf_link(
|
9320
9485
|
link,
|
@@ -9439,6 +9604,22 @@ class DeployAppSpec(DeploySpecKeyed[DeployAppKey]):
|
|
9439
9604
|
return DeployAppKey(self._key_str())
|
9440
9605
|
|
9441
9606
|
|
9607
|
+
@dc.dataclass(frozen=True)
|
9608
|
+
class DeployAppLinksSpec:
|
9609
|
+
apps: ta.Sequence[DeployApp] = ()
|
9610
|
+
|
9611
|
+
exclude_unspecified: bool = False
|
9612
|
+
|
9613
|
+
|
9614
|
+
##
|
9615
|
+
|
9616
|
+
|
9617
|
+
@dc.dataclass(frozen=True)
|
9618
|
+
class DeploySystemdSpec:
|
9619
|
+
# ~/.config/systemd/user/
|
9620
|
+
unit_dir: ta.Optional[str] = None
|
9621
|
+
|
9622
|
+
|
9442
9623
|
##
|
9443
9624
|
|
9444
9625
|
|
@@ -9446,7 +9627,11 @@ class DeployAppSpec(DeploySpecKeyed[DeployAppKey]):
|
|
9446
9627
|
class DeploySpec(DeploySpecKeyed[DeployKey]):
|
9447
9628
|
home: DeployHome
|
9448
9629
|
|
9449
|
-
apps: ta.Sequence[DeployAppSpec]
|
9630
|
+
apps: ta.Sequence[DeployAppSpec] = ()
|
9631
|
+
|
9632
|
+
app_links: DeployAppLinksSpec = DeployAppLinksSpec()
|
9633
|
+
|
9634
|
+
systemd: ta.Optional[DeploySystemdSpec] = None
|
9450
9635
|
|
9451
9636
|
def __post_init__(self) -> None:
|
9452
9637
|
check.non_empty_str(self.home)
|
@@ -10848,6 +11033,128 @@ def bind_deploy_paths() -> InjectorBindings:
|
|
10848
11033
|
return inj.as_bindings(*lst)
|
10849
11034
|
|
10850
11035
|
|
11036
|
+
########################################
|
11037
|
+
# ../deploy/systemd.py
|
11038
|
+
"""
|
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
|
11045
|
+
"""
|
11046
|
+
|
11047
|
+
|
11048
|
+
class DeploySystemdManager:
|
11049
|
+
def __init__(
|
11050
|
+
self,
|
11051
|
+
*,
|
11052
|
+
atomics: DeployHomeAtomics,
|
11053
|
+
) -> None:
|
11054
|
+
super().__init__()
|
11055
|
+
|
11056
|
+
self._atomics = atomics
|
11057
|
+
|
11058
|
+
def _scan_link_dir(
|
11059
|
+
self,
|
11060
|
+
d: str,
|
11061
|
+
*,
|
11062
|
+
strict: bool = False,
|
11063
|
+
) -> ta.Dict[str, str]:
|
11064
|
+
o: ta.Dict[str, str] = {}
|
11065
|
+
for f in os.listdir(d):
|
11066
|
+
fp = os.path.join(d, f)
|
11067
|
+
if strict:
|
11068
|
+
check.state(os.path.islink(fp))
|
11069
|
+
o[f] = abs_real_path(fp)
|
11070
|
+
return o
|
11071
|
+
|
11072
|
+
async def sync_systemd(
|
11073
|
+
self,
|
11074
|
+
spec: ta.Optional[DeploySystemdSpec],
|
11075
|
+
home: DeployHome,
|
11076
|
+
conf_dir: str,
|
11077
|
+
) -> None:
|
11078
|
+
check.non_empty_str(home)
|
11079
|
+
|
11080
|
+
if not spec:
|
11081
|
+
return
|
11082
|
+
|
11083
|
+
#
|
11084
|
+
|
11085
|
+
if not (ud := spec.unit_dir):
|
11086
|
+
return
|
11087
|
+
|
11088
|
+
ud = abs_real_path(os.path.expanduser(ud))
|
11089
|
+
|
11090
|
+
os.makedirs(ud, exist_ok=True)
|
11091
|
+
|
11092
|
+
#
|
11093
|
+
|
11094
|
+
uld = {
|
11095
|
+
n: p
|
11096
|
+
for n, p in self._scan_link_dir(ud).items()
|
11097
|
+
if is_path_in_dir(home, p)
|
11098
|
+
}
|
11099
|
+
|
11100
|
+
if os.path.exists(conf_dir):
|
11101
|
+
cld = self._scan_link_dir(conf_dir, strict=True)
|
11102
|
+
else:
|
11103
|
+
cld = {}
|
11104
|
+
|
11105
|
+
#
|
11106
|
+
|
11107
|
+
ns = sorted(set(uld) | set(cld))
|
11108
|
+
|
11109
|
+
for n in ns:
|
11110
|
+
cl = cld.get(n)
|
11111
|
+
if cl is None:
|
11112
|
+
os.unlink(os.path.join(ud, n))
|
11113
|
+
else:
|
11114
|
+
with self._atomics(home).begin_atomic_path_swap( # noqa
|
11115
|
+
'file',
|
11116
|
+
os.path.join(ud, n),
|
11117
|
+
auto_commit=True,
|
11118
|
+
skip_root_dir_check=True,
|
11119
|
+
) as dst_swap:
|
11120
|
+
os.unlink(dst_swap.tmp_path)
|
11121
|
+
os.symlink(
|
11122
|
+
os.path.relpath(cl, os.path.dirname(dst_swap.dst_path)),
|
11123
|
+
dst_swap.tmp_path,
|
11124
|
+
)
|
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
|
+
|
11157
|
+
|
10851
11158
|
########################################
|
10852
11159
|
# ../remote/inject.py
|
10853
11160
|
|
@@ -11202,7 +11509,6 @@ class DeployVenvManager:
|
|
11202
11509
|
async def setup_venv(
|
11203
11510
|
self,
|
11204
11511
|
spec: DeployVenvSpec,
|
11205
|
-
home: DeployHome,
|
11206
11512
|
git_dir: str,
|
11207
11513
|
venv_dir: str,
|
11208
11514
|
) -> None:
|
@@ -11229,12 +11535,15 @@ class DeployVenvManager:
|
|
11229
11535
|
|
11230
11536
|
if os.path.isfile(reqs_txt):
|
11231
11537
|
if spec.use_uv:
|
11232
|
-
|
11233
|
-
|
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']
|
11234
11543
|
else:
|
11235
|
-
pip_cmd = ['-m', 'pip']
|
11544
|
+
pip_cmd = [venv_exe, '-m', 'pip']
|
11236
11545
|
|
11237
|
-
await asyncio_subprocesses.check_call(
|
11546
|
+
await asyncio_subprocesses.check_call(*pip_cmd, 'install', '-r', reqs_txt, cwd=venv_dir)
|
11238
11547
|
|
11239
11548
|
|
11240
11549
|
########################################
|
@@ -11245,39 +11554,31 @@ class DeployAppManager(DeployPathOwner):
|
|
11245
11554
|
def __init__(
|
11246
11555
|
self,
|
11247
11556
|
*,
|
11248
|
-
conf: DeployConfManager,
|
11249
11557
|
git: DeployGitManager,
|
11250
11558
|
venvs: DeployVenvManager,
|
11559
|
+
conf: DeployConfManager,
|
11560
|
+
|
11561
|
+
msh: ObjMarshalerManager,
|
11251
11562
|
) -> None:
|
11252
11563
|
super().__init__()
|
11253
11564
|
|
11254
|
-
self._conf = conf
|
11255
11565
|
self._git = git
|
11256
11566
|
self._venvs = venvs
|
11567
|
+
self._conf = conf
|
11257
11568
|
|
11258
|
-
|
11259
|
-
|
11260
|
-
_APP_DIR_STR = 'apps/@app/@time--@app-rev--@app-key/'
|
11261
|
-
_APP_DIR = DeployPath.parse(_APP_DIR_STR)
|
11569
|
+
self._msh = msh
|
11262
11570
|
|
11263
|
-
|
11264
|
-
_DEPLOY_DIR = DeployPath.parse(_DEPLOY_DIR_STR)
|
11571
|
+
#
|
11265
11572
|
|
11266
|
-
|
11267
|
-
_CONF_DEPLOY_DIR = DeployPath.parse(f'{_DEPLOY_DIR_STR}conf/@conf/')
|
11573
|
+
APP_DIR = DeployPath.parse('apps/@app/@time--@app-rev--@app-key/')
|
11268
11574
|
|
11269
11575
|
@cached_nullary
|
11270
11576
|
def get_owned_deploy_paths(self) -> ta.AbstractSet[DeployPath]:
|
11271
11577
|
return {
|
11272
|
-
self.
|
11273
|
-
|
11274
|
-
self._DEPLOY_DIR,
|
11275
|
-
|
11276
|
-
self._APP_DEPLOY_LINK,
|
11277
|
-
self._CONF_DEPLOY_DIR,
|
11578
|
+
self.APP_DIR,
|
11278
11579
|
|
11279
11580
|
*[
|
11280
|
-
DeployPath.parse(f'{self.
|
11581
|
+
DeployPath.parse(f'{self.APP_DIR}{sfx}/')
|
11281
11582
|
for sfx in [
|
11282
11583
|
'conf',
|
11283
11584
|
'git',
|
@@ -11288,122 +11589,244 @@ class DeployAppManager(DeployPathOwner):
|
|
11288
11589
|
|
11289
11590
|
#
|
11290
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
|
+
|
11601
|
+
@dc.dataclass(frozen=True)
|
11602
|
+
class PreparedApp:
|
11603
|
+
spec: DeployAppSpec
|
11604
|
+
tags: DeployTagMap
|
11605
|
+
|
11606
|
+
dir: str
|
11607
|
+
|
11608
|
+
git_dir: ta.Optional[str] = None
|
11609
|
+
venv_dir: ta.Optional[str] = None
|
11610
|
+
conf_dir: ta.Optional[str] = None
|
11611
|
+
|
11291
11612
|
async def prepare_app(
|
11292
11613
|
self,
|
11293
11614
|
spec: DeployAppSpec,
|
11294
11615
|
home: DeployHome,
|
11295
11616
|
tags: DeployTagMap,
|
11296
|
-
) ->
|
11297
|
-
|
11617
|
+
) -> PreparedApp:
|
11618
|
+
spec_json = json_dumps_pretty(self._msh.marshal_obj(spec))
|
11298
11619
|
|
11299
|
-
|
11300
|
-
return os.path.join(home, pth.render(tags))
|
11620
|
+
#
|
11301
11621
|
|
11302
|
-
|
11303
|
-
deploy_dir = build_path(self._DEPLOY_DIR)
|
11304
|
-
app_deploy_link = build_path(self._APP_DEPLOY_LINK)
|
11622
|
+
app_tags = tags.add(*self._make_tags(spec))
|
11305
11623
|
|
11306
11624
|
#
|
11307
11625
|
|
11308
|
-
|
11309
|
-
|
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
|
-
)
|
11626
|
+
check.non_empty_str(home)
|
11319
11627
|
|
11320
|
-
|
11628
|
+
app_dir = os.path.join(home, self.APP_DIR.render(app_tags))
|
11321
11629
|
|
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
|
-
)
|
11630
|
+
os.makedirs(app_dir, exist_ok=True)
|
11329
11631
|
|
11330
11632
|
#
|
11331
11633
|
|
11332
|
-
|
11333
|
-
|
11634
|
+
rkw: ta.Dict[str, ta.Any] = dict(
|
11635
|
+
spec=spec,
|
11636
|
+
tags=app_tags,
|
11334
11637
|
|
11335
|
-
|
11638
|
+
dir=app_dir,
|
11639
|
+
)
|
11336
11640
|
|
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
11641
|
#
|
11350
|
-
|
11351
|
-
|
11352
|
-
|
11353
|
-
|
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
|
-
# )
|
11642
|
+
|
11643
|
+
spec_file = os.path.join(app_dir, 'spec.json')
|
11644
|
+
with open(spec_file, 'w') as f: # noqa
|
11645
|
+
f.write(spec_json)
|
11368
11646
|
|
11369
11647
|
#
|
11370
11648
|
|
11371
|
-
|
11649
|
+
git_dir = os.path.join(app_dir, 'git')
|
11650
|
+
rkw.update(git_dir=git_dir)
|
11372
11651
|
await self._git.checkout(
|
11373
11652
|
spec.git,
|
11374
11653
|
home,
|
11375
|
-
|
11654
|
+
git_dir,
|
11376
11655
|
)
|
11377
11656
|
|
11378
11657
|
#
|
11379
11658
|
|
11380
11659
|
if spec.venv is not None:
|
11381
|
-
|
11660
|
+
venv_dir = os.path.join(app_dir, 'venv')
|
11661
|
+
rkw.update(venv_dir=venv_dir)
|
11382
11662
|
await self._venvs.setup_venv(
|
11383
11663
|
spec.venv,
|
11384
|
-
|
11385
|
-
|
11386
|
-
app_venv_dir,
|
11664
|
+
git_dir,
|
11665
|
+
venv_dir,
|
11387
11666
|
)
|
11388
11667
|
|
11389
11668
|
#
|
11390
11669
|
|
11391
11670
|
if spec.conf is not None:
|
11392
|
-
|
11671
|
+
conf_dir = os.path.join(app_dir, 'conf')
|
11672
|
+
rkw.update(conf_dir=conf_dir)
|
11393
11673
|
await self._conf.write_app_conf(
|
11394
11674
|
spec.conf,
|
11395
|
-
|
11396
|
-
app_conf_dir,
|
11397
|
-
deploy_conf_dir,
|
11675
|
+
conf_dir,
|
11398
11676
|
)
|
11399
11677
|
|
11400
11678
|
#
|
11401
11679
|
|
11402
|
-
|
11680
|
+
return DeployAppManager.PreparedApp(**rkw)
|
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)
|
11403
11729
|
|
11404
11730
|
|
11405
11731
|
########################################
|
11406
|
-
# ../deploy/
|
11732
|
+
# ../deploy/deploy.py
|
11733
|
+
|
11734
|
+
|
11735
|
+
##
|
11736
|
+
|
11737
|
+
|
11738
|
+
DEPLOY_TAG_DATETIME_FMT = '%Y%m%dT%H%M%SZ'
|
11739
|
+
|
11740
|
+
|
11741
|
+
DeployManagerUtcClock = ta.NewType('DeployManagerUtcClock', Func0[datetime.datetime])
|
11742
|
+
|
11743
|
+
|
11744
|
+
class DeployManager(DeployPathOwner):
|
11745
|
+
def __init__(
|
11746
|
+
self,
|
11747
|
+
*,
|
11748
|
+
atomics: DeployHomeAtomics,
|
11749
|
+
|
11750
|
+
utc_clock: ta.Optional[DeployManagerUtcClock] = None,
|
11751
|
+
):
|
11752
|
+
super().__init__()
|
11753
|
+
|
11754
|
+
self._atomics = atomics
|
11755
|
+
|
11756
|
+
self._utc_clock = utc_clock
|
11757
|
+
|
11758
|
+
#
|
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
|
+
|
11763
|
+
DEPLOYS_DIR = DeployPath.parse('deploys/')
|
11764
|
+
|
11765
|
+
# Authoritative current symlink is not in deploy-home, just to prevent accidental corruption.
|
11766
|
+
CURRENT_DEPLOY_LINK = DeployPath.parse(f'{DEPLOYS_DIR}current')
|
11767
|
+
DEPLOYING_DEPLOY_LINK = DeployPath.parse(f'{DEPLOYS_DIR}deploying')
|
11768
|
+
|
11769
|
+
DEPLOY_DIR = DeployPath.parse(f'{DEPLOYS_DIR}@time--@deploy-key/')
|
11770
|
+
DEPLOY_SPEC_FILE = DeployPath.parse(f'{DEPLOY_DIR}spec.json')
|
11771
|
+
|
11772
|
+
APPS_DEPLOY_DIR = DeployPath.parse(f'{DEPLOY_DIR}apps/')
|
11773
|
+
APP_DEPLOY_LINK = DeployPath.parse(f'{APPS_DEPLOY_DIR}@app')
|
11774
|
+
|
11775
|
+
CONFS_DEPLOY_DIR = DeployPath.parse(f'{DEPLOY_DIR}conf/')
|
11776
|
+
CONF_DEPLOY_DIR = DeployPath.parse(f'{CONFS_DEPLOY_DIR}@conf/')
|
11777
|
+
|
11778
|
+
@cached_nullary
|
11779
|
+
def get_owned_deploy_paths(self) -> ta.AbstractSet[DeployPath]:
|
11780
|
+
return {
|
11781
|
+
self.DEPLOYS_DIR,
|
11782
|
+
|
11783
|
+
self.CURRENT_DEPLOY_LINK,
|
11784
|
+
self.DEPLOYING_DEPLOY_LINK,
|
11785
|
+
|
11786
|
+
self.DEPLOY_DIR,
|
11787
|
+
self.DEPLOY_SPEC_FILE,
|
11788
|
+
|
11789
|
+
self.APPS_DEPLOY_DIR,
|
11790
|
+
self.APP_DEPLOY_LINK,
|
11791
|
+
|
11792
|
+
self.CONFS_DEPLOY_DIR,
|
11793
|
+
self.CONF_DEPLOY_DIR,
|
11794
|
+
}
|
11795
|
+
|
11796
|
+
#
|
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
|
+
|
11803
|
+
def _utc_now(self) -> datetime.datetime:
|
11804
|
+
if self._utc_clock is not None:
|
11805
|
+
return self._utc_clock() # noqa
|
11806
|
+
else:
|
11807
|
+
return datetime.datetime.now(tz=datetime.timezone.utc) # noqa
|
11808
|
+
|
11809
|
+
def make_deploy_time(self) -> DeployTime:
|
11810
|
+
return DeployTime(self._utc_now().strftime(DEPLOY_TAG_DATETIME_FMT))
|
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
|
+
|
11828
|
+
|
11829
|
+
##
|
11407
11830
|
|
11408
11831
|
|
11409
11832
|
class DeployDriverFactory(Func1[DeploySpec, ta.ContextManager['DeployDriver']]):
|
@@ -11418,8 +11841,13 @@ class DeployDriver:
|
|
11418
11841
|
home: DeployHome,
|
11419
11842
|
time: DeployTime,
|
11420
11843
|
|
11844
|
+
deploys: DeployManager,
|
11421
11845
|
paths: DeployPathsManager,
|
11422
11846
|
apps: DeployAppManager,
|
11847
|
+
conf: DeployConfManager,
|
11848
|
+
systemd: DeploySystemdManager,
|
11849
|
+
|
11850
|
+
msh: ObjMarshalerManager,
|
11423
11851
|
) -> None:
|
11424
11852
|
super().__init__()
|
11425
11853
|
|
@@ -11427,32 +11855,180 @@ class DeployDriver:
|
|
11427
11855
|
self._home = home
|
11428
11856
|
self._time = time
|
11429
11857
|
|
11858
|
+
self._deploys = deploys
|
11430
11859
|
self._paths = paths
|
11431
11860
|
self._apps = apps
|
11861
|
+
self._conf = conf
|
11862
|
+
self._systemd = systemd
|
11863
|
+
|
11864
|
+
self._msh = msh
|
11865
|
+
|
11866
|
+
#
|
11867
|
+
|
11868
|
+
@property
|
11869
|
+
def tags(self) -> DeployTagMap:
|
11870
|
+
return DeployTagMap(
|
11871
|
+
self._time,
|
11872
|
+
self._spec.key(),
|
11873
|
+
)
|
11874
|
+
|
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))
|
11877
|
+
|
11878
|
+
@property
|
11879
|
+
def dir(self) -> str:
|
11880
|
+
return self.render_path(self._deploys.DEPLOY_DIR)
|
11881
|
+
|
11882
|
+
#
|
11432
11883
|
|
11433
11884
|
async def drive_deploy(self) -> None:
|
11885
|
+
spec_json = json_dumps_pretty(self._msh.marshal_obj(self._spec))
|
11886
|
+
|
11887
|
+
#
|
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
|
+
|
11434
11896
|
self._paths.validate_deploy_paths()
|
11435
11897
|
|
11436
11898
|
#
|
11437
11899
|
|
11438
|
-
|
11439
|
-
|
11440
|
-
|
11900
|
+
os.makedirs(self.dir)
|
11901
|
+
|
11902
|
+
#
|
11903
|
+
|
11904
|
+
spec_file = self.render_path(self._deploys.DEPLOY_SPEC_FILE)
|
11905
|
+
with open(spec_file, 'w') as f: # noqa
|
11906
|
+
f.write(spec_json)
|
11907
|
+
|
11908
|
+
#
|
11909
|
+
|
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
|
+
|
11915
|
+
if os.path.exists(deploying_link):
|
11916
|
+
os.unlink(deploying_link)
|
11917
|
+
relative_symlink(
|
11918
|
+
self.dir,
|
11919
|
+
deploying_link,
|
11920
|
+
target_is_directory=True,
|
11921
|
+
make_dirs=True,
|
11441
11922
|
)
|
11442
11923
|
|
11443
11924
|
#
|
11444
11925
|
|
11445
|
-
for
|
11446
|
-
|
11447
|
-
|
11448
|
-
|
11449
|
-
|
11926
|
+
for md in [
|
11927
|
+
self._deploys.APPS_DEPLOY_DIR,
|
11928
|
+
self._deploys.CONFS_DEPLOY_DIR,
|
11929
|
+
]:
|
11930
|
+
os.makedirs(self.render_path(md))
|
11931
|
+
|
11932
|
+
#
|
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,
|
11450
11945
|
)
|
11451
11946
|
|
11452
|
-
|
11947
|
+
for app in self._spec.apps:
|
11948
|
+
await self._drive_app_deploy(
|
11453
11949
|
app,
|
11454
|
-
|
11455
|
-
|
11950
|
+
)
|
11951
|
+
|
11952
|
+
#
|
11953
|
+
|
11954
|
+
os.replace(deploying_link, current_link)
|
11955
|
+
|
11956
|
+
#
|
11957
|
+
|
11958
|
+
await self._systemd.sync_systemd(
|
11959
|
+
self._spec.systemd,
|
11960
|
+
self._home,
|
11961
|
+
os.path.join(self.dir, 'conf', 'systemd'), # FIXME
|
11962
|
+
)
|
11963
|
+
|
11964
|
+
#
|
11965
|
+
|
11966
|
+
self._deploys.make_home_current_link(self._home)
|
11967
|
+
|
11968
|
+
#
|
11969
|
+
|
11970
|
+
async def _drive_app_deploy(self, app: DeployAppSpec) -> None:
|
11971
|
+
pa = await self._apps.prepare_app(
|
11972
|
+
app,
|
11973
|
+
self._home,
|
11974
|
+
self.tags,
|
11975
|
+
)
|
11976
|
+
|
11977
|
+
#
|
11978
|
+
|
11979
|
+
app_link = self.render_path(self._deploys.APP_DEPLOY_LINK, pa.tags)
|
11980
|
+
relative_symlink(
|
11981
|
+
pa.dir,
|
11982
|
+
app_link,
|
11983
|
+
target_is_directory=True,
|
11984
|
+
make_dirs=True,
|
11985
|
+
)
|
11986
|
+
|
11987
|
+
#
|
11988
|
+
|
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:
|
12027
|
+
await self._conf.link_app_conf(
|
12028
|
+
pa.spec.conf,
|
12029
|
+
pa.tags,
|
12030
|
+
check.non_empty_str(pa.conf_dir),
|
12031
|
+
deploy_conf_dir,
|
11456
12032
|
)
|
11457
12033
|
|
11458
12034
|
|
@@ -11558,13 +12134,10 @@ def bind_deploy(
|
|
11558
12134
|
|
11559
12135
|
lst.extend([
|
11560
12136
|
bind_deploy_manager(DeployAppManager),
|
11561
|
-
|
11562
12137
|
bind_deploy_manager(DeployGitManager),
|
11563
|
-
|
11564
12138
|
bind_deploy_manager(DeployManager),
|
11565
|
-
|
12139
|
+
bind_deploy_manager(DeploySystemdManager),
|
11566
12140
|
bind_deploy_manager(DeployTmpManager),
|
11567
|
-
|
11568
12141
|
bind_deploy_manager(DeployVenvManager),
|
11569
12142
|
])
|
11570
12143
|
|