ominfra 0.0.0.dev188__py3-none-any.whl → 0.0.0.dev190__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 +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/journald2aws.py +11 -0
- ominfra/scripts/manage.py +489 -152
- ominfra/scripts/supervisor.py +11 -0
- {ominfra-0.0.0.dev188.dist-info → ominfra-0.0.0.dev190.dist-info}/METADATA +4 -3
- {ominfra-0.0.0.dev188.dist-info → ominfra-0.0.0.dev190.dist-info}/RECORD +21 -20
- ominfra/manage/deploy/driver.py +0 -62
- {ominfra-0.0.0.dev188.dist-info → ominfra-0.0.0.dev190.dist-info}/LICENSE +0 -0
- {ominfra-0.0.0.dev188.dist-info → ominfra-0.0.0.dev190.dist-info}/WHEEL +0 -0
- {ominfra-0.0.0.dev188.dist-info → ominfra-0.0.0.dev190.dist-info}/entry_points.txt +0 -0
- {ominfra-0.0.0.dev188.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
|
|