ominfra 0.0.0.dev165__tar.gz → 0.0.0.dev167__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.dev165/ominfra.egg-info → ominfra-0.0.0.dev167}/PKG-INFO +3 -3
  2. ominfra-0.0.0.dev167/ominfra/manage/deploy/apps.py +125 -0
  3. ominfra-0.0.0.dev167/ominfra/manage/deploy/conf.py +143 -0
  4. {ominfra-0.0.0.dev165 → ominfra-0.0.0.dev167}/ominfra/manage/deploy/git.py +11 -7
  5. {ominfra-0.0.0.dev165 → ominfra-0.0.0.dev167}/ominfra/manage/deploy/inject.py +32 -7
  6. {ominfra-0.0.0.dev165 → ominfra-0.0.0.dev167}/ominfra/manage/deploy/paths.py +5 -1
  7. {ominfra-0.0.0.dev165 → ominfra-0.0.0.dev167}/ominfra/manage/deploy/specs.py +71 -5
  8. {ominfra-0.0.0.dev165 → ominfra-0.0.0.dev167}/ominfra/manage/deploy/venvs.py +4 -33
  9. {ominfra-0.0.0.dev165 → ominfra-0.0.0.dev167}/ominfra/scripts/journald2aws.py +9 -0
  10. {ominfra-0.0.0.dev165 → ominfra-0.0.0.dev167}/ominfra/scripts/manage.py +348 -54
  11. {ominfra-0.0.0.dev165 → ominfra-0.0.0.dev167}/ominfra/scripts/supervisor.py +10 -0
  12. {ominfra-0.0.0.dev165 → ominfra-0.0.0.dev167}/ominfra/supervisor/configs.py +1 -0
  13. {ominfra-0.0.0.dev165 → ominfra-0.0.0.dev167/ominfra.egg-info}/PKG-INFO +3 -3
  14. {ominfra-0.0.0.dev165 → ominfra-0.0.0.dev167}/ominfra.egg-info/SOURCES.txt +1 -0
  15. {ominfra-0.0.0.dev165 → ominfra-0.0.0.dev167}/ominfra.egg-info/requires.txt +2 -2
  16. {ominfra-0.0.0.dev165 → ominfra-0.0.0.dev167}/pyproject.toml +3 -3
  17. ominfra-0.0.0.dev165/ominfra/manage/deploy/apps.py +0 -74
  18. {ominfra-0.0.0.dev165 → ominfra-0.0.0.dev167}/LICENSE +0 -0
  19. {ominfra-0.0.0.dev165 → ominfra-0.0.0.dev167}/MANIFEST.in +0 -0
  20. {ominfra-0.0.0.dev165 → ominfra-0.0.0.dev167}/README.rst +0 -0
  21. {ominfra-0.0.0.dev165 → ominfra-0.0.0.dev167}/ominfra/.manifests.json +0 -0
  22. {ominfra-0.0.0.dev165 → ominfra-0.0.0.dev167}/ominfra/__about__.py +0 -0
  23. {ominfra-0.0.0.dev165 → ominfra-0.0.0.dev167}/ominfra/__init__.py +0 -0
  24. {ominfra-0.0.0.dev165 → ominfra-0.0.0.dev167}/ominfra/clouds/__init__.py +0 -0
  25. {ominfra-0.0.0.dev165 → ominfra-0.0.0.dev167}/ominfra/clouds/aws/__init__.py +0 -0
  26. {ominfra-0.0.0.dev165 → ominfra-0.0.0.dev167}/ominfra/clouds/aws/__main__.py +0 -0
  27. {ominfra-0.0.0.dev165 → ominfra-0.0.0.dev167}/ominfra/clouds/aws/auth.py +0 -0
  28. {ominfra-0.0.0.dev165 → ominfra-0.0.0.dev167}/ominfra/clouds/aws/cli.py +0 -0
  29. {ominfra-0.0.0.dev165 → ominfra-0.0.0.dev167}/ominfra/clouds/aws/dataclasses.py +0 -0
  30. {ominfra-0.0.0.dev165 → ominfra-0.0.0.dev167}/ominfra/clouds/aws/journald2aws/__init__.py +0 -0
  31. {ominfra-0.0.0.dev165 → ominfra-0.0.0.dev167}/ominfra/clouds/aws/journald2aws/__main__.py +0 -0
  32. {ominfra-0.0.0.dev165 → ominfra-0.0.0.dev167}/ominfra/clouds/aws/journald2aws/cursor.py +0 -0
  33. {ominfra-0.0.0.dev165 → ominfra-0.0.0.dev167}/ominfra/clouds/aws/journald2aws/driver.py +0 -0
  34. {ominfra-0.0.0.dev165 → ominfra-0.0.0.dev167}/ominfra/clouds/aws/journald2aws/main.py +0 -0
  35. {ominfra-0.0.0.dev165 → ominfra-0.0.0.dev167}/ominfra/clouds/aws/journald2aws/poster.py +0 -0
  36. {ominfra-0.0.0.dev165 → ominfra-0.0.0.dev167}/ominfra/clouds/aws/logs.py +0 -0
  37. {ominfra-0.0.0.dev165 → ominfra-0.0.0.dev167}/ominfra/clouds/aws/metadata.py +0 -0
  38. {ominfra-0.0.0.dev165 → ominfra-0.0.0.dev167}/ominfra/clouds/gcp/__init__.py +0 -0
  39. {ominfra-0.0.0.dev165 → ominfra-0.0.0.dev167}/ominfra/clouds/gcp/auth.py +0 -0
  40. {ominfra-0.0.0.dev165 → ominfra-0.0.0.dev167}/ominfra/cmds.py +0 -0
  41. {ominfra-0.0.0.dev165 → ominfra-0.0.0.dev167}/ominfra/configs.py +0 -0
  42. {ominfra-0.0.0.dev165 → ominfra-0.0.0.dev167}/ominfra/journald/__init__.py +0 -0
  43. {ominfra-0.0.0.dev165 → ominfra-0.0.0.dev167}/ominfra/journald/fields.py +0 -0
  44. {ominfra-0.0.0.dev165 → ominfra-0.0.0.dev167}/ominfra/journald/genmessages.py +0 -0
  45. {ominfra-0.0.0.dev165 → ominfra-0.0.0.dev167}/ominfra/journald/messages.py +0 -0
  46. {ominfra-0.0.0.dev165 → ominfra-0.0.0.dev167}/ominfra/journald/tailer.py +0 -0
  47. {ominfra-0.0.0.dev165 → ominfra-0.0.0.dev167}/ominfra/manage/__init__.py +0 -0
  48. {ominfra-0.0.0.dev165 → ominfra-0.0.0.dev167}/ominfra/manage/__main__.py +0 -0
  49. {ominfra-0.0.0.dev165 → ominfra-0.0.0.dev167}/ominfra/manage/bootstrap.py +0 -0
  50. {ominfra-0.0.0.dev165 → ominfra-0.0.0.dev167}/ominfra/manage/bootstrap_.py +0 -0
  51. {ominfra-0.0.0.dev165 → ominfra-0.0.0.dev167}/ominfra/manage/commands/__init__.py +0 -0
  52. {ominfra-0.0.0.dev165 → ominfra-0.0.0.dev167}/ominfra/manage/commands/base.py +0 -0
  53. {ominfra-0.0.0.dev165 → ominfra-0.0.0.dev167}/ominfra/manage/commands/inject.py +0 -0
  54. {ominfra-0.0.0.dev165 → ominfra-0.0.0.dev167}/ominfra/manage/commands/local.py +0 -0
  55. {ominfra-0.0.0.dev165 → ominfra-0.0.0.dev167}/ominfra/manage/commands/marshal.py +0 -0
  56. {ominfra-0.0.0.dev165 → ominfra-0.0.0.dev167}/ominfra/manage/commands/ping.py +0 -0
  57. {ominfra-0.0.0.dev165 → ominfra-0.0.0.dev167}/ominfra/manage/commands/subprocess.py +0 -0
  58. {ominfra-0.0.0.dev165 → ominfra-0.0.0.dev167}/ominfra/manage/commands/types.py +0 -0
  59. {ominfra-0.0.0.dev165 → ominfra-0.0.0.dev167}/ominfra/manage/config.py +0 -0
  60. {ominfra-0.0.0.dev165 → ominfra-0.0.0.dev167}/ominfra/manage/deploy/__init__.py +0 -0
  61. {ominfra-0.0.0.dev165 → ominfra-0.0.0.dev167}/ominfra/manage/deploy/commands.py +0 -0
  62. {ominfra-0.0.0.dev165 → ominfra-0.0.0.dev167}/ominfra/manage/deploy/config.py +0 -0
  63. {ominfra-0.0.0.dev165 → ominfra-0.0.0.dev167}/ominfra/manage/deploy/interp.py +0 -0
  64. {ominfra-0.0.0.dev165 → ominfra-0.0.0.dev167}/ominfra/manage/deploy/tmp.py +0 -0
  65. {ominfra-0.0.0.dev165 → ominfra-0.0.0.dev167}/ominfra/manage/deploy/types.py +0 -0
  66. {ominfra-0.0.0.dev165 → ominfra-0.0.0.dev167}/ominfra/manage/inject.py +0 -0
  67. {ominfra-0.0.0.dev165 → ominfra-0.0.0.dev167}/ominfra/manage/main.py +0 -0
  68. {ominfra-0.0.0.dev165 → ominfra-0.0.0.dev167}/ominfra/manage/marshal.py +0 -0
  69. {ominfra-0.0.0.dev165 → ominfra-0.0.0.dev167}/ominfra/manage/remote/__init__.py +0 -0
  70. {ominfra-0.0.0.dev165 → ominfra-0.0.0.dev167}/ominfra/manage/remote/_main.py +0 -0
  71. {ominfra-0.0.0.dev165 → ominfra-0.0.0.dev167}/ominfra/manage/remote/channel.py +0 -0
  72. {ominfra-0.0.0.dev165 → ominfra-0.0.0.dev167}/ominfra/manage/remote/config.py +0 -0
  73. {ominfra-0.0.0.dev165 → ominfra-0.0.0.dev167}/ominfra/manage/remote/connection.py +0 -0
  74. {ominfra-0.0.0.dev165 → ominfra-0.0.0.dev167}/ominfra/manage/remote/execution.py +0 -0
  75. {ominfra-0.0.0.dev165 → ominfra-0.0.0.dev167}/ominfra/manage/remote/inject.py +0 -0
  76. {ominfra-0.0.0.dev165 → ominfra-0.0.0.dev167}/ominfra/manage/remote/payload.py +0 -0
  77. {ominfra-0.0.0.dev165 → ominfra-0.0.0.dev167}/ominfra/manage/remote/spawning.py +0 -0
  78. {ominfra-0.0.0.dev165 → ominfra-0.0.0.dev167}/ominfra/manage/system/__init__.py +0 -0
  79. {ominfra-0.0.0.dev165 → ominfra-0.0.0.dev167}/ominfra/manage/system/commands.py +0 -0
  80. {ominfra-0.0.0.dev165 → ominfra-0.0.0.dev167}/ominfra/manage/system/config.py +0 -0
  81. {ominfra-0.0.0.dev165 → ominfra-0.0.0.dev167}/ominfra/manage/system/inject.py +0 -0
  82. {ominfra-0.0.0.dev165 → ominfra-0.0.0.dev167}/ominfra/manage/system/packages.py +0 -0
  83. {ominfra-0.0.0.dev165 → ominfra-0.0.0.dev167}/ominfra/manage/system/platforms.py +0 -0
  84. {ominfra-0.0.0.dev165 → ominfra-0.0.0.dev167}/ominfra/manage/targets/__init__.py +0 -0
  85. {ominfra-0.0.0.dev165 → ominfra-0.0.0.dev167}/ominfra/manage/targets/connection.py +0 -0
  86. {ominfra-0.0.0.dev165 → ominfra-0.0.0.dev167}/ominfra/manage/targets/inject.py +0 -0
  87. {ominfra-0.0.0.dev165 → ominfra-0.0.0.dev167}/ominfra/manage/targets/targets.py +0 -0
  88. {ominfra-0.0.0.dev165 → ominfra-0.0.0.dev167}/ominfra/pyremote.py +0 -0
  89. {ominfra-0.0.0.dev165 → ominfra-0.0.0.dev167}/ominfra/scripts/__init__.py +0 -0
  90. {ominfra-0.0.0.dev165 → ominfra-0.0.0.dev167}/ominfra/ssh.py +0 -0
  91. {ominfra-0.0.0.dev165 → ominfra-0.0.0.dev167}/ominfra/supervisor/LICENSE.txt +0 -0
  92. {ominfra-0.0.0.dev165 → ominfra-0.0.0.dev167}/ominfra/supervisor/__init__.py +0 -0
  93. {ominfra-0.0.0.dev165 → ominfra-0.0.0.dev167}/ominfra/supervisor/__main__.py +0 -0
  94. {ominfra-0.0.0.dev165 → ominfra-0.0.0.dev167}/ominfra/supervisor/dispatchers.py +0 -0
  95. {ominfra-0.0.0.dev165 → ominfra-0.0.0.dev167}/ominfra/supervisor/dispatchersimpl.py +0 -0
  96. {ominfra-0.0.0.dev165 → ominfra-0.0.0.dev167}/ominfra/supervisor/events.py +0 -0
  97. {ominfra-0.0.0.dev165 → ominfra-0.0.0.dev167}/ominfra/supervisor/exceptions.py +0 -0
  98. {ominfra-0.0.0.dev165 → ominfra-0.0.0.dev167}/ominfra/supervisor/groups.py +0 -0
  99. {ominfra-0.0.0.dev165 → ominfra-0.0.0.dev167}/ominfra/supervisor/groupsimpl.py +0 -0
  100. {ominfra-0.0.0.dev165 → ominfra-0.0.0.dev167}/ominfra/supervisor/http.py +0 -0
  101. {ominfra-0.0.0.dev165 → ominfra-0.0.0.dev167}/ominfra/supervisor/inject.py +0 -0
  102. {ominfra-0.0.0.dev165 → ominfra-0.0.0.dev167}/ominfra/supervisor/io.py +0 -0
  103. {ominfra-0.0.0.dev165 → ominfra-0.0.0.dev167}/ominfra/supervisor/main.py +0 -0
  104. {ominfra-0.0.0.dev165 → ominfra-0.0.0.dev167}/ominfra/supervisor/pipes.py +0 -0
  105. {ominfra-0.0.0.dev165 → ominfra-0.0.0.dev167}/ominfra/supervisor/privileges.py +0 -0
  106. {ominfra-0.0.0.dev165 → ominfra-0.0.0.dev167}/ominfra/supervisor/process.py +0 -0
  107. {ominfra-0.0.0.dev165 → ominfra-0.0.0.dev167}/ominfra/supervisor/processimpl.py +0 -0
  108. {ominfra-0.0.0.dev165 → ominfra-0.0.0.dev167}/ominfra/supervisor/setup.py +0 -0
  109. {ominfra-0.0.0.dev165 → ominfra-0.0.0.dev167}/ominfra/supervisor/setupimpl.py +0 -0
  110. {ominfra-0.0.0.dev165 → ominfra-0.0.0.dev167}/ominfra/supervisor/signals.py +0 -0
  111. {ominfra-0.0.0.dev165 → ominfra-0.0.0.dev167}/ominfra/supervisor/spawning.py +0 -0
  112. {ominfra-0.0.0.dev165 → ominfra-0.0.0.dev167}/ominfra/supervisor/spawningimpl.py +0 -0
  113. {ominfra-0.0.0.dev165 → ominfra-0.0.0.dev167}/ominfra/supervisor/states.py +0 -0
  114. {ominfra-0.0.0.dev165 → ominfra-0.0.0.dev167}/ominfra/supervisor/supervisor.py +0 -0
  115. {ominfra-0.0.0.dev165 → ominfra-0.0.0.dev167}/ominfra/supervisor/types.py +0 -0
  116. {ominfra-0.0.0.dev165 → ominfra-0.0.0.dev167}/ominfra/supervisor/utils/__init__.py +0 -0
  117. {ominfra-0.0.0.dev165 → ominfra-0.0.0.dev167}/ominfra/supervisor/utils/collections.py +0 -0
  118. {ominfra-0.0.0.dev165 → ominfra-0.0.0.dev167}/ominfra/supervisor/utils/diag.py +0 -0
  119. {ominfra-0.0.0.dev165 → ominfra-0.0.0.dev167}/ominfra/supervisor/utils/fds.py +0 -0
  120. {ominfra-0.0.0.dev165 → ominfra-0.0.0.dev167}/ominfra/supervisor/utils/fs.py +0 -0
  121. {ominfra-0.0.0.dev165 → ominfra-0.0.0.dev167}/ominfra/supervisor/utils/os.py +0 -0
  122. {ominfra-0.0.0.dev165 → ominfra-0.0.0.dev167}/ominfra/supervisor/utils/ostypes.py +0 -0
  123. {ominfra-0.0.0.dev165 → ominfra-0.0.0.dev167}/ominfra/supervisor/utils/signals.py +0 -0
  124. {ominfra-0.0.0.dev165 → ominfra-0.0.0.dev167}/ominfra/supervisor/utils/strings.py +0 -0
  125. {ominfra-0.0.0.dev165 → ominfra-0.0.0.dev167}/ominfra/supervisor/utils/users.py +0 -0
  126. {ominfra-0.0.0.dev165 → ominfra-0.0.0.dev167}/ominfra/tailscale/__init__.py +0 -0
  127. {ominfra-0.0.0.dev165 → ominfra-0.0.0.dev167}/ominfra/tailscale/api.py +0 -0
  128. {ominfra-0.0.0.dev165 → ominfra-0.0.0.dev167}/ominfra/tailscale/cli.py +0 -0
  129. {ominfra-0.0.0.dev165 → ominfra-0.0.0.dev167}/ominfra/threadworkers.py +0 -0
  130. {ominfra-0.0.0.dev165 → ominfra-0.0.0.dev167}/ominfra/tools/__init__.py +0 -0
  131. {ominfra-0.0.0.dev165 → ominfra-0.0.0.dev167}/ominfra/tools/listresources.py +0 -0
  132. {ominfra-0.0.0.dev165 → ominfra-0.0.0.dev167}/ominfra.egg-info/dependency_links.txt +0 -0
  133. {ominfra-0.0.0.dev165 → ominfra-0.0.0.dev167}/ominfra.egg-info/entry_points.txt +0 -0
  134. {ominfra-0.0.0.dev165 → ominfra-0.0.0.dev167}/ominfra.egg-info/top_level.txt +0 -0
  135. {ominfra-0.0.0.dev165 → ominfra-0.0.0.dev167}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: ominfra
