ominfra 0.0.0.dev190__tar.gz → 0.0.0.dev191__tar.gz

Sign up to get free protection for your applications and to get access to all the features.
Files changed (151) hide show
  1. {ominfra-0.0.0.dev190/ominfra.egg-info → ominfra-0.0.0.dev191}/PKG-INFO +4 -4
  2. {ominfra-0.0.0.dev190 → ominfra-0.0.0.dev191}/ominfra/manage/deploy/apps.py +72 -3
  3. {ominfra-0.0.0.dev190 → ominfra-0.0.0.dev191}/ominfra/manage/deploy/conf/manager.py +64 -7
  4. {ominfra-0.0.0.dev190 → ominfra-0.0.0.dev191}/ominfra/manage/deploy/deploy.py +119 -31
  5. {ominfra-0.0.0.dev190 → ominfra-0.0.0.dev191}/ominfra/manage/deploy/specs.py +9 -0
  6. {ominfra-0.0.0.dev190 → ominfra-0.0.0.dev191}/ominfra/manage/deploy/systemd.py +48 -27
  7. {ominfra-0.0.0.dev190 → ominfra-0.0.0.dev191}/ominfra/manage/deploy/tags.py +1 -1
  8. {ominfra-0.0.0.dev190 → ominfra-0.0.0.dev191}/ominfra/manage/deploy/venvs.py +8 -4
  9. {ominfra-0.0.0.dev190 → ominfra-0.0.0.dev191}/ominfra/scripts/manage.py +308 -72
  10. ominfra-0.0.0.dev191/ominfra/systemd.py +49 -0
  11. {ominfra-0.0.0.dev190 → ominfra-0.0.0.dev191/ominfra.egg-info}/PKG-INFO +4 -4
  12. {ominfra-0.0.0.dev190 → ominfra-0.0.0.dev191}/ominfra.egg-info/SOURCES.txt +1 -0
  13. {ominfra-0.0.0.dev190 → ominfra-0.0.0.dev191}/ominfra.egg-info/requires.txt +3 -3
  14. {ominfra-0.0.0.dev190 → ominfra-0.0.0.dev191}/pyproject.toml +4 -4
  15. {ominfra-0.0.0.dev190 → ominfra-0.0.0.dev191}/LICENSE +0 -0
  16. {ominfra-0.0.0.dev190 → ominfra-0.0.0.dev191}/MANIFEST.in +0 -0
  17. {ominfra-0.0.0.dev190 → ominfra-0.0.0.dev191}/README.rst +0 -0
  18. {ominfra-0.0.0.dev190 → ominfra-0.0.0.dev191}/ominfra/.manifests.json +0 -0
  19. {ominfra-0.0.0.dev190 → ominfra-0.0.0.dev191}/ominfra/__about__.py +0 -0
  20. {ominfra-0.0.0.dev190 → ominfra-0.0.0.dev191}/ominfra/__init__.py +0 -0
  21. {ominfra-0.0.0.dev190 → ominfra-0.0.0.dev191}/ominfra/clouds/__init__.py +0 -0
  22. {ominfra-0.0.0.dev190 → ominfra-0.0.0.dev191}/ominfra/clouds/aws/__init__.py +0 -0
  23. {ominfra-0.0.0.dev190 → ominfra-0.0.0.dev191}/ominfra/clouds/aws/__main__.py +0 -0
  24. {ominfra-0.0.0.dev190 → ominfra-0.0.0.dev191}/ominfra/clouds/aws/auth.py +0 -0
  25. {ominfra-0.0.0.dev190 → ominfra-0.0.0.dev191}/ominfra/clouds/aws/cli.py +0 -0
  26. {ominfra-0.0.0.dev190 → ominfra-0.0.0.dev191}/ominfra/clouds/aws/dataclasses.py +0 -0
  27. {ominfra-0.0.0.dev190 → ominfra-0.0.0.dev191}/ominfra/clouds/aws/journald2aws/__init__.py +0 -0
  28. {ominfra-0.0.0.dev190 → ominfra-0.0.0.dev191}/ominfra/clouds/aws/journald2aws/__main__.py +0 -0
  29. {ominfra-0.0.0.dev190 → ominfra-0.0.0.dev191}/ominfra/clouds/aws/journald2aws/cursor.py +0 -0
  30. {ominfra-0.0.0.dev190 → ominfra-0.0.0.dev191}/ominfra/clouds/aws/journald2aws/driver.py +0 -0
  31. {ominfra-0.0.0.dev190 → ominfra-0.0.0.dev191}/ominfra/clouds/aws/journald2aws/main.py +0 -0
  32. {ominfra-0.0.0.dev190 → ominfra-0.0.0.dev191}/ominfra/clouds/aws/journald2aws/poster.py +0 -0
  33. {ominfra-0.0.0.dev190 → ominfra-0.0.0.dev191}/ominfra/clouds/aws/logs.py +0 -0
  34. {ominfra-0.0.0.dev190 → ominfra-0.0.0.dev191}/ominfra/clouds/aws/metadata.py +0 -0
  35. {ominfra-0.0.0.dev190 → ominfra-0.0.0.dev191}/ominfra/clouds/gcp/__init__.py +0 -0
  36. {ominfra-0.0.0.dev190 → ominfra-0.0.0.dev191}/ominfra/clouds/gcp/auth.py +0 -0
  37. {ominfra-0.0.0.dev190 → ominfra-0.0.0.dev191}/ominfra/cmds.py +0 -0
  38. {ominfra-0.0.0.dev190 → ominfra-0.0.0.dev191}/ominfra/configs.py +0 -0
  39. {ominfra-0.0.0.dev190 → ominfra-0.0.0.dev191}/ominfra/journald/__init__.py +0 -0
  40. {ominfra-0.0.0.dev190 → ominfra-0.0.0.dev191}/ominfra/journald/fields.py +0 -0
  41. {ominfra-0.0.0.dev190 → ominfra-0.0.0.dev191}/ominfra/journald/genmessages.py +0 -0
  42. {ominfra-0.0.0.dev190 → ominfra-0.0.0.dev191}/ominfra/journald/messages.py +0 -0
  43. {ominfra-0.0.0.dev190 → ominfra-0.0.0.dev191}/ominfra/journald/tailer.py +0 -0
  44. {ominfra-0.0.0.dev190 → ominfra-0.0.0.dev191}/ominfra/manage/__init__.py +0 -0
  45. {ominfra-0.0.0.dev190 → ominfra-0.0.0.dev191}/ominfra/manage/__main__.py +0 -0
  46. {ominfra-0.0.0.dev190 → ominfra-0.0.0.dev191}/ominfra/manage/bootstrap.py +0 -0
  47. {ominfra-0.0.0.dev190 → ominfra-0.0.0.dev191}/ominfra/manage/bootstrap_.py +0 -0
  48. {ominfra-0.0.0.dev190 → ominfra-0.0.0.dev191}/ominfra/manage/commands/__init__.py +0 -0
  49. {ominfra-0.0.0.dev190 → ominfra-0.0.0.dev191}/ominfra/manage/commands/base.py +0 -0
  50. {ominfra-0.0.0.dev190 → ominfra-0.0.0.dev191}/ominfra/manage/commands/inject.py +0 -0
  51. {ominfra-0.0.0.dev190 → ominfra-0.0.0.dev191}/ominfra/manage/commands/local.py +0 -0
  52. {ominfra-0.0.0.dev190 → ominfra-0.0.0.dev191}/ominfra/manage/commands/marshal.py +0 -0
  53. {ominfra-0.0.0.dev190 → ominfra-0.0.0.dev191}/ominfra/manage/commands/ping.py +0 -0
  54. {ominfra-0.0.0.dev190 → ominfra-0.0.0.dev191}/ominfra/manage/commands/subprocess.py +0 -0
  55. {ominfra-0.0.0.dev190 → ominfra-0.0.0.dev191}/ominfra/manage/commands/types.py +0 -0
  56. {ominfra-0.0.0.dev190 → ominfra-0.0.0.dev191}/ominfra/manage/config.py +0 -0
  57. {ominfra-0.0.0.dev190 → ominfra-0.0.0.dev191}/ominfra/manage/deploy/__init__.py +0 -0
  58. {ominfra-0.0.0.dev190 → ominfra-0.0.0.dev191}/ominfra/manage/deploy/commands.py +0 -0
  59. {ominfra-0.0.0.dev190 → ominfra-0.0.0.dev191}/ominfra/manage/deploy/conf/__init__.py +0 -0
  60. {ominfra-0.0.0.dev190 → ominfra-0.0.0.dev191}/ominfra/manage/deploy/conf/inject.py +0 -0
  61. {ominfra-0.0.0.dev190 → ominfra-0.0.0.dev191}/ominfra/manage/deploy/conf/specs.py +0 -0
  62. {ominfra-0.0.0.dev190 → ominfra-0.0.0.dev191}/ominfra/manage/deploy/config.py +0 -0
  63. {ominfra-0.0.0.dev190 → ominfra-0.0.0.dev191}/ominfra/manage/deploy/git.py +0 -0
  64. {ominfra-0.0.0.dev190 → ominfra-0.0.0.dev191}/ominfra/manage/deploy/inject.py +0 -0
  65. {ominfra-0.0.0.dev190 → ominfra-0.0.0.dev191}/ominfra/manage/deploy/inject_.py +0 -0
  66. {ominfra-0.0.0.dev190 → ominfra-0.0.0.dev191}/ominfra/manage/deploy/interp.py +0 -0
  67. {ominfra-0.0.0.dev190 → ominfra-0.0.0.dev191}/ominfra/manage/deploy/nginx.py +0 -0
  68. {ominfra-0.0.0.dev190 → ominfra-0.0.0.dev191}/ominfra/manage/deploy/paths/__init__.py +0 -0
  69. {ominfra-0.0.0.dev190 → ominfra-0.0.0.dev191}/ominfra/manage/deploy/paths/inject.py +0 -0
  70. {ominfra-0.0.0.dev190 → ominfra-0.0.0.dev191}/ominfra/manage/deploy/paths/manager.py +0 -0
  71. {ominfra-0.0.0.dev190 → ominfra-0.0.0.dev191}/ominfra/manage/deploy/paths/owners.py +0 -0
  72. {ominfra-0.0.0.dev190 → ominfra-0.0.0.dev191}/ominfra/manage/deploy/paths/paths.py +0 -0
  73. {ominfra-0.0.0.dev190 → ominfra-0.0.0.dev191}/ominfra/manage/deploy/paths/specs.py +0 -0
  74. {ominfra-0.0.0.dev190 → ominfra-0.0.0.dev191}/ominfra/manage/deploy/paths/types.py +0 -0
  75. {ominfra-0.0.0.dev190 → ominfra-0.0.0.dev191}/ominfra/manage/deploy/tmp.py +0 -0
  76. {ominfra-0.0.0.dev190 → ominfra-0.0.0.dev191}/ominfra/manage/deploy/types.py +0 -0
  77. {ominfra-0.0.0.dev190 → ominfra-0.0.0.dev191}/ominfra/manage/inject.py +0 -0
  78. {ominfra-0.0.0.dev190 → ominfra-0.0.0.dev191}/ominfra/manage/main.py +0 -0
  79. {ominfra-0.0.0.dev190 → ominfra-0.0.0.dev191}/ominfra/manage/marshal.py +0 -0
  80. {ominfra-0.0.0.dev190 → ominfra-0.0.0.dev191}/ominfra/manage/remote/__init__.py +0 -0
  81. {ominfra-0.0.0.dev190 → ominfra-0.0.0.dev191}/ominfra/manage/remote/_main.py +0 -0
  82. {ominfra-0.0.0.dev190 → ominfra-0.0.0.dev191}/ominfra/manage/remote/channel.py +0 -0
  83. {ominfra-0.0.0.dev190 → ominfra-0.0.0.dev191}/ominfra/manage/remote/config.py +0 -0
  84. {ominfra-0.0.0.dev190 → ominfra-0.0.0.dev191}/ominfra/manage/remote/connection.py +0 -0
  85. {ominfra-0.0.0.dev190 → ominfra-0.0.0.dev191}/ominfra/manage/remote/execution.py +0 -0
  86. {ominfra-0.0.0.dev190 → ominfra-0.0.0.dev191}/ominfra/manage/remote/inject.py +0 -0
  87. {ominfra-0.0.0.dev190 → ominfra-0.0.0.dev191}/ominfra/manage/remote/payload.py +0 -0
  88. {ominfra-0.0.0.dev190 → ominfra-0.0.0.dev191}/ominfra/manage/remote/spawning.py +0 -0
  89. {ominfra-0.0.0.dev190 → ominfra-0.0.0.dev191}/ominfra/manage/system/__init__.py +0 -0
  90. {ominfra-0.0.0.dev190 → ominfra-0.0.0.dev191}/ominfra/manage/system/commands.py +0 -0
  91. {ominfra-0.0.0.dev190 → ominfra-0.0.0.dev191}/ominfra/manage/system/config.py +0 -0
  92. {ominfra-0.0.0.dev190 → ominfra-0.0.0.dev191}/ominfra/manage/system/inject.py +0 -0
  93. {ominfra-0.0.0.dev190 → ominfra-0.0.0.dev191}/ominfra/manage/system/packages.py +0 -0
  94. {ominfra-0.0.0.dev190 → ominfra-0.0.0.dev191}/ominfra/manage/system/platforms.py +0 -0
  95. {ominfra-0.0.0.dev190 → ominfra-0.0.0.dev191}/ominfra/manage/targets/__init__.py +0 -0
  96. {ominfra-0.0.0.dev190 → ominfra-0.0.0.dev191}/ominfra/manage/targets/bestpython.py +0 -0
  97. {ominfra-0.0.0.dev190 → ominfra-0.0.0.dev191}/ominfra/manage/targets/bestpython.sh +0 -0
  98. {ominfra-0.0.0.dev190 → ominfra-0.0.0.dev191}/ominfra/manage/targets/connection.py +0 -0
  99. {ominfra-0.0.0.dev190 → ominfra-0.0.0.dev191}/ominfra/manage/targets/inject.py +0 -0
  100. {ominfra-0.0.0.dev190 → ominfra-0.0.0.dev191}/ominfra/manage/targets/targets.py +0 -0
  101. {ominfra-0.0.0.dev190 → ominfra-0.0.0.dev191}/ominfra/pyremote.py +0 -0
  102. {ominfra-0.0.0.dev190 → ominfra-0.0.0.dev191}/ominfra/scripts/__init__.py +0 -0
  103. {ominfra-0.0.0.dev190 → ominfra-0.0.0.dev191}/ominfra/scripts/journald2aws.py +0 -0
  104. {ominfra-0.0.0.dev190 → ominfra-0.0.0.dev191}/ominfra/scripts/supervisor.py +0 -0
  105. {ominfra-0.0.0.dev190 → ominfra-0.0.0.dev191}/ominfra/ssh.py +0 -0
  106. {ominfra-0.0.0.dev190 → ominfra-0.0.0.dev191}/ominfra/supervisor/LICENSE.txt +0 -0
  107. {ominfra-0.0.0.dev190 → ominfra-0.0.0.dev191}/ominfra/supervisor/__init__.py +0 -0
  108. {ominfra-0.0.0.dev190 → ominfra-0.0.0.dev191}/ominfra/supervisor/__main__.py +0 -0
  109. {ominfra-0.0.0.dev190 → ominfra-0.0.0.dev191}/ominfra/supervisor/configs.py +0 -0
  110. {ominfra-0.0.0.dev190 → ominfra-0.0.0.dev191}/ominfra/supervisor/dispatchers.py +0 -0
  111. {ominfra-0.0.0.dev190 → ominfra-0.0.0.dev191}/ominfra/supervisor/dispatchersimpl.py +0 -0
  112. {ominfra-0.0.0.dev190 → ominfra-0.0.0.dev191}/ominfra/supervisor/events.py +0 -0
  113. {ominfra-0.0.0.dev190 → ominfra-0.0.0.dev191}/ominfra/supervisor/exceptions.py +0 -0
  114. {ominfra-0.0.0.dev190 → ominfra-0.0.0.dev191}/ominfra/supervisor/groups.py +0 -0
  115. {ominfra-0.0.0.dev190 → ominfra-0.0.0.dev191}/ominfra/supervisor/groupsimpl.py +0 -0
  116. {ominfra-0.0.0.dev190 → ominfra-0.0.0.dev191}/ominfra/supervisor/http.py +0 -0
  117. {ominfra-0.0.0.dev190 → ominfra-0.0.0.dev191}/ominfra/supervisor/inject.py +0 -0
  118. {ominfra-0.0.0.dev190 → ominfra-0.0.0.dev191}/ominfra/supervisor/io.py +0 -0
  119. {ominfra-0.0.0.dev190 → ominfra-0.0.0.dev191}/ominfra/supervisor/main.py +0 -0
  120. {ominfra-0.0.0.dev190 → ominfra-0.0.0.dev191}/ominfra/supervisor/pipes.py +0 -0
  121. {ominfra-0.0.0.dev190 → ominfra-0.0.0.dev191}/ominfra/supervisor/privileges.py +0 -0
  122. {ominfra-0.0.0.dev190 → ominfra-0.0.0.dev191}/ominfra/supervisor/process.py +0 -0
  123. {ominfra-0.0.0.dev190 → ominfra-0.0.0.dev191}/ominfra/supervisor/processimpl.py +0 -0
  124. {ominfra-0.0.0.dev190 → ominfra-0.0.0.dev191}/ominfra/supervisor/setup.py +0 -0
  125. {ominfra-0.0.0.dev190 → ominfra-0.0.0.dev191}/ominfra/supervisor/setupimpl.py +0 -0
  126. {ominfra-0.0.0.dev190 → ominfra-0.0.0.dev191}/ominfra/supervisor/signals.py +0 -0
  127. {ominfra-0.0.0.dev190 → ominfra-0.0.0.dev191}/ominfra/supervisor/spawning.py +0 -0
  128. {ominfra-0.0.0.dev190 → ominfra-0.0.0.dev191}/ominfra/supervisor/spawningimpl.py +0 -0
  129. {ominfra-0.0.0.dev190 → ominfra-0.0.0.dev191}/ominfra/supervisor/states.py +0 -0
  130. {ominfra-0.0.0.dev190 → ominfra-0.0.0.dev191}/ominfra/supervisor/supervisor.py +0 -0
  131. {ominfra-0.0.0.dev190 → ominfra-0.0.0.dev191}/ominfra/supervisor/types.py +0 -0
  132. {ominfra-0.0.0.dev190 → ominfra-0.0.0.dev191}/ominfra/supervisor/utils/__init__.py +0 -0
  133. {ominfra-0.0.0.dev190 → ominfra-0.0.0.dev191}/ominfra/supervisor/utils/collections.py +0 -0
  134. {ominfra-0.0.0.dev190 → ominfra-0.0.0.dev191}/ominfra/supervisor/utils/diag.py +0 -0
  135. {ominfra-0.0.0.dev190 → ominfra-0.0.0.dev191}/ominfra/supervisor/utils/fds.py +0 -0
  136. {ominfra-0.0.0.dev190 → ominfra-0.0.0.dev191}/ominfra/supervisor/utils/fs.py +0 -0
  137. {ominfra-0.0.0.dev190 → ominfra-0.0.0.dev191}/ominfra/supervisor/utils/os.py +0 -0
  138. {ominfra-0.0.0.dev190 → ominfra-0.0.0.dev191}/ominfra/supervisor/utils/ostypes.py +0 -0
  139. {ominfra-0.0.0.dev190 → ominfra-0.0.0.dev191}/ominfra/supervisor/utils/signals.py +0 -0
  140. {ominfra-0.0.0.dev190 → ominfra-0.0.0.dev191}/ominfra/supervisor/utils/strings.py +0 -0
  141. {ominfra-0.0.0.dev190 → ominfra-0.0.0.dev191}/ominfra/supervisor/utils/users.py +0 -0
  142. {ominfra-0.0.0.dev190 → ominfra-0.0.0.dev191}/ominfra/tailscale/__init__.py +0 -0
  143. {ominfra-0.0.0.dev190 → ominfra-0.0.0.dev191}/ominfra/tailscale/api.py +0 -0
  144. {ominfra-0.0.0.dev190 → ominfra-0.0.0.dev191}/ominfra/tailscale/cli.py +0 -0
  145. {ominfra-0.0.0.dev190 → ominfra-0.0.0.dev191}/ominfra/threadworkers.py +0 -0
  146. {ominfra-0.0.0.dev190 → ominfra-0.0.0.dev191}/ominfra/tools/__init__.py +0 -0
  147. {ominfra-0.0.0.dev190 → ominfra-0.0.0.dev191}/ominfra/tools/listresources.py +0 -0
  148. {ominfra-0.0.0.dev190 → ominfra-0.0.0.dev191}/ominfra.egg-info/dependency_links.txt +0 -0
  149. {ominfra-0.0.0.dev190 → ominfra-0.0.0.dev191}/ominfra.egg-info/entry_points.txt +0 -0
  150. {ominfra-0.0.0.dev190 → ominfra-0.0.0.dev191}/ominfra.egg-info/top_level.txt +0 -0
  151. {ominfra-0.0.0.dev190 → ominfra-0.0.0.dev191}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: ominfra
