ominfra 0.0.0.dev166__tar.gz → 0.0.0.dev168__tar.gz

Sign up to get free protection for your applications and to get access to all the features.
Files changed (142) hide show
  1. {ominfra-0.0.0.dev166/ominfra.egg-info → ominfra-0.0.0.dev168}/PKG-INFO +3 -3
  2. ominfra-0.0.0.dev168/ominfra/manage/deploy/apps.py +175 -0
  3. {ominfra-0.0.0.dev166 → ominfra-0.0.0.dev168}/ominfra/manage/deploy/commands.py +3 -3
  4. ominfra-0.0.0.dev168/ominfra/manage/deploy/conf.py +183 -0
  5. ominfra-0.0.0.dev168/ominfra/manage/deploy/deploy.py +27 -0
  6. {ominfra-0.0.0.dev166 → ominfra-0.0.0.dev168}/ominfra/manage/deploy/git.py +12 -8
  7. {ominfra-0.0.0.dev166 → ominfra-0.0.0.dev168}/ominfra/manage/deploy/inject.py +32 -7
  8. ominfra-0.0.0.dev168/ominfra/manage/deploy/paths/inject.py +21 -0
  9. ominfra-0.0.0.dev168/ominfra/manage/deploy/paths/manager.py +36 -0
  10. ominfra-0.0.0.dev168/ominfra/manage/deploy/paths/owners.py +50 -0
  11. ominfra-0.0.0.dev168/ominfra/manage/deploy/paths/paths.py +216 -0
  12. {ominfra-0.0.0.dev166 → ominfra-0.0.0.dev168}/ominfra/manage/deploy/specs.py +71 -6
  13. {ominfra-0.0.0.dev166 → ominfra-0.0.0.dev168}/ominfra/manage/deploy/tmp.py +1 -1
  14. ominfra-0.0.0.dev168/ominfra/manage/deploy/types.py +39 -0
  15. {ominfra-0.0.0.dev166 → ominfra-0.0.0.dev168}/ominfra/manage/deploy/venvs.py +4 -33
  16. {ominfra-0.0.0.dev166 → ominfra-0.0.0.dev168}/ominfra/scripts/journald2aws.py +33 -0
  17. {ominfra-0.0.0.dev166 → ominfra-0.0.0.dev168}/ominfra/scripts/manage.py +1038 -518
  18. {ominfra-0.0.0.dev166 → ominfra-0.0.0.dev168}/ominfra/scripts/supervisor.py +34 -0
  19. {ominfra-0.0.0.dev166 → ominfra-0.0.0.dev168}/ominfra/supervisor/configs.py +1 -0
  20. ominfra-0.0.0.dev168/ominfra/tools/__init__.py +0 -0
  21. {ominfra-0.0.0.dev166 → ominfra-0.0.0.dev168/ominfra.egg-info}/PKG-INFO +3 -3
  22. {ominfra-0.0.0.dev166 → ominfra-0.0.0.dev168}/ominfra.egg-info/SOURCES.txt +7 -1
  23. {ominfra-0.0.0.dev166 → ominfra-0.0.0.dev168}/ominfra.egg-info/requires.txt +2 -2
  24. {ominfra-0.0.0.dev166 → ominfra-0.0.0.dev168}/pyproject.toml +3 -3
  25. ominfra-0.0.0.dev166/ominfra/manage/deploy/apps.py +0 -74
  26. ominfra-0.0.0.dev166/ominfra/manage/deploy/paths.py +0 -239
  27. ominfra-0.0.0.dev166/ominfra/manage/deploy/types.py +0 -14
  28. {ominfra-0.0.0.dev166 → ominfra-0.0.0.dev168}/LICENSE +0 -0
  29. {ominfra-0.0.0.dev166 → ominfra-0.0.0.dev168}/MANIFEST.in +0 -0
  30. {ominfra-0.0.0.dev166 → ominfra-0.0.0.dev168}/README.rst +0 -0
  31. {ominfra-0.0.0.dev166 → ominfra-0.0.0.dev168}/ominfra/.manifests.json +0 -0
  32. {ominfra-0.0.0.dev166 → ominfra-0.0.0.dev168}/ominfra/__about__.py +0 -0
  33. {ominfra-0.0.0.dev166 → ominfra-0.0.0.dev168}/ominfra/__init__.py +0 -0
  34. {ominfra-0.0.0.dev166 → ominfra-0.0.0.dev168}/ominfra/clouds/__init__.py +0 -0
  35. {ominfra-0.0.0.dev166 → ominfra-0.0.0.dev168}/ominfra/clouds/aws/__init__.py +0 -0
  36. {ominfra-0.0.0.dev166 → ominfra-0.0.0.dev168}/ominfra/clouds/aws/__main__.py +0 -0
  37. {ominfra-0.0.0.dev166 → ominfra-0.0.0.dev168}/ominfra/clouds/aws/auth.py +0 -0
  38. {ominfra-0.0.0.dev166 → ominfra-0.0.0.dev168}/ominfra/clouds/aws/cli.py +0 -0
  39. {ominfra-0.0.0.dev166 → ominfra-0.0.0.dev168}/ominfra/clouds/aws/dataclasses.py +0 -0
  40. {ominfra-0.0.0.dev166 → ominfra-0.0.0.dev168}/ominfra/clouds/aws/journald2aws/__init__.py +0 -0
  41. {ominfra-0.0.0.dev166 → ominfra-0.0.0.dev168}/ominfra/clouds/aws/journald2aws/__main__.py +0 -0
  42. {ominfra-0.0.0.dev166 → ominfra-0.0.0.dev168}/ominfra/clouds/aws/journald2aws/cursor.py +0 -0
  43. {ominfra-0.0.0.dev166 → ominfra-0.0.0.dev168}/ominfra/clouds/aws/journald2aws/driver.py +0 -0
  44. {ominfra-0.0.0.dev166 → ominfra-0.0.0.dev168}/ominfra/clouds/aws/journald2aws/main.py +0 -0
  45. {ominfra-0.0.0.dev166 → ominfra-0.0.0.dev168}/ominfra/clouds/aws/journald2aws/poster.py +0 -0
  46. {ominfra-0.0.0.dev166 → ominfra-0.0.0.dev168}/ominfra/clouds/aws/logs.py +0 -0
  47. {ominfra-0.0.0.dev166 → ominfra-0.0.0.dev168}/ominfra/clouds/aws/metadata.py +0 -0
  48. {ominfra-0.0.0.dev166 → ominfra-0.0.0.dev168}/ominfra/clouds/gcp/__init__.py +0 -0
  49. {ominfra-0.0.0.dev166 → ominfra-0.0.0.dev168}/ominfra/clouds/gcp/auth.py +0 -0
  50. {ominfra-0.0.0.dev166 → ominfra-0.0.0.dev168}/ominfra/cmds.py +0 -0
  51. {ominfra-0.0.0.dev166 → ominfra-0.0.0.dev168}/ominfra/configs.py +0 -0
  52. {ominfra-0.0.0.dev166 → ominfra-0.0.0.dev168}/ominfra/journald/__init__.py +0 -0
  53. {ominfra-0.0.0.dev166 → ominfra-0.0.0.dev168}/ominfra/journald/fields.py +0 -0
  54. {ominfra-0.0.0.dev166 → ominfra-0.0.0.dev168}/ominfra/journald/genmessages.py +0 -0
  55. {ominfra-0.0.0.dev166 → ominfra-0.0.0.dev168}/ominfra/journald/messages.py +0 -0
  56. {ominfra-0.0.0.dev166 → ominfra-0.0.0.dev168}/ominfra/journald/tailer.py +0 -0
  57. {ominfra-0.0.0.dev166 → ominfra-0.0.0.dev168}/ominfra/manage/__init__.py +0 -0
  58. {ominfra-0.0.0.dev166 → ominfra-0.0.0.dev168}/ominfra/manage/__main__.py +0 -0
  59. {ominfra-0.0.0.dev166 → ominfra-0.0.0.dev168}/ominfra/manage/bootstrap.py +0 -0
  60. {ominfra-0.0.0.dev166 → ominfra-0.0.0.dev168}/ominfra/manage/bootstrap_.py +0 -0
  61. {ominfra-0.0.0.dev166 → ominfra-0.0.0.dev168}/ominfra/manage/commands/__init__.py +0 -0
  62. {ominfra-0.0.0.dev166 → ominfra-0.0.0.dev168}/ominfra/manage/commands/base.py +0 -0
  63. {ominfra-0.0.0.dev166 → ominfra-0.0.0.dev168}/ominfra/manage/commands/inject.py +0 -0
  64. {ominfra-0.0.0.dev166 → ominfra-0.0.0.dev168}/ominfra/manage/commands/local.py +0 -0
  65. {ominfra-0.0.0.dev166 → ominfra-0.0.0.dev168}/ominfra/manage/commands/marshal.py +0 -0
  66. {ominfra-0.0.0.dev166 → ominfra-0.0.0.dev168}/ominfra/manage/commands/ping.py +0 -0
  67. {ominfra-0.0.0.dev166 → ominfra-0.0.0.dev168}/ominfra/manage/commands/subprocess.py +0 -0
  68. {ominfra-0.0.0.dev166 → ominfra-0.0.0.dev168}/ominfra/manage/commands/types.py +0 -0
  69. {ominfra-0.0.0.dev166 → ominfra-0.0.0.dev168}/ominfra/manage/config.py +0 -0
  70. {ominfra-0.0.0.dev166 → ominfra-0.0.0.dev168}/ominfra/manage/deploy/__init__.py +0 -0
  71. {ominfra-0.0.0.dev166 → ominfra-0.0.0.dev168}/ominfra/manage/deploy/config.py +0 -0
  72. {ominfra-0.0.0.dev166 → ominfra-0.0.0.dev168}/ominfra/manage/deploy/interp.py +0 -0
  73. {ominfra-0.0.0.dev166/ominfra/manage/remote → ominfra-0.0.0.dev168/ominfra/manage/deploy/paths}/__init__.py +0 -0
  74. {ominfra-0.0.0.dev166 → ominfra-0.0.0.dev168}/ominfra/manage/inject.py +0 -0
  75. {ominfra-0.0.0.dev166 → ominfra-0.0.0.dev168}/ominfra/manage/main.py +0 -0
  76. {ominfra-0.0.0.dev166 → ominfra-0.0.0.dev168}/ominfra/manage/marshal.py +0 -0
  77. {ominfra-0.0.0.dev166/ominfra/manage/system → ominfra-0.0.0.dev168/ominfra/manage/remote}/__init__.py +0 -0
  78. {ominfra-0.0.0.dev166 → ominfra-0.0.0.dev168}/ominfra/manage/remote/_main.py +0 -0
  79. {ominfra-0.0.0.dev166 → ominfra-0.0.0.dev168}/ominfra/manage/remote/channel.py +0 -0
  80. {ominfra-0.0.0.dev166 → ominfra-0.0.0.dev168}/ominfra/manage/remote/config.py +0 -0
  81. {ominfra-0.0.0.dev166 → ominfra-0.0.0.dev168}/ominfra/manage/remote/connection.py +0 -0
  82. {ominfra-0.0.0.dev166 → ominfra-0.0.0.dev168}/ominfra/manage/remote/execution.py +0 -0
  83. {ominfra-0.0.0.dev166 → ominfra-0.0.0.dev168}/ominfra/manage/remote/inject.py +0 -0
  84. {ominfra-0.0.0.dev166 → ominfra-0.0.0.dev168}/ominfra/manage/remote/payload.py +0 -0
  85. {ominfra-0.0.0.dev166 → ominfra-0.0.0.dev168}/ominfra/manage/remote/spawning.py +0 -0
  86. {ominfra-0.0.0.dev166/ominfra/manage/targets → ominfra-0.0.0.dev168/ominfra/manage/system}/__init__.py +0 -0
  87. {ominfra-0.0.0.dev166 → ominfra-0.0.0.dev168}/ominfra/manage/system/commands.py +0 -0
  88. {ominfra-0.0.0.dev166 → ominfra-0.0.0.dev168}/ominfra/manage/system/config.py +0 -0
  89. {ominfra-0.0.0.dev166 → ominfra-0.0.0.dev168}/ominfra/manage/system/inject.py +0 -0
  90. {ominfra-0.0.0.dev166 → ominfra-0.0.0.dev168}/ominfra/manage/system/packages.py +0 -0
  91. {ominfra-0.0.0.dev166 → ominfra-0.0.0.dev168}/ominfra/manage/system/platforms.py +0 -0
  92. {ominfra-0.0.0.dev166/ominfra/scripts → ominfra-0.0.0.dev168/ominfra/manage/targets}/__init__.py +0 -0
  93. {ominfra-0.0.0.dev166 → ominfra-0.0.0.dev168}/ominfra/manage/targets/connection.py +0 -0
  94. {ominfra-0.0.0.dev166 → ominfra-0.0.0.dev168}/ominfra/manage/targets/inject.py +0 -0
  95. {ominfra-0.0.0.dev166 → ominfra-0.0.0.dev168}/ominfra/manage/targets/targets.py +0 -0
  96. {ominfra-0.0.0.dev166 → ominfra-0.0.0.dev168}/ominfra/pyremote.py +0 -0
  97. {ominfra-0.0.0.dev166/ominfra/supervisor/utils → ominfra-0.0.0.dev168/ominfra/scripts}/__init__.py +0 -0
  98. {ominfra-0.0.0.dev166 → ominfra-0.0.0.dev168}/ominfra/ssh.py +0 -0
  99. {ominfra-0.0.0.dev166 → ominfra-0.0.0.dev168}/ominfra/supervisor/LICENSE.txt +0 -0
  100. {ominfra-0.0.0.dev166 → ominfra-0.0.0.dev168}/ominfra/supervisor/__init__.py +0 -0
  101. {ominfra-0.0.0.dev166 → ominfra-0.0.0.dev168}/ominfra/supervisor/__main__.py +0 -0
  102. {ominfra-0.0.0.dev166 → ominfra-0.0.0.dev168}/ominfra/supervisor/dispatchers.py +0 -0
  103. {ominfra-0.0.0.dev166 → ominfra-0.0.0.dev168}/ominfra/supervisor/dispatchersimpl.py +0 -0
  104. {ominfra-0.0.0.dev166 → ominfra-0.0.0.dev168}/ominfra/supervisor/events.py +0 -0
  105. {ominfra-0.0.0.dev166 → ominfra-0.0.0.dev168}/ominfra/supervisor/exceptions.py +0 -0
  106. {ominfra-0.0.0.dev166 → ominfra-0.0.0.dev168}/ominfra/supervisor/groups.py +0 -0
  107. {ominfra-0.0.0.dev166 → ominfra-0.0.0.dev168}/ominfra/supervisor/groupsimpl.py +0 -0
  108. {ominfra-0.0.0.dev166 → ominfra-0.0.0.dev168}/ominfra/supervisor/http.py +0 -0
  109. {ominfra-0.0.0.dev166 → ominfra-0.0.0.dev168}/ominfra/supervisor/inject.py +0 -0
  110. {ominfra-0.0.0.dev166 → ominfra-0.0.0.dev168}/ominfra/supervisor/io.py +0 -0
  111. {ominfra-0.0.0.dev166 → ominfra-0.0.0.dev168}/ominfra/supervisor/main.py +0 -0
  112. {ominfra-0.0.0.dev166 → ominfra-0.0.0.dev168}/ominfra/supervisor/pipes.py +0 -0
  113. {ominfra-0.0.0.dev166 → ominfra-0.0.0.dev168}/ominfra/supervisor/privileges.py +0 -0
  114. {ominfra-0.0.0.dev166 → ominfra-0.0.0.dev168}/ominfra/supervisor/process.py +0 -0
  115. {ominfra-0.0.0.dev166 → ominfra-0.0.0.dev168}/ominfra/supervisor/processimpl.py +0 -0
  116. {ominfra-0.0.0.dev166 → ominfra-0.0.0.dev168}/ominfra/supervisor/setup.py +0 -0
  117. {ominfra-0.0.0.dev166 → ominfra-0.0.0.dev168}/ominfra/supervisor/setupimpl.py +0 -0
  118. {ominfra-0.0.0.dev166 → ominfra-0.0.0.dev168}/ominfra/supervisor/signals.py +0 -0
  119. {ominfra-0.0.0.dev166 → ominfra-0.0.0.dev168}/ominfra/supervisor/spawning.py +0 -0
  120. {ominfra-0.0.0.dev166 → ominfra-0.0.0.dev168}/ominfra/supervisor/spawningimpl.py +0 -0
  121. {ominfra-0.0.0.dev166 → ominfra-0.0.0.dev168}/ominfra/supervisor/states.py +0 -0
  122. {ominfra-0.0.0.dev166 → ominfra-0.0.0.dev168}/ominfra/supervisor/supervisor.py +0 -0
  123. {ominfra-0.0.0.dev166 → ominfra-0.0.0.dev168}/ominfra/supervisor/types.py +0 -0
  124. {ominfra-0.0.0.dev166/ominfra/tailscale → ominfra-0.0.0.dev168/ominfra/supervisor/utils}/__init__.py +0 -0
  125. {ominfra-0.0.0.dev166 → ominfra-0.0.0.dev168}/ominfra/supervisor/utils/collections.py +0 -0
  126. {ominfra-0.0.0.dev166 → ominfra-0.0.0.dev168}/ominfra/supervisor/utils/diag.py +0 -0
  127. {ominfra-0.0.0.dev166 → ominfra-0.0.0.dev168}/ominfra/supervisor/utils/fds.py +0 -0
  128. {ominfra-0.0.0.dev166 → ominfra-0.0.0.dev168}/ominfra/supervisor/utils/fs.py +0 -0
  129. {ominfra-0.0.0.dev166 → ominfra-0.0.0.dev168}/ominfra/supervisor/utils/os.py +0 -0
  130. {ominfra-0.0.0.dev166 → ominfra-0.0.0.dev168}/ominfra/supervisor/utils/ostypes.py +0 -0
  131. {ominfra-0.0.0.dev166 → ominfra-0.0.0.dev168}/ominfra/supervisor/utils/signals.py +0 -0
  132. {ominfra-0.0.0.dev166 → ominfra-0.0.0.dev168}/ominfra/supervisor/utils/strings.py +0 -0
  133. {ominfra-0.0.0.dev166 → ominfra-0.0.0.dev168}/ominfra/supervisor/utils/users.py +0 -0
  134. {ominfra-0.0.0.dev166/ominfra/tools → ominfra-0.0.0.dev168/ominfra/tailscale}/__init__.py +0 -0
  135. {ominfra-0.0.0.dev166 → ominfra-0.0.0.dev168}/ominfra/tailscale/api.py +0 -0
  136. {ominfra-0.0.0.dev166 → ominfra-0.0.0.dev168}/ominfra/tailscale/cli.py +0 -0
  137. {ominfra-0.0.0.dev166 → ominfra-0.0.0.dev168}/ominfra/threadworkers.py +0 -0
  138. {ominfra-0.0.0.dev166 → ominfra-0.0.0.dev168}/ominfra/tools/listresources.py +0 -0
  139. {ominfra-0.0.0.dev166 → ominfra-0.0.0.dev168}/ominfra.egg-info/dependency_links.txt +0 -0
  140. {ominfra-0.0.0.dev166 → ominfra-0.0.0.dev168}/ominfra.egg-info/entry_points.txt +0 -0
  141. {ominfra-0.0.0.dev166 → ominfra-0.0.0.dev168}/ominfra.egg-info/top_level.txt +0 -0
  142. {ominfra-0.0.0.dev166 → ominfra-0.0.0.dev168}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: ominfra
