ominfra 0.0.0.dev154__tar.gz → 0.0.0.dev155__tar.gz
Sign up to get free protection for your applications and to get access to all the features.
- {ominfra-0.0.0.dev154/ominfra.egg-info → ominfra-0.0.0.dev155}/PKG-INFO +3 -3
- {ominfra-0.0.0.dev154 → ominfra-0.0.0.dev155}/ominfra/manage/bootstrap.py +4 -0
- {ominfra-0.0.0.dev154 → ominfra-0.0.0.dev155}/ominfra/manage/bootstrap_.py +5 -0
- {ominfra-0.0.0.dev154 → ominfra-0.0.0.dev155}/ominfra/manage/commands/inject.py +8 -11
- ominfra-0.0.0.dev154/ominfra/manage/commands/execution.py → ominfra-0.0.0.dev155/ominfra/manage/commands/local.py +1 -5
- ominfra-0.0.0.dev155/ominfra/manage/commands/ping.py +23 -0
- ominfra-0.0.0.dev155/ominfra/manage/commands/types.py +8 -0
- ominfra-0.0.0.dev155/ominfra/manage/deploy/apps.py +72 -0
- {ominfra-0.0.0.dev154/ominfra/manage/system → ominfra-0.0.0.dev155/ominfra/manage/deploy}/config.py +2 -2
- ominfra-0.0.0.dev155/ominfra/manage/deploy/git.py +136 -0
- ominfra-0.0.0.dev155/ominfra/manage/deploy/inject.py +40 -0
- ominfra-0.0.0.dev155/ominfra/manage/deploy/paths.py +230 -0
- ominfra-0.0.0.dev155/ominfra/manage/deploy/types.py +13 -0
- ominfra-0.0.0.dev155/ominfra/manage/deploy/venvs.py +66 -0
- {ominfra-0.0.0.dev154 → ominfra-0.0.0.dev155}/ominfra/manage/inject.py +20 -4
- {ominfra-0.0.0.dev154 → ominfra-0.0.0.dev155}/ominfra/manage/main.py +15 -27
- {ominfra-0.0.0.dev154 → ominfra-0.0.0.dev155}/ominfra/manage/remote/_main.py +1 -1
- {ominfra-0.0.0.dev154 → ominfra-0.0.0.dev155}/ominfra/manage/remote/config.py +0 -2
- {ominfra-0.0.0.dev154 → ominfra-0.0.0.dev155}/ominfra/manage/remote/connection.py +7 -24
- {ominfra-0.0.0.dev154 → ominfra-0.0.0.dev155}/ominfra/manage/remote/execution.py +1 -1
- {ominfra-0.0.0.dev154 → ominfra-0.0.0.dev155}/ominfra/manage/remote/inject.py +3 -14
- {ominfra-0.0.0.dev154 → ominfra-0.0.0.dev155}/ominfra/manage/system/commands.py +22 -2
- ominfra-0.0.0.dev155/ominfra/manage/system/config.py +10 -0
- {ominfra-0.0.0.dev154 → ominfra-0.0.0.dev155}/ominfra/manage/system/inject.py +16 -6
- {ominfra-0.0.0.dev154 → ominfra-0.0.0.dev155}/ominfra/manage/system/packages.py +33 -7
- ominfra-0.0.0.dev155/ominfra/manage/system/platforms.py +72 -0
- ominfra-0.0.0.dev155/ominfra/manage/targets/connection.py +150 -0
- ominfra-0.0.0.dev155/ominfra/manage/targets/inject.py +42 -0
- ominfra-0.0.0.dev155/ominfra/manage/targets/targets.py +87 -0
- {ominfra-0.0.0.dev154 → ominfra-0.0.0.dev155}/ominfra/scripts/journald2aws.py +24 -7
- {ominfra-0.0.0.dev154 → ominfra-0.0.0.dev155}/ominfra/scripts/manage.py +1880 -438
- {ominfra-0.0.0.dev154 → ominfra-0.0.0.dev155}/ominfra/scripts/supervisor.py +187 -25
- ominfra-0.0.0.dev155/ominfra/supervisor/configs.py +299 -0
- ominfra-0.0.0.dev155/ominfra/tools/__init__.py +0 -0
- {ominfra-0.0.0.dev154 → ominfra-0.0.0.dev155/ominfra.egg-info}/PKG-INFO +3 -3
- {ominfra-0.0.0.dev154 → ominfra-0.0.0.dev155}/ominfra.egg-info/SOURCES.txt +14 -3
- {ominfra-0.0.0.dev154 → ominfra-0.0.0.dev155}/ominfra.egg-info/requires.txt +2 -2
- {ominfra-0.0.0.dev154 → ominfra-0.0.0.dev155}/pyproject.toml +3 -3
- ominfra-0.0.0.dev154/ominfra/manage/deploy/inject.py +0 -19
- ominfra-0.0.0.dev154/ominfra/manage/deploy/paths.py +0 -177
- ominfra-0.0.0.dev154/ominfra/manage/system/types.py +0 -5
- ominfra-0.0.0.dev154/ominfra/supervisor/configs.py +0 -154
- {ominfra-0.0.0.dev154 → ominfra-0.0.0.dev155}/LICENSE +0 -0
- {ominfra-0.0.0.dev154 → ominfra-0.0.0.dev155}/MANIFEST.in +0 -0
- {ominfra-0.0.0.dev154 → ominfra-0.0.0.dev155}/README.rst +0 -0
- {ominfra-0.0.0.dev154 → ominfra-0.0.0.dev155}/ominfra/.manifests.json +0 -0
- {ominfra-0.0.0.dev154 → ominfra-0.0.0.dev155}/ominfra/__about__.py +0 -0
- {ominfra-0.0.0.dev154 → ominfra-0.0.0.dev155}/ominfra/__init__.py +0 -0
- {ominfra-0.0.0.dev154 → ominfra-0.0.0.dev155}/ominfra/clouds/__init__.py +0 -0
- {ominfra-0.0.0.dev154 → ominfra-0.0.0.dev155}/ominfra/clouds/aws/__init__.py +0 -0
- {ominfra-0.0.0.dev154 → ominfra-0.0.0.dev155}/ominfra/clouds/aws/__main__.py +0 -0
- {ominfra-0.0.0.dev154 → ominfra-0.0.0.dev155}/ominfra/clouds/aws/auth.py +0 -0
- {ominfra-0.0.0.dev154 → ominfra-0.0.0.dev155}/ominfra/clouds/aws/cli.py +0 -0
- {ominfra-0.0.0.dev154 → ominfra-0.0.0.dev155}/ominfra/clouds/aws/dataclasses.py +0 -0
- {ominfra-0.0.0.dev154 → ominfra-0.0.0.dev155}/ominfra/clouds/aws/journald2aws/__init__.py +0 -0
- {ominfra-0.0.0.dev154 → ominfra-0.0.0.dev155}/ominfra/clouds/aws/journald2aws/__main__.py +0 -0
- {ominfra-0.0.0.dev154 → ominfra-0.0.0.dev155}/ominfra/clouds/aws/journald2aws/cursor.py +0 -0
- {ominfra-0.0.0.dev154 → ominfra-0.0.0.dev155}/ominfra/clouds/aws/journald2aws/driver.py +0 -0
- {ominfra-0.0.0.dev154 → ominfra-0.0.0.dev155}/ominfra/clouds/aws/journald2aws/main.py +0 -0
- {ominfra-0.0.0.dev154 → ominfra-0.0.0.dev155}/ominfra/clouds/aws/journald2aws/poster.py +0 -0
- {ominfra-0.0.0.dev154 → ominfra-0.0.0.dev155}/ominfra/clouds/aws/logs.py +0 -0
- {ominfra-0.0.0.dev154 → ominfra-0.0.0.dev155}/ominfra/clouds/aws/metadata.py +0 -0
- {ominfra-0.0.0.dev154 → ominfra-0.0.0.dev155}/ominfra/clouds/gcp/__init__.py +0 -0
- {ominfra-0.0.0.dev154 → ominfra-0.0.0.dev155}/ominfra/clouds/gcp/auth.py +0 -0
- {ominfra-0.0.0.dev154 → ominfra-0.0.0.dev155}/ominfra/cmds.py +0 -0
- {ominfra-0.0.0.dev154 → ominfra-0.0.0.dev155}/ominfra/configs.py +0 -0
- {ominfra-0.0.0.dev154 → ominfra-0.0.0.dev155}/ominfra/journald/__init__.py +0 -0
- {ominfra-0.0.0.dev154 → ominfra-0.0.0.dev155}/ominfra/journald/fields.py +0 -0
- {ominfra-0.0.0.dev154 → ominfra-0.0.0.dev155}/ominfra/journald/genmessages.py +0 -0
- {ominfra-0.0.0.dev154 → ominfra-0.0.0.dev155}/ominfra/journald/messages.py +0 -0
- {ominfra-0.0.0.dev154 → ominfra-0.0.0.dev155}/ominfra/journald/tailer.py +0 -0
- {ominfra-0.0.0.dev154 → ominfra-0.0.0.dev155}/ominfra/manage/__init__.py +0 -0
- {ominfra-0.0.0.dev154 → ominfra-0.0.0.dev155}/ominfra/manage/__main__.py +0 -0
- {ominfra-0.0.0.dev154 → ominfra-0.0.0.dev155}/ominfra/manage/commands/__init__.py +0 -0
- {ominfra-0.0.0.dev154 → ominfra-0.0.0.dev155}/ominfra/manage/commands/base.py +0 -0
- {ominfra-0.0.0.dev154 → ominfra-0.0.0.dev155}/ominfra/manage/commands/marshal.py +0 -0
- {ominfra-0.0.0.dev154 → ominfra-0.0.0.dev155}/ominfra/manage/commands/subprocess.py +0 -0
- {ominfra-0.0.0.dev154 → ominfra-0.0.0.dev155}/ominfra/manage/config.py +0 -0
- {ominfra-0.0.0.dev154 → ominfra-0.0.0.dev155}/ominfra/manage/deploy/__init__.py +0 -0
- {ominfra-0.0.0.dev154 → ominfra-0.0.0.dev155}/ominfra/manage/deploy/commands.py +0 -0
- {ominfra-0.0.0.dev154/ominfra/manage/commands → ominfra-0.0.0.dev155/ominfra/manage/deploy}/interp.py +0 -0
- {ominfra-0.0.0.dev154 → ominfra-0.0.0.dev155}/ominfra/manage/marshal.py +0 -0
- {ominfra-0.0.0.dev154 → ominfra-0.0.0.dev155}/ominfra/manage/remote/__init__.py +0 -0
- {ominfra-0.0.0.dev154 → ominfra-0.0.0.dev155}/ominfra/manage/remote/channel.py +0 -0
- {ominfra-0.0.0.dev154 → ominfra-0.0.0.dev155}/ominfra/manage/remote/payload.py +0 -0
- {ominfra-0.0.0.dev154 → ominfra-0.0.0.dev155}/ominfra/manage/remote/spawning.py +0 -0
- {ominfra-0.0.0.dev154 → ominfra-0.0.0.dev155}/ominfra/manage/system/__init__.py +0 -0
- {ominfra-0.0.0.dev154/ominfra/scripts → ominfra-0.0.0.dev155/ominfra/manage/targets}/__init__.py +0 -0
- {ominfra-0.0.0.dev154 → ominfra-0.0.0.dev155}/ominfra/pyremote.py +0 -0
- {ominfra-0.0.0.dev154/ominfra/supervisor/utils → ominfra-0.0.0.dev155/ominfra/scripts}/__init__.py +0 -0
- {ominfra-0.0.0.dev154 → ominfra-0.0.0.dev155}/ominfra/ssh.py +0 -0
- {ominfra-0.0.0.dev154 → ominfra-0.0.0.dev155}/ominfra/supervisor/LICENSE.txt +0 -0
- {ominfra-0.0.0.dev154 → ominfra-0.0.0.dev155}/ominfra/supervisor/__init__.py +0 -0
- {ominfra-0.0.0.dev154 → ominfra-0.0.0.dev155}/ominfra/supervisor/__main__.py +0 -0
- {ominfra-0.0.0.dev154 → ominfra-0.0.0.dev155}/ominfra/supervisor/dispatchers.py +0 -0
- {ominfra-0.0.0.dev154 → ominfra-0.0.0.dev155}/ominfra/supervisor/dispatchersimpl.py +0 -0
- {ominfra-0.0.0.dev154 → ominfra-0.0.0.dev155}/ominfra/supervisor/events.py +0 -0
- {ominfra-0.0.0.dev154 → ominfra-0.0.0.dev155}/ominfra/supervisor/exceptions.py +0 -0
- {ominfra-0.0.0.dev154 → ominfra-0.0.0.dev155}/ominfra/supervisor/groups.py +0 -0
- {ominfra-0.0.0.dev154 → ominfra-0.0.0.dev155}/ominfra/supervisor/groupsimpl.py +0 -0
- {ominfra-0.0.0.dev154 → ominfra-0.0.0.dev155}/ominfra/supervisor/http.py +0 -0
- {ominfra-0.0.0.dev154 → ominfra-0.0.0.dev155}/ominfra/supervisor/inject.py +0 -0
- {ominfra-0.0.0.dev154 → ominfra-0.0.0.dev155}/ominfra/supervisor/io.py +0 -0
- {ominfra-0.0.0.dev154 → ominfra-0.0.0.dev155}/ominfra/supervisor/main.py +0 -0
- {ominfra-0.0.0.dev154 → ominfra-0.0.0.dev155}/ominfra/supervisor/pipes.py +0 -0
- {ominfra-0.0.0.dev154 → ominfra-0.0.0.dev155}/ominfra/supervisor/privileges.py +0 -0
- {ominfra-0.0.0.dev154 → ominfra-0.0.0.dev155}/ominfra/supervisor/process.py +0 -0
- {ominfra-0.0.0.dev154 → ominfra-0.0.0.dev155}/ominfra/supervisor/processimpl.py +0 -0
- {ominfra-0.0.0.dev154 → ominfra-0.0.0.dev155}/ominfra/supervisor/setup.py +0 -0
- {ominfra-0.0.0.dev154 → ominfra-0.0.0.dev155}/ominfra/supervisor/setupimpl.py +0 -0
- {ominfra-0.0.0.dev154 → ominfra-0.0.0.dev155}/ominfra/supervisor/signals.py +0 -0
- {ominfra-0.0.0.dev154 → ominfra-0.0.0.dev155}/ominfra/supervisor/spawning.py +0 -0
- {ominfra-0.0.0.dev154 → ominfra-0.0.0.dev155}/ominfra/supervisor/spawningimpl.py +0 -0
- {ominfra-0.0.0.dev154 → ominfra-0.0.0.dev155}/ominfra/supervisor/states.py +0 -0
- {ominfra-0.0.0.dev154 → ominfra-0.0.0.dev155}/ominfra/supervisor/supervisor.py +0 -0
- {ominfra-0.0.0.dev154 → ominfra-0.0.0.dev155}/ominfra/supervisor/types.py +0 -0
- {ominfra-0.0.0.dev154/ominfra/tailscale → ominfra-0.0.0.dev155/ominfra/supervisor/utils}/__init__.py +0 -0
- {ominfra-0.0.0.dev154 → ominfra-0.0.0.dev155}/ominfra/supervisor/utils/collections.py +0 -0
- {ominfra-0.0.0.dev154 → ominfra-0.0.0.dev155}/ominfra/supervisor/utils/diag.py +0 -0
- {ominfra-0.0.0.dev154 → ominfra-0.0.0.dev155}/ominfra/supervisor/utils/fds.py +0 -0
- {ominfra-0.0.0.dev154 → ominfra-0.0.0.dev155}/ominfra/supervisor/utils/fs.py +0 -0
- {ominfra-0.0.0.dev154 → ominfra-0.0.0.dev155}/ominfra/supervisor/utils/os.py +0 -0
- {ominfra-0.0.0.dev154 → ominfra-0.0.0.dev155}/ominfra/supervisor/utils/ostypes.py +0 -0
- {ominfra-0.0.0.dev154 → ominfra-0.0.0.dev155}/ominfra/supervisor/utils/signals.py +0 -0
- {ominfra-0.0.0.dev154 → ominfra-0.0.0.dev155}/ominfra/supervisor/utils/strings.py +0 -0
- {ominfra-0.0.0.dev154 → ominfra-0.0.0.dev155}/ominfra/supervisor/utils/users.py +0 -0
- {ominfra-0.0.0.dev154/ominfra/tools → ominfra-0.0.0.dev155/ominfra/tailscale}/__init__.py +0 -0
- {ominfra-0.0.0.dev154 → ominfra-0.0.0.dev155}/ominfra/tailscale/api.py +0 -0
- {ominfra-0.0.0.dev154 → ominfra-0.0.0.dev155}/ominfra/tailscale/cli.py +0 -0
- {ominfra-0.0.0.dev154 → ominfra-0.0.0.dev155}/ominfra/threadworkers.py +0 -0
- {ominfra-0.0.0.dev154 → ominfra-0.0.0.dev155}/ominfra/tools/listresources.py +0 -0
- {ominfra-0.0.0.dev154 → ominfra-0.0.0.dev155}/ominfra.egg-info/dependency_links.txt +0 -0
- {ominfra-0.0.0.dev154 → ominfra-0.0.0.dev155}/ominfra.egg-info/entry_points.txt +0 -0
- {ominfra-0.0.0.dev154 → ominfra-0.0.0.dev155}/ominfra.egg-info/top_level.txt +0 -0
- {ominfra-0.0.0.dev154 → ominfra-0.0.0.dev155}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.1
|
2
2
|
Name: ominfra
|
3
|
-
Version: 0.0.0.
|
3
|
+
Version: 0.0.0.dev155
|
4
4
|
Summary: ominfra
|
5
5
|
Author: wrmsr
|
6
6
|
License: BSD-3-Clause
|
@@ -12,8 +12,8 @@ Classifier: Operating System :: OS Independent
|
|
12
12
|
Classifier: Operating System :: POSIX
|
13
13
|
Requires-Python: >=3.12
|
14
14
|
License-File: LICENSE
|
15
|
-
Requires-Dist: omdev==0.0.0.
|
16
|
-
Requires-Dist: omlish==0.0.0.
|
15
|
+
Requires-Dist: omdev==0.0.0.dev155
|
16
|
+
Requires-Dist: omlish==0.0.0.dev155
|
17
17
|
Provides-Extra: all
|
18
18
|
Requires-Dist: paramiko~=3.5; extra == "all"
|
19
19
|
Requires-Dist: asyncssh~=2.18; extra == "all"
|
@@ -1,6 +1,8 @@
|
|
1
|
+
# ruff: noqa: UP006 UP007
|
1
2
|
import dataclasses as dc
|
2
3
|
|
3
4
|
from .config import MainConfig
|
5
|
+
from .deploy.config import DeployConfig
|
4
6
|
from .remote.config import RemoteConfig
|
5
7
|
from .system.config import SystemConfig
|
6
8
|
|
@@ -9,6 +11,8 @@ from .system.config import SystemConfig
|
|
9
11
|
class MainBootstrap:
|
10
12
|
main_config: MainConfig = MainConfig()
|
11
13
|
|
14
|
+
deploy_config: DeployConfig = DeployConfig()
|
15
|
+
|
12
16
|
remote_config: RemoteConfig = RemoteConfig()
|
13
17
|
|
14
18
|
system_config: SystemConfig = SystemConfig()
|
@@ -1,3 +1,4 @@
|
|
1
|
+
# ruff: noqa: UP006 UP007
|
1
2
|
from omlish.lite.inject import Injector
|
2
3
|
from omlish.lite.inject import inj
|
3
4
|
from omlish.lite.logs import configure_standard_logging
|
@@ -12,8 +13,12 @@ def main_bootstrap(bs: MainBootstrap) -> Injector:
|
|
12
13
|
|
13
14
|
injector = inj.create_injector(bind_main( # noqa
|
14
15
|
main_config=bs.main_config,
|
16
|
+
|
17
|
+
deploy_config=bs.deploy_config,
|
15
18
|
remote_config=bs.remote_config,
|
16
19
|
system_config=bs.system_config,
|
20
|
+
|
21
|
+
main_bootstrap=bs,
|
17
22
|
))
|
18
23
|
|
19
24
|
return injector
|
@@ -18,13 +18,13 @@ from .base import CommandNameMap
|
|
18
18
|
from .base import CommandRegistration
|
19
19
|
from .base import CommandRegistrations
|
20
20
|
from .base import build_command_name_map
|
21
|
-
from .
|
22
|
-
from .execution import LocalCommandExecutor
|
23
|
-
from .interp import InterpCommand
|
24
|
-
from .interp import InterpCommandExecutor
|
21
|
+
from .local import LocalCommandExecutor
|
25
22
|
from .marshal import install_command_marshaling
|
23
|
+
from .ping import PingCommand
|
24
|
+
from .ping import PingCommandExecutor
|
26
25
|
from .subprocess import SubprocessCommand
|
27
26
|
from .subprocess import SubprocessCommandExecutor
|
27
|
+
from .types import CommandExecutorMap
|
28
28
|
|
29
29
|
|
30
30
|
##
|
@@ -113,13 +113,10 @@ def bind_commands(
|
|
113
113
|
|
114
114
|
#
|
115
115
|
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
(InterpCommand, InterpCommandExecutor),
|
121
|
-
]:
|
122
|
-
lst.append(bind_command(command_cls, executor_cls))
|
116
|
+
lst.extend([
|
117
|
+
bind_command(PingCommand, PingCommandExecutor),
|
118
|
+
bind_command(SubprocessCommand, SubprocessCommandExecutor),
|
119
|
+
])
|
123
120
|
|
124
121
|
#
|
125
122
|
|
@@ -1,11 +1,7 @@
|
|
1
1
|
# ruff: noqa: UP006 UP007
|
2
|
-
import typing as ta
|
3
|
-
|
4
2
|
from .base import Command
|
5
3
|
from .base import CommandExecutor
|
6
|
-
|
7
|
-
|
8
|
-
CommandExecutorMap = ta.NewType('CommandExecutorMap', ta.Mapping[ta.Type[Command], CommandExecutor])
|
4
|
+
from .types import CommandExecutorMap
|
9
5
|
|
10
6
|
|
11
7
|
class LocalCommandExecutor(CommandExecutor):
|
@@ -0,0 +1,23 @@
|
|
1
|
+
# ruff: noqa: TC003 UP006 UP007
|
2
|
+
import dataclasses as dc
|
3
|
+
import time
|
4
|
+
|
5
|
+
from .base import Command
|
6
|
+
from .base import CommandExecutor
|
7
|
+
|
8
|
+
|
9
|
+
##
|
10
|
+
|
11
|
+
|
12
|
+
@dc.dataclass(frozen=True)
|
13
|
+
class PingCommand(Command['PingCommand.Output']):
|
14
|
+
time: float = dc.field(default_factory=time.time)
|
15
|
+
|
16
|
+
@dc.dataclass(frozen=True)
|
17
|
+
class Output(Command.Output):
|
18
|
+
time: float
|
19
|
+
|
20
|
+
|
21
|
+
class PingCommandExecutor(CommandExecutor[PingCommand, PingCommand.Output]):
|
22
|
+
async def execute(self, cmd: PingCommand) -> PingCommand.Output:
|
23
|
+
return PingCommand.Output(cmd.time)
|
@@ -0,0 +1,72 @@
|
|
1
|
+
# ruff: noqa: UP006 UP007
|
2
|
+
import datetime
|
3
|
+
import os.path
|
4
|
+
import typing as ta
|
5
|
+
|
6
|
+
from .git import DeployGitManager
|
7
|
+
from .git import DeployGitRepo
|
8
|
+
from .git import DeployGitSpec
|
9
|
+
from .paths import DeployPath
|
10
|
+
from .paths import DeployPathOwner
|
11
|
+
from .types import DeployApp
|
12
|
+
from .types import DeployAppTag
|
13
|
+
from .types import DeployHome
|
14
|
+
from .types import DeployRev
|
15
|
+
from .types import DeployTag
|
16
|
+
from .venvs import DeployVenvManager
|
17
|
+
|
18
|
+
|
19
|
+
def make_deploy_tag(
|
20
|
+
rev: DeployRev,
|
21
|
+
now: ta.Optional[datetime.datetime] = None,
|
22
|
+
) -> DeployTag:
|
23
|
+
if now is None:
|
24
|
+
now = datetime.datetime.utcnow() # noqa
|
25
|
+
now_fmt = '%Y%m%dT%H%M%S'
|
26
|
+
now_str = now.strftime(now_fmt)
|
27
|
+
return DeployTag('-'.join([rev, now_str]))
|
28
|
+
|
29
|
+
|
30
|
+
class DeployAppManager(DeployPathOwner):
|
31
|
+
def __init__(
|
32
|
+
self,
|
33
|
+
*,
|
34
|
+
deploy_home: DeployHome,
|
35
|
+
git: DeployGitManager,
|
36
|
+
venvs: DeployVenvManager,
|
37
|
+
) -> None:
|
38
|
+
super().__init__()
|
39
|
+
|
40
|
+
self._deploy_home = deploy_home
|
41
|
+
self._git = git
|
42
|
+
self._venvs = venvs
|
43
|
+
|
44
|
+
self._dir = os.path.join(deploy_home, 'apps')
|
45
|
+
|
46
|
+
def get_deploy_paths(self) -> ta.AbstractSet[DeployPath]:
|
47
|
+
return {
|
48
|
+
DeployPath.parse('apps/@app/@tag'),
|
49
|
+
}
|
50
|
+
|
51
|
+
async def prepare_app(
|
52
|
+
self,
|
53
|
+
app: DeployApp,
|
54
|
+
rev: DeployRev,
|
55
|
+
repo: DeployGitRepo,
|
56
|
+
):
|
57
|
+
app_tag = DeployAppTag(app, make_deploy_tag(rev))
|
58
|
+
app_dir = os.path.join(self._dir, app, app_tag.tag)
|
59
|
+
|
60
|
+
#
|
61
|
+
|
62
|
+
await self._git.checkout(
|
63
|
+
DeployGitSpec(
|
64
|
+
repo=repo,
|
65
|
+
rev=rev,
|
66
|
+
),
|
67
|
+
app_dir,
|
68
|
+
)
|
69
|
+
|
70
|
+
#
|
71
|
+
|
72
|
+
await self._venvs.setup_app_venv(app_tag)
|
@@ -0,0 +1,136 @@
|
|
1
|
+
# ruff: noqa: UP006 UP007
|
2
|
+
"""
|
3
|
+
TODO:
|
4
|
+
- 'repos'?
|
5
|
+
|
6
|
+
git/github.com/wrmsr/omlish <- bootstrap repo
|
7
|
+
- shallow clone off bootstrap into /apps
|
8
|
+
|
9
|
+
github.com/wrmsr/omlish@rev
|
10
|
+
"""
|
11
|
+
import dataclasses as dc
|
12
|
+
import functools
|
13
|
+
import os.path
|
14
|
+
import typing as ta
|
15
|
+
|
16
|
+
from omlish.lite.asyncio.subprocesses import asyncio_subprocess_check_call
|
17
|
+
from omlish.lite.cached import async_cached_nullary
|
18
|
+
from omlish.lite.check import check
|
19
|
+
|
20
|
+
from .paths import DeployPath
|
21
|
+
from .paths import DeployPathOwner
|
22
|
+
from .types import DeployHome
|
23
|
+
from .types import DeployRev
|
24
|
+
|
25
|
+
|
26
|
+
##
|
27
|
+
|
28
|
+
|
29
|
+
@dc.dataclass(frozen=True)
|
30
|
+
class DeployGitRepo:
|
31
|
+
host: ta.Optional[str] = None
|
32
|
+
username: ta.Optional[str] = None
|
33
|
+
path: ta.Optional[str] = None
|
34
|
+
|
35
|
+
def __post_init__(self) -> None:
|
36
|
+
check.not_in('..', check.non_empty_str(self.host))
|
37
|
+
check.not_in('.', check.non_empty_str(self.path))
|
38
|
+
|
39
|
+
|
40
|
+
@dc.dataclass(frozen=True)
|
41
|
+
class DeployGitSpec:
|
42
|
+
repo: DeployGitRepo
|
43
|
+
rev: DeployRev
|
44
|
+
|
45
|
+
|
46
|
+
##
|
47
|
+
|
48
|
+
|
49
|
+
class DeployGitManager(DeployPathOwner):
|
50
|
+
def __init__(
|
51
|
+
self,
|
52
|
+
*,
|
53
|
+
deploy_home: DeployHome,
|
54
|
+
) -> None:
|
55
|
+
super().__init__()
|
56
|
+
|
57
|
+
self._deploy_home = deploy_home
|
58
|
+
self._dir = os.path.join(deploy_home, 'git')
|
59
|
+
|
60
|
+
self._repo_dirs: ta.Dict[DeployGitRepo, DeployGitManager.RepoDir] = {}
|
61
|
+
|
62
|
+
def get_deploy_paths(self) -> ta.AbstractSet[DeployPath]:
|
63
|
+
return {
|
64
|
+
DeployPath.parse('git'),
|
65
|
+
}
|
66
|
+
|
67
|
+
class RepoDir:
|
68
|
+
def __init__(
|
69
|
+
self,
|
70
|
+
git: 'DeployGitManager',
|
71
|
+
repo: DeployGitRepo,
|
72
|
+
) -> None:
|
73
|
+
super().__init__()
|
74
|
+
|
75
|
+
self._git = git
|
76
|
+
self._repo = repo
|
77
|
+
self._dir = os.path.join(
|
78
|
+
self._git._dir, # noqa
|
79
|
+
check.non_empty_str(repo.host),
|
80
|
+
check.non_empty_str(repo.path),
|
81
|
+
)
|
82
|
+
|
83
|
+
@property
|
84
|
+
def repo(self) -> DeployGitRepo:
|
85
|
+
return self._repo
|
86
|
+
|
87
|
+
@property
|
88
|
+
def url(self) -> str:
|
89
|
+
if self._repo.username is not None:
|
90
|
+
return f'{self._repo.username}@{self._repo.host}:{self._repo.path}'
|
91
|
+
else:
|
92
|
+
return f'https://{self._repo.host}/{self._repo.path}'
|
93
|
+
|
94
|
+
async def _call(self, *cmd: str) -> None:
|
95
|
+
await asyncio_subprocess_check_call(
|
96
|
+
*cmd,
|
97
|
+
cwd=self._dir,
|
98
|
+
)
|
99
|
+
|
100
|
+
@async_cached_nullary
|
101
|
+
async def init(self) -> None:
|
102
|
+
os.makedirs(self._dir, exist_ok=True)
|
103
|
+
if os.path.exists(os.path.join(self._dir, '.git')):
|
104
|
+
return
|
105
|
+
|
106
|
+
await self._call('git', 'init')
|
107
|
+
await self._call('git', 'remote', 'add', 'origin', self.url)
|
108
|
+
|
109
|
+
async def fetch(self, rev: DeployRev) -> None:
|
110
|
+
await self.init()
|
111
|
+
await self._call('git', 'fetch', '--depth=1', 'origin', rev)
|
112
|
+
|
113
|
+
async def checkout(self, rev: DeployRev, dst_dir: str) -> None:
|
114
|
+
check.state(not os.path.exists(dst_dir))
|
115
|
+
|
116
|
+
await self.fetch(rev)
|
117
|
+
|
118
|
+
# FIXME: temp dir swap
|
119
|
+
os.makedirs(dst_dir)
|
120
|
+
|
121
|
+
dst_call = functools.partial(asyncio_subprocess_check_call, cwd=dst_dir)
|
122
|
+
await dst_call('git', 'init')
|
123
|
+
|
124
|
+
await dst_call('git', 'remote', 'add', 'local', self._dir)
|
125
|
+
await dst_call('git', 'fetch', '--depth=1', 'local', rev)
|
126
|
+
await dst_call('git', 'checkout', rev)
|
127
|
+
|
128
|
+
def get_repo_dir(self, repo: DeployGitRepo) -> RepoDir:
|
129
|
+
try:
|
130
|
+
return self._repo_dirs[repo]
|
131
|
+
except KeyError:
|
132
|
+
repo_dir = self._repo_dirs[repo] = DeployGitManager.RepoDir(self, repo)
|
133
|
+
return repo_dir
|
134
|
+
|
135
|
+
async def checkout(self, spec: DeployGitSpec, dst_dir: str) -> None:
|
136
|
+
await self.get_repo_dir(spec.repo).checkout(spec.rev, dst_dir)
|
@@ -0,0 +1,40 @@
|
|
1
|
+
# ruff: noqa: UP006 UP007
|
2
|
+
import os.path
|
3
|
+
import typing as ta
|
4
|
+
|
5
|
+
from omlish.lite.inject import InjectorBindingOrBindings
|
6
|
+
from omlish.lite.inject import InjectorBindings
|
7
|
+
from omlish.lite.inject import inj
|
8
|
+
|
9
|
+
from ..commands.inject import bind_command
|
10
|
+
from .apps import DeployAppManager
|
11
|
+
from .commands import DeployCommand
|
12
|
+
from .commands import DeployCommandExecutor
|
13
|
+
from .config import DeployConfig
|
14
|
+
from .git import DeployGitManager
|
15
|
+
from .interp import InterpCommand
|
16
|
+
from .interp import InterpCommandExecutor
|
17
|
+
from .types import DeployHome
|
18
|
+
from .venvs import DeployVenvManager
|
19
|
+
|
20
|
+
|
21
|
+
def bind_deploy(
|
22
|
+
*,
|
23
|
+
deploy_config: DeployConfig,
|
24
|
+
) -> InjectorBindings:
|
25
|
+
lst: ta.List[InjectorBindingOrBindings] = [
|
26
|
+
inj.bind(deploy_config),
|
27
|
+
|
28
|
+
inj.bind(DeployAppManager, singleton=True),
|
29
|
+
inj.bind(DeployGitManager, singleton=True),
|
30
|
+
inj.bind(DeployVenvManager, singleton=True),
|
31
|
+
|
32
|
+
bind_command(DeployCommand, DeployCommandExecutor),
|
33
|
+
bind_command(InterpCommand, InterpCommandExecutor),
|
34
|
+
]
|
35
|
+
|
36
|
+
if (dh := deploy_config.deploy_home) is not None:
|
37
|
+
dh = os.path.abspath(os.path.expanduser(dh))
|
38
|
+
lst.append(inj.bind(dh, key=DeployHome))
|
39
|
+
|
40
|
+
return inj.as_bindings(*lst)
|
@@ -0,0 +1,230 @@
|
|
1
|
+
# ruff: noqa: UP006 UP007
|
2
|
+
"""
|
3
|
+
~deploy
|
4
|
+
deploy.pid (flock)
|
5
|
+
/app
|
6
|
+
/<appspec> - shallow clone
|
7
|
+
/conf
|
8
|
+
/env
|
9
|
+
<appspec>.env
|
10
|
+
/nginx
|
11
|
+
<appspec>.conf
|
12
|
+
/supervisor
|
13
|
+
<appspec>.conf
|
14
|
+
/venv
|
15
|
+
/<appspec>
|
16
|
+
|
17
|
+
?
|
18
|
+
/logs
|
19
|
+
/wrmsr--omlish--<spec>
|
20
|
+
|
21
|
+
spec = <name>--<rev>--<when>
|
22
|
+
|
23
|
+
==
|
24
|
+
|
25
|
+
for dn in [
|
26
|
+
'app',
|
27
|
+
'conf',
|
28
|
+
'conf/env',
|
29
|
+
'conf/nginx',
|
30
|
+
'conf/supervisor',
|
31
|
+
'venv',
|
32
|
+
]:
|
33
|
+
|
34
|
+
==
|
35
|
+
|
36
|
+
"""
|
37
|
+
import abc
|
38
|
+
import dataclasses as dc
|
39
|
+
import os.path
|
40
|
+
import typing as ta
|
41
|
+
|
42
|
+
from omlish.lite.check import check
|
43
|
+
|
44
|
+
|
45
|
+
DeployPathKind = ta.Literal['dir', 'file'] # ta.TypeAlias
|
46
|
+
DeployPathSpec = ta.Literal['app', 'tag'] # ta.TypeAlias
|
47
|
+
|
48
|
+
|
49
|
+
##
|
50
|
+
|
51
|
+
|
52
|
+
DEPLOY_PATH_SPEC_PLACEHOLDER = '@'
|
53
|
+
DEPLOY_PATH_SPEC_SEPARATORS = '-.'
|
54
|
+
|
55
|
+
DEPLOY_PATH_SPECS: ta.FrozenSet[str] = frozenset([
|
56
|
+
'app',
|
57
|
+
'tag', # <rev>-<dt>
|
58
|
+
])
|
59
|
+
|
60
|
+
|
61
|
+
class DeployPathError(Exception):
|
62
|
+
pass
|
63
|
+
|
64
|
+
|
65
|
+
@dc.dataclass(frozen=True)
|
66
|
+
class DeployPathPart(abc.ABC): # noqa
|
67
|
+
@property
|
68
|
+
@abc.abstractmethod
|
69
|
+
def kind(self) -> DeployPathKind:
|
70
|
+
raise NotImplementedError
|
71
|
+
|
72
|
+
@abc.abstractmethod
|
73
|
+
def render(self, specs: ta.Optional[ta.Mapping[DeployPathSpec, str]] = None) -> str:
|
74
|
+
raise NotImplementedError
|
75
|
+
|
76
|
+
|
77
|
+
#
|
78
|
+
|
79
|
+
|
80
|
+
class DirDeployPathPart(DeployPathPart, abc.ABC):
|
81
|
+
@property
|
82
|
+
def kind(self) -> DeployPathKind:
|
83
|
+
return 'dir'
|
84
|
+
|
85
|
+
@classmethod
|
86
|
+
def parse(cls, s: str) -> 'DirDeployPathPart':
|
87
|
+
if DEPLOY_PATH_SPEC_PLACEHOLDER in s:
|
88
|
+
check.equal(s[0], DEPLOY_PATH_SPEC_PLACEHOLDER)
|
89
|
+
return SpecDirDeployPathPart(s[1:])
|
90
|
+
else:
|
91
|
+
return ConstDirDeployPathPart(s)
|
92
|
+
|
93
|
+
|
94
|
+
class FileDeployPathPart(DeployPathPart, abc.ABC):
|
95
|
+
@property
|
96
|
+
def kind(self) -> DeployPathKind:
|
97
|
+
return 'file'
|
98
|
+
|
99
|
+
@classmethod
|
100
|
+
def parse(cls, s: str) -> 'FileDeployPathPart':
|
101
|
+
if DEPLOY_PATH_SPEC_PLACEHOLDER in s:
|
102
|
+
check.equal(s[0], DEPLOY_PATH_SPEC_PLACEHOLDER)
|
103
|
+
if not any(c in s for c in DEPLOY_PATH_SPEC_SEPARATORS):
|
104
|
+
return SpecFileDeployPathPart(s[1:], '')
|
105
|
+
else:
|
106
|
+
p = min(f for c in DEPLOY_PATH_SPEC_SEPARATORS if (f := s.find(c)) > 0)
|
107
|
+
return SpecFileDeployPathPart(s[1:p], s[p:])
|
108
|
+
else:
|
109
|
+
return ConstFileDeployPathPart(s)
|
110
|
+
|
111
|
+
|
112
|
+
#
|
113
|
+
|
114
|
+
|
115
|
+
@dc.dataclass(frozen=True)
|
116
|
+
class ConstDeployPathPart(DeployPathPart, abc.ABC):
|
117
|
+
name: str
|
118
|
+
|
119
|
+
def __post_init__(self) -> None:
|
120
|
+
check.non_empty_str(self.name)
|
121
|
+
check.not_in('/', self.name)
|
122
|
+
check.not_in(DEPLOY_PATH_SPEC_PLACEHOLDER, self.name)
|
123
|
+
|
124
|
+
def render(self, specs: ta.Optional[ta.Mapping[DeployPathSpec, str]] = None) -> str:
|
125
|
+
return self.name
|
126
|
+
|
127
|
+
|
128
|
+
class ConstDirDeployPathPart(ConstDeployPathPart, DirDeployPathPart):
|
129
|
+
pass
|
130
|
+
|
131
|
+
|
132
|
+
class ConstFileDeployPathPart(ConstDeployPathPart, FileDeployPathPart):
|
133
|
+
pass
|
134
|
+
|
135
|
+
|
136
|
+
#
|
137
|
+
|
138
|
+
|
139
|
+
@dc.dataclass(frozen=True)
|
140
|
+
class SpecDeployPathPart(DeployPathPart, abc.ABC):
|
141
|
+
spec: str # DeployPathSpec
|
142
|
+
|
143
|
+
def __post_init__(self) -> None:
|
144
|
+
check.non_empty_str(self.spec)
|
145
|
+
for c in [*DEPLOY_PATH_SPEC_SEPARATORS, DEPLOY_PATH_SPEC_PLACEHOLDER, '/']:
|
146
|
+
check.not_in(c, self.spec)
|
147
|
+
check.in_(self.spec, DEPLOY_PATH_SPECS)
|
148
|
+
|
149
|
+
def _render_spec(self, specs: ta.Optional[ta.Mapping[DeployPathSpec, str]] = None) -> str:
|
150
|
+
if specs is not None:
|
151
|
+
return specs[self.spec] # type: ignore
|
152
|
+
else:
|
153
|
+
return DEPLOY_PATH_SPEC_PLACEHOLDER + self.spec
|
154
|
+
|
155
|
+
|
156
|
+
@dc.dataclass(frozen=True)
|
157
|
+
class SpecDirDeployPathPart(SpecDeployPathPart, DirDeployPathPart):
|
158
|
+
def render(self, specs: ta.Optional[ta.Mapping[DeployPathSpec, str]] = None) -> str:
|
159
|
+
return self._render_spec(specs)
|
160
|
+
|
161
|
+
|
162
|
+
@dc.dataclass(frozen=True)
|
163
|
+
class SpecFileDeployPathPart(SpecDeployPathPart, FileDeployPathPart):
|
164
|
+
suffix: str
|
165
|
+
|
166
|
+
def __post_init__(self) -> None:
|
167
|
+
super().__post_init__()
|
168
|
+
if self.suffix:
|
169
|
+
for c in [DEPLOY_PATH_SPEC_PLACEHOLDER, '/']:
|
170
|
+
check.not_in(c, self.suffix)
|
171
|
+
|
172
|
+
def render(self, specs: ta.Optional[ta.Mapping[DeployPathSpec, str]] = None) -> str:
|
173
|
+
return self._render_spec(specs) + self.suffix
|
174
|
+
|
175
|
+
|
176
|
+
##
|
177
|
+
|
178
|
+
|
179
|
+
@dc.dataclass(frozen=True)
|
180
|
+
class DeployPath:
|
181
|
+
parts: ta.Sequence[DeployPathPart]
|
182
|
+
|
183
|
+
def __post_init__(self) -> None:
|
184
|
+
check.not_empty(self.parts)
|
185
|
+
for p in self.parts[:-1]:
|
186
|
+
check.equal(p.kind, 'dir')
|
187
|
+
|
188
|
+
pd = {}
|
189
|
+
for i, p in enumerate(self.parts):
|
190
|
+
if isinstance(p, SpecDeployPathPart):
|
191
|
+
if p.spec in pd:
|
192
|
+
raise DeployPathError('Duplicate specs in path', self)
|
193
|
+
pd[p.spec] = i
|
194
|
+
|
195
|
+
if 'tag' in pd:
|
196
|
+
if 'app' not in pd or pd['app'] >= pd['tag']:
|
197
|
+
raise DeployPathError('Tag spec in path without preceding app', self)
|
198
|
+
|
199
|
+
@property
|
200
|
+
def kind(self) -> ta.Literal['file', 'dir']:
|
201
|
+
return self.parts[-1].kind
|
202
|
+
|
203
|
+
def render(self, specs: ta.Optional[ta.Mapping[DeployPathSpec, str]] = None) -> str:
|
204
|
+
return os.path.join( # noqa
|
205
|
+
*[p.render(specs) for p in self.parts],
|
206
|
+
*([''] if self.kind == 'dir' else []),
|
207
|
+
)
|
208
|
+
|
209
|
+
@classmethod
|
210
|
+
def parse(cls, s: str) -> 'DeployPath':
|
211
|
+
tail_parse: ta.Callable[[str], DeployPathPart]
|
212
|
+
if s.endswith('/'):
|
213
|
+
tail_parse = DirDeployPathPart.parse
|
214
|
+
s = s[:-1]
|
215
|
+
else:
|
216
|
+
tail_parse = FileDeployPathPart.parse
|
217
|
+
ps = check.non_empty_str(s).split('/')
|
218
|
+
return cls([
|
219
|
+
*([DirDeployPathPart.parse(p) for p in ps[:-1]] if len(ps) > 1 else []),
|
220
|
+
tail_parse(ps[-1]),
|
221
|
+
])
|
222
|
+
|
223
|
+
|
224
|
+
##
|
225
|
+
|
226
|
+
|
227
|
+
class DeployPathOwner(abc.ABC):
|
228
|
+
@abc.abstractmethod
|
229
|
+
def get_deploy_paths(self) -> ta.AbstractSet[DeployPath]:
|
230
|
+
raise NotImplementedError
|
@@ -0,0 +1,13 @@
|
|
1
|
+
import typing as ta
|
2
|
+
|
3
|
+
|
4
|
+
DeployHome = ta.NewType('DeployHome', str)
|
5
|
+
|
6
|
+
DeployApp = ta.NewType('DeployApp', str)
|
7
|
+
DeployTag = ta.NewType('DeployTag', str)
|
8
|
+
DeployRev = ta.NewType('DeployRev', str)
|
9
|
+
|
10
|
+
|
11
|
+
class DeployAppTag(ta.NamedTuple):
|
12
|
+
app: DeployApp
|
13
|
+
tag: DeployTag
|