3
- Version: 0.0.0.dev190
3
+ Version: 0.0.0.dev191
4
4
  Summary: ominfra
5
5
  Author: wrmsr
6
6
  License: BSD-3-Clause
@@ -12,9 +12,9 @@ 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.dev190
16
- Requires-Dist: omlish==0.0.0.dev190
17
- Requires-Dist: omserv==0.0.0.dev190
15
+ Requires-Dist: omdev==0.0.0.dev191
16
+ Requires-Dist: omlish==0.0.0.dev191
17
+ Requires-Dist: omserv==0.0.0.dev191
18
18
  Provides-Extra: all
19
19
  Requires-Dist: paramiko~=3.5; extra == "all"
20
20
  Requires-Dist: asyncssh~=2.18; extra == "all"
@@ -1,5 +1,6 @@
1
1
  # ruff: noqa: UP006 UP007
2
2
  import dataclasses as dc
3
+ import json
3
4
  import os.path
4
5
  import typing as ta
5
6
 
@@ -13,6 +14,7 @@ from .git import DeployGitManager
13
14
  from .paths.owners import DeployPathOwner
14
15
  from .paths.paths import DeployPath
15
16
  from .specs import DeployAppSpec
17
+ from .tags import DeployAppRev
16
18
  from .tags import DeployTagMap
