ominfra 0.0.0.dev138__py3-none-any.whl → 0.0.0.dev139__py3-none-any.whl

Sign up to get free protection for your applications and to get access to all the features.
Files changed (41) hide show
  1. ominfra/manage/__init__.py +13 -0
  2. ominfra/manage/{new/main.py → main.py} +6 -6
  3. ominfra/{manage/new/_manage.py → scripts/manage.py} +13 -13
  4. {ominfra-0.0.0.dev138.dist-info → ominfra-0.0.0.dev139.dist-info}/METADATA +3 -3
  5. {ominfra-0.0.0.dev138.dist-info → ominfra-0.0.0.dev139.dist-info}/RECORD +12 -41
  6. ominfra/manage/deploy/_executor.py +0 -1415
  7. ominfra/manage/deploy/configs.py +0 -19
  8. ominfra/manage/deploy/executor/__init__.py +0 -1
  9. ominfra/manage/deploy/executor/base.py +0 -115
  10. ominfra/manage/deploy/executor/concerns/__init__.py +0 -0
  11. ominfra/manage/deploy/executor/concerns/dirs.py +0 -28
  12. ominfra/manage/deploy/executor/concerns/nginx.py +0 -47
  13. ominfra/manage/deploy/executor/concerns/repo.py +0 -17
  14. ominfra/manage/deploy/executor/concerns/supervisor.py +0 -46
  15. ominfra/manage/deploy/executor/concerns/systemd.py +0 -88
  16. ominfra/manage/deploy/executor/concerns/user.py +0 -25
  17. ominfra/manage/deploy/executor/concerns/venv.py +0 -22
  18. ominfra/manage/deploy/executor/main.py +0 -119
  19. ominfra/manage/deploy/poly/__init__.py +0 -1
  20. ominfra/manage/deploy/poly/_main.py +0 -975
  21. ominfra/manage/deploy/poly/base.py +0 -178
  22. ominfra/manage/deploy/poly/configs.py +0 -38
  23. ominfra/manage/deploy/poly/deploy.py +0 -25
  24. ominfra/manage/deploy/poly/main.py +0 -18
  25. ominfra/manage/deploy/poly/nginx.py +0 -60
  26. ominfra/manage/deploy/poly/repo.py +0 -41
  27. ominfra/manage/deploy/poly/runtime.py +0 -39
  28. ominfra/manage/deploy/poly/site.py +0 -11
  29. ominfra/manage/deploy/poly/supervisor.py +0 -64
  30. ominfra/manage/deploy/poly/venv.py +0 -52
  31. ominfra/manage/deploy/remote.py +0 -91
  32. ominfra/manage/manage.py +0 -12
  33. ominfra/manage/new/__init__.py +0 -1
  34. ominfra/manage/new/commands/__init__.py +0 -0
  35. /ominfra/manage/{deploy → commands}/__init__.py +0 -0
  36. /ominfra/manage/{new/commands → commands}/base.py +0 -0
  37. /ominfra/manage/{new/commands → commands}/subprocess.py +0 -0
  38. {ominfra-0.0.0.dev138.dist-info → ominfra-0.0.0.dev139.dist-info}/LICENSE +0 -0
  39. {ominfra-0.0.0.dev138.dist-info → ominfra-0.0.0.dev139.dist-info}/WHEEL +0 -0
  40. {ominfra-0.0.0.dev138.dist-info → ominfra-0.0.0.dev139.dist-info}/entry_points.txt +0 -0
  41. {ominfra-0.0.0.dev138.dist-info → ominfra-0.0.0.dev139.dist-info}/top_level.txt +0 -0
@@ -1,19 +0,0 @@
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'
@@ -1 +0,0 @@
1
- # @omlish-lite
@@ -1,115 +0,0 @@
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
@@ -1,28 +0,0 @@
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)
@@ -1,47 +0,0 @@
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')
@@ -1,17 +0,0 @@
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
- )
@@ -1,46 +0,0 @@
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')
@@ -1,88 +0,0 @@
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}')
@@ -1,25 +0,0 @@
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)
@@ -1,22 +0,0 @@
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
- )
@@ -1,119 +0,0 @@
1
- #!/usr/bin/env python3
2
- # @omlish-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()
@@ -1 +0,0 @@
1
- # @omlish-lite