3
- Version: 0.0.0.dev165
3
+ Version: 0.0.0.dev167
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.dev165
16
- Requires-Dist: omlish==0.0.0.dev165
15
+ Requires-Dist: omdev==0.0.0.dev167
16
+ Requires-Dist: omlish==0.0.0.dev167
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,125 @@
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 import DeployPath
13
+ from .paths import DeployPathOwner
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
+ @cached_nullary
55
+ def _dir(self) -> str:
56
+ return os.path.join(check.non_empty_str(self._deploy_home), 'apps')
57
+
58
+ def get_owned_deploy_paths(self) -> ta.AbstractSet[DeployPath]:
59
+ return {
60
+ DeployPath.parse('apps/@app/current'),
61
+ DeployPath.parse('apps/@app/deploying'),
62
+
63
+ DeployPath.parse('apps/@app/tags/@tag/conf/'),
64
+ DeployPath.parse('apps/@app/tags/@tag/git/'),
65
+ DeployPath.parse('apps/@app/tags/@tag/venv/'),
66
+ }
67
+
68
+ async def prepare_app(
69
+ self,
70
+ spec: DeploySpec,
71
+ ) -> None:
72
+ app_tag = DeployAppTag(spec.app, make_deploy_tag(spec.git.rev, spec.key()))
73
+
74
+ #
75
+
76
+ app_dir = os.path.join(self._dir(), spec.app)
77
+ os.makedirs(app_dir, exist_ok=True)
78
+
79
+ #
80
+
81
+ tag_dir = os.path.join(app_dir, 'tags', app_tag.tag)
82
+ os.makedirs(tag_dir)
83
+
84
+ #
85
+
86
+ deploying_file = os.path.join(app_dir, 'deploying')
87
+ current_file = os.path.join(app_dir, 'current')
88
+
89
+ if os.path.exists(deploying_file):
90
+ os.unlink(deploying_file)
91
+ relative_symlink(tag_dir, deploying_file, target_is_directory=True)
92
+
93
+ #
94
+
95
+ git_dir = os.path.join(tag_dir, 'git')
96
+ await self._git.checkout(
97
+ spec.git,
98
+ git_dir,
99
+ )
100
+
101
+ #
102
+
103
+ if spec.venv is not None:
104
+ venv_dir = os.path.join(tag_dir, 'venv')
105
+ await self._venvs.setup_venv(
106
+ spec.venv,
107
+ git_dir,
108
+ venv_dir,
109
+ )
110
+
111
+ #
112
+
113
+ if spec.conf is not None:
114
+ conf_dir = os.path.join(tag_dir, 'conf')
115
+ conf_link_dir = os.path.join(current_file, 'conf')
116
+ await self._conf.write_conf(
117
+ spec.conf,
118
+ conf_dir,
119
+ app_tag,
120
+ conf_link_dir,
121
+ )
122
+
123
+ #
124
+
125
+ os.replace(deploying_file, current_file)
@@ -0,0 +1,143 @@
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 import SingleDirDeployPathOwner
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(SingleDirDeployPathOwner):
37
+ def __init__(
38
+ self,
39
+ *,
40
+ deploy_home: ta.Optional[DeployHome] = None,
41
+ ) -> None:
42
+ super().__init__(
43
+ owned_dir='conf',
44
+ deploy_home=deploy_home,
45
+ )
46
+
47
+ async def _write_conf_file(
48
+ self,
49
+ cf: DeployConfFile,
50
+ conf_dir: str,
51
+ ) -> None:
52
+ conf_file = os.path.join(conf_dir, cf.path)
53
+ check.arg(is_path_in_dir(conf_dir, conf_file))
54
+
55
+ os.makedirs(os.path.dirname(conf_file), exist_ok=True)
56
+
57
+ with open(conf_file, 'w') as f: # noqa
58
+ f.write(cf.body)
59
+
60
+ async def _make_conf_link(
61
+ self,
62
+ link: DeployConfLink,
63
+ conf_dir: str,
64
+ app_tag: DeployAppTag,
65
+ link_dir: str,
66
+ ) -> None:
67
+ link_src = os.path.join(conf_dir, link.src)
68
+ check.arg(is_path_in_dir(conf_dir, link_src))
69
+
70
+ is_link_dir = link.src.endswith('/')
71
+ if is_link_dir:
72
+ check.arg(link.src.count('/') == 1)
73
+ check.arg(os.path.isdir(link_src))
74
+ link_dst_pfx = link.src
75
+ link_dst_sfx = ''
76
+
77
+ elif '/' in link.src:
78
+ check.arg(os.path.isfile(link_src))
79
+ d, f = os.path.split(link.src)
80
+ # TODO: check filename :|
81
+ link_dst_pfx = d + '/'
82
+ link_dst_sfx = '-' + f
83
+
84
+ else:
85
+ check.arg(os.path.isfile(link_src))
86
+ if '.' in link.src:
87
+ l, _, r = link.src.partition('.')
88
+ link_dst_pfx = l + '/'
89
+ link_dst_sfx = '.' + r
90
+ else:
91
+ link_dst_pfx = link.src + '/'
92
+ link_dst_sfx = ''
93
+
94
+ if isinstance(link, AppDeployConfLink):
95
+ link_dst_mid = str(app_tag.app)
96
+ sym_root = link_dir
97
+ elif isinstance(link, TagDeployConfLink):
98
+ link_dst_mid = '-'.join([app_tag.app, app_tag.tag])
99
+ sym_root = conf_dir
100
+ else:
101
+ raise TypeError(link)
102
+
103
+ link_dst = ''.join([
104
+ link_dst_pfx,
105
+ link_dst_mid,
106
+ link_dst_sfx,
107
+ ])
108
+
109
+ root_conf_dir = self._make_dir()
110
+ sym_src = os.path.join(sym_root, link.src)
111
+ sym_dst = os.path.join(root_conf_dir, link_dst)
112
+ check.arg(is_path_in_dir(root_conf_dir, sym_dst))
113
+
114
+ os.makedirs(os.path.dirname(sym_dst), exist_ok=True)
115
+ relative_symlink(sym_src, sym_dst, target_is_directory=is_link_dir)
116
+
117
+ async def write_conf(
118
+ self,
119
+ spec: DeployConfSpec,
120
+ conf_dir: str,
121
+ app_tag: DeployAppTag,
122
+ link_dir: str,
123
+ ) -> None:
124
+ conf_dir = os.path.abspath(conf_dir)
125
+ os.makedirs(conf_dir)
126
+
127
+ #
128
+
129
+ for cf in spec.files or []:
130
+ await self._write_conf_file(
131
+ cf,
132
+ conf_dir,
133
+ )
134
+
135
+ #
136
+
137
+ for link in spec.links or []:
138
+ await self._make_conf_link(
139
+ link,
140
+ conf_dir,
141
+ app_tag,
142
+ link_dir,
143
+ )
@@ -18,8 +18,8 @@ from omlish.lite.check import check
18
18
  from omlish.os.atomics import AtomicPathSwapping