3
- Version: 0.0.0.dev166
3
+ Version: 0.0.0.dev168
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.dev166
16
- Requires-Dist: omlish==0.0.0.dev166
15
+ Requires-Dist: omdev==0.0.0.dev168
16
+ Requires-Dist: omlish==0.0.0.dev168
17
17
  Provides-Extra: all
18
18
  Requires-Dist: paramiko~=3.5; extra == "all"
19
19
  Requires-Dist: asyncssh~=2.18; extra == "all"
@@ -0,0 +1,175 @@
1
+ # ruff: noqa: UP006 UP007
2
+ import datetime
3
+ import os.path
4
+ import typing as ta
5
+
6
+ from omlish.lite.cached import cached_nullary
7
+ from omlish.lite.check import check
8
+ from omlish.os.paths import relative_symlink
9
+
10
+ from .conf import DeployConfManager
11
+ from .git import DeployGitManager
12
+ from .paths.owners import DeployPathOwner
13
+ from .paths.paths import DeployPath
14
+ from .specs import DeploySpec
15
+ from .types import DeployAppTag
16
+ from .types import DeployHome
17
+ from .types import DeployKey
18
+ from .types import DeployRev
19
+ from .types import DeployTag
20
+ from .venvs import DeployVenvManager
21
+
22
+
23
+ def make_deploy_tag(
24
+ rev: DeployRev,
25
+ key: DeployKey,
26
+ *,
27
+ utcnow: ta.Optional[datetime.datetime] = None,
28
+ ) -> DeployTag:
29
+ if utcnow is None:
30
+ utcnow = datetime.datetime.now(tz=datetime.timezone.utc) # noqa
31
+ now_fmt = '%Y%m%dT%H%M%SZ'
32
+ now_str = utcnow.strftime(now_fmt)
33
+ return DeployTag('-'.join([now_str, rev, key]))
34
+
35
+
36
+ class DeployAppManager(DeployPathOwner):
37
+ def __init__(
38
+ self,
39
+ *,
40
+ deploy_home: ta.Optional[DeployHome] = None,
41
+
42
+ conf: DeployConfManager,
43
+ git: DeployGitManager,
44
+ venvs: DeployVenvManager,
45
+ ) -> None:
46
+ super().__init__()
47
+
48
+ self._deploy_home = deploy_home
49
+
50
+ self._conf = conf
51
+ self._git = git
52
+ self._venvs = venvs
53
+
54
+ #
55
+
56
+ _APP_TAG_DIR_STR = 'tags/apps/@app/@tag/'
57
+ _APP_TAG_DIR = DeployPath.parse(_APP_TAG_DIR_STR)
58
+
59
+ _CONF_TAG_DIR_STR = 'tags/conf/@tag--@app/'
60
+ _CONF_TAG_DIR = DeployPath.parse(_CONF_TAG_DIR_STR)
61
+
62
+ _DEPLOY_DIR_STR = 'deploys/@tag--@app/'
63
+ _DEPLOY_DIR = DeployPath.parse(_DEPLOY_DIR_STR)
64
+
65
+ _APP_DEPLOY_LINK = DeployPath.parse(f'{_DEPLOY_DIR_STR}apps/@app')
66
+ _CONF_DEPLOY_LINK = DeployPath.parse(f'{_DEPLOY_DIR_STR}conf')
67
+
68
+ @cached_nullary
69
+ def get_owned_deploy_paths(self) -> ta.AbstractSet[DeployPath]:
70
+ return {
71
+ self._APP_TAG_DIR,
72
+
73
+ self._CONF_TAG_DIR,
74
+
75
+ self._DEPLOY_DIR,
76
+
77
+ self._APP_DEPLOY_LINK,
78
+ self._CONF_DEPLOY_LINK,
79
+
80
+ *[
81
+ DeployPath.parse(f'{self._APP_TAG_DIR_STR}{sfx}/')
82
+ for sfx in [
83
+ 'conf',
84
+ 'git',
85
+ 'venv',
86
+ ]
87
+ ],
88
+ }
89
+
90
+ #
91
+
92
+ async def prepare_app(
93
+ self,
94
+ spec: DeploySpec,
95
+ ) -> None:
96
+ app_tag = DeployAppTag(spec.app, make_deploy_tag(spec.git.rev, spec.key()))
97
+
98
+ #
99
+
100
+ deploy_home = check.non_empty_str(self._deploy_home)
101
+
102
+ def build_path(pth: DeployPath) -> str:
103
+ return os.path.join(deploy_home, pth.render(app_tag.placeholders()))
104
+
105
+ app_tag_dir = build_path(self._APP_TAG_DIR)
106
+ conf_tag_dir = build_path(self._CONF_TAG_DIR)
107
+ deploy_dir = build_path(self._DEPLOY_DIR)
108
+ app_deploy_link = build_path(self._APP_DEPLOY_LINK)
109
+ conf_deploy_link_file = build_path(self._CONF_DEPLOY_LINK)
110
+
111
+ #
112
+
113
+ os.makedirs(deploy_dir)
114
+
115
+ deploying_link = os.path.join(deploy_home, 'deploys/deploying')
116
+ relative_symlink(
117
+ deploy_dir,
118
+ deploying_link,
119
+ target_is_directory=True,
120
+ make_dirs=True,
121
+ )
122
+
123
+ #
124
+
125
+ os.makedirs(app_tag_dir)
126
+ relative_symlink(
127
+ app_tag_dir,
128
+ app_deploy_link,
129
+ target_is_directory=True,
130
+ make_dirs=True,
131
+ )
132
+
133
+ #
134
+
135
+ os.makedirs(conf_tag_dir)
136
+ relative_symlink(
137
+ conf_tag_dir,
138
+ conf_deploy_link_file,
139
+ target_is_directory=True,
140
+ make_dirs=True,
141
+ )
142
+
143
+ #
144
+
145
+ git_dir = os.path.join(app_tag_dir, 'git')
146
+ await self._git.checkout(
147
+ spec.git,
148
+ git_dir,
149
+ )
150
+
151
+ #
152
+
153
+ if spec.venv is not None:
154
+ venv_dir = os.path.join(app_tag_dir, 'venv')
155
+ await self._venvs.setup_venv(
156
+ spec.venv,
157
+ git_dir,
158
+ venv_dir,
159
+ )
160
+
161
+ #
162
+
163
+ if spec.conf is not None:
164
+ conf_dir = os.path.join(app_tag_dir, 'conf')
165
+ await self._conf.write_conf(
166
+ spec.conf,
167
+ app_tag,
168
+ conf_dir,
169
+ conf_tag_dir,
170
+ )
171
+
172
+ #
173
+
174
+ current_link = os.path.join(deploy_home, 'deploys/current')
175
+ os.replace(deploying_link, current_link)
@@ -5,7 +5,7 @@ from omlish.lite.logs import log
5
5
 
