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,179 @@
|
|
1
|
+
# ruff: noqa: UP006 UP007
|
2
|
+
import abc
|
3
|
+
import dataclasses as dc
|
4
|
+
import typing as ta
|
5
|
+
|
6
|
+
from .configs import DeployConcernConfig # noqa
|
7
|
+
from .configs import DeployConfig
|
8
|
+
from .configs import SiteConcernConfig # noqa
|
9
|
+
from .configs import SiteConfig
|
10
|
+
|
11
|
+
|
12
|
+
T = ta.TypeVar('T')
|
13
|
+
ConcernT = ta.TypeVar('ConcernT')
|
14
|
+
ConfigT = ta.TypeVar('ConfigT')
|
15
|
+
|
16
|
+
|
17
|
+
##
|
18
|
+
|
19
|
+
|
20
|
+
@dc.dataclass(frozen=True)
|
21
|
+
class FsItem(abc.ABC):
|
22
|
+
path: str
|
23
|
+
|
24
|
+
@property
|
25
|
+
@abc.abstractmethod
|
26
|
+
def is_dir(self) -> bool:
|
27
|
+
raise NotImplementedError
|
28
|
+
|
29
|
+
|
30
|
+
@dc.dataclass(frozen=True)
|
31
|
+
class FsFile(FsItem):
|
32
|
+
@property
|
33
|
+
def is_dir(self) -> bool:
|
34
|
+
return False
|
35
|
+
|
36
|
+
|
37
|
+
@dc.dataclass(frozen=True)
|
38
|
+
class FsDir(FsItem):
|
39
|
+
@property
|
40
|
+
def is_dir(self) -> bool:
|
41
|
+
return True
|
42
|
+
|
43
|
+
|
44
|
+
##
|
45
|
+
|
46
|
+
|
47
|
+
class Runtime(abc.ABC):
|
48
|
+
class Stat(ta.NamedTuple):
|
49
|
+
path: str
|
50
|
+
is_dir: bool
|
51
|
+
|
52
|
+
@abc.abstractmethod
|
53
|
+
def stat(self, p: str) -> ta.Optional[Stat]:
|
54
|
+
raise NotImplementedError
|
55
|
+
|
56
|
+
@abc.abstractmethod
|
57
|
+
def make_dirs(self, p: str, exist_ok: bool = False) -> None:
|
58
|
+
raise NotImplementedError
|
59
|
+
|
60
|
+
@abc.abstractmethod
|
61
|
+
def write_file(self, p: str, c: ta.Union[str, bytes]) -> None:
|
62
|
+
raise NotImplementedError
|
63
|
+
|
64
|
+
@abc.abstractmethod
|
65
|
+
def sh(self, *ss: str) -> None:
|
66
|
+
raise NotImplementedError
|
67
|
+
|
68
|
+
|
69
|
+
##
|
70
|
+
|
71
|
+
|
72
|
+
class ConcernsContainer(abc.ABC, ta.Generic[ConcernT, ConfigT]):
|
73
|
+
concern_cls: ta.ClassVar[type]
|
74
|
+
|
75
|
+
def __init__(
|
76
|
+
self,
|
77
|
+
config: ConfigT,
|
78
|
+
) -> None:
|
79
|
+
super().__init__()
|
80
|
+
self._config = config
|
81
|
+
|
82
|
+
concern_cls_dct = self._concern_cls_by_config_cls()
|
83
|
+
self._concerns = [
|
84
|
+
concern_cls_dct[type(c)](c, self) # type: ignore
|
85
|
+
for c in config.concerns # type: ignore
|
86
|
+
]
|
87
|
+
self._concerns_by_cls: ta.Dict[ta.Type[ConcernT], ConcernT] = {}
|
88
|
+
for c in self._concerns:
|
89
|
+
if type(c) in self._concerns_by_cls:
|
90
|
+
raise TypeError(f'Duplicate concern type: {c}')
|
91
|
+
self._concerns_by_cls[type(c)] = c
|
92
|
+
|
93
|
+
@classmethod
|
94
|
+
def _concern_cls_by_config_cls(cls) -> ta.Mapping[type, ta.Type[ConcernT]]:
|
95
|
+
return { # noqa
|
96
|
+
c.Config: c # type: ignore
|
97
|
+
for c in cls.concern_cls.__subclasses__()
|
98
|
+
}
|
99
|
+
|
100
|
+
@property
|
101
|
+
def config(self) -> ConfigT:
|
102
|
+
return self._config
|
103
|
+
|
104
|
+
@property
|
105
|
+
def concerns(self) -> ta.List[ConcernT]:
|
106
|
+
return self._concerns
|
107
|
+
|
108
|
+
def concern(self, cls: ta.Type[T]) -> T:
|
109
|
+
return self._concerns_by_cls[cls] # type: ignore
|
110
|
+
|
111
|
+
|
112
|
+
##
|
113
|
+
|
114
|
+
|
115
|
+
SiteConcernT = ta.TypeVar('SiteConcernT', bound='SiteConcern')
|
116
|
+
SiteConcernConfigT = ta.TypeVar('SiteConcernConfigT', bound='SiteConcernConfig')
|
117
|
+
|
118
|
+
|
119
|
+
class SiteConcern(abc.ABC, ta.Generic[SiteConcernConfigT]):
|
120
|
+
def __init__(self, config: SiteConcernConfigT, site: 'Site') -> None:
|
121
|
+
super().__init__()
|
122
|
+
self._config = config
|
123
|
+
self._site = site
|
124
|
+
|
125
|
+
@property
|
126
|
+
def config(self) -> SiteConcernConfigT:
|
127
|
+
return self._config
|
128
|
+
|
129
|
+
@abc.abstractmethod
|
130
|
+
def run(self, runtime: Runtime) -> None:
|
131
|
+
raise NotImplementedError
|
132
|
+
|
133
|
+
|
134
|
+
##
|
135
|
+
|
136
|
+
|
137
|
+
class Site(ConcernsContainer[SiteConcern, SiteConfig]):
|
138
|
+
@abc.abstractmethod
|
139
|
+
def run(self, runtime: Runtime) -> None:
|
140
|
+
raise NotImplementedError
|
141
|
+
|
142
|
+
|
143
|
+
##
|
144
|
+
|
145
|
+
|
146
|
+
DeployConcernT = ta.TypeVar('DeployConcernT', bound='DeployConcern')
|
147
|
+
DeployConcernConfigT = ta.TypeVar('DeployConcernConfigT', bound='DeployConcernConfig')
|
148
|
+
|
149
|
+
|
150
|
+
class DeployConcern(abc.ABC, ta.Generic[DeployConcernConfigT]):
|
151
|
+
def __init__(self, config: DeployConcernConfigT, deploy: 'Deploy') -> None:
|
152
|
+
super().__init__()
|
153
|
+
self._config = config
|
154
|
+
self._deploy = deploy
|
155
|
+
|
156
|
+
@property
|
157
|
+
def config(self) -> DeployConcernConfigT:
|
158
|
+
return self._config
|
159
|
+
|
160
|
+
def fs_items(self) -> ta.Sequence[FsItem]:
|
161
|
+
return []
|
162
|
+
|
163
|
+
@abc.abstractmethod
|
164
|
+
def run(self, runtime: Runtime) -> None:
|
165
|
+
raise NotImplementedError
|
166
|
+
|
167
|
+
|
168
|
+
##
|
169
|
+
|
170
|
+
|
171
|
+
class Deploy(ConcernsContainer[DeployConcern, DeployConfig]):
|
172
|
+
@property
|
173
|
+
@abc.abstractmethod
|
174
|
+
def site(self) -> Site:
|
175
|
+
raise NotImplementedError
|
176
|
+
|
177
|
+
@abc.abstractmethod
|
178
|
+
def run(self, runtime: Runtime) -> None:
|
179
|
+
raise NotImplementedError
|
@@ -0,0 +1,38 @@
|
|
1
|
+
# ruff: noqa: UP006
|
2
|
+
import abc
|
3
|
+
import dataclasses as dc
|
4
|
+
import typing as ta
|
5
|
+
|
6
|
+
|
7
|
+
##
|
8
|
+
|
9
|
+
|
10
|
+
@dc.dataclass(frozen=True)
|
11
|
+
class SiteConcernConfig(abc.ABC): # noqa
|
12
|
+
pass
|
13
|
+
|
14
|
+
|
15
|
+
@dc.dataclass(frozen=True)
|
16
|
+
class SiteConfig:
|
17
|
+
user = 'omlish'
|
18
|
+
|
19
|
+
root_dir: str = '~/deploy'
|
20
|
+
|
21
|
+
concerns: ta.List[SiteConcernConfig] = dc.field(default_factory=list)
|
22
|
+
|
23
|
+
|
24
|
+
##
|
25
|
+
|
26
|
+
|
27
|
+
@dc.dataclass(frozen=True)
|
28
|
+
class DeployConcernConfig(abc.ABC): # noqa
|
29
|
+
pass
|
30
|
+
|
31
|
+
|
32
|
+
@dc.dataclass(frozen=True)
|
33
|
+
class DeployConfig:
|
34
|
+
site: SiteConfig
|
35
|
+
|
36
|
+
name: str
|
37
|
+
|
38
|
+
concerns: ta.List[DeployConcernConfig] = dc.field(default_factory=list)
|
@@ -0,0 +1,25 @@
|
|
1
|
+
from .base import Deploy
|
2
|
+
from .base import DeployConcern
|
3
|
+
from .base import Runtime
|
4
|
+
from .base import Site
|
5
|
+
from .configs import DeployConfig
|
6
|
+
|
7
|
+
|
8
|
+
class DeployImpl(Deploy):
|
9
|
+
concern_cls = DeployConcern
|
10
|
+
|
11
|
+
def __init__(
|
12
|
+
self,
|
13
|
+
config: DeployConfig,
|
14
|
+
site: Site,
|
15
|
+
) -> None:
|
16
|
+
super().__init__(config)
|
17
|
+
self._site = site
|
18
|
+
|
19
|
+
@property
|
20
|
+
def site(self) -> Site:
|
21
|
+
return self._site
|
22
|
+
|
23
|
+
def run(self, runtime: Runtime) -> None:
|
24
|
+
for c in self._concerns:
|
25
|
+
c.run(runtime)
|
@@ -0,0 +1,18 @@
|
|
1
|
+
#!/usr/bin/env python3
|
2
|
+
# @omdev-amalg ./_main.py
|
3
|
+
from .deploy import DeployImpl # noqa
|
4
|
+
from .nginx import NginxDeployConcern # noqa
|
5
|
+
from .nginx import NginxSiteConcern # noqa
|
6
|
+
from .repo import RepoDeployConcern # noqa
|
7
|
+
from .runtime import RuntimeImpl # noqa
|
8
|
+
from .site import SiteImpl # noqa
|
9
|
+
from .supervisor import SupervisorDeployConcern # noqa
|
10
|
+
from .venv import VenvDeployConcern # noqa
|
11
|
+
|
12
|
+
|
13
|
+
def _main() -> None:
|
14
|
+
pass
|
15
|
+
|
16
|
+
|
17
|
+
if __name__ == '__main__':
|
18
|
+
_main()
|
@@ -0,0 +1,60 @@
|
|
1
|
+
import dataclasses as dc
|
2
|
+
import os.path
|
3
|
+
import textwrap
|
4
|
+
import typing as ta
|
5
|
+
|
6
|
+
from omlish.lite.cached import cached_nullary
|
7
|
+
|
8
|
+
from .base import DeployConcern
|
9
|
+
from .base import FsFile
|
10
|
+
from .base import FsItem
|
11
|
+
from .base import Runtime
|
12
|
+
from .base import SiteConcern
|
13
|
+
from .configs import DeployConcernConfig
|
14
|
+
from .configs import SiteConcernConfig
|
15
|
+
|
16
|
+
|
17
|
+
class NginxSiteConcern(SiteConcern['NginxSiteConcern.Config']):
|
18
|
+
@dc.dataclass(frozen=True)
|
19
|
+
class Config(SiteConcernConfig):
|
20
|
+
global_conf_file: str = '/etc/nginx/sites-enabled/omlish.conf'
|
21
|
+
|
22
|
+
@cached_nullary
|
23
|
+
def confs_dir(self) -> str:
|
24
|
+
return os.path.join(self._site.config.root_dir, 'conf', 'nginx')
|
25
|
+
|
26
|
+
def run(self, runtime: Runtime) -> None:
|
27
|
+
if runtime.stat(self._config.global_conf_file) is None:
|
28
|
+
runtime.write_file(
|
29
|
+
self._config.global_conf_file,
|
30
|
+
f'include {self.confs_dir()}/*.conf;\n',
|
31
|
+
)
|
32
|
+
|
33
|
+
|
34
|
+
class NginxDeployConcern(DeployConcern['NginxDeployConcern.Config']):
|
35
|
+
@dc.dataclass(frozen=True)
|
36
|
+
class Config(DeployConcernConfig):
|
37
|
+
listen_port: int = 80
|
38
|
+
proxy_port: int = 8000
|
39
|
+
|
40
|
+
@cached_nullary
|
41
|
+
def conf_file(self) -> str:
|
42
|
+
return os.path.join(self._deploy.site.concern(NginxSiteConcern).confs_dir(), self._deploy.config.name + '.conf')
|
43
|
+
|
44
|
+
@cached_nullary
|
45
|
+
def fs_items(self) -> ta.Sequence[FsItem]:
|
46
|
+
return [FsFile(self.conf_file())]
|
47
|
+
|
48
|
+
def run(self, runtime: Runtime) -> None:
|
49
|
+
runtime.make_dirs(os.path.dirname(self.conf_file()))
|
50
|
+
|
51
|
+
conf = textwrap.dedent(f"""
|
52
|
+
server {{
|
53
|
+
listen {self._config.listen_port};
|
54
|
+
location / {{
|
55
|
+
proxy_pass http://127.0.0.1:{self._config.proxy_port}/;
|
56
|
+
}}
|
57
|
+
}}
|
58
|
+
""")
|
59
|
+
|
60
|
+
runtime.write_file(self.conf_file(), conf)
|
@@ -0,0 +1,41 @@
|
|
1
|
+
import dataclasses as dc
|
2
|
+
import os.path
|
3
|
+
import typing as ta
|
4
|
+
|
5
|
+
from omlish.lite.cached import cached_nullary
|
6
|
+
|
7
|
+
from .base import DeployConcern
|
8
|
+
from .base import FsDir
|
9
|
+
from .base import FsItem
|
10
|
+
from .base import Runtime
|
11
|
+
from .configs import DeployConcernConfig
|
12
|
+
|
13
|
+
|
14
|
+
class RepoDeployConcern(DeployConcern['RepoDeployConcern.Config']):
|
15
|
+
@dc.dataclass(frozen=True)
|
16
|
+
class Config(DeployConcernConfig):
|
17
|
+
url: str
|
18
|
+
revision: str = 'master'
|
19
|
+
init_submodules: bool = False
|
20
|
+
|
21
|
+
@cached_nullary
|
22
|
+
def repo_dir(self) -> str:
|
23
|
+
return os.path.join(self._deploy.site.config.root_dir, 'repos', self._deploy.config.name)
|
24
|
+
|
25
|
+
@cached_nullary
|
26
|
+
def fs_items(self) -> ta.Sequence[FsItem]:
|
27
|
+
return [FsDir(self.repo_dir())]
|
28
|
+
|
29
|
+
def run(self, runtime: Runtime) -> None:
|
30
|
+
runtime.make_dirs(self.repo_dir())
|
31
|
+
|
32
|
+
runtime.sh(
|
33
|
+
f'cd {self.repo_dir()}',
|
34
|
+
'git init',
|
35
|
+
f'git remote add origin {self._config.url}',
|
36
|
+
f'git fetch --depth 1 origin {self._config.revision}',
|
37
|
+
'git checkout FETCH_HEAD',
|
38
|
+
*([
|
39
|
+
'git submodule update --init',
|
40
|
+
] if self._config.init_submodules else []),
|
41
|
+
)
|
@@ -0,0 +1,39 @@
|
|
1
|
+
# ruff: noqa: UP007
|
2
|
+
import os.path
|
3
|
+
import stat
|
4
|
+
import typing as ta
|
5
|
+
|
6
|
+
from omlish.lite.logs import log
|
7
|
+
from omlish.lite.subprocesses import subprocess_check_call
|
8
|
+
|
9
|
+
from .base import Runtime
|
10
|
+
|
11
|
+
|
12
|
+
class RuntimeImpl(Runtime):
|
13
|
+
def __init__(self) -> None:
|
14
|
+
super().__init__()
|
15
|
+
|
16
|
+
def stat(self, p: str) -> ta.Optional[Runtime.Stat]:
|
17
|
+
try:
|
18
|
+
st = os.stat(p)
|
19
|
+
except FileNotFoundError:
|
20
|
+
return None
|
21
|
+
else:
|
22
|
+
return Runtime.Stat(
|
23
|
+
path=p,
|
24
|
+
is_dir=bool(st.st_mode & stat.S_IFDIR),
|
25
|
+
)
|
26
|
+
|
27
|
+
def make_dirs(self, p: str, exist_ok: bool = False) -> None:
|
28
|
+
os.makedirs(p, exist_ok=exist_ok)
|
29
|
+
|
30
|
+
def write_file(self, p: str, c: ta.Union[str, bytes]) -> None:
|
31
|
+
if os.path.exists(p):
|
32
|
+
raise RuntimeError(f'Path exists: {p}')
|
33
|
+
with open(p, 'w' if isinstance(c, str) else 'wb') as f:
|
34
|
+
f.write(c)
|
35
|
+
|
36
|
+
def sh(self, *ss: str) -> None:
|
37
|
+
s = ' && '.join(ss)
|
38
|
+
log.info('Executing: %s', s)
|
39
|
+
subprocess_check_call(s, shell=True)
|
@@ -0,0 +1,64 @@
|
|
1
|
+
import dataclasses as dc
|
2
|
+
import os.path
|
3
|
+
import textwrap
|
4
|
+
import typing as ta
|
5
|
+
|
6
|
+
from omlish.lite.cached import cached_nullary
|
7
|
+
|
8
|
+
from .base import DeployConcern
|
9
|
+
from .base import FsFile
|
10
|
+
from .base import FsItem
|
11
|
+
from .base import Runtime
|
12
|
+
from .configs import DeployConcernConfig
|
13
|
+
from .repo import RepoDeployConcern
|
14
|
+
from .venv import VenvDeployConcern
|
15
|
+
|
16
|
+
|
17
|
+
# class SupervisorSiteConcern(SiteConcern['SupervisorSiteConcern.Config']):
|
18
|
+
# @dc.dataclass(frozen=True)
|
19
|
+
# class Config(DeployConcern.Config):
|
20
|
+
# global_conf_file: str = '/etc/supervisor/conf.d/supervisord.conf'
|
21
|
+
#
|
22
|
+
# def run(self) -> None:
|
23
|
+
# sup_conf_dir = os.path.join(self._d.home_dir(), 'conf/supervisor')
|
24
|
+
# with open(self._d.host_cfg.global_supervisor_conf_file_path) as f:
|
25
|
+
# glo_sup_conf = f.read()
|
26
|
+
# if sup_conf_dir not in glo_sup_conf:
|
27
|
+
# log.info('Updating global supervisor conf at %s', self._d.host_cfg.global_supervisor_conf_file_path) # noqa
|
28
|
+
# glo_sup_conf += textwrap.dedent(f"""
|
29
|
+
# [include]
|
30
|
+
# files = {self._d.home_dir()}/conf/supervisor/*.conf
|
31
|
+
# """)
|
32
|
+
# with open(self._d.host_cfg.global_supervisor_conf_file_path, 'w') as f:
|
33
|
+
# f.write(glo_sup_conf)
|
34
|
+
|
35
|
+
|
36
|
+
class SupervisorDeployConcern(DeployConcern['SupervisorDeployConcern.Config']):
|
37
|
+
@dc.dataclass(frozen=True)
|
38
|
+
class Config(DeployConcernConfig):
|
39
|
+
entrypoint: str
|
40
|
+
|
41
|
+
@cached_nullary
|
42
|
+
def conf_file(self) -> str:
|
43
|
+
return os.path.join(self._deploy.site.config.root_dir, 'conf', 'supervisor', self._deploy.config.name + '.conf')
|
44
|
+
|
45
|
+
@cached_nullary
|
46
|
+
def fs_items(self) -> ta.Sequence[FsItem]:
|
47
|
+
return [FsFile(self.conf_file())]
|
48
|
+
|
49
|
+
def run(self, runtime: Runtime) -> None:
|
50
|
+
runtime.make_dirs(os.path.dirname(self.conf_file()))
|
51
|
+
|
52
|
+
rd = self._deploy.concern(RepoDeployConcern).repo_dir()
|
53
|
+
vx = self._deploy.concern(VenvDeployConcern).exe()
|
54
|
+
|
55
|
+
conf = textwrap.dedent(f"""
|
56
|
+
[program:{self._deploy.config.name}]
|
57
|
+
command={vx} -m {self._config.entrypoint}
|
58
|
+
directory={rd}
|
59
|
+
user={self._deploy.site.config.user}
|
60
|
+
autostart=true
|
61
|
+
autorestart=true
|
62
|
+
""")
|
63
|
+
|
64
|
+
runtime.write_file(self.conf_file(), conf)
|
@@ -0,0 +1,52 @@
|
|
1
|
+
import dataclasses as dc
|
2
|
+
import os.path
|
3
|
+
import typing as ta
|
4
|
+
|
5
|
+
from omlish.lite.cached import cached_nullary
|
6
|
+
|
7
|
+
from .base import DeployConcern
|
8
|
+
from .base import FsDir
|
9
|
+
from .base import FsItem
|
10
|
+
from .base import Runtime
|
11
|
+
from .configs import DeployConcernConfig
|
12
|
+
from .repo import RepoDeployConcern
|
13
|
+
|
14
|
+
|
15
|
+
class VenvDeployConcern(DeployConcern['VenvDeployConcern.Config']):
|
16
|
+
@dc.dataclass(frozen=True)
|
17
|
+
class Config(DeployConcernConfig):
|
18
|
+
interp_version: str
|
19
|
+
requirements_txt: str = 'requirements.txt'
|
20
|
+
|
21
|
+
@cached_nullary
|
22
|
+
def venv_dir(self) -> str:
|
23
|
+
return os.path.join(self._deploy.site.config.root_dir, 'venvs', self._deploy.config.name)
|
24
|
+
|
25
|
+
@cached_nullary
|
26
|
+
def fs_items(self) -> ta.Sequence[FsItem]:
|
27
|
+
return [FsDir(self.venv_dir())]
|
28
|
+
|
29
|
+
@cached_nullary
|
30
|
+
def exe(self) -> str:
|
31
|
+
return os.path.join(self.venv_dir(), 'bin', 'python')
|
32
|
+
|
33
|
+
def run(self, runtime: Runtime) -> None:
|
34
|
+
runtime.make_dirs(self.venv_dir())
|
35
|
+
|
36
|
+
rd = self._deploy.concern(RepoDeployConcern).repo_dir()
|
37
|
+
|
38
|
+
l, r = os.path.split(self.venv_dir())
|
39
|
+
|
40
|
+
# FIXME: lol
|
41
|
+
py_exe = 'python3'
|
42
|
+
|
43
|
+
runtime.sh(
|
44
|
+
f'cd {l}',
|
45
|
+
f'{py_exe} -mvenv {r}',
|
46
|
+
|
47
|
+
# https://stackoverflow.com/questions/77364550/attributeerror-module-pkgutil-has-no-attribute-impimporter-did-you-mean
|
48
|
+
f'{self.exe()} -m ensurepip',
|
49
|
+
f'{self.exe()} -mpip install --upgrade setuptools pip',
|
50
|
+
|
51
|
+
f'{self.exe()} -mpip install -r {rd}/{self._config.requirements_txt}', # noqa
|
52
|
+
)
|
ominfra/deploy/remote.py
ADDED
@@ -0,0 +1,91 @@
|
|
1
|
+
"""
|
2
|
+
lookit:
|
3
|
+
- piku, obviously
|
4
|
+
- https://github.com/mitogen-hq/mitogen/
|
5
|
+
|
6
|
+
git init
|
7
|
+
git remote add local ~/src/wrmsr/omlish/.git
|
8
|
+
git fetch --depth=1 local master
|
9
|
+
git remote add origin https://github.com/wrmsr/omlish
|
10
|
+
git fetch --depth=1 origin master
|
11
|
+
git checkout origin/master
|
12
|
+
|
13
|
+
{base_path}/{deploys}/
|
14
|
+
current ->
|
15
|
+
previous ->
|
16
|
+
20240522T120000_{rev}
|
17
|
+
"""
|
18
|
+
import asyncio
|
19
|
+
import itertools
|
20
|
+
import os.path
|
21
|
+
import shlex
|
22
|
+
import tempfile
|
23
|
+
|
24
|
+
from omlish import check
|
25
|
+
|
26
|
+
from .. import cmds
|
27
|
+
|
28
|
+
|
29
|
+
def render_script(*cs: list[str] | tuple[str, ...]) -> str:
|
30
|
+
return ' '.join(itertools.chain.from_iterable(
|
31
|
+
[
|
32
|
+
*(['&&'] if i > 0 else []),
|
33
|
+
shlex.join(check.not_isinstance(l, str)),
|
34
|
+
]
|
35
|
+
for i, l in enumerate(cs)
|
36
|
+
))
|
37
|
+
|
38
|
+
|
39
|
+
async def do_remote_deploy(
|
40
|
+
cr: cmds.CommandRunner,
|
41
|
+
rev: str = 'master',
|
42
|
+
*,
|
43
|
+
local_repo_path: str | None = None,
|
44
|
+
skip_submodules: bool = False,
|
45
|
+
) -> None:
|
46
|
+
clone_script = [
|
47
|
+
['git', 'init'],
|
48
|
+
|
49
|
+
*([
|
50
|
+
['git', 'remote', 'add', 'local', local_repo_path],
|
51
|
+
['git', 'fetch', '--depth=1', 'local', rev],
|
52
|
+
] if local_repo_path is not None else ()),
|
53
|
+
|
54
|
+
['git', 'remote', 'add', 'origin', 'https://github.com/wrmsr/omlish'],
|
55
|
+
['git', 'fetch', '--depth=1', 'origin', rev],
|
56
|
+
['git', 'checkout', f'origin/{rev}'],
|
57
|
+
|
58
|
+
*([['git', 'submodule', 'update', '--init']] if not skip_submodules else ()),
|
59
|
+
|
60
|
+
['make', 'venv-deploy'],
|
61
|
+
]
|
62
|
+
|
63
|
+
res = await cr.run_command(cr.Command([
|
64
|
+
'sh', '-c', render_script(
|
65
|
+
['mkdir', 'omlish'],
|
66
|
+
['cd', 'omlish'],
|
67
|
+
*clone_script,
|
68
|
+
),
|
69
|
+
]))
|
70
|
+
res.check()
|
71
|
+
|
72
|
+
|
73
|
+
async def _a_main():
|
74
|
+
cwd = tempfile.mkdtemp()
|
75
|
+
print(cwd)
|
76
|
+
|
77
|
+
bootstrap_git_path = os.path.join(os.getcwd(), '.git')
|
78
|
+
check.state(os.path.isdir(bootstrap_git_path))
|
79
|
+
|
80
|
+
cr: cmds.CommandRunner = cmds.LocalCommandRunner(cmds.LocalCommandRunner.Config(
|
81
|
+
cwd=cwd,
|
82
|
+
))
|
83
|
+
|
84
|
+
await do_remote_deploy(
|
85
|
+
cr,
|
86
|
+
local_repo_path=os.path.expanduser('~/src/wrmsr/omlish/.git'),
|
87
|
+
)
|
88
|
+
|
89
|
+
|
90
|
+
if __name__ == '__main__':
|
91
|
+
asyncio.run(_a_main())
|
File without changes
|