19
19
 
20
20
  from .paths import SingleDirDeployPathOwner
21
- from .specs import DeployGitCheckout
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,13 @@ 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
15
16
  from .git import DeployGitManager
16
17
  from .interp import InterpCommand
17
18
  from .interp import InterpCommandExecutor
19
+ from .paths import DeployPathOwner
20
+ from .paths import DeployPathOwners
18
21
  from .tmp import DeployTmpManager
19
22
  from .types import DeployHome
20
23
  from .venvs import DeployVenvManager
@@ -26,23 +29,45 @@ def bind_deploy(
26
29
  ) -> InjectorBindings:
27
30
  lst: ta.List[InjectorBindingOrBindings] = [
28
31
  inj.bind(deploy_config),
32
+ ]
33
+
34
+ #
35
+
36
+ def bind_manager(cls: type) -> InjectorBindings:
37
+ return inj.as_bindings(
38
+ inj.bind(cls, singleton=True),
39
+
40
+ *([inj.bind(DeployPathOwner, to_key=cls, array=True)] if issubclass(cls, DeployPathOwner) else []),
41
+ )
29
42
 
30
- #
43
+ lst.extend([
44
+ inj.bind_array(DeployPathOwner),
45
+ inj.bind_array_type(DeployPathOwner, DeployPathOwners),
46
+ ])
31
47
 
