ominfra 0.0.0.dev189__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/__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
|
|