ominfra 0.0.0.dev154__tar.gz → 0.0.0.dev156__tar.gz

Sign up to get free protection for your applications and to get access to all the features.
Files changed (135) hide show
  1. {ominfra-0.0.0.dev154/ominfra.egg-info → ominfra-0.0.0.dev156}/PKG-INFO +3 -3
  2. {ominfra-0.0.0.dev154 → ominfra-0.0.0.dev156}/ominfra/manage/bootstrap.py +4 -0
  3. {ominfra-0.0.0.dev154 → ominfra-0.0.0.dev156}/ominfra/manage/bootstrap_.py +5 -0
  4. {ominfra-0.0.0.dev154 → ominfra-0.0.0.dev156}/ominfra/manage/commands/inject.py +8 -11
  5. ominfra-0.0.0.dev154/ominfra/manage/commands/execution.py → ominfra-0.0.0.dev156/ominfra/manage/commands/local.py +1 -5
  6. ominfra-0.0.0.dev156/ominfra/manage/commands/ping.py +23 -0
  7. {ominfra-0.0.0.dev154 → ominfra-0.0.0.dev156}/ominfra/manage/commands/subprocess.py +3 -4
  8. ominfra-0.0.0.dev156/ominfra/manage/commands/types.py +8 -0
  9. ominfra-0.0.0.dev156/ominfra/manage/deploy/apps.py +72 -0
  10. {ominfra-0.0.0.dev154/ominfra/manage/system → ominfra-0.0.0.dev156/ominfra/manage/deploy}/config.py +2 -2
  11. ominfra-0.0.0.dev156/ominfra/manage/deploy/git.py +136 -0
  12. ominfra-0.0.0.dev156/ominfra/manage/deploy/inject.py +40 -0
  13. ominfra-0.0.0.dev156/ominfra/manage/deploy/paths.py +230 -0
  14. ominfra-0.0.0.dev156/ominfra/manage/deploy/types.py +13 -0
  15. ominfra-0.0.0.dev156/ominfra/manage/deploy/venvs.py +66 -0
  16. {ominfra-0.0.0.dev154 → ominfra-0.0.0.dev156}/ominfra/manage/inject.py +20 -4
  17. {ominfra-0.0.0.dev154 → ominfra-0.0.0.dev156}/ominfra/manage/main.py +15 -27
  18. {ominfra-0.0.0.dev154 → ominfra-0.0.0.dev156}/ominfra/manage/remote/_main.py +1 -1
  19. {ominfra-0.0.0.dev154 → ominfra-0.0.0.dev156}/ominfra/manage/remote/config.py +0 -2
  20. {ominfra-0.0.0.dev154 → ominfra-0.0.0.dev156}/ominfra/manage/remote/connection.py +7 -24
  21. {ominfra-0.0.0.dev154 → ominfra-0.0.0.dev156}/ominfra/manage/remote/execution.py +1 -1
  22. {ominfra-0.0.0.dev154 → ominfra-0.0.0.dev156}/ominfra/manage/remote/inject.py +3 -14
  23. {ominfra-0.0.0.dev154 → ominfra-0.0.0.dev156}/ominfra/manage/remote/spawning.py +2 -2
  24. {ominfra-0.0.0.dev154 → ominfra-0.0.0.dev156}/ominfra/manage/system/commands.py +22 -2
  25. ominfra-0.0.0.dev156/ominfra/manage/system/config.py +10 -0
  26. {ominfra-0.0.0.dev154 → ominfra-0.0.0.dev156}/ominfra/manage/system/inject.py +16 -6
  27. {ominfra-0.0.0.dev154 → ominfra-0.0.0.dev156}/ominfra/manage/system/packages.py +38 -14
  28. ominfra-0.0.0.dev156/ominfra/manage/system/platforms.py +72 -0
  29. ominfra-0.0.0.dev156/ominfra/manage/targets/connection.py +150 -0
  30. ominfra-0.0.0.dev156/ominfra/manage/targets/inject.py +42 -0
  31. ominfra-0.0.0.dev156/ominfra/manage/targets/targets.py +87 -0
  32. {ominfra-0.0.0.dev154 → ominfra-0.0.0.dev156}/ominfra/scripts/journald2aws.py +205 -134
  33. {ominfra-0.0.0.dev154 → ominfra-0.0.0.dev156}/ominfra/scripts/manage.py +2192 -734
  34. {ominfra-0.0.0.dev154 → ominfra-0.0.0.dev156}/ominfra/scripts/supervisor.py +187 -25
  35. ominfra-0.0.0.dev156/ominfra/supervisor/configs.py +299 -0
  36. ominfra-0.0.0.dev156/ominfra/tools/__init__.py +0 -0
  37. {ominfra-0.0.0.dev154 → ominfra-0.0.0.dev156/ominfra.egg-info}/PKG-INFO +3 -3
  38. {ominfra-0.0.0.dev154 → ominfra-0.0.0.dev156}/ominfra.egg-info/SOURCES.txt +14 -3
  39. {ominfra-0.0.0.dev154 → ominfra-0.0.0.dev156}/ominfra.egg-info/requires.txt +2 -2
  40. {ominfra-0.0.0.dev154 → ominfra-0.0.0.dev156}/pyproject.toml +3 -3
  41. ominfra-0.0.0.dev154/ominfra/manage/deploy/inject.py +0 -19
  42. ominfra-0.0.0.dev154/ominfra/manage/deploy/paths.py +0 -177
  43. ominfra-0.0.0.dev154/ominfra/manage/system/types.py +0 -5
  44. ominfra-0.0.0.dev154/ominfra/supervisor/configs.py +0 -154
  45. {ominfra-0.0.0.dev154 → ominfra-0.0.0.dev156}/LICENSE +0 -0
  46. {ominfra-0.0.0.dev154 → ominfra-0.0.0.dev156}/MANIFEST.in +0 -0
  47. {ominfra-0.0.0.dev154 → ominfra-0.0.0.dev156}/README.rst +0 -0
  48. {ominfra-0.0.0.dev154 → ominfra-0.0.0.dev156}/ominfra/.manifests.json +0 -0
  49. {ominfra-0.0.0.dev154 → ominfra-0.0.0.dev156}/ominfra/__about__.py +0 -0
  50. {ominfra-0.0.0.dev154 → ominfra-0.0.0.dev156}/ominfra/__init__.py +0 -0
  51. {ominfra-0.0.0.dev154 → ominfra-0.0.0.dev156}/ominfra/clouds/__init__.py +0 -0
  52. {ominfra-0.0.0.dev154 → ominfra-0.0.0.dev156}/ominfra/clouds/aws/__init__.py +0 -0
  53. {ominfra-0.0.0.dev154 → ominfra-0.0.0.dev156}/ominfra/clouds/aws/__main__.py +0 -0
  54. {ominfra-0.0.0.dev154 → ominfra-0.0.0.dev156}/ominfra/clouds/aws/auth.py +0 -0
  55. {ominfra-0.0.0.dev154 → ominfra-0.0.0.dev156}/ominfra/clouds/aws/cli.py +0 -0
  56. {ominfra-0.0.0.dev154 → ominfra-0.0.0.dev156}/ominfra/clouds/aws/dataclasses.py +0 -0
  57. {ominfra-0.0.0.dev154 → ominfra-0.0.0.dev156}/ominfra/clouds/aws/journald2aws/__init__.py +0 -0
  58. {ominfra-0.0.0.dev154 → ominfra-0.0.0.dev156}/ominfra/clouds/aws/journald2aws/__main__.py +0 -0
  59. {ominfra-0.0.0.dev154 → ominfra-0.0.0.dev156}/ominfra/clouds/aws/journald2aws/cursor.py +0 -0
  60. {ominfra-0.0.0.dev154 → ominfra-0.0.0.dev156}/ominfra/clouds/aws/journald2aws/driver.py +0 -0
  61. {ominfra-0.0.0.dev154 → ominfra-0.0.0.dev156}/ominfra/clouds/aws/journald2aws/main.py +0 -0
  62. {ominfra-0.0.0.dev154 → ominfra-0.0.0.dev156}/ominfra/clouds/aws/journald2aws/poster.py +0 -0
  63. {ominfra-0.0.0.dev154 → ominfra-0.0.0.dev156}/ominfra/clouds/aws/logs.py +0 -0
  64. {ominfra-0.0.0.dev154 → ominfra-0.0.0.dev156}/ominfra/clouds/aws/metadata.py +0 -0
  65. {ominfra-0.0.0.dev154 → ominfra-0.0.0.dev156}/ominfra/clouds/gcp/__init__.py +0 -0
  66. {ominfra-0.0.0.dev154 → ominfra-0.0.0.dev156}/ominfra/clouds/gcp/auth.py +0 -0
  67. {ominfra-0.0.0.dev154 → ominfra-0.0.0.dev156}/ominfra/cmds.py +0 -0
  68. {ominfra-0.0.0.dev154 → ominfra-0.0.0.dev156}/ominfra/configs.py +0 -0
  69. {ominfra-0.0.0.dev154 → ominfra-0.0.0.dev156}/ominfra/journald/__init__.py +0 -0
  70. {ominfra-0.0.0.dev154 → ominfra-0.0.0.dev156}/ominfra/journald/fields.py +0 -0
  71. {ominfra-0.0.0.dev154 → ominfra-0.0.0.dev156}/ominfra/journald/genmessages.py +0 -0
  72. {ominfra-0.0.0.dev154 → ominfra-0.0.0.dev156}/ominfra/journald/messages.py +0 -0
  73. {ominfra-0.0.0.dev154 → ominfra-0.0.0.dev156}/ominfra/journald/tailer.py +0 -0
  74. {ominfra-0.0.0.dev154 → ominfra-0.0.0.dev156}/ominfra/manage/__init__.py +0 -0
  75. {ominfra-0.0.0.dev154 → ominfra-0.0.0.dev156}/ominfra/manage/__main__.py +0 -0
  76. {ominfra-0.0.0.dev154 → ominfra-0.0.0.dev156}/ominfra/manage/commands/__init__.py +0 -0
  77. {ominfra-0.0.0.dev154 → ominfra-0.0.0.dev156}/ominfra/manage/commands/base.py +0 -0
  78. {ominfra-0.0.0.dev154 → ominfra-0.0.0.dev156}/ominfra/manage/commands/marshal.py +0 -0
  79. {ominfra-0.0.0.dev154 → ominfra-0.0.0.dev156}/ominfra/manage/config.py +0 -0
  80. {ominfra-0.0.0.dev154 → ominfra-0.0.0.dev156}/ominfra/manage/deploy/__init__.py +0 -0
  81. {ominfra-0.0.0.dev154 → ominfra-0.0.0.dev156}/ominfra/manage/deploy/commands.py +0 -0
  82. {ominfra-0.0.0.dev154/ominfra/manage/commands → ominfra-0.0.0.dev156/ominfra/manage/deploy}/interp.py +0 -0
  83. {ominfra-0.0.0.dev154 → ominfra-0.0.0.dev156}/ominfra/manage/marshal.py +0 -0
  84. {ominfra-0.0.0.dev154 → ominfra-0.0.0.dev156}/ominfra/manage/remote/__init__.py +0 -0
  85. {ominfra-0.0.0.dev154 → ominfra-0.0.0.dev156}/ominfra/manage/remote/channel.py +0 -0
  86. {ominfra-0.0.0.dev154 → ominfra-0.0.0.dev156}/ominfra/manage/remote/payload.py +0 -0
  87. {ominfra-0.0.0.dev154 → ominfra-0.0.0.dev156}/ominfra/manage/system/__init__.py +0 -0
  88. {ominfra-0.0.0.dev154/ominfra/scripts → ominfra-0.0.0.dev156/ominfra/manage/targets}/__init__.py +0 -0
  89. {ominfra-0.0.0.dev154 → ominfra-0.0.0.dev156}/ominfra/pyremote.py +0 -0
  90. {ominfra-0.0.0.dev154/ominfra/supervisor/utils → ominfra-0.0.0.dev156/ominfra/scripts}/__init__.py +0 -0
  91. {ominfra-0.0.0.dev154 → ominfra-0.0.0.dev156}/ominfra/ssh.py +0 -0
  92. {ominfra-0.0.0.dev154 → ominfra-0.0.0.dev156}/ominfra/supervisor/LICENSE.txt +0 -0
  93. {ominfra-0.0.0.dev154 → ominfra-0.0.0.dev156}/ominfra/supervisor/__init__.py +0 -0
  94. {ominfra-0.0.0.dev154 → ominfra-0.0.0.dev156}/ominfra/supervisor/__main__.py +0 -0
  95. {ominfra-0.0.0.dev154 → ominfra-0.0.0.dev156}/ominfra/supervisor/dispatchers.py +0 -0
  96. {ominfra-0.0.0.dev154 → ominfra-0.0.0.dev156}/ominfra/supervisor/dispatchersimpl.py +0 -0
  97. {ominfra-0.0.0.dev154 → ominfra-0.0.0.dev156}/ominfra/supervisor/events.py +0 -0
  98. {ominfra-0.0.0.dev154 → ominfra-0.0.0.dev156}/ominfra/supervisor/exceptions.py +0 -0
  99. {ominfra-0.0.0.dev154 → ominfra-0.0.0.dev156}/ominfra/supervisor/groups.py +0 -0
  100. {ominfra-0.0.0.dev154 → ominfra-0.0.0.dev156}/ominfra/supervisor/groupsimpl.py +0 -0
  101. {ominfra-0.0.0.dev154 → ominfra-0.0.0.dev156}/ominfra/supervisor/http.py +0 -0
  102. {ominfra-0.0.0.dev154 → ominfra-0.0.0.dev156}/ominfra/supervisor/inject.py +0 -0
  103. {ominfra-0.0.0.dev154 → ominfra-0.0.0.dev156}/ominfra/supervisor/io.py +0 -0
  104. {ominfra-0.0.0.dev154 → ominfra-0.0.0.dev156}/ominfra/supervisor/main.py +0 -0
  105. {ominfra-0.0.0.dev154 → ominfra-0.0.0.dev156}/ominfra/supervisor/pipes.py +0 -0
  106. {ominfra-0.0.0.dev154 → ominfra-0.0.0.dev156}/ominfra/supervisor/privileges.py +0 -0
  107. {ominfra-0.0.0.dev154 → ominfra-0.0.0.dev156}/ominfra/supervisor/process.py +0 -0
  108. {ominfra-0.0.0.dev154 → ominfra-0.0.0.dev156}/ominfra/supervisor/processimpl.py +0 -0
  109. {ominfra-0.0.0.dev154 → ominfra-0.0.0.dev156}/ominfra/supervisor/setup.py +0 -0
  110. {ominfra-0.0.0.dev154 → ominfra-0.0.0.dev156}/ominfra/supervisor/setupimpl.py +0 -0
  111. {ominfra-0.0.0.dev154 → ominfra-0.0.0.dev156}/ominfra/supervisor/signals.py +0 -0
  112. {ominfra-0.0.0.dev154 → ominfra-0.0.0.dev156}/ominfra/supervisor/spawning.py +0 -0
  113. {ominfra-0.0.0.dev154 → ominfra-0.0.0.dev156}/ominfra/supervisor/spawningimpl.py +0 -0
  114. {ominfra-0.0.0.dev154 → ominfra-0.0.0.dev156}/ominfra/supervisor/states.py +0 -0
  115. {ominfra-0.0.0.dev154 → ominfra-0.0.0.dev156}/ominfra/supervisor/supervisor.py +0 -0
  116. {ominfra-0.0.0.dev154 → ominfra-0.0.0.dev156}/ominfra/supervisor/types.py +0 -0
  117. {ominfra-0.0.0.dev154/ominfra/tailscale → ominfra-0.0.0.dev156/ominfra/supervisor/utils}/__init__.py +0 -0
  118. {ominfra-0.0.0.dev154 → ominfra-0.0.0.dev156}/ominfra/supervisor/utils/collections.py +0 -0
  119. {ominfra-0.0.0.dev154 → ominfra-0.0.0.dev156}/ominfra/supervisor/utils/diag.py +0 -0
  120. {ominfra-0.0.0.dev154 → ominfra-0.0.0.dev156}/ominfra/supervisor/utils/fds.py +0 -0
  121. {ominfra-0.0.0.dev154 → ominfra-0.0.0.dev156}/ominfra/supervisor/utils/fs.py +0 -0
  122. {ominfra-0.0.0.dev154 → ominfra-0.0.0.dev156}/ominfra/supervisor/utils/os.py +0 -0
  123. {ominfra-0.0.0.dev154 → ominfra-0.0.0.dev156}/ominfra/supervisor/utils/ostypes.py +0 -0
  124. {ominfra-0.0.0.dev154 → ominfra-0.0.0.dev156}/ominfra/supervisor/utils/signals.py +0 -0
  125. {ominfra-0.0.0.dev154 → ominfra-0.0.0.dev156}/ominfra/supervisor/utils/strings.py +0 -0
  126. {ominfra-0.0.0.dev154 → ominfra-0.0.0.dev156}/ominfra/supervisor/utils/users.py +0 -0
  127. {ominfra-0.0.0.dev154/ominfra/tools → ominfra-0.0.0.dev156/ominfra/tailscale}/__init__.py +0 -0
  128. {ominfra-0.0.0.dev154 → ominfra-0.0.0.dev156}/ominfra/tailscale/api.py +0 -0
  129. {ominfra-0.0.0.dev154 → ominfra-0.0.0.dev156}/ominfra/tailscale/cli.py +0 -0
  130. {ominfra-0.0.0.dev154 → ominfra-0.0.0.dev156}/ominfra/threadworkers.py +0 -0
  131. {ominfra-0.0.0.dev154 → ominfra-0.0.0.dev156}/ominfra/tools/listresources.py +0 -0
  132. {ominfra-0.0.0.dev154 → ominfra-0.0.0.dev156}/ominfra.egg-info/dependency_links.txt +0 -0
  133. {ominfra-0.0.0.dev154 → ominfra-0.0.0.dev156}/ominfra.egg-info/entry_points.txt +0 -0
  134. {ominfra-0.0.0.dev154 → ominfra-0.0.0.dev156}/ominfra.egg-info/top_level.txt +0 -0
  135. {ominfra-0.0.0.dev154 → ominfra-0.0.0.dev156}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: ominfra