17
19
  from .types import DeployHome
18
20
  from .venvs import DeployVenvManager
@@ -57,9 +59,21 @@ class DeployAppManager(DeployPathOwner):
57
59
 
58
60
  #
59
61
 
62
+ def _make_tags(self, spec: DeployAppSpec) -> DeployTagMap:
63
+ return DeployTagMap(
64
+ spec.app,
65
+ spec.key(),
66
+ DeployAppRev(spec.git.rev),
67
+ )
68
+
69
+ #
70
+
60
71
  @dc.dataclass(frozen=True)
61
72
  class PreparedApp:
62
- app_dir: str
73
+ spec: DeployAppSpec
74
+ tags: DeployTagMap
75
+
76
+ dir: str
63
77
 
64
78
  git_dir: ta.Optional[str] = None
65
79
  venv_dir: ta.Optional[str] = None
@@ -75,16 +89,23 @@ class DeployAppManager(DeployPathOwner):
75
89
 
76
90
  #
77
91
 
92
+ app_tags = tags.add(*self._make_tags(spec))
93
+
94
+ #
95
+
78
96
  check.non_empty_str(home)
79
97
 
80
- app_dir = os.path.join(home, self.APP_DIR.render(tags))
98
+ app_dir = os.path.join(home, self.APP_DIR.render(app_tags))
81
99
 
