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.
Files changed (135) hide show
  1. {ominfra-0.0.0.dev153/ominfra.egg-info → ominfra-0.0.0.dev155}/PKG-INFO +3 -3
  2. {ominfra-0.0.0.dev153 → ominfra-0.0.0.dev155}/ominfra/manage/bootstrap.py +4 -0
  3. {ominfra-0.0.0.dev153 → ominfra-0.0.0.dev155}/ominfra/manage/bootstrap_.py +5 -0
  4. {ominfra-0.0.0.dev153 → ominfra-0.0.0.dev155}/ominfra/manage/commands/inject.py +8 -11
  5. ominfra-0.0.0.dev153/ominfra/manage/commands/execution.py → ominfra-0.0.0.dev155/ominfra/manage/commands/local.py +1 -5
  6. ominfra-0.0.0.dev155/ominfra/manage/commands/ping.py +23 -0
  7. ominfra-0.0.0.dev155/ominfra/manage/commands/types.py +8 -0
  8. ominfra-0.0.0.dev155/ominfra/manage/deploy/apps.py +72 -0
  9. {ominfra-0.0.0.dev153/ominfra/manage/system → ominfra-0.0.0.dev155/ominfra/manage/deploy}/config.py +2 -2
  10. ominfra-0.0.0.dev155/ominfra/manage/deploy/git.py +136 -0
  11. ominfra-0.0.0.dev155/ominfra/manage/deploy/inject.py +40 -0
  12. ominfra-0.0.0.dev155/ominfra/manage/deploy/paths.py +230 -0
  13. ominfra-0.0.0.dev155/ominfra/manage/deploy/types.py +13 -0
  14. ominfra-0.0.0.dev155/ominfra/manage/deploy/venvs.py +66 -0
  15. {ominfra-0.0.0.dev153 → ominfra-0.0.0.dev155}/ominfra/manage/inject.py +20 -4
  16. {ominfra-0.0.0.dev153 → ominfra-0.0.0.dev155}/ominfra/manage/main.py +15 -27
  17. {ominfra-0.0.0.dev153 → ominfra-0.0.0.dev155}/ominfra/manage/remote/_main.py +1 -1
  18. {ominfra-0.0.0.dev153 → ominfra-0.0.0.dev155}/ominfra/manage/remote/config.py +0 -2
  19. {ominfra-0.0.0.dev153 → ominfra-0.0.0.dev155}/ominfra/manage/remote/connection.py +7 -24
  20. {ominfra-0.0.0.dev153 → ominfra-0.0.0.dev155}/ominfra/manage/remote/execution.py +1 -1
  21. {ominfra-0.0.0.dev153 → ominfra-0.0.0.dev155}/ominfra/manage/remote/inject.py +3 -14
  22. {ominfra-0.0.0.dev153 → ominfra-0.0.0.dev155}/ominfra/manage/system/commands.py +22 -2
  23. ominfra-0.0.0.dev155/ominfra/manage/system/config.py +10 -0
  24. {ominfra-0.0.0.dev153 → ominfra-0.0.0.dev155}/ominfra/manage/system/inject.py +16 -6
  25. {ominfra-0.0.0.dev153 → ominfra-0.0.0.dev155}/ominfra/manage/system/packages.py +33 -7
  26. ominfra-0.0.0.dev155/ominfra/manage/system/platforms.py +72 -0
  27. ominfra-0.0.0.dev155/ominfra/manage/targets/connection.py +150 -0
  28. ominfra-0.0.0.dev155/ominfra/manage/targets/inject.py +42 -0
  29. ominfra-0.0.0.dev155/ominfra/manage/targets/targets.py +87 -0
  30. {ominfra-0.0.0.dev153 → ominfra-0.0.0.dev155}/ominfra/scripts/journald2aws.py +24 -7
  31. {ominfra-0.0.0.dev153 → ominfra-0.0.0.dev155}/ominfra/scripts/manage.py +1880 -438
  32. {ominfra-0.0.0.dev153 → ominfra-0.0.0.dev155}/ominfra/scripts/supervisor.py +187 -25
  33. ominfra-0.0.0.dev155/ominfra/supervisor/configs.py +299 -0
  34. ominfra-0.0.0.dev155/ominfra/tools/__init__.py +0 -0
  35. {ominfra-0.0.0.dev153 → ominfra-0.0.0.dev155/ominfra.egg-info}/PKG-INFO +3 -3
  36. {ominfra-0.0.0.dev153 → ominfra-0.0.0.dev155}/ominfra.egg-info/SOURCES.txt +14 -3
  37. {ominfra-0.0.0.dev153 → ominfra-0.0.0.dev155}/ominfra.egg-info/requires.txt +2 -2
  38. {ominfra-0.0.0.dev153 → ominfra-0.0.0.dev155}/pyproject.toml +3 -3
  39. ominfra-0.0.0.dev153/ominfra/manage/deploy/inject.py +0 -19
  40. ominfra-0.0.0.dev153/ominfra/manage/deploy/paths.py +0 -177
  41. ominfra-0.0.0.dev153/ominfra/manage/system/types.py +0 -5
  42. ominfra-0.0.0.dev153/ominfra/supervisor/configs.py +0 -154
  43. {ominfra-0.0.0.dev153 → ominfra-0.0.0.dev155}/LICENSE +0 -0
  44. {ominfra-0.0.0.dev153 → ominfra-0.0.0.dev155}/MANIFEST.in +0 -0
  45. {ominfra-0.0.0.dev153 → ominfra-0.0.0.dev155}/README.rst +0 -0
  46. {ominfra-0.0.0.dev153 → ominfra-0.0.0.dev155}/ominfra/.manifests.json +0 -0
  47. {ominfra-0.0.0.dev153 → ominfra-0.0.0.dev155}/ominfra/__about__.py +0 -0
  48. {ominfra-0.0.0.dev153 → ominfra-0.0.0.dev155}/ominfra/__init__.py +0 -0
  49. {ominfra-0.0.0.dev153 → ominfra-0.0.0.dev155}/ominfra/clouds/__init__.py +0 -0
  50. {ominfra-0.0.0.dev153 → ominfra-0.0.0.dev155}/ominfra/clouds/aws/__init__.py +0 -0
  51. {ominfra-0.0.0.dev153 → ominfra-0.0.0.dev155}/ominfra/clouds/aws/__main__.py +0 -0
  52. {ominfra-0.0.0.dev153 → ominfra-0.0.0.dev155}/ominfra/clouds/aws/auth.py +0 -0
  53. {ominfra-0.0.0.dev153 → ominfra-0.0.0.dev155}/ominfra/clouds/aws/cli.py +0 -0
  54. {ominfra-0.0.0.dev153 → ominfra-0.0.0.dev155}/ominfra/clouds/aws/dataclasses.py +0 -0
  55. {ominfra-0.0.0.dev153 → ominfra-0.0.0.dev155}/ominfra/clouds/aws/journald2aws/__init__.py +0 -0
  56. {ominfra-0.0.0.dev153 → ominfra-0.0.0.dev155}/ominfra/clouds/aws/journald2aws/__main__.py +0 -0
  57. {ominfra-0.0.0.dev153 → ominfra-0.0.0.dev155}/ominfra/clouds/aws/journald2aws/cursor.py +0 -0
  58. {ominfra-0.0.0.dev153 → ominfra-0.0.0.dev155}/ominfra/clouds/aws/journald2aws/driver.py +0 -0
  59. {ominfra-0.0.0.dev153 → ominfra-0.0.0.dev155}/ominfra/clouds/aws/journald2aws/main.py +0 -0
  60. {ominfra-0.0.0.dev153 → ominfra-0.0.0.dev155}/ominfra/clouds/aws/journald2aws/poster.py +0 -0
  61. {ominfra-0.0.0.dev153 → ominfra-0.0.0.dev155}/ominfra/clouds/aws/logs.py +0 -0
  62. {ominfra-0.0.0.dev153 → ominfra-0.0.0.dev155}/ominfra/clouds/aws/metadata.py +0 -0
  63. {ominfra-0.0.0.dev153 → ominfra-0.0.0.dev155}/ominfra/clouds/gcp/__init__.py +0 -0
  64. {ominfra-0.0.0.dev153 → ominfra-0.0.0.dev155}/ominfra/clouds/gcp/auth.py +0 -0
  65. {ominfra-0.0.0.dev153 → ominfra-0.0.0.dev155}/ominfra/cmds.py +0 -0
  66. {ominfra-0.0.0.dev153 → ominfra-0.0.0.dev155}/ominfra/configs.py +0 -0
  67. {ominfra-0.0.0.dev153 → ominfra-0.0.0.dev155}/ominfra/journald/__init__.py +0 -0
  68. {ominfra-0.0.0.dev153 → ominfra-0.0.0.dev155}/ominfra/journald/fields.py +0 -0
  69. {ominfra-0.0.0.dev153 → ominfra-0.0.0.dev155}/ominfra/journald/genmessages.py +0 -0
  70. {ominfra-0.0.0.dev153 → ominfra-0.0.0.dev155}/ominfra/journald/messages.py +0 -0
  71. {ominfra-0.0.0.dev153 → ominfra-0.0.0.dev155}/ominfra/journald/tailer.py +0 -0
  72. {ominfra-0.0.0.dev153 → ominfra-0.0.0.dev155}/ominfra/manage/__init__.py +0 -0
  73. {ominfra-0.0.0.dev153 → ominfra-0.0.0.dev155}/ominfra/manage/__main__.py +0 -0
  74. {ominfra-0.0.0.dev153 → ominfra-0.0.0.dev155}/ominfra/manage/commands/__init__.py +0 -0
  75. {ominfra-0.0.0.dev153 → ominfra-0.0.0.dev155}/ominfra/manage/commands/base.py +0 -0
  76. {ominfra-0.0.0.dev153 → ominfra-0.0.0.dev155}/ominfra/manage/commands/marshal.py +0 -0
  77. {ominfra-0.0.0.dev153 → ominfra-0.0.0.dev155}/ominfra/manage/commands/subprocess.py +0 -0
  78. {ominfra-0.0.0.dev153 → ominfra-0.0.0.dev155}/ominfra/manage/config.py +0 -0
  79. {ominfra-0.0.0.dev153 → ominfra-0.0.0.dev155}/ominfra/manage/deploy/__init__.py +0 -0
  80. {ominfra-0.0.0.dev153 → ominfra-0.0.0.dev155}/ominfra/manage/deploy/commands.py +0 -0
  81. {ominfra-0.0.0.dev153/ominfra/manage/commands → ominfra-0.0.0.dev155/ominfra/manage/deploy}/interp.py +0 -0
  82. {ominfra-0.0.0.dev153 → ominfra-0.0.0.dev155}/ominfra/manage/marshal.py +0 -0
  83. {ominfra-0.0.0.dev153 → ominfra-0.0.0.dev155}/ominfra/manage/remote/__init__.py +0 -0
  84. {ominfra-0.0.0.dev153 → ominfra-0.0.0.dev155}/ominfra/manage/remote/channel.py +0 -0
  85. {ominfra-0.0.0.dev153 → ominfra-0.0.0.dev155}/ominfra/manage/remote/payload.py +0 -0
  86. {ominfra-0.0.0.dev153 → ominfra-0.0.0.dev155}/ominfra/manage/remote/spawning.py +0 -0
  87. {ominfra-0.0.0.dev153 → ominfra-0.0.0.dev155}/ominfra/manage/system/__init__.py +0 -0
  88. {ominfra-0.0.0.dev153/ominfra/scripts → ominfra-0.0.0.dev155/ominfra/manage/targets}/__init__.py +0 -0
  89. {ominfra-0.0.0.dev153 → ominfra-0.0.0.dev155}/ominfra/pyremote.py +0 -0
  90. {ominfra-0.0.0.dev153/ominfra/supervisor/utils → ominfra-0.0.0.dev155/ominfra/scripts}/__init__.py +0 -0
  91. {ominfra-0.0.0.dev153 → ominfra-0.0.0.dev155}/ominfra/ssh.py +0 -0
  92. {ominfra-0.0.0.dev153 → ominfra-0.0.0.dev155}/ominfra/supervisor/LICENSE.txt +0 -0
  93. {ominfra-0.0.0.dev153 → ominfra-0.0.0.dev155}/ominfra/supervisor/__init__.py +0 -0
  94. {ominfra-0.0.0.dev153 → ominfra-0.0.0.dev155}/ominfra/supervisor/__main__.py +0 -0
  95. {ominfra-0.0.0.dev153 → ominfra-0.0.0.dev155}/ominfra/supervisor/dispatchers.py +0 -0
  96. {ominfra-0.0.0.dev153 → ominfra-0.0.0.dev155}/ominfra/supervisor/dispatchersimpl.py +0 -0
  97. {ominfra-0.0.0.dev153 → ominfra-0.0.0.dev155}/ominfra/supervisor/events.py +0 -0
  98. {ominfra-0.0.0.dev153 → ominfra-0.0.0.dev155}/ominfra/supervisor/exceptions.py +0 -0
  99. {ominfra-0.0.0.dev153 → ominfra-0.0.0.dev155}/ominfra/supervisor/groups.py +0 -0
  100. {ominfra-0.0.0.dev153 → ominfra-0.0.0.dev155}/ominfra/supervisor/groupsimpl.py +0 -0
  101. {ominfra-0.0.0.dev153 → ominfra-0.0.0.dev155}/ominfra/supervisor/http.py +0 -0
  102. {ominfra-0.0.0.dev153 → ominfra-0.0.0.dev155}/ominfra/supervisor/inject.py +0 -0
  103. {ominfra-0.0.0.dev153 → ominfra-0.0.0.dev155}/ominfra/supervisor/io.py +0 -0
  104. {ominfra-0.0.0.dev153 → ominfra-0.0.0.dev155}/ominfra/supervisor/main.py +0 -0
  105. {ominfra-0.0.0.dev153 → ominfra-0.0.0.dev155}/ominfra/supervisor/pipes.py +0 -0
  106. {ominfra-0.0.0.dev153 → ominfra-0.0.0.dev155}/ominfra/supervisor/privileges.py +0 -0
  107. {ominfra-0.0.0.dev153 → ominfra-0.0.0.dev155}/ominfra/supervisor/process.py +0 -0
  108. {ominfra-0.0.0.dev153 → ominfra-0.0.0.dev155}/ominfra/supervisor/processimpl.py +0 -0
  109. {ominfra-0.0.0.dev153 → ominfra-0.0.0.dev155}/ominfra/supervisor/setup.py +0 -0
  110. {ominfra-0.0.0.dev153 → ominfra-0.0.0.dev155}/ominfra/supervisor/setupimpl.py +0 -0
  111. {ominfra-0.0.0.dev153 → ominfra-0.0.0.dev155}/ominfra/supervisor/signals.py +0 -0
  112. {ominfra-0.0.0.dev153 → ominfra-0.0.0.dev155}/ominfra/supervisor/spawning.py +0 -0
  113. {ominfra-0.0.0.dev153 → ominfra-0.0.0.dev155}/ominfra/supervisor/spawningimpl.py +0 -0
  114. {ominfra-0.0.0.dev153 → ominfra-0.0.0.dev155}/ominfra/supervisor/states.py +0 -0
  115. {ominfra-0.0.0.dev153 → ominfra-0.0.0.dev155}/ominfra/supervisor/supervisor.py +0 -0
  116. {ominfra-0.0.0.dev153 → ominfra-0.0.0.dev155}/ominfra/supervisor/types.py +0 -0
  117. {ominfra-0.0.0.dev153/ominfra/tailscale → ominfra-0.0.0.dev155/ominfra/supervisor/utils}/__init__.py +0 -0
  118. {ominfra-0.0.0.dev153 → ominfra-0.0.0.dev155}/ominfra/supervisor/utils/collections.py +0 -0
  119. {ominfra-0.0.0.dev153 → ominfra-0.0.0.dev155}/ominfra/supervisor/utils/diag.py +0 -0
  120. {ominfra-0.0.0.dev153 → ominfra-0.0.0.dev155}/ominfra/supervisor/utils/fds.py +0 -0
  121. {ominfra-0.0.0.dev153 → ominfra-0.0.0.dev155}/ominfra/supervisor/utils/fs.py +0 -0
  122. {ominfra-0.0.0.dev153 → ominfra-0.0.0.dev155}/ominfra/supervisor/utils/os.py +0 -0
  123. {ominfra-0.0.0.dev153 → ominfra-0.0.0.dev155}/ominfra/supervisor/utils/ostypes.py +0 -0
  124. {ominfra-0.0.0.dev153 → ominfra-0.0.0.dev155}/ominfra/supervisor/utils/signals.py +0 -0
  125. {ominfra-0.0.0.dev153 → ominfra-0.0.0.dev155}/ominfra/supervisor/utils/strings.py +0 -0
  126. {ominfra-0.0.0.dev153 → ominfra-0.0.0.dev155}/ominfra/supervisor/utils/users.py +0 -0
  127. {ominfra-0.0.0.dev153/ominfra/tools → ominfra-0.0.0.dev155/ominfra/tailscale}/__init__.py +0 -0
  128. {ominfra-0.0.0.dev153 → ominfra-0.0.0.dev155}/ominfra/tailscale/api.py +0 -0
  129. {ominfra-0.0.0.dev153 → ominfra-0.0.0.dev155}/ominfra/tailscale/cli.py +0 -0
  130. {ominfra-0.0.0.dev153 → ominfra-0.0.0.dev155}/ominfra/threadworkers.py +0 -0
  131. {ominfra-0.0.0.dev153 → ominfra-0.0.0.dev155}/ominfra/tools/listresources.py +0 -0
  132. {ominfra-0.0.0.dev153 → ominfra-0.0.0.dev155}/ominfra.egg-info/dependency_links.txt +0 -0
  133. {ominfra-0.0.0.dev153 → ominfra-0.0.0.dev155}/ominfra.egg-info/entry_points.txt +0 -0
  134. {ominfra-0.0.0.dev153 → ominfra-0.0.0.dev155}/ominfra.egg-info/top_level.txt +0 -0
  135. {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.dev153
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.dev153
16
- Requires-Dist: omlish==0.0.0.dev153
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 .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)
@@ -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_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