3
- Version: 0.0.0.dev154
3
+ Version: 0.0.0.dev156
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.dev154
16
- Requires-Dist: omlish==0.0.0.dev154
15
+ Requires-Dist: omdev==0.0.0.dev156
16
+ Requires-Dist: omlish==0.0.0.dev156
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 .execution import CommandExecutorMap
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
- command_cls: ta.Any
117
- executor_cls: ta.Any
118
- for command_cls, executor_cls in [
119
- (SubprocessCommand, SubprocessCommandExecutor),
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)
@@ -6,8 +6,7 @@ import subprocess
6
6
  import time
7
7
  import typing as ta
8
8
 
9
- from omlish.lite.asyncio.subprocesses import asyncio_subprocess_communicate
10
- from omlish.lite.asyncio.subprocesses import asyncio_subprocess_popen
9
+ from omlish.lite.asyncio.subprocesses import asyncio_subprocesses
11
10
  from omlish.lite.check import check
12
11
  from omlish.lite.subprocesses import SUBPROCESS_CHANNEL_OPTION_VALUES
13
12
  from omlish.lite.subprocesses import SubprocessChannelOption
@@ -51,7 +50,7 @@ class SubprocessCommand(Command['SubprocessCommand.Output']):
51
50
  class SubprocessCommandExecutor(CommandExecutor[SubprocessCommand, SubprocessCommand.Output]):
