ominfra 0.0.0.dev7__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 +27 -0
- ominfra/__init__.py +0 -0
- ominfra/bootstrap/__init__.py +0 -0
- ominfra/bootstrap/bootstrap.py +8 -0
- ominfra/cmds.py +83 -0
- ominfra/deploy/__init__.py +0 -0
- ominfra/deploy/_executor.py +1036 -0
- ominfra/deploy/configs.py +19 -0
- ominfra/deploy/executor/__init__.py +1 -0
- ominfra/deploy/executor/base.py +115 -0
- ominfra/deploy/executor/concerns/__init__.py +0 -0
- ominfra/deploy/executor/concerns/dirs.py +28 -0
- ominfra/deploy/executor/concerns/nginx.py +47 -0
- ominfra/deploy/executor/concerns/repo.py +17 -0
- ominfra/deploy/executor/concerns/supervisor.py +46 -0
- ominfra/deploy/executor/concerns/systemd.py +88 -0
- ominfra/deploy/executor/concerns/user.py +25 -0
- ominfra/deploy/executor/concerns/venv.py +22 -0
- ominfra/deploy/executor/main.py +119 -0
- ominfra/deploy/poly/__init__.py +1 -0
- ominfra/deploy/poly/_main.py +725 -0
- ominfra/deploy/poly/base.py +179 -0
- ominfra/deploy/poly/configs.py +38 -0
- ominfra/deploy/poly/deploy.py +25 -0
- ominfra/deploy/poly/main.py +18 -0
- ominfra/deploy/poly/nginx.py +60 -0
- ominfra/deploy/poly/repo.py +41 -0
- ominfra/deploy/poly/runtime.py +39 -0
- ominfra/deploy/poly/site.py +11 -0
- ominfra/deploy/poly/supervisor.py +64 -0
- ominfra/deploy/poly/venv.py +52 -0
- ominfra/deploy/remote.py +91 -0
- ominfra/pyremote/__init__.py +0 -0
- ominfra/pyremote/_runcommands.py +824 -0
- ominfra/pyremote/bootstrap.py +149 -0
- ominfra/pyremote/runcommands.py +56 -0
- ominfra/ssh.py +191 -0
- ominfra/tools/__init__.py +0 -0
- ominfra/tools/listresources.py +256 -0
- ominfra-0.0.0.dev7.dist-info/LICENSE +21 -0
- ominfra-0.0.0.dev7.dist-info/METADATA +19 -0
- ominfra-0.0.0.dev7.dist-info/RECORD +44 -0
- ominfra-0.0.0.dev7.dist-info/WHEEL +5 -0
- ominfra-0.0.0.dev7.dist-info/top_level.txt +1 -0
@@ -0,0 +1,19 @@
|
|
1
|
+
import dataclasses as dc
|
2
|
+
|
3
|
+
|
4
|
+
@dc.dataclass(frozen=True)
|
5
|
+
class DeployConfig:
|
6
|
+
python_bin: str
|
7
|
+
app_name: str
|
8
|
+
repo_url: str
|
9
|
+
revision: str
|
10
|
+
requirements_txt: str
|
11
|
+
entrypoint: str
|
12
|
+
|
13
|
+
|
14
|
+
@dc.dataclass(frozen=True)
|
15
|
+
class HostConfig:
|
16
|
+
username: str = 'deploy'
|
17
|
+
|
18
|
+
global_supervisor_conf_file_path: str = '/etc/supervisor/conf.d/supervisord.conf'
|
19
|
+
global_nginx_conf_file_path: str = '/etc/nginx/sites-enabled/deploy.conf'
|
@@ -0,0 +1 @@
|
|
1
|
+
# @omlish-lite
|
@@ -0,0 +1,115 @@
|
|
1
|
+
# ruff: noqa: UP006
|
2
|
+
import abc
|
3
|
+
import dataclasses as dc
|
4
|
+
import enum
|
5
|
+
import os.path
|
6
|
+
import shlex
|
7
|
+
import typing as ta
|
8
|
+
|
9
|
+
from omlish.lite.cached import cached_nullary
|
10
|
+
from omlish.lite.logs import log
|
11
|
+
from omlish.lite.subprocesses import subprocess_check_call
|
12
|
+
|
13
|
+
from ..configs import DeployConfig
|
14
|
+
from ..configs import HostConfig
|
15
|
+
|
16
|
+
|
17
|
+
##
|
18
|
+
|
19
|
+
|
20
|
+
class Phase(enum.Enum):
|
21
|
+
HOST = enum.auto()
|
22
|
+
ENV = enum.auto()
|
23
|
+
BACKEND = enum.auto()
|
24
|
+
FRONTEND = enum.auto()
|
25
|
+
START_BACKEND = enum.auto()
|
26
|
+
START_FRONTEND = enum.auto()
|
27
|
+
|
28
|
+
|
29
|
+
def run_in_phase(*ps: Phase):
|
30
|
+
def inner(fn):
|
31
|
+
fn.__deployment_phases__ = ps
|
32
|
+
return fn
|
33
|
+
return inner
|
34
|
+
|
35
|
+
|
36
|
+
class Concern(abc.ABC):
|
37
|
+
def __init__(self, d: 'Deployment') -> None:
|
38
|
+
super().__init__()
|
39
|
+
self._d = d
|
40
|
+
|
41
|
+
_phase_fns: ta.ClassVar[ta.Mapping[Phase, ta.Sequence[ta.Callable]]]
|
42
|
+
|
43
|
+
def __init_subclass__(cls, **kwargs):
|
44
|
+
super().__init_subclass__(**kwargs)
|
45
|
+
dct: ta.Dict[Phase, ta.List[ta.Callable]] = {}
|
46
|
+
for fn, ps in [
|
47
|
+
(v, ps)
|
48
|
+
for a in dir(cls)
|
49
|
+
if not (a.startswith('__') and a.endswith('__'))
|
50
|
+
for v in [getattr(cls, a, None)]
|
51
|
+
for ps in [getattr(v, '__deployment_phases__', None)]
|
52
|
+
if ps
|
53
|
+
]:
|
54
|
+
dct.update({p: [*dct.get(p, []), fn] for p in ps})
|
55
|
+
cls._phase_fns = dct
|
56
|
+
|
57
|
+
@dc.dataclass(frozen=True)
|
58
|
+
class Output(abc.ABC):
|
59
|
+
path: str
|
60
|
+
is_file: bool
|
61
|
+
|
62
|
+
def outputs(self) -> ta.Sequence[Output]:
|
63
|
+
return ()
|
64
|
+
|
65
|
+
def run_phase(self, p: Phase) -> None:
|
66
|
+
for fn in self._phase_fns.get(p, ()):
|
67
|
+
fn.__get__(self, type(self))()
|
68
|
+
|
69
|
+
|
70
|
+
##
|
71
|
+
|
72
|
+
|
73
|
+
class Deployment:
|
74
|
+
|
75
|
+
def __init__(
|
76
|
+
self,
|
77
|
+
cfg: DeployConfig,
|
78
|
+
concern_cls_list: ta.List[ta.Type[Concern]],
|
79
|
+
host_cfg: HostConfig = HostConfig(),
|
80
|
+
) -> None:
|
81
|
+
super().__init__()
|
82
|
+
self._cfg = cfg
|
83
|
+
self._host_cfg = host_cfg
|
84
|
+
|
85
|
+
self._concerns: ta.List[Concern] = [cls(self) for cls in concern_cls_list]
|
86
|
+
|
87
|
+
@property
|
88
|
+
def cfg(self) -> DeployConfig:
|
89
|
+
return self._cfg
|
90
|
+
|
91
|
+
@property
|
92
|
+
def host_cfg(self) -> HostConfig:
|
93
|
+
return self._host_cfg
|
94
|
+
|
95
|
+
def sh(self, *ss: str) -> None:
|
96
|
+
s = ' && '.join(ss)
|
97
|
+
log.info('Executing: %s', s)
|
98
|
+
subprocess_check_call(s, shell=True)
|
99
|
+
|
100
|
+
def ush(self, *ss: str) -> None:
|
101
|
+
s = ' && '.join(ss)
|
102
|
+
self.sh(f'su - {self._host_cfg.username} -c {shlex.quote(s)}')
|
103
|
+
|
104
|
+
@cached_nullary
|
105
|
+
def home_dir(self) -> str:
|
106
|
+
return os.path.expanduser(f'~{self._host_cfg.username}')
|
107
|
+
|
108
|
+
@cached_nullary
|
109
|
+
def deploy(self) -> None:
|
110
|
+
for p in Phase:
|
111
|
+
log.info('Phase %s', p.name)
|
112
|
+
for c in self._concerns:
|
113
|
+
c.run_phase(p)
|
114
|
+
|
115
|
+
log.info('Shitty deploy complete!')
|
File without changes
|
@@ -0,0 +1,28 @@
|
|
1
|
+
import os.path
|
2
|
+
import pwd
|
3
|
+
|
4
|
+
from omlish.lite.logs import log
|
5
|
+
|
6
|
+
from ..base import Concern
|
7
|
+
from ..base import Phase
|
8
|
+
from ..base import run_in_phase
|
9
|
+
|
10
|
+
|
11
|
+
class DirsConcern(Concern):
|
12
|
+
@run_in_phase(Phase.HOST)
|
13
|
+
def create_dirs(self) -> None:
|
14
|
+
pwn = pwd.getpwnam(self._d.host_cfg.username)
|
15
|
+
|
16
|
+
for dn in [
|
17
|
+
'app',
|
18
|
+
'conf',
|
19
|
+
'conf/env',
|
20
|
+
'conf/nginx',
|
21
|
+
'conf/supervisor',
|
22
|
+
'venv',
|
23
|
+
]:
|
24
|
+
fp = os.path.join(self._d.home_dir(), dn)
|
25
|
+
if not os.path.exists(fp):
|
26
|
+
log.info('Creating directory: %s', fp)
|
27
|
+
os.mkdir(fp)
|
28
|
+
os.chown(fp, pwn.pw_uid, pwn.pw_gid)
|
@@ -0,0 +1,47 @@
|
|
1
|
+
"""
|
2
|
+
TODO:
|
3
|
+
- https://stackoverflow.com/questions/3011067/restart-nginx-without-sudo
|
4
|
+
"""
|
5
|
+
import os.path
|
6
|
+
import textwrap
|
7
|
+
|
8
|
+
from omlish.lite.logs import log
|
9
|
+
|
10
|
+
from ..base import Concern
|
11
|
+
from ..base import Phase
|
12
|
+
from ..base import run_in_phase
|
13
|
+
|
14
|
+
|
15
|
+
class GlobalNginxConcern(Concern):
|
16
|
+
@run_in_phase(Phase.HOST)
|
17
|
+
def create_global_nginx_conf(self) -> None:
|
18
|
+
nginx_conf_dir = os.path.join(self._d.home_dir(), 'conf/nginx')
|
19
|
+
if not os.path.isfile(self._d.host_cfg.global_nginx_conf_file_path):
|
20
|
+
log.info('Writing global nginx conf at %s', self._d.host_cfg.global_nginx_conf_file_path)
|
21
|
+
with open(self._d.host_cfg.global_nginx_conf_file_path, 'w') as f:
|
22
|
+
f.write(f'include {nginx_conf_dir}/*.conf;\n')
|
23
|
+
|
24
|
+
|
25
|
+
class NginxConcern(Concern):
|
26
|
+
@run_in_phase(Phase.FRONTEND)
|
27
|
+
def create_nginx_conf(self) -> None:
|
28
|
+
nginx_conf = textwrap.dedent(f"""
|
29
|
+
server {{
|
30
|
+
listen 80;
|
31
|
+
location / {{
|
32
|
+
proxy_pass http://127.0.0.1:8000/;
|
33
|
+
}}
|
34
|
+
}}
|
35
|
+
""")
|
36
|
+
nginx_conf_file = os.path.join(self._d.home_dir(), f'conf/nginx/{self._d.cfg.app_name}.conf')
|
37
|
+
log.info('Writing nginx conf to %s', nginx_conf_file)
|
38
|
+
with open(nginx_conf_file, 'w') as f:
|
39
|
+
f.write(nginx_conf)
|
40
|
+
|
41
|
+
@run_in_phase(Phase.START_FRONTEND)
|
42
|
+
def poke_nginx(self) -> None:
|
43
|
+
log.info('Starting nginx')
|
44
|
+
self._d.sh('service nginx start')
|
45
|
+
|
46
|
+
log.info('Poking nginx')
|
47
|
+
self._d.sh('nginx -s reload')
|
@@ -0,0 +1,17 @@
|
|
1
|
+
from ..base import Concern
|
2
|
+
from ..base import Phase
|
3
|
+
from ..base import run_in_phase
|
4
|
+
|
5
|
+
|
6
|
+
class RepoConcern(Concern):
|
7
|
+
@run_in_phase(Phase.ENV)
|
8
|
+
def clone_repo(self) -> None:
|
9
|
+
clone_submodules = False
|
10
|
+
self._d.ush(
|
11
|
+
'cd ~/app',
|
12
|
+
f'git clone --depth 1 {self._d.cfg.repo_url} {self._d.cfg.app_name}',
|
13
|
+
*([
|
14
|
+
f'cd {self._d.cfg.app_name}',
|
15
|
+
'git submodule update --init',
|
16
|
+
] if clone_submodules else []),
|
17
|
+
)
|
@@ -0,0 +1,46 @@
|
|
1
|
+
import os.path
|
2
|
+
import textwrap
|
3
|
+
|
4
|
+
from omlish.lite.logs import log
|
5
|
+
|
6
|
+
from ..base import Concern
|
7
|
+
from ..base import Phase
|
8
|
+
from ..base import run_in_phase
|
9
|
+
|
10
|
+
|
11
|
+
class GlobalSupervisorConcern(Concern):
|
12
|
+
@run_in_phase(Phase.HOST)
|
13
|
+
def create_global_supervisor_conf(self) -> None:
|
14
|
+
sup_conf_dir = os.path.join(self._d.home_dir(), 'conf/supervisor')
|
15
|
+
with open(self._d.host_cfg.global_supervisor_conf_file_path) as f:
|
16
|
+
glo_sup_conf = f.read()
|
17
|
+
if sup_conf_dir not in glo_sup_conf:
|
18
|
+
log.info('Updating global supervisor conf at %s', self._d.host_cfg.global_supervisor_conf_file_path) # noqa
|
19
|
+
glo_sup_conf += textwrap.dedent(f"""
|
20
|
+
[include]
|
21
|
+
files = {self._d.home_dir()}/conf/supervisor/*.conf
|
22
|
+
""")
|
23
|
+
with open(self._d.host_cfg.global_supervisor_conf_file_path, 'w') as f:
|
24
|
+
f.write(glo_sup_conf)
|
25
|
+
|
26
|
+
|
27
|
+
class SupervisorConcern(Concern):
|
28
|
+
@run_in_phase(Phase.BACKEND)
|
29
|
+
def create_supervisor_conf(self) -> None:
|
30
|
+
sup_conf = textwrap.dedent(f"""
|
31
|
+
[program:{self._d.cfg.app_name}]
|
32
|
+
command={self._d.home_dir()}/venv/{self._d.cfg.app_name}/bin/python -m {self._d.cfg.entrypoint}
|
33
|
+
directory={self._d.home_dir()}/app/{self._d.cfg.app_name}
|
34
|
+
user={self._d.host_cfg.username}
|
35
|
+
autostart=true
|
36
|
+
autorestart=true
|
37
|
+
""")
|
38
|
+
sup_conf_file = os.path.join(self._d.home_dir(), f'conf/supervisor/{self._d.cfg.app_name}.conf')
|
39
|
+
log.info('Writing supervisor conf to %s', sup_conf_file)
|
40
|
+
with open(sup_conf_file, 'w') as f:
|
41
|
+
f.write(sup_conf)
|
42
|
+
|
43
|
+
@run_in_phase(Phase.START_BACKEND)
|
44
|
+
def poke_supervisor(self) -> None:
|
45
|
+
log.info('Poking supervisor')
|
46
|
+
self._d.sh('kill -HUP 1')
|
@@ -0,0 +1,88 @@
|
|
1
|
+
"""
|
2
|
+
# https://serverfault.com/questions/617823/how-to-set-systemd-service-dependencies
|
3
|
+
PIDFile=/run/nginx.pid
|
4
|
+
ExecStartPre=/usr/sbin/nginx -t
|
5
|
+
ExecStart=/usr/sbin/nginx
|
6
|
+
ExecReload=/bin/kill -s HUP $MAINPID
|
7
|
+
ExecStop=/bin/kill -s QUIT $MAINPID
|
8
|
+
PrivateTmp=true
|
9
|
+
|
10
|
+
# https://gist.github.com/clemensg/7dd024169efe8ce6e7fa4a0b3caa3780
|
11
|
+
Type=forking
|
12
|
+
PIDFile=/var/run/nginx.pid
|
13
|
+
ExecStartPre=/usr/sbin/nginx -t
|
14
|
+
ExecStart=/usr/sbin/nginx
|
15
|
+
ExecReload=/usr/bin/kill -s HUP $MAINPID
|
16
|
+
ExecStop=/usr/bin/kill -s QUIT $MAINPID
|
17
|
+
# Hardening
|
18
|
+
InaccessiblePaths=/etc/gnupg /etc/shadow /etc/ssh
|
19
|
+
ProtectSystem=full
|
20
|
+
ProtectKernelTunables=yes
|
21
|
+
ProtectControlGroups=yes
|
22
|
+
SystemCallFilter=~@clock @cpu-emulation @debug @keyring @module @mount @obsolete @raw-io
|
23
|
+
MemoryDenyWriteExecute=yes
|
24
|
+
RestrictAddressFamilies=AF_INET AF_INET6 AF_UNIX
|
25
|
+
RestrictRealtime=yes
|
26
|
+
"""
|
27
|
+
import os.path
|
28
|
+
import textwrap
|
29
|
+
|
30
|
+
from omlish.lite.logs import log
|
31
|
+
|
32
|
+
from ..base import Concern
|
33
|
+
from ..base import Phase
|
34
|
+
from ..base import run_in_phase
|
35
|
+
|
36
|
+
|
37
|
+
class GlobalSystemdConcern(Concern):
|
38
|
+
@run_in_phase(Phase.HOST)
|
39
|
+
def enable_user_linger(self) -> None:
|
40
|
+
log.info('Enabling user linger')
|
41
|
+
self._d.sh(f'loginctl enable-linger {self._d.host_cfg.username}')
|
42
|
+
|
43
|
+
|
44
|
+
class SystemdConcern(Concern):
|
45
|
+
service_name: str
|
46
|
+
|
47
|
+
@run_in_phase(Phase.HOST)
|
48
|
+
def create_systemd_path(self) -> None:
|
49
|
+
sd_svc_dir = os.path.join(self._d.home_dir(), '.config/systemd/user')
|
50
|
+
if not os.path.exists(sd_svc_dir):
|
51
|
+
log.info('Creating directory: %s', sd_svc_dir)
|
52
|
+
os.makedirs(sd_svc_dir)
|
53
|
+
|
54
|
+
@run_in_phase(Phase.BACKEND)
|
55
|
+
def create_systemd_service(self) -> None:
|
56
|
+
sd_svc = textwrap.dedent(f"""
|
57
|
+
[Unit]
|
58
|
+
Description={self.service_name}
|
59
|
+
After= \
|
60
|
+
syslog.target \
|
61
|
+
network.target \
|
62
|
+
remote-fs.target \
|
63
|
+
nss-lookup.target \
|
64
|
+
network-online.target
|
65
|
+
|
66
|
+
[Service]
|
67
|
+
Type=simple
|
68
|
+
StandardOutput=journal
|
69
|
+
ExecStart={self._d.home_dir()}/venv/{self._d.cfg.app_name}/bin/python -m {self._d.cfg.entrypoint}
|
70
|
+
WorkingDirectory={self._d.home_dir()}/app/{self._d.cfg.app_name}
|
71
|
+
|
72
|
+
Restart=always
|
73
|
+
RestartSec=3
|
74
|
+
|
75
|
+
[Install]
|
76
|
+
WantedBy=multi-user.target
|
77
|
+
""")
|
78
|
+
sd_svc_file = os.path.join(self._d.home_dir(), f'.config/systemd/user/{self.service_name}.service')
|
79
|
+
log.info('Writing systemd service to %s', sd_svc_file)
|
80
|
+
with open(sd_svc_file, 'w') as f:
|
81
|
+
f.write(sd_svc)
|
82
|
+
|
83
|
+
@run_in_phase(Phase.START_BACKEND)
|
84
|
+
def poke_systemd(self) -> None:
|
85
|
+
log.info('Poking systemd')
|
86
|
+
self._d.sh('systemctl --user daemon-reload')
|
87
|
+
self._d.sh(f'systemctl --user enable {self.service_name}')
|
88
|
+
self._d.sh(f'systemctl --user restart {self.service_name}')
|
@@ -0,0 +1,25 @@
|
|
1
|
+
import pwd
|
2
|
+
|
3
|
+
from omlish.lite.logs import log
|
4
|
+
|
5
|
+
from ..base import Concern
|
6
|
+
from ..base import Phase
|
7
|
+
from ..base import run_in_phase
|
8
|
+
|
9
|
+
|
10
|
+
class UserConcern(Concern):
|
11
|
+
@run_in_phase(Phase.HOST)
|
12
|
+
def create_user(self) -> None:
|
13
|
+
try:
|
14
|
+
pwd.getpwnam(self._d.host_cfg.username)
|
15
|
+
except KeyError:
|
16
|
+
log.info('Creating user %s', self._d.host_cfg.username)
|
17
|
+
self._d.sh(' '.join([
|
18
|
+
'adduser',
|
19
|
+
'--system',
|
20
|
+
'--disabled-password',
|
21
|
+
'--group',
|
22
|
+
'--shell /bin/bash',
|
23
|
+
self._d.host_cfg.username,
|
24
|
+
]))
|
25
|
+
pwd.getpwnam(self._d.host_cfg.username)
|
@@ -0,0 +1,22 @@
|
|
1
|
+
"""
|
2
|
+
TODO:
|
3
|
+
- use LinuxInterpResolver lol
|
4
|
+
"""
|
5
|
+
from ..base import Concern
|
6
|
+
from ..base import Phase
|
7
|
+
from ..base import run_in_phase
|
8
|
+
|
9
|
+
|
10
|
+
class VenvConcern(Concern):
|
11
|
+
@run_in_phase(Phase.ENV)
|
12
|
+
def setup_venv(self) -> None:
|
13
|
+
self._d.ush(
|
14
|
+
'cd ~/venv',
|
15
|
+
f'{self._d.cfg.python_bin} -mvenv {self._d.cfg.app_name}',
|
16
|
+
|
17
|
+
# https://stackoverflow.com/questions/77364550/attributeerror-module-pkgutil-has-no-attribute-impimporter-did-you-mean
|
18
|
+
f'{self._d.cfg.app_name}/bin/python -m ensurepip',
|
19
|
+
f'{self._d.cfg.app_name}/bin/python -mpip install --upgrade setuptools pip',
|
20
|
+
|
21
|
+
f'{self._d.cfg.app_name}/bin/python -mpip install -r ~deploy/app/{self._d.cfg.app_name}/{self._d.cfg.requirements_txt}', # noqa
|
22
|
+
)
|
@@ -0,0 +1,119 @@
|
|
1
|
+
#!/usr/bin/env python3
|
2
|
+
# @omdev-amalg ../_executor.py
|
3
|
+
r"""
|
4
|
+
TODO:
|
5
|
+
- flock
|
6
|
+
- interp.py
|
7
|
+
- systemd
|
8
|
+
|
9
|
+
deployment matrix
|
10
|
+
- os: ubuntu / amzn / generic
|
11
|
+
- arch: amd64 / arm64
|
12
|
+
- host: bare / docker
|
13
|
+
- init: supervisor-provided / supervisor-must-configure / systemd (/ self?)
|
14
|
+
- interp: system / pyenv / interp.py
|
15
|
+
- venv: none / yes
|
16
|
+
- nginx: no / provided / must-configure
|
17
|
+
|
18
|
+
==
|
19
|
+
|
20
|
+
~deploy
|
21
|
+
deploy.pid (flock)
|
22
|
+
/app
|
23
|
+
/<appspec> - shallow clone
|
24
|
+
/conf
|
25
|
+
/env
|
26
|
+
<appspec>.env
|
27
|
+
/nginx
|
28
|
+
<appspec>.conf
|
29
|
+
/supervisor
|
30
|
+
<appspec>.conf
|
31
|
+
/venv
|
32
|
+
/<appspec>
|
33
|
+
|
34
|
+
?
|
35
|
+
/logs
|
36
|
+
/wrmsr--omlish--<spec>
|
37
|
+
|
38
|
+
spec = <name>--<rev>--<when>
|
39
|
+
|
40
|
+
https://docs.docker.com/config/containers/multi-service_container/#use-a-process-manager
|
41
|
+
https://serverfault.com/questions/211525/supervisor-not-loading-new-configuration-files
|
42
|
+
""" # noqa
|
43
|
+
# ruff: noqa: UP007
|
44
|
+
import argparse
|
45
|
+
import json
|
46
|
+
import sys
|
47
|
+
import typing as ta
|
48
|
+
|
49
|
+
from omlish.lite.logs import configure_standard_logging
|
50
|
+
from omlish.lite.marshal import unmarshal_obj
|
51
|
+
from omlish.lite.runtime import check_runtime_version
|
52
|
+
|
53
|
+
from ..configs import DeployConfig
|
54
|
+
from .base import Deployment
|
55
|
+
from .concerns.dirs import DirsConcern
|
56
|
+
from .concerns.nginx import GlobalNginxConcern
|
57
|
+
from .concerns.nginx import NginxConcern
|
58
|
+
from .concerns.repo import RepoConcern
|
59
|
+
from .concerns.supervisor import GlobalSupervisorConcern
|
60
|
+
from .concerns.supervisor import SupervisorConcern
|
61
|
+
from .concerns.user import UserConcern
|
62
|
+
from .concerns.venv import VenvConcern
|
63
|
+
|
64
|
+
|
65
|
+
##
|
66
|
+
|
67
|
+
|
68
|
+
def _deploy_cmd(args) -> None:
|
69
|
+
dct = json.loads(args.cfg)
|
70
|
+
cfg: DeployConfig = unmarshal_obj(dct, DeployConfig)
|
71
|
+
dp = Deployment(
|
72
|
+
cfg,
|
73
|
+
[
|
74
|
+
UserConcern,
|
75
|
+
DirsConcern,
|
76
|
+
GlobalNginxConcern,
|
77
|
+
GlobalSupervisorConcern,
|
78
|
+
RepoConcern,
|
79
|
+
VenvConcern,
|
80
|
+
SupervisorConcern,
|
81
|
+
NginxConcern,
|
82
|
+
],
|
83
|
+
)
|
84
|
+
dp.deploy()
|
85
|
+
|
86
|
+
|
87
|
+
##
|
88
|
+
|
89
|
+
|
90
|
+
def _build_parser() -> argparse.ArgumentParser:
|
91
|
+
parser = argparse.ArgumentParser()
|
92
|
+
|
93
|
+
subparsers = parser.add_subparsers()
|
94
|
+
|
95
|
+
parser_resolve = subparsers.add_parser('deploy')
|
96
|
+
parser_resolve.add_argument('cfg')
|
97
|
+
parser_resolve.set_defaults(func=_deploy_cmd)
|
98
|
+
|
99
|
+
return parser
|
100
|
+
|
101
|
+
|
102
|
+
def _main(argv: ta.Optional[ta.Sequence[str]] = None) -> None:
|
103
|
+
check_runtime_version()
|
104
|
+
|
105
|
+
if getattr(sys, 'platform') != 'linux': # noqa
|
106
|
+
raise OSError('must run on linux')
|
107
|
+
|
108
|
+
configure_standard_logging()
|
109
|
+
|
110
|
+
parser = _build_parser()
|
111
|
+
args = parser.parse_args(argv)
|
112
|
+
if not getattr(args, 'func', None):
|
113
|
+
parser.print_help()
|
114
|
+
else:
|
115
|
+
args.func(args)
|
116
|
+
|
117
|
+
|
118
|
+
if __name__ == '__main__':
|
119
|
+
_main()
|
@@ -0,0 +1 @@
|
|
1
|
+
# @omlish-lite
|