ominfra 0.0.0.dev189__py3-none-any.whl → 0.0.0.dev190__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 +42 -90
- ominfra/manage/deploy/commands.py +1 -1
- ominfra/manage/deploy/conf/manager.py +20 -12
- ominfra/manage/deploy/conf/specs.py +9 -0
- ominfra/manage/deploy/deploy.py +207 -2
- 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 +12 -1
- ominfra/manage/deploy/systemd.py +110 -0
- ominfra/manage/deploy/venvs.py +0 -2
- ominfra/scripts/manage.py +489 -152
- {ominfra-0.0.0.dev189.dist-info → ominfra-0.0.0.dev190.dist-info}/METADATA +4 -3
- {ominfra-0.0.0.dev189.dist-info → ominfra-0.0.0.dev190.dist-info}/RECORD +19 -18
- ominfra/manage/deploy/driver.py +0 -62
- {ominfra-0.0.0.dev189.dist-info → ominfra-0.0.0.dev190.dist-info}/LICENSE +0 -0
- {ominfra-0.0.0.dev189.dist-info → ominfra-0.0.0.dev190.dist-info}/WHEEL +0 -0
- {ominfra-0.0.0.dev189.dist-info → ominfra-0.0.0.dev190.dist-info}/entry_points.txt +0 -0
- {ominfra-0.0.0.dev189.dist-info → ominfra-0.0.0.dev190.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
|
|
@@ -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
|
@@ -9171,6 +9279,10 @@ class DeployConfManager:
|
|
9171
9279
|
elif isinstance(ac, IniDeployAppConfContent):
|
9172
9280
|
return strip_with_newline(render_ini_config(ac.sections))
|
9173
9281
|
|
9282
|
+
elif isinstance(ac, NginxDeployAppConfContent):
|
9283
|
+
ni = NginxConfigItems.of(ac.items)
|
9284
|
+
return strip_with_newline(render_nginx_config_str(ni))
|
9285
|
+
|
9174
9286
|
else:
|
9175
9287
|
raise TypeError(ac)
|
9176
9288
|
|
@@ -9189,6 +9301,17 @@ class DeployConfManager:
|
|
9189
9301
|
with open(conf_file, 'w') as f: # noqa
|
9190
9302
|
f.write(body)
|
9191
9303
|
|
9304
|
+
async def write_app_conf(
|
9305
|
+
self,
|
9306
|
+
spec: DeployAppConfSpec,
|
9307
|
+
app_conf_dir: str,
|
9308
|
+
) -> None:
|
9309
|
+
for acf in spec.files or []:
|
9310
|
+
await self._write_app_conf_file(
|
9311
|
+
acf,
|
9312
|
+
app_conf_dir,
|
9313
|
+
)
|
9314
|
+
|
9192
9315
|
#
|
9193
9316
|
|
9194
9317
|
class _ComputedConfLink(ta.NamedTuple):
|
@@ -9298,23 +9421,13 @@ class DeployConfManager:
|
|
9298
9421
|
make_dirs=True,
|
9299
9422
|
)
|
9300
9423
|
|
9301
|
-
|
9302
|
-
|
9303
|
-
async def write_app_conf(
|
9424
|
+
async def link_app_conf(
|
9304
9425
|
self,
|
9305
9426
|
spec: DeployAppConfSpec,
|
9306
9427
|
tags: DeployTagMap,
|
9307
9428
|
app_conf_dir: str,
|
9308
9429
|
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
|
-
|
9430
|
+
):
|
9318
9431
|
for link in spec.links or []:
|
9319
9432
|
await self._make_app_conf_link(
|
9320
9433
|
link,
|
@@ -9442,11 +9555,22 @@ class DeployAppSpec(DeploySpecKeyed[DeployAppKey]):
|
|
9442
9555
|
##
|
9443
9556
|
|
9444
9557
|
|
9558
|
+
@dc.dataclass(frozen=True)
|
9559
|
+
class DeploySystemdSpec:
|
9560
|
+
# ~/.config/systemd/user/
|
9561
|
+
unit_dir: ta.Optional[str] = None
|
9562
|
+
|
9563
|
+
|
9564
|
+
##
|
9565
|
+
|
9566
|
+
|
9445
9567
|
@dc.dataclass(frozen=True)
|
9446
9568
|
class DeploySpec(DeploySpecKeyed[DeployKey]):
|
9447
9569
|
home: DeployHome
|
9448
9570
|
|
9449
|
-
apps: ta.Sequence[DeployAppSpec]
|
9571
|
+
apps: ta.Sequence[DeployAppSpec] = ()
|
9572
|
+
|
9573
|
+
systemd: ta.Optional[DeploySystemdSpec] = None
|
9450
9574
|
|
9451
9575
|
def __post_init__(self) -> None:
|
9452
9576
|
check.non_empty_str(self.home)
|
@@ -10848,6 +10972,109 @@ def bind_deploy_paths() -> InjectorBindings:
|
|
10848
10972
|
return inj.as_bindings(*lst)
|
10849
10973
|
|
10850
10974
|
|
10975
|
+
########################################
|
10976
|
+
# ../deploy/systemd.py
|
10977
|
+
"""
|
10978
|
+
~/.config/systemd/user/
|
10979
|
+
|
10980
|
+
verify - systemd-analyze
|
10981
|
+
|
10982
|
+
sudo loginctl enable-linger "$USER"
|
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
|
11003
|
+
"""
|
11004
|
+
|
11005
|
+
|
11006
|
+
class DeploySystemdManager:
|
11007
|
+
def __init__(
|
11008
|
+
self,
|
11009
|
+
*,
|
11010
|
+
atomics: DeployHomeAtomics,
|
11011
|
+
) -> None:
|
11012
|
+
super().__init__()
|
11013
|
+
|
11014
|
+
self._atomics = atomics
|
11015
|
+
|
11016
|
+
def _scan_link_dir(
|
11017
|
+
self,
|
11018
|
+
d: str,
|
11019
|
+
*,
|
11020
|
+
strict: bool = False,
|
11021
|
+
) -> ta.Dict[str, str]:
|
11022
|
+
o: ta.Dict[str, str] = {}
|
11023
|
+
for f in os.listdir(d):
|
11024
|
+
fp = os.path.join(d, f)
|
11025
|
+
if strict:
|
11026
|
+
check.state(os.path.islink(fp))
|
11027
|
+
o[f] = abs_real_path(fp)
|
11028
|
+
return o
|
11029
|
+
|
11030
|
+
async def sync_systemd(
|
11031
|
+
self,
|
11032
|
+
spec: ta.Optional[DeploySystemdSpec],
|
11033
|
+
home: DeployHome,
|
11034
|
+
conf_dir: str,
|
11035
|
+
) -> None:
|
11036
|
+
check.non_empty_str(home)
|
11037
|
+
|
11038
|
+
if not spec:
|
11039
|
+
return
|
11040
|
+
|
11041
|
+
if not (ud := spec.unit_dir):
|
11042
|
+
return
|
11043
|
+
|
11044
|
+
ud = abs_real_path(os.path.expanduser(ud))
|
11045
|
+
|
11046
|
+
os.makedirs(ud, exist_ok=True)
|
11047
|
+
|
11048
|
+
uld = {
|
11049
|
+
n: p
|
11050
|
+
for n, p in self._scan_link_dir(ud).items()
|
11051
|
+
if is_path_in_dir(home, p)
|
11052
|
+
}
|
11053
|
+
|
11054
|
+
if os.path.exists(conf_dir):
|
11055
|
+
cld = self._scan_link_dir(conf_dir, strict=True)
|
11056
|
+
else:
|
11057
|
+
cld = {}
|
11058
|
+
|
11059
|
+
for n in sorted(set(uld) | set(cld)):
|
11060
|
+
ul = uld.get(n) # noqa
|
11061
|
+
cl = cld.get(n)
|
11062
|
+
if cl is None:
|
11063
|
+
os.unlink(os.path.join(ud, n))
|
11064
|
+
else:
|
11065
|
+
with self._atomics(home).begin_atomic_path_swap( # noqa
|
11066
|
+
'file',
|
11067
|
+
os.path.join(ud, n),
|
11068
|
+
auto_commit=True,
|
11069
|
+
skip_root_dir_check=True,
|
11070
|
+
) as dst_swap:
|
11071
|
+
os.unlink(dst_swap.tmp_path)
|
11072
|
+
os.symlink(
|
11073
|
+
os.path.relpath(cl, os.path.dirname(dst_swap.dst_path)),
|
11074
|
+
dst_swap.tmp_path,
|
11075
|
+
)
|
11076
|
+
|
11077
|
+
|
10851
11078
|
########################################
|
10852
11079
|
# ../remote/inject.py
|
10853
11080
|
|
@@ -11202,7 +11429,6 @@ class DeployVenvManager:
|
|
11202
11429
|
async def setup_venv(
|
11203
11430
|
self,
|
11204
11431
|
spec: DeployVenvSpec,
|
11205
|
-
home: DeployHome,
|
11206
11432
|
git_dir: str,
|
11207
11433
|
venv_dir: str,
|
11208
11434
|
) -> None:
|
@@ -11245,39 +11471,31 @@ class DeployAppManager(DeployPathOwner):
|
|
11245
11471
|
def __init__(
|
11246
11472
|
self,
|
11247
11473
|
*,
|
11248
|
-
conf: DeployConfManager,
|
11249
11474
|
git: DeployGitManager,
|
11250
11475
|
venvs: DeployVenvManager,
|
11476
|
+
conf: DeployConfManager,
|
11477
|
+
|
11478
|
+
msh: ObjMarshalerManager,
|
11251
11479
|
) -> None:
|
11252
11480
|
super().__init__()
|
11253
11481
|
|
11254
|
-
self._conf = conf
|
11255
11482
|
self._git = git
|
11256
11483
|
self._venvs = venvs
|
11484
|
+
self._conf = conf
|
11257
11485
|
|
11258
|
-
|
11259
|
-
|
11260
|
-
_APP_DIR_STR = 'apps/@app/@time--@app-rev--@app-key/'
|
11261
|
-
_APP_DIR = DeployPath.parse(_APP_DIR_STR)
|
11486
|
+
self._msh = msh
|
11262
11487
|
|
11263
|
-
|
11264
|
-
_DEPLOY_DIR = DeployPath.parse(_DEPLOY_DIR_STR)
|
11488
|
+
#
|
11265
11489
|
|
11266
|
-
|
11267
|
-
_CONF_DEPLOY_DIR = DeployPath.parse(f'{_DEPLOY_DIR_STR}conf/@conf/')
|
11490
|
+
APP_DIR = DeployPath.parse('apps/@app/@time--@app-rev--@app-key/')
|
11268
11491
|
|
11269
11492
|
@cached_nullary
|
11270
11493
|
def get_owned_deploy_paths(self) -> ta.AbstractSet[DeployPath]:
|
11271
11494
|
return {
|
11272
|
-
self.
|
11273
|
-
|
11274
|
-
self._DEPLOY_DIR,
|
11275
|
-
|
11276
|
-
self._APP_DEPLOY_LINK,
|
11277
|
-
self._CONF_DEPLOY_DIR,
|
11495
|
+
self.APP_DIR,
|
11278
11496
|
|
11279
11497
|
*[
|
11280
|
-
DeployPath.parse(f'{self.
|
11498
|
+
DeployPath.parse(f'{self.APP_DIR}{sfx}/')
|
11281
11499
|
for sfx in [
|
11282
11500
|
'conf',
|
11283
11501
|
'git',
|
@@ -11288,122 +11506,148 @@ class DeployAppManager(DeployPathOwner):
|
|
11288
11506
|
|
11289
11507
|
#
|
11290
11508
|
|
11509
|
+
@dc.dataclass(frozen=True)
|
11510
|
+
class PreparedApp:
|
11511
|
+
app_dir: str
|
11512
|
+
|
11513
|
+
git_dir: ta.Optional[str] = None
|
11514
|
+
venv_dir: ta.Optional[str] = None
|
11515
|
+
conf_dir: ta.Optional[str] = None
|
11516
|
+
|
11291
11517
|
async def prepare_app(
|
11292
11518
|
self,
|
11293
11519
|
spec: DeployAppSpec,
|
11294
11520
|
home: DeployHome,
|
11295
11521
|
tags: DeployTagMap,
|
11296
|
-
) ->
|
11297
|
-
|
11298
|
-
|
11299
|
-
def build_path(pth: DeployPath) -> str:
|
11300
|
-
return os.path.join(home, pth.render(tags))
|
11301
|
-
|
11302
|
-
app_dir = build_path(self._APP_DIR)
|
11303
|
-
deploy_dir = build_path(self._DEPLOY_DIR)
|
11304
|
-
app_deploy_link = build_path(self._APP_DEPLOY_LINK)
|
11522
|
+
) -> PreparedApp:
|
11523
|
+
spec_json = json_dumps_pretty(self._msh.marshal_obj(spec))
|
11305
11524
|
|
11306
11525
|
#
|
11307
11526
|
|
11308
|
-
|
11527
|
+
check.non_empty_str(home)
|
11309
11528
|
|
11310
|
-
|
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
|
-
)
|
11529
|
+
app_dir = os.path.join(home, self.APP_DIR.render(tags))
|
11319
11530
|
|
11320
|
-
|
11321
|
-
|
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
|
-
)
|
11531
|
+
os.makedirs(app_dir, exist_ok=True)
|
11329
11532
|
|
11330
11533
|
#
|
11331
11534
|
|
11332
|
-
|
11333
|
-
|
11535
|
+
rkw: ta.Dict[str, ta.Any] = dict(
|
11536
|
+
app_dir=app_dir,
|
11537
|
+
)
|
11334
11538
|
|
11335
11539
|
#
|
11336
11540
|
|
11337
|
-
|
11338
|
-
|
11339
|
-
|
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
|
-
#
|
11350
|
-
# for dn in dns:
|
11351
|
-
# dp2 = os.path.join(dp, dn)
|
11352
|
-
# if os.path.islink(dp2):
|
11353
|
-
# mirror_link(dp2)
|
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
|
-
# )
|
11541
|
+
spec_file = os.path.join(app_dir, 'spec.json')
|
11542
|
+
with open(spec_file, 'w') as f: # noqa
|
11543
|
+
f.write(spec_json)
|
11368
11544
|
|
11369
11545
|
#
|
11370
11546
|
|
11371
|
-
|
11547
|
+
git_dir = os.path.join(app_dir, 'git')
|
11548
|
+
rkw.update(git_dir=git_dir)
|
11372
11549
|
await self._git.checkout(
|
11373
11550
|
spec.git,
|
11374
11551
|
home,
|
11375
|
-
|
11552
|
+
git_dir,
|
11376
11553
|
)
|
11377
11554
|
|
11378
11555
|
#
|
11379
11556
|
|
11380
11557
|
if spec.venv is not None:
|
11381
|
-
|
11558
|
+
venv_dir = os.path.join(app_dir, 'venv')
|
11559
|
+
rkw.update(venv_dir=venv_dir)
|
11382
11560
|
await self._venvs.setup_venv(
|
11383
11561
|
spec.venv,
|
11384
|
-
|
11385
|
-
|
11386
|
-
app_venv_dir,
|
11562
|
+
git_dir,
|
11563
|
+
venv_dir,
|
11387
11564
|
)
|
11388
11565
|
|
11389
11566
|
#
|
11390
11567
|
|
11391
11568
|
if spec.conf is not None:
|
11392
|
-
|
11569
|
+
conf_dir = os.path.join(app_dir, 'conf')
|
11570
|
+
rkw.update(conf_dir=conf_dir)
|
11393
11571
|
await self._conf.write_app_conf(
|
11394
11572
|
spec.conf,
|
11395
|
-
|
11396
|
-
app_conf_dir,
|
11397
|
-
deploy_conf_dir,
|
11573
|
+
conf_dir,
|
11398
11574
|
)
|
11399
11575
|
|
11400
11576
|
#
|
11401
11577
|
|
11402
|
-
|
11578
|
+
return DeployAppManager.PreparedApp(**rkw)
|
11403
11579
|
|
11404
11580
|
|
11405
11581
|
########################################
|
11406
|
-
# ../deploy/
|
11582
|
+
# ../deploy/deploy.py
|
11583
|
+
|
11584
|
+
|
11585
|
+
##
|
11586
|
+
|
11587
|
+
|
11588
|
+
DEPLOY_TAG_DATETIME_FMT = '%Y%m%dT%H%M%SZ'
|
11589
|
+
|
11590
|
+
|
11591
|
+
DeployManagerUtcClock = ta.NewType('DeployManagerUtcClock', Func0[datetime.datetime])
|
11592
|
+
|
11593
|
+
|
11594
|
+
class DeployManager(DeployPathOwner):
|
11595
|
+
def __init__(
|
11596
|
+
self,
|
11597
|
+
*,
|
11598
|
+
utc_clock: ta.Optional[DeployManagerUtcClock] = None,
|
11599
|
+
):
|
11600
|
+
super().__init__()
|
11601
|
+
|
11602
|
+
self._utc_clock = utc_clock
|
11603
|
+
|
11604
|
+
#
|
11605
|
+
|
11606
|
+
DEPLOYS_DIR = DeployPath.parse('deploys/')
|
11607
|
+
|
11608
|
+
CURRENT_DEPLOY_LINK = DeployPath.parse(f'{DEPLOYS_DIR}current')
|
11609
|
+
DEPLOYING_DEPLOY_LINK = DeployPath.parse(f'{DEPLOYS_DIR}deploying')
|
11610
|
+
|
11611
|
+
DEPLOY_DIR = DeployPath.parse(f'{DEPLOYS_DIR}@time--@deploy-key/')
|
11612
|
+
DEPLOY_SPEC_FILE = DeployPath.parse(f'{DEPLOY_DIR}spec.json')
|
11613
|
+
|
11614
|
+
APPS_DEPLOY_DIR = DeployPath.parse(f'{DEPLOY_DIR}apps/')
|
11615
|
+
APP_DEPLOY_LINK = DeployPath.parse(f'{APPS_DEPLOY_DIR}@app')
|
11616
|
+
|
11617
|
+
CONFS_DEPLOY_DIR = DeployPath.parse(f'{DEPLOY_DIR}conf/')
|
11618
|
+
CONF_DEPLOY_DIR = DeployPath.parse(f'{CONFS_DEPLOY_DIR}@conf/')
|
11619
|
+
|
11620
|
+
@cached_nullary
|
11621
|
+
def get_owned_deploy_paths(self) -> ta.AbstractSet[DeployPath]:
|
11622
|
+
return {
|
11623
|
+
self.DEPLOYS_DIR,
|
11624
|
+
|
11625
|
+
self.CURRENT_DEPLOY_LINK,
|
11626
|
+
self.DEPLOYING_DEPLOY_LINK,
|
11627
|
+
|
11628
|
+
self.DEPLOY_DIR,
|
11629
|
+
self.DEPLOY_SPEC_FILE,
|
11630
|
+
|
11631
|
+
self.APPS_DEPLOY_DIR,
|
11632
|
+
self.APP_DEPLOY_LINK,
|
11633
|
+
|
11634
|
+
self.CONFS_DEPLOY_DIR,
|
11635
|
+
self.CONF_DEPLOY_DIR,
|
11636
|
+
}
|
11637
|
+
|
11638
|
+
#
|
11639
|
+
|
11640
|
+
def _utc_now(self) -> datetime.datetime:
|
11641
|
+
if self._utc_clock is not None:
|
11642
|
+
return self._utc_clock() # noqa
|
11643
|
+
else:
|
11644
|
+
return datetime.datetime.now(tz=datetime.timezone.utc) # noqa
|
11645
|
+
|
11646
|
+
def make_deploy_time(self) -> DeployTime:
|
11647
|
+
return DeployTime(self._utc_now().strftime(DEPLOY_TAG_DATETIME_FMT))
|
11648
|
+
|
11649
|
+
|
11650
|
+
##
|
11407
11651
|
|
11408
11652
|
|
11409
11653
|
class DeployDriverFactory(Func1[DeploySpec, ta.ContextManager['DeployDriver']]):
|
@@ -11418,8 +11662,13 @@ class DeployDriver:
|
|
11418
11662
|
home: DeployHome,
|
11419
11663
|
time: DeployTime,
|
11420
11664
|
|
11665
|
+
deploys: DeployManager,
|
11421
11666
|
paths: DeployPathsManager,
|
11422
11667
|
apps: DeployAppManager,
|
11668
|
+
conf: DeployConfManager,
|
11669
|
+
systemd: DeploySystemdManager,
|
11670
|
+
|
11671
|
+
msh: ObjMarshalerManager,
|
11423
11672
|
) -> None:
|
11424
11673
|
super().__init__()
|
11425
11674
|
|
@@ -11427,32 +11676,123 @@ class DeployDriver:
|
|
11427
11676
|
self._home = home
|
11428
11677
|
self._time = time
|
11429
11678
|
|
11679
|
+
self._deploys = deploys
|
11430
11680
|
self._paths = paths
|
11431
11681
|
self._apps = apps
|
11682
|
+
self._conf = conf
|
11683
|
+
self._systemd = systemd
|
11684
|
+
|
11685
|
+
self._msh = msh
|
11686
|
+
|
11687
|
+
#
|
11688
|
+
|
11689
|
+
@property
|
11690
|
+
def deploy_tags(self) -> DeployTagMap:
|
11691
|
+
return DeployTagMap(
|
11692
|
+
self._time,
|
11693
|
+
self._spec.key(),
|
11694
|
+
)
|
11695
|
+
|
11696
|
+
def render_deploy_path(self, pth: DeployPath, tags: ta.Optional[DeployTagMap] = None) -> str:
|
11697
|
+
return os.path.join(self._home, pth.render(tags if tags is not None else self.deploy_tags))
|
11698
|
+
|
11699
|
+
@property
|
11700
|
+
def deploy_dir(self) -> str:
|
11701
|
+
return self.render_deploy_path(self._deploys.DEPLOY_DIR)
|
11702
|
+
|
11703
|
+
#
|
11432
11704
|
|
11433
11705
|
async def drive_deploy(self) -> None:
|
11706
|
+
spec_json = json_dumps_pretty(self._msh.marshal_obj(self._spec))
|
11707
|
+
|
11708
|
+
#
|
11709
|
+
|
11434
11710
|
self._paths.validate_deploy_paths()
|
11435
11711
|
|
11436
11712
|
#
|
11437
11713
|
|
11438
|
-
|
11439
|
-
|
11440
|
-
|
11714
|
+
os.makedirs(self.deploy_dir)
|
11715
|
+
|
11716
|
+
#
|
11717
|
+
|
11718
|
+
spec_file = self.render_deploy_path(self._deploys.DEPLOY_SPEC_FILE)
|
11719
|
+
with open(spec_file, 'w') as f: # noqa
|
11720
|
+
f.write(spec_json)
|
11721
|
+
|
11722
|
+
#
|
11723
|
+
|
11724
|
+
deploying_link = self.render_deploy_path(self._deploys.DEPLOYING_DEPLOY_LINK)
|
11725
|
+
if os.path.exists(deploying_link):
|
11726
|
+
os.unlink(deploying_link)
|
11727
|
+
relative_symlink(
|
11728
|
+
self.deploy_dir,
|
11729
|
+
deploying_link,
|
11730
|
+
target_is_directory=True,
|
11731
|
+
make_dirs=True,
|
11441
11732
|
)
|
11442
11733
|
|
11443
11734
|
#
|
11444
11735
|
|
11736
|
+
for md in [
|
11737
|
+
self._deploys.APPS_DEPLOY_DIR,
|
11738
|
+
self._deploys.CONFS_DEPLOY_DIR,
|
11739
|
+
]:
|
11740
|
+
os.makedirs(self.render_deploy_path(md))
|
11741
|
+
|
11742
|
+
#
|
11743
|
+
|
11445
11744
|
for app in self._spec.apps:
|
11446
|
-
|
11447
|
-
|
11448
|
-
|
11449
|
-
|
11450
|
-
|
11745
|
+
await self.drive_app_deploy(app)
|
11746
|
+
|
11747
|
+
#
|
11748
|
+
|
11749
|
+
current_link = self.render_deploy_path(self._deploys.CURRENT_DEPLOY_LINK)
|
11750
|
+
os.replace(deploying_link, current_link)
|
11751
|
+
|
11752
|
+
#
|
11753
|
+
|
11754
|
+
await self._systemd.sync_systemd(
|
11755
|
+
self._spec.systemd,
|
11756
|
+
self._home,
|
11757
|
+
os.path.join(self.deploy_dir, 'conf', 'systemd'), # FIXME
|
11758
|
+
)
|
11759
|
+
|
11760
|
+
#
|
11451
11761
|
|
11452
|
-
|
11453
|
-
|
11454
|
-
|
11762
|
+
async def drive_app_deploy(self, app: DeployAppSpec) -> None:
|
11763
|
+
app_tags = self.deploy_tags.add(
|
11764
|
+
app.app,
|
11765
|
+
app.key(),
|
11766
|
+
DeployAppRev(app.git.rev),
|
11767
|
+
)
|
11768
|
+
|
11769
|
+
#
|
11770
|
+
|
11771
|
+
da = await self._apps.prepare_app(
|
11772
|
+
app,
|
11773
|
+
self._home,
|
11774
|
+
app_tags,
|
11775
|
+
)
|
11776
|
+
|
11777
|
+
#
|
11778
|
+
|
11779
|
+
app_link = self.render_deploy_path(self._deploys.APP_DEPLOY_LINK, app_tags)
|
11780
|
+
relative_symlink(
|
11781
|
+
da.app_dir,
|
11782
|
+
app_link,
|
11783
|
+
target_is_directory=True,
|
11784
|
+
make_dirs=True,
|
11785
|
+
)
|
11786
|
+
|
11787
|
+
#
|
11788
|
+
|
11789
|
+
deploy_conf_dir = self.render_deploy_path(self._deploys.CONFS_DEPLOY_DIR)
|
11790
|
+
if app.conf is not None:
|
11791
|
+
await self._conf.link_app_conf(
|
11792
|
+
app.conf,
|
11455
11793
|
app_tags,
|
11794
|
+
check.non_empty_str(da.conf_dir),
|
11795
|
+
deploy_conf_dir,
|
11456
11796
|
)
|
11457
11797
|
|
11458
11798
|
|
@@ -11558,13 +11898,10 @@ def bind_deploy(
|
|
11558
11898
|
|
11559
11899
|
lst.extend([
|
11560
11900
|
bind_deploy_manager(DeployAppManager),
|
11561
|
-
|
11562
11901
|
bind_deploy_manager(DeployGitManager),
|
11563
|
-
|
11564
11902
|
bind_deploy_manager(DeployManager),
|
11565
|
-
|
11903
|
+
bind_deploy_manager(DeploySystemdManager),
|
11566
11904
|
bind_deploy_manager(DeployTmpManager),
|
11567
|
-
|
11568
11905
|
bind_deploy_manager(DeployVenvManager),
|
11569
11906
|
])
|
11570
11907
|
|