52
51
  async def execute(self, cmd: SubprocessCommand) -> SubprocessCommand.Output:
53
52
  proc: asyncio.subprocess.Process
54
- async with asyncio_subprocess_popen(
53
+ async with asyncio_subprocesses.popen(
55
54
  *subprocess_maybe_shell_wrap_exec(*cmd.cmd),
56
55
 
57
56
  shell=cmd.shell,
@@ -65,7 +64,7 @@ class SubprocessCommandExecutor(CommandExecutor[SubprocessCommand, SubprocessCom
65
64
  timeout=cmd.timeout,
66
65
  ) as proc:
67
66
  start_time = time.time()
68
- stdout, stderr = await asyncio_subprocess_communicate(
67
+ stdout, stderr = await asyncio_subprocesses.communicate(
69
68
  proc,
70
69
  input=cmd.input,
71
70
  timeout=cmd.timeout,
@@ -0,0 +1,8 @@
1
+ # ruff: noqa: UP006 UP007
2
+ import typing as ta
3
+
4
+ from .base import Command
5
+ from .base import CommandExecutor
6
+
7
+
8
+ CommandExecutorMap = ta.NewType('CommandExecutorMap', ta.Mapping[ta.Type[Command], CommandExecutor])
@@ -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)
@@ -4,5 +4,5 @@ import typing as ta
4
4
 
5
5
 
6
6
  @dc.dataclass(frozen=True)
7
- class SystemConfig:
8
- platform: ta.Optional[str] = None
7
+ class DeployConfig:
8
+ deploy_home: ta.Optional[str] = None
@@ -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_subprocesses
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_subprocesses.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_subprocesses.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