6
6
  from ..commands.base import Command
7
7
  from ..commands.base import CommandExecutor
8
- from .apps import DeployAppManager
8
+ from .deploy import DeployManager
9
9
  from .specs import DeploySpec
10
10
 
11
11
 
@@ -23,11 +23,11 @@ class DeployCommand(Command['DeployCommand.Output']):
23
23
 
24
24
  @dc.dataclass(frozen=True)
25
25
  class DeployCommandExecutor(CommandExecutor[DeployCommand, DeployCommand.Output]):
26
- _apps: DeployAppManager
26
+ _deploy: DeployManager
27
27
 
28
28
  async def execute(self, cmd: DeployCommand) -> DeployCommand.Output:
29
29
  log.info('Deploying! %r', cmd.spec)
30
30
 
31
- await self._apps.prepare_app(cmd.spec)
31
+ await self._deploy.run_deploy(cmd.spec)
32
32
 
33
33
  return DeployCommand.Output()
@@ -0,0 +1,183 @@
1
+ # ruff: noqa: UP006 UP007
2
+ """
3
+ TODO:
4
+ - @conf DeployPathPlaceholder? :|
5
+ - post-deploy: remove any dir_links not present in new spec
6
+ - * only if succeeded * - otherwise, remove any dir_links present in new spec but not previously present?
7
+ - no such thing as 'previously present'.. build a 'deploy state' and pass it back?
8
+ - ** whole thing can be atomic **
9
+ - 1) new atomic temp dir
10
+ - 2) for each subdir not needing modification, hardlink into temp dir
11
+ - 3) for each subdir needing modification, new subdir, hardlink all files not needing modification
12
+ - 4) write (or if deleting, omit) new files
13
+ - 5) swap top level
14
+ - ** whole deploy can be atomic(-ish) - do this for everything **
15
+ - just a '/deploy/current' dir
16
+ - some things (venvs) cannot be moved, thus the /deploy/venvs dir
17
+ - ** ensure (enforce) equivalent relpath nesting
18
+ """
19
+ import os.path
20
+ import typing as ta
21
+
22
+ from omlish.lite.check import check
23
+ from omlish.os.paths import is_path_in_dir
24
+ from omlish.os.paths import relative_symlink
25
+
26
+ from .paths.paths import DEPLOY_PATH_PLACEHOLDER_SEPARATOR
27
+ from .specs import AppDeployConfLink
28
+ from .specs import DeployConfFile
29
+ from .specs import DeployConfLink
30
+ from .specs import DeployConfSpec
31
+ from .specs import TagDeployConfLink
32
+ from .types import DeployAppTag
33
+ from .types import DeployHome
34
+
35
+
36
+ class DeployConfManager:
37
+ def __init__(
38
+ self,
39
+ *,
40
+ deploy_home: ta.Optional[DeployHome] = None,
41
+ ) -> None:
42
+ super().__init__()
43
+
44
+ self._deploy_home = deploy_home
45
+
46
+ #
47
+
48
+ async def _write_conf_file(
49
+ self,
50
+ cf: DeployConfFile,
51
+ conf_dir: str,
52
+ ) -> None:
53
+ conf_file = os.path.join(conf_dir, cf.path)
54
+ check.arg(is_path_in_dir(conf_dir, conf_file))
55
+
56
+ os.makedirs(os.path.dirname(conf_file), exist_ok=True)
57
+
58
+ with open(conf_file, 'w') as f: # noqa
59
+ f.write(cf.body)
60
+
61
+ #
62
+
63
+ class _ComputedConfLink(ta.NamedTuple):
64
+ is_dir: bool
65
+ link_src: str
66
+ link_dst: str
67
+
68
+ def _compute_conf_link_dst(
69
+ self,
70
+ link: DeployConfLink,
71
+ app_tag: DeployAppTag,
72
+ conf_dir: str,
73
+ link_dir: str,
74
+ ) -> _ComputedConfLink:
75
+ link_src = os.path.join(conf_dir, link.src)
76
+ check.arg(is_path_in_dir(conf_dir, link_src))
77
+
78
+ #
79
+
80
+ if (is_dir := link.src.endswith('/')):
81
+ # @conf/ - links a directory in root of app conf dir to conf/@conf/@dst/
82
+ check.arg(link.src.count('/') == 1)
83
+ link_dst_pfx = link.src
84
+ link_dst_sfx = ''
85
+
86
+ elif '/' in link.src:
87
+ # @conf/file - links a single file in a single subdir to conf/@conf/@dst--file
88
+ d, f = os.path.split(link.src)
89
+ # TODO: check filename :|
90
+ link_dst_pfx = d + '/'
91
+ link_dst_sfx = DEPLOY_PATH_PLACEHOLDER_SEPARATOR + f
92
+
93
+ else: # noqa
94
+ # @conf(.ext)* - links a single file in root of app conf dir to conf/@conf/@dst(.ext)*
95
+ if '.' in link.src:
96
+ l, _, r = link.src.partition('.')
97
+ link_dst_pfx = l + '/'
98
+ link_dst_sfx = '.' + r
99
+ else:
100
+ link_dst_pfx = link.src + '/'
101
+ link_dst_sfx = ''
102
+
103
+ #
104
+
105
+ if isinstance(link, AppDeployConfLink):
106
+ link_dst_mid = str(app_tag.app)
107
+ elif isinstance(link, TagDeployConfLink):
108
+ link_dst_mid = DEPLOY_PATH_PLACEHOLDER_SEPARATOR.join([app_tag.app, app_tag.tag])
109
+ else:
110
+ raise TypeError(link)
111
+
112
+ #
113
+
114
+ link_dst_name = ''.join([
115
+ link_dst_pfx,
116
+ link_dst_mid,
117
+ link_dst_sfx,
118
+ ])
119
+ link_dst = os.path.join(link_dir, link_dst_name)
120
+
121
+ return DeployConfManager._ComputedConfLink(
122
+ is_dir=is_dir,
123
+ link_src=link_src,
124
+ link_dst=link_dst,
125
+ )
126
+
127
+ async def _make_conf_link(
128
+ self,
129
+ link: DeployConfLink,
130
+ app_tag: DeployAppTag,
131
+ conf_dir: str,
132
+ link_dir: str,
133
+ ) -> None:
134
+ comp = self._compute_conf_link_dst(
135
+ link,
136
+ app_tag,
137
+ conf_dir,
138
+ link_dir,
139
+ )
140
+
141
+ #
142
+
143
+ check.arg(is_path_in_dir(conf_dir, comp.link_src))
144
+ check.arg(is_path_in_dir(link_dir, comp.link_dst))
145
+
146
+ if comp.is_dir:
147
+ check.arg(os.path.isdir(comp.link_src))
148
+ else:
149
+ check.arg(os.path.isfile(comp.link_src))
150
+
151
+ #
152
+
153
+ relative_symlink( # noqa
154
+ comp.link_src,
155
+ comp.link_dst,
156
+ target_is_directory=comp.is_dir,
157
+ make_dirs=True,
158
+ )
159
+
160
+ #
161
+
162
+ async def write_conf(
163
+ self,
164
+ spec: DeployConfSpec,
165
+ app_tag: DeployAppTag,
166
+ conf_dir: str,
167
+ link_dir: str,
168
+ ) -> None:
169
+ for cf in spec.files or []:
170
+ await self._write_conf_file(
171
+ cf,
172
+ conf_dir,
173
+ )
174
+
175
+ #
176
+
177
+ for link in spec.links or []:
178
+ await self._make_conf_link(
179
+ link,
180
+ app_tag,
181
+ conf_dir,
182
+ link_dir,
183
+ )
@@ -0,0 +1,27 @@
1
+ # ruff: noqa: UP006 UP007
2
+ from .apps import DeployAppManager
3
+ from .paths.manager import DeployPathsManager
4
+ from .specs import DeploySpec
5
+
6
+
7
+ class DeployManager:
8
+ def __init__(
9
+ self,
10
+ *,
11
+ apps: DeployAppManager,
12
+ paths: DeployPathsManager,
13
+ ):
14
+ super().__init__()
15
+
16
+ self._apps = apps
17
+ self._paths = paths
18
+
19
+ async def run_deploy(
20
+ self,
21
+ spec: DeploySpec,
22
+ ) -> None:
23
+ self._paths.validate_deploy_paths()
24
+
25
+ #
26
+
27
+ await self._apps.prepare_app(spec)
@@ -17,9 +17,9 @@ from omlish.lite.cached import async_cached_nullary
17
17
  from omlish.lite.check import check