82
100
  os.makedirs(app_dir, exist_ok=True)
83
101
 
84
102
  #
85
103
 
86
104
  rkw: ta.Dict[str, ta.Any] = dict(
87
- app_dir=app_dir,
105
+ spec=spec,
106
+ tags=app_tags,
107
+
108
+ dir=app_dir,
88
109
  )
89
110
 
90
111
  #
@@ -127,3 +148,51 @@ class DeployAppManager(DeployPathOwner):
127
148
  #
128
149
 
129
150
  return DeployAppManager.PreparedApp(**rkw)
151
+
152
+ async def prepare_app_link(
153
+ self,
154
+ tags: DeployTagMap,
155
+ app_dir: str,
156
+ ) -> PreparedApp:
157
+ spec_file = os.path.join(app_dir, 'spec.json')
158
+ with open(spec_file) as f: # noqa
159
+ spec_json = f.read()
160
+
161
+ spec: DeployAppSpec = self._msh.unmarshal_obj(json.loads(spec_json), DeployAppSpec)
162
+
163
+ #
164
+
165
+ app_tags = tags.add(*self._make_tags(spec))
166
+
167
+ #
168
+
169
+ rkw: ta.Dict[str, ta.Any] = dict(
170
+ spec=spec,
171
+ tags=app_tags,
172
+
173
+ dir=app_dir,
174
+ )
175
+
176
+ #
177
+
178
+ git_dir = os.path.join(app_dir, 'git')
179
+ check.state(os.path.isdir(git_dir))
180
+ rkw.update(git_dir=git_dir)
181
+
182
+ #
183
+
184
+ if spec.venv is not None:
185
+ venv_dir = os.path.join(app_dir, 'venv')
186
+ check.state(os.path.isdir(venv_dir))
187
+ rkw.update(venv_dir=venv_dir)
188
+
189
+ #
190
+
191
+ if spec.conf is not None:
192
+ conf_dir = os.path.join(app_dir, 'conf')
193
+ check.state(os.path.isdir(conf_dir))
194
+ rkw.update(conf_dir=conf_dir)
195
+
196
+ #
197
+
198
+ return DeployAppManager.PreparedApp(**rkw)
@@ -16,6 +16,8 @@ TODO:
16
16
  - some things (venvs) cannot be moved, thus the /deploy/venvs dir
