ominfra 0.0.0.dev153__tar.gz → 0.0.0.dev155__tar.gz
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-0.0.0.dev153/ominfra.egg-info → ominfra-0.0.0.dev155}/PKG-INFO +3 -3
- {ominfra-0.0.0.dev153 → ominfra-0.0.0.dev155}/ominfra/manage/bootstrap.py +4 -0
- {ominfra-0.0.0.dev153 → ominfra-0.0.0.dev155}/ominfra/manage/bootstrap_.py +5 -0
- {ominfra-0.0.0.dev153 → ominfra-0.0.0.dev155}/ominfra/manage/commands/inject.py +8 -11
- ominfra-0.0.0.dev153/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.dev153/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.dev153 → ominfra-0.0.0.dev155}/ominfra/manage/inject.py +20 -4
- {ominfra-0.0.0.dev153 → ominfra-0.0.0.dev155}/ominfra/manage/main.py +15 -27
- {ominfra-0.0.0.dev153 → ominfra-0.0.0.dev155}/ominfra/manage/remote/_main.py +1 -1
- {ominfra-0.0.0.dev153 → ominfra-0.0.0.dev155}/ominfra/manage/remote/config.py +0 -2
- {ominfra-0.0.0.dev153 → ominfra-0.0.0.dev155}/ominfra/manage/remote/connection.py +7 -24
- {ominfra-0.0.0.dev153 → ominfra-0.0.0.dev155}/ominfra/manage/remote/execution.py +1 -1
- {ominfra-0.0.0.dev153 → ominfra-0.0.0.dev155}/ominfra/manage/remote/inject.py +3 -14
- {ominfra-0.0.0.dev153 → 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.dev153 → ominfra-0.0.0.dev155}/ominfra/manage/system/inject.py +16 -6
- {ominfra-0.0.0.dev153 → 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.dev153 → ominfra-0.0.0.dev155}/ominfra/scripts/journald2aws.py +24 -7
- {ominfra-0.0.0.dev153 → ominfra-0.0.0.dev155}/ominfra/scripts/manage.py +1880 -438
- {ominfra-0.0.0.dev153 → 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.dev153 → ominfra-0.0.0.dev155/ominfra.egg-info}/PKG-INFO +3 -3
- {ominfra-0.0.0.dev153 → ominfra-0.0.0.dev155}/ominfra.egg-info/SOURCES.txt +14 -3
- {ominfra-0.0.0.dev153 → ominfra-0.0.0.dev155}/ominfra.egg-info/requires.txt +2 -2
- {ominfra-0.0.0.dev153 → ominfra-0.0.0.dev155}/pyproject.toml +3 -3
- ominfra-0.0.0.dev153/ominfra/manage/deploy/inject.py +0 -19
- ominfra-0.0.0.dev153/ominfra/manage/deploy/paths.py +0 -177
- ominfra-0.0.0.dev153/ominfra/manage/system/types.py +0 -5
- ominfra-0.0.0.dev153/ominfra/supervisor/configs.py +0 -154
- {ominfra-0.0.0.dev153 → ominfra-0.0.0.dev155}/LICENSE +0 -0
- {ominfra-0.0.0.dev153 → ominfra-0.0.0.dev155}/MANIFEST.in +0 -0
- {ominfra-0.0.0.dev153 → ominfra-0.0.0.dev155}/README.rst +0 -0
- {ominfra-0.0.0.dev153 → ominfra-0.0.0.dev155}/ominfra/.manifests.json +0 -0
- {ominfra-0.0.0.dev153 → ominfra-0.0.0.dev155}/ominfra/__about__.py +0 -0
- {ominfra-0.0.0.dev153 → ominfra-0.0.0.dev155}/ominfra/__init__.py +0 -0
- {ominfra-0.0.0.dev153 → ominfra-0.0.0.dev155}/ominfra/clouds/__init__.py +0 -0
- {ominfra-0.0.0.dev153 → ominfra-0.0.0.dev155}/ominfra/clouds/aws/__init__.py +0 -0
- {ominfra-0.0.0.dev153 → ominfra-0.0.0.dev155}/ominfra/clouds/aws/__main__.py +0 -0
- {ominfra-0.0.0.dev153 → ominfra-0.0.0.dev155}/ominfra/clouds/aws/auth.py +0 -0
- {ominfra-0.0.0.dev153 → ominfra-0.0.0.dev155}/ominfra/clouds/aws/cli.py +0 -0
- {ominfra-0.0.0.dev153 → ominfra-0.0.0.dev155}/ominfra/clouds/aws/dataclasses.py +0 -0
- {ominfra-0.0.0.dev153 → ominfra-0.0.0.dev155}/ominfra/clouds/aws/journald2aws/__init__.py +0 -0
- {ominfra-0.0.0.dev153 → ominfra-0.0.0.dev155}/ominfra/clouds/aws/journald2aws/__main__.py +0 -0
- {ominfra-0.0.0.dev153 → ominfra-0.0.0.dev155}/ominfra/clouds/aws/journald2aws/cursor.py +0 -0
- {ominfra-0.0.0.dev153 → ominfra-0.0.0.dev155}/ominfra/clouds/aws/journald2aws/driver.py +0 -0
- {ominfra-0.0.0.dev153 → ominfra-0.0.0.dev155}/ominfra/clouds/aws/journald2aws/main.py +0 -0
- {ominfra-0.0.0.dev153 → ominfra-0.0.0.dev155}/ominfra/clouds/aws/journald2aws/poster.py +0 -0
- {ominfra-0.0.0.dev153 → ominfra-0.0.0.dev155}/ominfra/clouds/aws/logs.py +0 -0
- {ominfra-0.0.0.dev153 → ominfra-0.0.0.dev155}/ominfra/clouds/aws/metadata.py +0 -0
- {ominfra-0.0.0.dev153 → ominfra-0.0.0.dev155}/ominfra/clouds/gcp/__init__.py +0 -0
- {ominfra-0.0.0.dev153 → ominfra-0.0.0.dev155}/ominfra/clouds/gcp/auth.py +0 -0
- {ominfra-0.0.0.dev153 → ominfra-0.0.0.dev155}/ominfra/cmds.py +0 -0
- {ominfra-0.0.0.dev153 → ominfra-0.0.0.dev155}/ominfra/configs.py +0 -0
- {ominfra-0.0.0.dev153 → ominfra-0.0.0.dev155}/ominfra/journald/__init__.py +0 -0
- {ominfra-0.0.0.dev153 → ominfra-0.0.0.dev155}/ominfra/journald/fields.py +0 -0
- {ominfra-0.0.0.dev153 → ominfra-0.0.0.dev155}/ominfra/journald/genmessages.py +0 -0
- {ominfra-0.0.0.dev153 → ominfra-0.0.0.dev155}/ominfra/journald/messages.py +0 -0
- {ominfra-0.0.0.dev153 → ominfra-0.0.0.dev155}/ominfra/journald/tailer.py +0 -0
- {ominfra-0.0.0.dev153 → ominfra-0.0.0.dev155}/ominfra/manage/__init__.py +0 -0
- {ominfra-0.0.0.dev153 → ominfra-0.0.0.dev155}/ominfra/manage/__main__.py +0 -0
- {ominfra-0.0.0.dev153 → ominfra-0.0.0.dev155}/ominfra/manage/commands/__init__.py +0 -0
- {ominfra-0.0.0.dev153 → ominfra-0.0.0.dev155}/ominfra/manage/commands/base.py +0 -0
- {ominfra-0.0.0.dev153 → ominfra-0.0.0.dev155}/ominfra/manage/commands/marshal.py +0 -0
- {ominfra-0.0.0.dev153 → ominfra-0.0.0.dev155}/ominfra/manage/commands/subprocess.py +0 -0
- {ominfra-0.0.0.dev153 → ominfra-0.0.0.dev155}/ominfra/manage/config.py +0 -0
- {ominfra-0.0.0.dev153 → ominfra-0.0.0.dev155}/ominfra/manage/deploy/__init__.py +0 -0
- {ominfra-0.0.0.dev153 → ominfra-0.0.0.dev155}/ominfra/manage/deploy/commands.py +0 -0
- {ominfra-0.0.0.dev153/ominfra/manage/commands → ominfra-0.0.0.dev155/ominfra/manage/deploy}/interp.py +0 -0
- {ominfra-0.0.0.dev153 → ominfra-0.0.0.dev155}/ominfra/manage/marshal.py +0 -0
- {ominfra-0.0.0.dev153 → ominfra-0.0.0.dev155}/ominfra/manage/remote/__init__.py +0 -0
- {ominfra-0.0.0.dev153 → ominfra-0.0.0.dev155}/ominfra/manage/remote/channel.py +0 -0
- {ominfra-0.0.0.dev153 → ominfra-0.0.0.dev155}/ominfra/manage/remote/payload.py +0 -0
- {ominfra-0.0.0.dev153 → ominfra-0.0.0.dev155}/ominfra/manage/remote/spawning.py +0 -0
- {ominfra-0.0.0.dev153 → ominfra-0.0.0.dev155}/ominfra/manage/system/__init__.py +0 -0
- {ominfra-0.0.0.dev153/ominfra/scripts → ominfra-0.0.0.dev155/ominfra/manage/targets}/__init__.py +0 -0
- {ominfra-0.0.0.dev153 → ominfra-0.0.0.dev155}/ominfra/pyremote.py +0 -0
- {ominfra-0.0.0.dev153/ominfra/supervisor/utils → ominfra-0.0.0.dev155/ominfra/scripts}/__init__.py +0 -0
- {ominfra-0.0.0.dev153 → ominfra-0.0.0.dev155}/ominfra/ssh.py +0 -0
- {ominfra-0.0.0.dev153 → ominfra-0.0.0.dev155}/ominfra/supervisor/LICENSE.txt +0 -0
- {ominfra-0.0.0.dev153 → ominfra-0.0.0.dev155}/ominfra/supervisor/__init__.py +0 -0
- {ominfra-0.0.0.dev153 → ominfra-0.0.0.dev155}/ominfra/supervisor/__main__.py +0 -0
- {ominfra-0.0.0.dev153 → ominfra-0.0.0.dev155}/ominfra/supervisor/dispatchers.py +0 -0
- {ominfra-0.0.0.dev153 → ominfra-0.0.0.dev155}/ominfra/supervisor/dispatchersimpl.py +0 -0
- {ominfra-0.0.0.dev153 → ominfra-0.0.0.dev155}/ominfra/supervisor/events.py +0 -0
- {ominfra-0.0.0.dev153 → ominfra-0.0.0.dev155}/ominfra/supervisor/exceptions.py +0 -0
- {ominfra-0.0.0.dev153 → ominfra-0.0.0.dev155}/ominfra/supervisor/groups.py +0 -0
- {ominfra-0.0.0.dev153 → ominfra-0.0.0.dev155}/ominfra/supervisor/groupsimpl.py +0 -0
- {ominfra-0.0.0.dev153 → ominfra-0.0.0.dev155}/ominfra/supervisor/http.py +0 -0
- {ominfra-0.0.0.dev153 → ominfra-0.0.0.dev155}/ominfra/supervisor/inject.py +0 -0
- {ominfra-0.0.0.dev153 → ominfra-0.0.0.dev155}/ominfra/supervisor/io.py +0 -0
- {ominfra-0.0.0.dev153 → ominfra-0.0.0.dev155}/ominfra/supervisor/main.py +0 -0
- {ominfra-0.0.0.dev153 → ominfra-0.0.0.dev155}/ominfra/supervisor/pipes.py +0 -0
- {ominfra-0.0.0.dev153 → ominfra-0.0.0.dev155}/ominfra/supervisor/privileges.py +0 -0
- {ominfra-0.0.0.dev153 → ominfra-0.0.0.dev155}/ominfra/supervisor/process.py +0 -0
- {ominfra-0.0.0.dev153 → ominfra-0.0.0.dev155}/ominfra/supervisor/processimpl.py +0 -0
- {ominfra-0.0.0.dev153 → ominfra-0.0.0.dev155}/ominfra/supervisor/setup.py +0 -0
- {ominfra-0.0.0.dev153 → ominfra-0.0.0.dev155}/ominfra/supervisor/setupimpl.py +0 -0
- {ominfra-0.0.0.dev153 → ominfra-0.0.0.dev155}/ominfra/supervisor/signals.py +0 -0
- {ominfra-0.0.0.dev153 → ominfra-0.0.0.dev155}/ominfra/supervisor/spawning.py +0 -0
- {ominfra-0.0.0.dev153 → ominfra-0.0.0.dev155}/ominfra/supervisor/spawningimpl.py +0 -0
- {ominfra-0.0.0.dev153 → ominfra-0.0.0.dev155}/ominfra/supervisor/states.py +0 -0
- {ominfra-0.0.0.dev153 → ominfra-0.0.0.dev155}/ominfra/supervisor/supervisor.py +0 -0
- {ominfra-0.0.0.dev153 → ominfra-0.0.0.dev155}/ominfra/supervisor/types.py +0 -0
- {ominfra-0.0.0.dev153/ominfra/tailscale → ominfra-0.0.0.dev155/ominfra/supervisor/utils}/__init__.py +0 -0
- {ominfra-0.0.0.dev153 → ominfra-0.0.0.dev155}/ominfra/supervisor/utils/collections.py +0 -0
- {ominfra-0.0.0.dev153 → ominfra-0.0.0.dev155}/ominfra/supervisor/utils/diag.py +0 -0
- {ominfra-0.0.0.dev153 → ominfra-0.0.0.dev155}/ominfra/supervisor/utils/fds.py +0 -0
- {ominfra-0.0.0.dev153 → ominfra-0.0.0.dev155}/ominfra/supervisor/utils/fs.py +0 -0
- {ominfra-0.0.0.dev153 → ominfra-0.0.0.dev155}/ominfra/supervisor/utils/os.py +0 -0
- {ominfra-0.0.0.dev153 → ominfra-0.0.0.dev155}/ominfra/supervisor/utils/ostypes.py +0 -0
- {ominfra-0.0.0.dev153 → ominfra-0.0.0.dev155}/ominfra/supervisor/utils/signals.py +0 -0
- {ominfra-0.0.0.dev153 → ominfra-0.0.0.dev155}/ominfra/supervisor/utils/strings.py +0 -0
- {ominfra-0.0.0.dev153 → ominfra-0.0.0.dev155}/ominfra/supervisor/utils/users.py +0 -0
- {ominfra-0.0.0.dev153/ominfra/tools → ominfra-0.0.0.dev155/ominfra/tailscale}/__init__.py +0 -0
- {ominfra-0.0.0.dev153 → ominfra-0.0.0.dev155}/ominfra/tailscale/api.py +0 -0
- {ominfra-0.0.0.dev153 → ominfra-0.0.0.dev155}/ominfra/tailscale/cli.py +0 -0
- {ominfra-0.0.0.dev153 → ominfra-0.0.0.dev155}/ominfra/threadworkers.py +0 -0
- {ominfra-0.0.0.dev153 → ominfra-0.0.0.dev155}/ominfra/tools/listresources.py +0 -0
- {ominfra-0.0.0.dev153 → ominfra-0.0.0.dev155}/ominfra.egg-info/dependency_links.txt +0 -0
- {ominfra-0.0.0.dev153 → ominfra-0.0.0.dev155}/ominfra.egg-info/entry_points.txt +0 -0
- {ominfra-0.0.0.dev153 → ominfra-0.0.0.dev155}/ominfra.egg-info/top_level.txt +0 -0
- {ominfra-0.0.0.dev153 → 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
|