18
18
  from omlish.os.atomics import AtomicPathSwapping
19
19
 
20
- from .paths import SingleDirDeployPathOwner
21
- from .specs import DeployGitCheckout
20
+ from .paths.owners import SingleDirDeployPathOwner
22
21
  from .specs import DeployGitRepo
22
+ from .specs import DeployGitSpec
23
23
  from .types import DeployHome
24
24
  from .types import DeployRev
25
25
 
@@ -95,7 +95,7 @@ class DeployGitManager(SingleDirDeployPathOwner):
95
95
 
96
96
  #
97
97
 
98
- async def checkout(self, checkout: DeployGitCheckout, dst_dir: str) -> None:
98
+ async def checkout(self, spec: DeployGitSpec, dst_dir: str) -> None:
99
99
  check.state(not os.path.exists(dst_dir))
100
100
  with self._git._atomics.begin_atomic_path_swap( # noqa
101
101
  'dir',
@@ -103,14 +103,14 @@ class DeployGitManager(SingleDirDeployPathOwner):
103
103
  auto_commit=True,
104
104
  make_dirs=True,
105
105
  ) as dst_swap:
106
- await self.fetch(checkout.rev)
106
+ await self.fetch(spec.rev)
107
107
 
108
108
  dst_call = functools.partial(asyncio_subprocesses.check_call, cwd=dst_swap.tmp_path)