17
17
  - ** ensure (enforce) equivalent relpath nesting
18
18
  """
19
+ import collections.abc
20
+ import functools
19
21
  import os.path
20
22
  import typing as ta
21
23
 
@@ -43,20 +45,66 @@ from .specs import NginxDeployAppConfContent
43
45
  from .specs import RawDeployAppConfContent
44
46
 
45
47
 
48
+ T = ta.TypeVar('T')
49
+
50
+
51
+ ##
52
+
53
+
46
54
  class DeployConfManager:
47
- def _render_app_conf_content(self, ac: DeployAppConfContent) -> str:
55
+ def _process_conf_content(
56
+ self,
57
+ content: T,
58
+ *,
59
+ str_processor: ta.Optional[ta.Callable[[str], str]] = None,
60
+ ) -> T:
61
+ def rec(o):
62
+ if isinstance(o, str):
63
+ if str_processor is not None:
64
+ return type(o)(str_processor(o))
65
+
66
+ elif isinstance(o, collections.abc.Mapping):
67
+ return type(o)([ # type: ignore
68
+ (rec(k), rec(v))
69
+ for k, v in o.items()
70
+ ])
71
+
72
+ elif isinstance(o, collections.abc.Iterable):
73
+ return type(o)([ # type: ignore
74
+ rec(e) for e in o
75
+ ])
76
+
77
+ return o
78
+
79
+ return rec(content)
80
+
81
+ #
82
+
83
+ def _render_app_conf_content(
84
+ self,
85
+ ac: DeployAppConfContent,
86
+ *,
87
+ str_processor: ta.Optional[ta.Callable[[str], str]] = None,
88
+ ) -> str:
89
+ pcc = functools.partial(
90
+ self._process_conf_content,
91
+ str_processor=str_processor,
92
+ )
93
+
48
94
  if isinstance(ac, RawDeployAppConfContent):
49
- return ac.body
95
+ return pcc(ac.body)
50
96
 
51
97
  elif isinstance(ac, JsonDeployAppConfContent):
52
- return strip_with_newline(json_dumps_pretty(ac.obj))
98
+ json_obj = pcc(ac.obj)
99
+ return strip_with_newline(json_dumps_pretty(json_obj))
53
100
 
54
101
  elif isinstance(ac, IniDeployAppConfContent):
55
- return strip_with_newline(render_ini_config(ac.sections))
102
+ ini_sections = pcc(ac.sections)
103
+ return strip_with_newline(render_ini_config(ini_sections))
56
104
 
57
105
  elif isinstance(ac, NginxDeployAppConfContent):
58
- ni = NginxConfigItems.of(ac.items)
59
- return strip_with_newline(render_nginx_config_str(ni))
106
+ nginx_items = NginxConfigItems.of(pcc(ac.items))
107
+ return strip_with_newline(render_nginx_config_str(nginx_items))
60
108
 
61
109
  else:
62
110
  raise TypeError(ac)
@@ -65,11 +113,16 @@ class DeployConfManager:
65
113
  self,
66
114
  acf: DeployAppConfFile,
67
115
  app_conf_dir: str,
116
+ *,
117
+ str_processor: ta.Optional[ta.Callable[[str], str]] = None,
68
118
  ) -> None:
69
119
  conf_file = os.path.join(app_conf_dir, acf.path)
70
120
  check.arg(is_path_in_dir(app_conf_dir, conf_file))
71
121
 
72
- body = self._render_app_conf_content(acf.content)
122
+ body = self._render_app_conf_content(
123
+ acf.content,
124
+ str_processor=str_processor,
125
+ )
73
126
 
74
127
  os.makedirs(os.path.dirname(conf_file), exist_ok=True)
75
128
 
@@ -81,10 +134,14 @@ class DeployConfManager:
81
134
  spec: DeployAppConfSpec,
82
135
  app_conf_dir: str,
83
136
  ) -> None:
137
+ def process_str(s: str) -> str:
138
+ return s
139
+
84
140
  for acf in spec.files or []:
85
141
  await self._write_app_conf_file(
86
142
  acf,
87
143
  app_conf_dir,
144
+ str_processor=process_str,
88
145
  )
89
146
 
90
147
  #
@@ -9,6 +9,7 @@ from omlish.lite.json import json_dumps_pretty
9
9
  from omlish.lite.marshal import ObjMarshalerManager
10
10
  from omlish.lite.typing import Func0
11
11
  from omlish.lite.typing import Func1
12
+ from omlish.os.paths import abs_real_path
12
13
  from omlish.os.paths import relative_symlink
13
14
 
14
15
  from .apps import DeployAppManager
@@ -19,9 +20,10 @@ from .paths.paths import DeployPath
19
20
  from .specs import DeployAppSpec
20
21
  from .specs import DeploySpec
21
22
  from .systemd import DeploySystemdManager
22
- from .tags import DeployAppRev
23
+ from .tags import DeployApp
23
24
  from .tags import DeployTagMap
24
25
  from .tags import DeployTime
26
+ from .tmp import DeployHomeAtomics
25
27
  from .types import DeployHome
26
28
 
27
29
 
@@ -38,16 +40,24 @@ class DeployManager(DeployPathOwner):
38
40
  def __init__(
39
41
  self,
40
42
  *,
43
+ atomics: DeployHomeAtomics,
44
+
41
45
  utc_clock: ta.Optional[DeployManagerUtcClock] = None,
42
46
  ):
43
47
  super().__init__()
44
48
 
49
+ self._atomics = atomics
50
+
45
51
  self._utc_clock = utc_clock
46
52
 
47
53
  #
48
54
 
55
+ # Home current link just points to CURRENT_DEPLOY_LINK, and is intended for user convenience.
56
+ HOME_CURRENT_LINK = DeployPath.parse('current')
57
+
49
58
  DEPLOYS_DIR = DeployPath.parse('deploys/')
50
59
 
60
+ # Authoritative current symlink is not in deploy-home, just to prevent accidental corruption.
51
61
  CURRENT_DEPLOY_LINK = DeployPath.parse(f'{DEPLOYS_DIR}current')
52
62
  DEPLOYING_DEPLOY_LINK = DeployPath.parse(f'{DEPLOYS_DIR}deploying')
53
63
 
@@ -80,6 +90,11 @@ class DeployManager(DeployPathOwner):
80
90
 
81
91
  #
82
92
 
93
+ def render_path(self, home: DeployHome, pth: DeployPath, tags: ta.Optional[DeployTagMap] = None) -> str:
94
+ return os.path.join(check.non_empty_str(home), pth.render(tags))
95
+
96
+ #
97
+
83
98
  def _utc_now(self) -> datetime.datetime:
84
99
  if self._utc_clock is not None:
85
100
  return self._utc_clock() # noqa
@@ -89,6 +104,22 @@ class DeployManager(DeployPathOwner):
89
104
  def make_deploy_time(self) -> DeployTime:
90
105
  return DeployTime(self._utc_now().strftime(DEPLOY_TAG_DATETIME_FMT))
91
106
 
107
+ #
108
+
109
+ def make_home_current_link(self, home: DeployHome) -> None:
110
+ home_current_link = os.path.join(check.non_empty_str(home), self.HOME_CURRENT_LINK.render())
111
+ current_deploy_link = os.path.join(check.non_empty_str(home), self.CURRENT_DEPLOY_LINK.render())
112
+ with self._atomics(home).begin_atomic_path_swap( # noqa
113
+ 'file',
114
+ home_current_link,
115
+ auto_commit=True,
116
+ ) as dst_swap:
117
+ os.unlink(dst_swap.tmp_path)
118
+ os.symlink(
119
+ os.path.relpath(current_deploy_link, os.path.dirname(dst_swap.dst_path)),
120
+ dst_swap.tmp_path,
121
+ )
122
+
92
123
 
93
124
  ##
94
125
 
@@ -130,18 +161,18 @@ class DeployDriver:
130
161
  #
131
162
 
132
163
  @property
133
- def deploy_tags(self) -> DeployTagMap:
164
+ def tags(self) -> DeployTagMap:
134
165
  return DeployTagMap(
135
166
  self._time,
136
167
  self._spec.key(),
137
168
  )
138
169
 
139
- def render_deploy_path(self, pth: DeployPath, tags: ta.Optional[DeployTagMap] = None) -> str:
140
- return os.path.join(self._home, pth.render(tags if tags is not None else self.deploy_tags))
170
+ def render_path(self, pth: DeployPath, tags: ta.Optional[DeployTagMap] = None) -> str:
171
+ return os.path.join(self._home, pth.render(tags if tags is not None else self.tags))
141
172
 
142
173
  @property
143
- def deploy_dir(self) -> str:
144
- return self.render_deploy_path(self._deploys.DEPLOY_DIR)
174
+ def dir(self) -> str:
175
+ return self.render_path(self._deploys.DEPLOY_DIR)
145
176
 
146
177
  #
147
178
 
@@ -150,25 +181,36 @@ class DeployDriver:
150
181
 
151
182
  #
152
183
 
184
+ das: ta.Set[DeployApp] = {a.app for a in self._spec.apps}
185
+ las: ta.Set[DeployApp] = set(self._spec.app_links.apps)
186
+ if (ras := das & las):
187
+ raise RuntimeError(f'Must not specify apps as both deploy and link: {sorted(a.s for a in ras)}')
188
+
189
+ #
190
+
153
191
  self._paths.validate_deploy_paths()
154
192
 
155
193
  #
156
194
 
157
- os.makedirs(self.deploy_dir)
195
+ os.makedirs(self.dir)
158
196
 
159
197
  #
160
198
 
161
- spec_file = self.render_deploy_path(self._deploys.DEPLOY_SPEC_FILE)
199
+ spec_file = self.render_path(self._deploys.DEPLOY_SPEC_FILE)
162
200
  with open(spec_file, 'w') as f: # noqa
163
201
  f.write(spec_json)
164
202
 
165
203
  #
166
204
 
167
- deploying_link = self.render_deploy_path(self._deploys.DEPLOYING_DEPLOY_LINK)
205
+ deploying_link = self.render_path(self._deploys.DEPLOYING_DEPLOY_LINK)
206
+ current_link = self.render_path(self._deploys.CURRENT_DEPLOY_LINK)
207
+
208
+ #
209
+
168
210
  if os.path.exists(deploying_link):
169
211
  os.unlink(deploying_link)
170
212
  relative_symlink(
171
- self.deploy_dir,
213
+ self.dir,
172
214
  deploying_link,
173
215
  target_is_directory=True,
174
216
  make_dirs=True,
@@ -180,16 +222,30 @@ class DeployDriver:
180
222
  self._deploys.APPS_DEPLOY_DIR,
181
223
  self._deploys.CONFS_DEPLOY_DIR,
182
224
  ]:
183
- os.makedirs(self.render_deploy_path(md))
225
+ os.makedirs(self.render_path(md))
184
226
 
185
227
  #
186
228
 
229
+ if not self._spec.app_links.exclude_unspecified:
230
+ cad = abs_real_path(os.path.join(current_link, 'apps'))
231
+ if os.path.exists(cad):
232
+ for d in os.listdir(cad):
233
+ if (da := DeployApp(d)) not in das:
234
+ las.add(da)
235
+
236
+ for la in self._spec.app_links.apps:
237
+ await self._drive_app_link(
238
+ la,
239
+ current_link,
240
+ )
241
+
187
242
  for app in self._spec.apps:
188
- await self.drive_app_deploy(app)
243
+ await self._drive_app_deploy(
244
+ app,
245
+ )
189
246
 
190
247
  #
191
248
 
192
- current_link = self.render_deploy_path(self._deploys.CURRENT_DEPLOY_LINK)
193
249
  os.replace(deploying_link, current_link)
194
250
 
195
251
  #
@@ -197,31 +253,27 @@ class DeployDriver:
197
253
  await self._systemd.sync_systemd(
198
254
  self._spec.systemd,
199
255
  self._home,
200
- os.path.join(self.deploy_dir, 'conf', 'systemd'), # FIXME
256
+ os.path.join(self.dir, 'conf', 'systemd'), # FIXME
201
257
  )
202
258
 
203
- #
259
+ #
204
260
 
205
- async def drive_app_deploy(self, app: DeployAppSpec) -> None:
206
- app_tags = self.deploy_tags.add(
207
- app.app,
208
- app.key(),
209
- DeployAppRev(app.git.rev),
210
- )
261
+ self._deploys.make_home_current_link(self._home)
211
262
 
212
- #
263
+ #
213
264
 
214
- da = await self._apps.prepare_app(
265
+ async def _drive_app_deploy(self, app: DeployAppSpec) -> None:
266
+ pa = await self._apps.prepare_app(
215
267
  app,
216
268
  self._home,
217
- app_tags,
269
+ self.tags,
218
270
  )
219
271
 
220
272
  #
221
273
 
222
- app_link = self.render_deploy_path(self._deploys.APP_DEPLOY_LINK, app_tags)
274
+ app_link = self.render_path(self._deploys.APP_DEPLOY_LINK, pa.tags)
223
275
  relative_symlink(
224
- da.app_dir,
276
+ pa.dir,
225
277
  app_link,
226
278
  target_is_directory=True,
227
279
  make_dirs=True,
@@ -229,11 +281,47 @@ class DeployDriver:
229
281
 
230
282
  #
231
283
 
232
- deploy_conf_dir = self.render_deploy_path(self._deploys.CONFS_DEPLOY_DIR)
233
- if app.conf is not None:
284
+ await self._drive_app_configure(pa)
285
+
286
+ async def _drive_app_link(
287
+ self,
288
+ app: DeployApp,
289
+ current_link: str,
290
+ ) -> None:
291
+ app_link = os.path.join(abs_real_path(current_link), 'apps', app.s)
292
+ check.state(os.path.islink(app_link))
293
+
294
+ app_dir = abs_real_path(app_link)
295
+ check.state(os.path.isdir(app_dir))
296
+
297
+ #
298
+
299
+ pa = await self._apps.prepare_app_link(
300
+ self.tags,
301
+ app_dir,
302
+ )
303
+
304
+ #
305
+
306
+ relative_symlink(
307
+ app_dir,
308
+ os.path.join(self.dir, 'apps', app.s),
309
+ target_is_directory=True,
310
+ )
311
+
312
+ #
313
+
314
+ await self._drive_app_configure(pa)
315
+
316
+ async def _drive_app_configure(
317
+ self,
318
+ pa: DeployAppManager.PreparedApp,
319
+ ) -> None:
320
+ deploy_conf_dir = self.render_path(self._deploys.CONFS_DEPLOY_DIR)
321
+ if pa.spec.conf is not None:
234
322
  await self._conf.link_app_conf(
235
- app.conf,
236
- app_tags,
237
- check.non_empty_str(da.conf_dir),
323
+ pa.spec.conf,
324
+ pa.tags,
325
+ check.non_empty_str(pa.conf_dir),
238
326
  deploy_conf_dir,
239
327
  )
@@ -91,6 +91,13 @@ class DeployAppSpec(DeploySpecKeyed[DeployAppKey]):
91
91
  return DeployAppKey(self._key_str())
92
92
 
93
93
 
94
+ @dc.dataclass(frozen=True)
95
+ class DeployAppLinksSpec:
96
+ apps: ta.Sequence[DeployApp] = ()
97
+
98
+ exclude_unspecified: bool = False
99
+
100
+
94
101
  ##
95
102
 
96
103
 
@@ -109,6 +116,8 @@ class DeploySpec(DeploySpecKeyed[DeployKey]):
109
116
 
110
117
  apps: ta.Sequence[DeployAppSpec] = ()
111
118
 
119
+ app_links: DeployAppLinksSpec = DeployAppLinksSpec()
120
+
112
121
  systemd: ta.Optional[DeploySystemdSpec] = None
113
122
 
114
123
  def __post_init__(self) -> None:
@@ -1,34 +1,17 @@
1
1
  # ruff: noqa: UP006 UP007
2
2
  """