32
- inj.bind(DeployAppManager, singleton=True),
48
+ #
33
49
 
34
- inj.bind(DeployGitManager, singleton=True),
50
+ lst.extend([
51
+ bind_manager(DeployAppManager),
35
52
 
36
- inj.bind(DeployTmpManager, singleton=True),
53
+ bind_manager(DeployConfManager),
54
+
55
+ bind_manager(DeployGitManager),
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))
@@ -1,11 +1,12 @@
1
1
  # ruff: noqa: UP006 UP007
2
2
  """
3
3
  TODO:
4
- - run/pidfile
4
+ - run/{.pid,.sock}
5
5
  - logs/...
6
6
  - current symlink
7
7
  - conf/{nginx,supervisor}
8
8
  - env/?
9
+ - apps/<app>/shared
9
10
  """
10
11
  import abc
11
12
  import dataclasses as dc
@@ -208,6 +209,9 @@ class DeployPathOwner(abc.ABC):
208
209
  raise NotImplementedError
209
210
 
210
211
 
212
+ DeployPathOwners = ta.NewType('DeployPathOwners', ta.Sequence[DeployPathOwner])
213
+
214
+
211
215
  class SingleDirDeployPathOwner(DeployPathOwner, abc.ABC):
212
216
  def __init__(
213
217
  self,
@@ -1,4 +1,5 @@
1
1
  # ruff: noqa: UP006 UP007
2
+ import abc
2
3
  import dataclasses as dc
3
4
  import hashlib
4
5
  import typing as ta
@@ -14,6 +15,17 @@ from .types import DeployRev
14
15
  ##
15
16
 
16
17
 
18
+ def check_valid_deploy_spec_path(s: str) -> str:
19
+ check.non_empty_str(s)
20
+ for c in ['..', '//']:
21
+ check.not_in(c, s)
22
+ check.arg(not s.startswith('/'))
23
+ return s
24
+
25
+
26
+ ##
27
+
28
+
17
29
  @dc.dataclass(frozen=True)
18
30
  class DeployGitRepo:
19
31
  host: ta.Optional[str] = None
@@ -26,7 +38,7 @@ class DeployGitRepo:
26
38
 
27
39
 
28
40
  @dc.dataclass(frozen=True)
29
- class DeployGitCheckout:
41
+ class DeployGitSpec:
30
42
  repo: DeployGitRepo
31
43
  rev: DeployRev
32
44
 
@@ -52,8 +64,62 @@ class DeployVenvSpec:
52
64
 
53
65
  use_uv: bool = False
54
66
 
67
+
68
+ ##
69
+
70
+
71
+ @dc.dataclass(frozen=True)
72
+ class DeployConfFile:
73
+ path: str
74
+ body: str
75
+
55
76
  def __post_init__(self) -> None:
56
- hash(self)
77
+ check_valid_deploy_spec_path(self.path)
78
+
79
+
80
+ #
81
+
82
+
83
+ @dc.dataclass(frozen=True)
84
+ class DeployConfLink(abc.ABC): # noqa
85
+ """
86
+ May be either:
87
+ - @conf(.ext)* - links a single file in root of app conf dir to conf/@conf/@dst(.ext)*
88
+ - @conf/file - links a single file in a single subdir to conf/@conf/@dst-file
89
+ - @conf/ - links a directory in root of app conf dir to conf/@conf/@dst/
90
+ """
91
+
92
+ src: str
93
+
94
+ def __post_init__(self) -> None:
95
+ check_valid_deploy_spec_path(self.src)
96
+ if '/' in self.src:
97
+ check.equal(self.src.count('/'), 1)
98
+
99
+
100
+ class AppDeployConfLink(DeployConfLink):
101
+ pass
102
+
103
+
104
+ class TagDeployConfLink(DeployConfLink):
105
+ pass
106
+
107
+
108
+ #
109
+
110
+
111
+ @dc.dataclass(frozen=True)
112
+ class DeployConfSpec:
113
+ files: ta.Optional[ta.Sequence[DeployConfFile]] = None
114
+
115
+ links: ta.Optional[ta.Sequence[DeployConfLink]] = None
116
+
117
+ def __post_init__(self) -> None:
118
+ if self.files:
119
+ seen: ta.Set[str] = set()
120
+ for f in self.files:
121
+ check.not_in(f.path, seen)
122
+ seen.add(f.path)
57
123
 
58
124
 
59
125
  ##
@@ -62,12 +128,12 @@ class DeployVenvSpec:
62
128
  @dc.dataclass(frozen=True)
63
129
  class DeploySpec:
64
130
  app: DeployApp
65
- checkout: DeployGitCheckout
131
+
132
+ git: DeployGitSpec
66
133
 
67
134
  venv: ta.Optional[DeployVenvSpec] = None
68
135
 
69
- def __post_init__(self) -> None:
70
- hash(self)
136
+ conf: ta.Optional[DeployConfSpec] = None
71
137
 
72
138
  @cached_nullary
73
139
  def key(self) -> DeployKey:
@@ -5,46 +5,28 @@ TODO:
5
5
  - share more code with pyproject?
6
6
  """
7
7
  import os.path
8
- import typing as ta
9
8
 
10
9
  from omlish.asyncs.asyncio.subprocesses import asyncio_subprocesses
11
- from omlish.lite.cached import cached_nullary
12
- from omlish.lite.check import check
13
10
  from omlish.os.atomics import AtomicPathSwapping
14
11
 
15
- from .paths import DeployPath
16
- from .paths import DeployPathOwner
17
12
  from .specs import DeployVenvSpec
18
- from .types import DeployAppTag
19
- from .types import DeployHome
20
13
 
21
14
 
22
- class DeployVenvManager(DeployPathOwner):
15
+ class DeployVenvManager:
23
16
  def __init__(
24
17
  self,
25
18
  *,
26
- deploy_home: ta.Optional[DeployHome] = None,
27
19
  atomics: AtomicPathSwapping,
28
20
  ) -> None:
29
21
  super().__init__()
30
22
 
31
- self._deploy_home = deploy_home
32
23
  self._atomics = atomics
33
24
 
34
- @cached_nullary
35
- def _dir(self) -> str:
36
- return os.path.join(check.non_empty_str(self._deploy_home), 'venvs')
37
-
38
- def get_owned_deploy_paths(self) -> ta.AbstractSet[DeployPath]:
39
- return {
40
- DeployPath.parse('venvs/@app/@tag/'),
41
- }
42
-
43
25
  async def setup_venv(
44
26
  self,
45
- app_dir: str,
46
- venv_dir: str,
47
27
  spec: DeployVenvSpec,
28
+ git_dir: str,
29
+ venv_dir: str,
48
30
  ) -> None:
49
31
  sys_exe = 'python3'
50
32
 
@@ -58,7 +40,7 @@ class DeployVenvManager(DeployPathOwner):
58
40
 
59
41
  #
60
42
 
61
- reqs_txt = os.path.join(app_dir, 'requirements.txt')
43
+ reqs_txt = os.path.join(git_dir, 'requirements.txt')
62
44
 
63
45
  if os.path.isfile(reqs_txt):
64
46
  if spec.use_uv:
@@ -68,14 +50,3 @@ class DeployVenvManager(DeployPathOwner):
68
50
  pip_cmd = ['-m', 'pip']
69
51
 
70
52
  await asyncio_subprocesses.check_call(venv_exe, *pip_cmd,'install', '-r', reqs_txt)
71
-
72
- async def setup_app_venv(
73
- self,
74
- app_tag: DeployAppTag,
75
- spec: DeployVenvSpec,
76
- ) -> None:
77
- await self.setup_venv(
78
- os.path.join(check.non_empty_str(self._deploy_home), 'apps', app_tag.app, app_tag.tag),
79
- os.path.join(self._dir(), app_tag.app, app_tag.tag),
80
- spec,
81
- )
@@ -1527,6 +1527,15 @@ def snake_case(name: str) -> str:
1527
1527
  ##
1528
1528
 
1529
1529
 
1530
+ def strip_with_newline(s: str) -> str:
1531
+ if not s:
1532
+ return ''
1533
+ return s.strip() + '\n'
1534
+
1535
+
1536
+ ##
1537
+
1538
+
1530
1539
  def is_dunder(name: str) -> bool:
1531
1540
  return (
1532
1541
  name[:2] == name[-2:] == '__' and