109
109
  await dst_call('git', 'init')
110
110
 
111
111
  await dst_call('git', 'remote', 'add', 'local', self._dir)
112
- await dst_call('git', 'fetch', '--depth=1', 'local', checkout.rev)
113
- await dst_call('git', 'checkout', checkout.rev, *(checkout.subtrees or []))
112
+ await dst_call('git', 'fetch', '--depth=1', 'local', spec.rev)
113
+ await dst_call('git', 'checkout', spec.rev, *(spec.subtrees or []))
114
114
 
115
115
  def get_repo_dir(self, repo: DeployGitRepo) -> RepoDir:
116
116
  try:
@@ -119,5 +119,9 @@ class DeployGitManager(SingleDirDeployPathOwner):
119
119
  repo_dir = self._repo_dirs[repo] = DeployGitManager.RepoDir(self, repo)
120
120
  return repo_dir
121
121
 
122
- async def checkout(self, checkout: DeployGitCheckout, dst_dir: str) -> None:
123
- await self.get_repo_dir(checkout.repo).checkout(checkout, dst_dir)
122
+ async def checkout(
123
+ self,
124
+ spec: DeployGitSpec,
125
+ dst_dir: str,
126
+ ) -> None:
127
+ await self.get_repo_dir(spec.repo).checkout(spec, dst_dir)
@@ -11,10 +11,14 @@ from ..commands.inject import bind_command
11
11
  from .apps import DeployAppManager