3
- ~/.config/systemd/user/
4
-
5
- verify - systemd-analyze
6
-
7
- sudo loginctl enable-linger "$USER"
8
-
9
- cat ~/.config/systemd/user/sleep-infinity.service
10
- [Unit]
11
- Description=User-specific service to run 'sleep infinity'
12
- After=default.target
13
-
14
- [Service]
15
- ExecStart=/bin/sleep infinity
16
- Restart=always
17
- RestartSec=5
18
-
19
- [Install]
20
- WantedBy=default.target
21
-
22
- systemctl --user daemon-reload
23
-
24
- systemctl --user enable sleep-infinity.service
25
- systemctl --user start sleep-infinity.service
26
-
27
- systemctl --user status sleep-infinity.service
3
+ TODO:
4
+ - verify - systemd-analyze
5
+ - sudo loginctl enable-linger "$USER"
6
+ - idemp kill services that shouldn't be running, start ones that should
7
+ - ideally only those defined by links to deploy home
8
+ - ominfra.systemd / x.sd_orphans
28
9
  """
29
10
  import os.path
11
+ import sys
30
12
  import typing as ta
31
13
 
14
+ from omlish.asyncs.asyncio.subprocesses import asyncio_subprocesses
32
15
  from omlish.lite.check import check
33
16
  from omlish.os.paths import abs_real_path
34
17
  from omlish.os.paths import is_path_in_dir
@@ -73,6 +56,8 @@ class DeploySystemdManager:
73
56
  if not spec:
74
57
  return
75
58
 
59
+ #
60
+
76
61
  if not (ud := spec.unit_dir):
77
62
  return
78
63
 
@@ -80,6 +65,8 @@ class DeploySystemdManager:
80
65
 
81
66
  os.makedirs(ud, exist_ok=True)
82
67
 
68
+ #
69
+
83
70
  uld = {
84
71
  n: p
85
72
  for n, p in self._scan_link_dir(ud).items()
@@ -91,8 +78,11 @@ class DeploySystemdManager:
91
78
  else:
92
79
  cld = {}
93
80
 
94
- for n in sorted(set(uld) | set(cld)):
95
- ul = uld.get(n) # noqa
81
+ #
82
+
83
+ ns = sorted(set(uld) | set(cld))
84
+
85
+ for n in ns:
96
86
  cl = cld.get(n)
97
87
  if cl is None:
98
88
  os.unlink(os.path.join(ud, n))
@@ -108,3 +98,34 @@ class DeploySystemdManager:
108
98
  os.path.relpath(cl, os.path.dirname(dst_swap.dst_path)),
109
99
  dst_swap.tmp_path,
110
100
  )
101
+
102
+ #
103
+
104
+ if sys.platform == 'linux':
105
+ async def reload() -> None:
106
+ await asyncio_subprocesses.check_call('systemctl', '--user', 'daemon-reload')
107
+
108
+ await reload()
109
+
110
+ num_deleted = 0
111
+ for n in ns:
112
+ if n.endswith('.service'):
113
+ cl = cld.get(n)
114
+ ul = uld.get(n)
115
+ if cl is not None:
116
+ if ul is None:
117
+ cs = ['enable', 'start']
118
+ else:
119
+ cs = ['restart']
120
+ else: # noqa
121
+ if ul is not None:
122
+ cs = ['stop']
123
+ num_deleted += 1
124
+ else:
125
+ cs = []
126
+
127
+ for c in cs:
128
+ await asyncio_subprocesses.check_call('systemctl', '--user', c, n)
129
+
130
+ if num_deleted:
131
+ await reload()
@@ -29,7 +29,7 @@ DEPLOY_TAG_ILLEGAL_STRS: ta.AbstractSet[str] = frozenset([
29
29
  ##
30
30
 
31
31
 
32
- @dc.dataclass(frozen=True)
32
+ @dc.dataclass(frozen=True, order=True)
33
33
  class DeployTag(abc.ABC): # noqa
34
34
  s: str
35
35
 
@@ -5,6 +5,7 @@ TODO:
5
5
  - share more code with pyproject?
6
6
  """
7
7
  import os.path
8
+ import shutil
8
9
 
9
10
  from omdev.interp.default import get_default_interp_resolver
10
11
  from omdev.interp.types import InterpSpecifier
@@ -44,9 +45,12 @@ class DeployVenvManager:
44
45
 
45
46
  if os.path.isfile(reqs_txt):
46
47
  if spec.use_uv:
47
- await asyncio_subprocesses.check_call(venv_exe, '-m', 'pip', 'install', 'uv')
48
- pip_cmd = ['-m', 'uv', 'pip']
48
+ if shutil.which('uv') is not None:
49
+ pip_cmd = ['uv', 'pip']
50
+ else:
51
+ await asyncio_subprocesses.check_call(venv_exe, '-m', 'pip', 'install', 'uv')
52
+ pip_cmd = [venv_exe, '-m', 'uv', 'pip']
49
53
  else:
50
- pip_cmd = ['-m', 'pip']
54
+ pip_cmd = [venv_exe, '-m', 'pip']
51
55
 
52
- await asyncio_subprocesses.check_call(venv_exe, *pip_cmd,'install', '-r', reqs_txt)
56
+ await asyncio_subprocesses.check_call(*pip_cmd, 'install', '-r', reqs_txt, cwd=venv_dir)