12
12
  from .commands import DeployCommand
13
13
  from .commands import DeployCommandExecutor
14
+ from .conf import DeployConfManager
14
15
  from .config import DeployConfig
16
+ from .deploy import DeployManager
15
17
  from .git import DeployGitManager
16
18
  from .interp import InterpCommand
17
19
  from .interp import InterpCommandExecutor
20
+ from .paths.inject import bind_deploy_paths
21
+ from .paths.owners import DeployPathOwner
18
22
  from .tmp import DeployTmpManager
19
23
  from .types import DeployHome
20
24
  from .venvs import DeployVenvManager
@@ -27,22 +31,43 @@ def bind_deploy(
27
31
  lst: ta.List[InjectorBindingOrBindings] = [
28
32
  inj.bind(deploy_config),
29
33
 
30
- #
34
+ bind_deploy_paths(),
35
+ ]
36
+
37
+ #
38
+
39
+ def bind_manager(cls: type) -> InjectorBindings:
40
+ return inj.as_bindings(
41
+ inj.bind(cls, singleton=True),
42
+
43
+ *([inj.bind(DeployPathOwner, to_key=cls, array=True)] if issubclass(cls, DeployPathOwner) else []),
44
+ )
45
+
46
+ #
31
47
 
32
- inj.bind(DeployAppManager, singleton=True),
48
+ lst.extend([
49
+ bind_manager(DeployAppManager),
33
50
 
34
- inj.bind(DeployGitManager, singleton=True),
51
+ bind_manager(DeployConfManager),
35
52
 
36
- inj.bind(DeployTmpManager, singleton=True),
53
+ bind_manager(DeployGitManager),
54
+
55
+ bind_manager(DeployManager),
56
+
57
+ bind_manager(DeployTmpManager),
37
58
  inj.bind(AtomicPathSwapping, to_key=DeployTmpManager),
38
59
 
39
- inj.bind(DeployVenvManager, singleton=True),
60
+ bind_manager(DeployVenvManager),
61
+ ])
40
62
 
41
- #
63
+ #
42
64
 
65
+ lst.extend([
43
66
  bind_command(DeployCommand, DeployCommandExecutor),
44
67
  bind_command(InterpCommand, InterpCommandExecutor),
45
- ]
68
+ ])
69
+
70
+ #
46
71
 
47
72
  if (dh := deploy_config.deploy_home) is not None:
48
73
  dh = os.path.abspath(os.path.expanduser(dh))
@@ -0,0 +1,21 @@
1
+ # ruff: noqa: UP006 UP007
2
+ import typing as ta
3
+
4
+ from omlish.lite.inject import InjectorBindingOrBindings
5
+ from omlish.lite.inject import InjectorBindings
6
+ from omlish.lite.inject import inj
7
+
8
+ from .manager import DeployPathsManager
9
+ from .owners import DeployPathOwner
10
+ from .owners import DeployPathOwners
11
+
12
+
13
+ def bind_deploy_paths() -> InjectorBindings:
14
+ lst: ta.List[InjectorBindingOrBindings] = [
15
+ inj.bind_array(DeployPathOwner),
16
+ inj.bind_array_type(DeployPathOwner, DeployPathOwners),
17
+
18
+ inj.bind(DeployPathsManager, singleton=True),
19
+ ]
20
+
21
+ return inj.as_bindings(*lst)
@@ -0,0 +1,36 @@
1
+ # ruff: noqa: UP006 UP007
2
+ import typing as ta
3
+
4
+ from omlish.lite.cached import cached_nullary
5
+
6
+ from ..types import DeployHome
7
+ from .owners import DeployPathOwner
8
+ from .owners import DeployPathOwners
9
+ from .paths import DeployPath
10
+ from .paths import DeployPathError
11
+
12
+
13
+ class DeployPathsManager:
14
+ def __init__(
15
+ self,
16
+ *,
17
+ deploy_home: ta.Optional[DeployHome],
18
+ deploy_path_owners: DeployPathOwners,
19
+ ) -> None:
20
+ super().__init__()
21
+
22
+ self._deploy_home = deploy_home
23
+ self._deploy_path_owners = deploy_path_owners
24
+
25
+ @cached_nullary
26
+ def owners_by_path(self) -> ta.Mapping[DeployPath, DeployPathOwner]:
27
+ dct: ta.Dict[DeployPath, DeployPathOwner] = {}
28
+ for o in self._deploy_path_owners:
29
+ for p in o.get_owned_deploy_paths():
30
+ if p in dct:
31
+ raise DeployPathError(f'Duplicate deploy path owner: {p}')
32
+ dct[p] = o
33
+ return dct
34
+
35
+ def validate_deploy_paths(self) -> None:
36
+ self.owners_by_path()
@@ -0,0 +1,50 @@
1
+ # ruff: noqa: UP006 UP007
2
+ import abc
3
+ import os.path
4
+ import typing as ta
5
+
6
+ from omlish.lite.cached import cached_nullary
7
+ from omlish.lite.check import check
8
+
9
+ from ..types import DeployHome
10
+ from .paths import DeployPath
11
+
12
+
13
+ class DeployPathOwner(abc.ABC):
14
+ @abc.abstractmethod
15
+ def get_owned_deploy_paths(self) -> ta.AbstractSet[DeployPath]:
16
+ raise NotImplementedError
17
+
18
+
19
+ DeployPathOwners = ta.NewType('DeployPathOwners', ta.Sequence[DeployPathOwner])
20
+
21
+
22
+ class SingleDirDeployPathOwner(DeployPathOwner, abc.ABC):
23
+ def __init__(
24
+ self,
25
+ *args: ta.Any,
26
+ owned_dir: str,
27
+ deploy_home: ta.Optional[DeployHome],
28
+ **kwargs: ta.Any,
29
+ ) -> None:
30
+ super().__init__(*args, **kwargs)
31
+
32
+ check.not_in('/', owned_dir)
33
+ self._owned_dir: str = check.non_empty_str(owned_dir)
34
+
35
+ self._deploy_home = deploy_home
36
+
37
+ self._owned_deploy_paths = frozenset([DeployPath.parse(self._owned_dir + '/')])
38
+
39
+ @cached_nullary
40
+ def _dir(self) -> str:
41
+ return os.path.join(check.non_empty_str(self._deploy_home), self._owned_dir)
42
+
43
+ @cached_nullary
44
+ def _make_dir(self) -> str:
45
+ if not os.path.isdir(d := self._dir()):
46
+ os.makedirs(d, exist_ok=True)
47
+ return d
48
+
49
+ def get_owned_deploy_paths(self) -> ta.AbstractSet[DeployPath]:
50
+ return self._owned_deploy_paths