ominfra 0.0.0.dev190__tar.gz → 0.0.0.dev192__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.dev192}/PKG-INFO +4 -4
  2. {ominfra-0.0.0.dev190 → ominfra-0.0.0.dev192}/ominfra/manage/deploy/apps.py +82 -3
  3. {ominfra-0.0.0.dev190 → ominfra-0.0.0.dev192}/ominfra/manage/deploy/conf/manager.py +70 -7
  4. {ominfra-0.0.0.dev190 → ominfra-0.0.0.dev192}/ominfra/manage/deploy/deploy.py +127 -32
  5. {ominfra-0.0.0.dev190 → ominfra-0.0.0.dev192}/ominfra/manage/deploy/git.py +3 -1
  6. {ominfra-0.0.0.dev190 → ominfra-0.0.0.dev192}/ominfra/manage/deploy/specs.py +11 -0
  7. {ominfra-0.0.0.dev190 → ominfra-0.0.0.dev192}/ominfra/manage/deploy/systemd.py +49 -27
  8. {ominfra-0.0.0.dev190 → ominfra-0.0.0.dev192}/ominfra/manage/deploy/tags.py +1 -1
  9. {ominfra-0.0.0.dev190 → ominfra-0.0.0.dev192}/ominfra/manage/deploy/venvs.py +8 -4
  10. {ominfra-0.0.0.dev190 → ominfra-0.0.0.dev192}/ominfra/scripts/manage.py +337 -74
  11. {ominfra-0.0.0.dev190 → ominfra-0.0.0.dev192}/ominfra/scripts/supervisor.py +18 -9
  12. {ominfra-0.0.0.dev190 → ominfra-0.0.0.dev192}/ominfra/supervisor/configs.py +6 -0
  13. {ominfra-0.0.0.dev190 → ominfra-0.0.0.dev192}/ominfra/supervisor/http.py +1 -1
  14. {ominfra-0.0.0.dev190 → ominfra-0.0.0.dev192}/ominfra/supervisor/inject.py +11 -8
  15. ominfra-0.0.0.dev192/ominfra/systemd.py +49 -0
  16. {ominfra-0.0.0.dev190 → ominfra-0.0.0.dev192/ominfra.egg-info}/PKG-INFO +4 -4
  17. {ominfra-0.0.0.dev190 → ominfra-0.0.0.dev192}/ominfra.egg-info/SOURCES.txt +1 -0
  18. {ominfra-0.0.0.dev190 → ominfra-0.0.0.dev192}/ominfra.egg-info/requires.txt +3 -3
  19. {ominfra-0.0.0.dev190 → ominfra-0.0.0.dev192}/pyproject.toml +4 -4
  20. {ominfra-0.0.0.dev190 → ominfra-0.0.0.dev192}/LICENSE +0 -0
  21. {ominfra-0.0.0.dev190 → ominfra-0.0.0.dev192}/MANIFEST.in +0 -0
  22. {ominfra-0.0.0.dev190 → ominfra-0.0.0.dev192}/README.rst +0 -0
  23. {ominfra-0.0.0.dev190 → ominfra-0.0.0.dev192}/ominfra/.manifests.json +0 -0
  24. {ominfra-0.0.0.dev190 → ominfra-0.0.0.dev192}/ominfra/__about__.py +0 -0
  25. {ominfra-0.0.0.dev190 → ominfra-0.0.0.dev192}/ominfra/__init__.py +0 -0
  26. {ominfra-0.0.0.dev190 → ominfra-0.0.0.dev192}/ominfra/clouds/__init__.py +0 -0
  27. {ominfra-0.0.0.dev190 → ominfra-0.0.0.dev192}/ominfra/clouds/aws/__init__.py +0 -0
  28. {ominfra-0.0.0.dev190 → ominfra-0.0.0.dev192}/ominfra/clouds/aws/__main__.py +0 -0
  29. {ominfra-0.0.0.dev190 → ominfra-0.0.0.dev192}/ominfra/clouds/aws/auth.py +0 -0
  30. {ominfra-0.0.0.dev190 → ominfra-0.0.0.dev192}/ominfra/clouds/aws/cli.py +0 -0
  31. {ominfra-0.0.0.dev190 → ominfra-0.0.0.dev192}/ominfra/clouds/aws/dataclasses.py +0 -0
  32. {ominfra-0.0.0.dev190 → ominfra-0.0.0.dev192}/ominfra/clouds/aws/journald2aws/__init__.py +0 -0
  33. {ominfra-0.0.0.dev190 → ominfra-0.0.0.dev192}/ominfra/clouds/aws/journald2aws/__main__.py +0 -0
  34. {ominfra-0.0.0.dev190 → ominfra-0.0.0.dev192}/ominfra/clouds/aws/journald2aws/cursor.py +0 -0
  35. {ominfra-0.0.0.dev190 → ominfra-0.0.0.dev192}/ominfra/clouds/aws/journald2aws/driver.py +0 -0
  36. {ominfra-0.0.0.dev190 → ominfra-0.0.0.dev192}/ominfra/clouds/aws/journald2aws/main.py +0 -0
  37. {ominfra-0.0.0.dev190 → ominfra-0.0.0.dev192}/ominfra/clouds/aws/journald2aws/poster.py +0 -0
  38. {ominfra-0.0.0.dev190 → ominfra-0.0.0.dev192}/ominfra/clouds/aws/logs.py +0 -0
  39. {ominfra-0.0.0.dev190 → ominfra-0.0.0.dev192}/ominfra/clouds/aws/metadata.py +0 -0
  40. {ominfra-0.0.0.dev190 → ominfra-0.0.0.dev192}/ominfra/clouds/gcp/__init__.py +0 -0
  41. {ominfra-0.0.0.dev190 → ominfra-0.0.0.dev192}/ominfra/clouds/gcp/auth.py +0 -0
  42. {ominfra-0.0.0.dev190 → ominfra-0.0.0.dev192}/ominfra/cmds.py +0 -0
  43. {ominfra-0.0.0.dev190 → ominfra-0.0.0.dev192}/ominfra/configs.py +0 -0
  44. {ominfra-0.0.0.dev190 → ominfra-0.0.0.dev192}/ominfra/journald/__init__.py +0 -0
  45. {ominfra-0.0.0.dev190 → ominfra-0.0.0.dev192}/ominfra/journald/fields.py +0 -0
  46. {ominfra-0.0.0.dev190 → ominfra-0.0.0.dev192}/ominfra/journald/genmessages.py +0 -0
  47. {ominfra-0.0.0.dev190 → ominfra-0.0.0.dev192}/ominfra/journald/messages.py +0 -0
  48. {ominfra-0.0.0.dev190 → ominfra-0.0.0.dev192}/ominfra/journald/tailer.py +0 -0
  49. {ominfra-0.0.0.dev190 → ominfra-0.0.0.dev192}/ominfra/manage/__init__.py +0 -0
  50. {ominfra-0.0.0.dev190 → ominfra-0.0.0.dev192}/ominfra/manage/__main__.py +0 -0
  51. {ominfra-0.0.0.dev190 → ominfra-0.0.0.dev192}/ominfra/manage/bootstrap.py +0 -0
  52. {ominfra-0.0.0.dev190 → ominfra-0.0.0.dev192}/ominfra/manage/bootstrap_.py +0 -0
  53. {ominfra-0.0.0.dev190 → ominfra-0.0.0.dev192}/ominfra/manage/commands/__init__.py +0 -0
  54. {ominfra-0.0.0.dev190 → ominfra-0.0.0.dev192}/ominfra/manage/commands/base.py +0 -0
  55. {ominfra-0.0.0.dev190 → ominfra-0.0.0.dev192}/ominfra/manage/commands/inject.py +0 -0
  56. {ominfra-0.0.0.dev190 → ominfra-0.0.0.dev192}/ominfra/manage/commands/local.py +0 -0
  57. {ominfra-0.0.0.dev190 → ominfra-0.0.0.dev192}/ominfra/manage/commands/marshal.py +0 -0
  58. {ominfra-0.0.0.dev190 → ominfra-0.0.0.dev192}/ominfra/manage/commands/ping.py +0 -0
  59. {ominfra-0.0.0.dev190 → ominfra-0.0.0.dev192}/ominfra/manage/commands/subprocess.py +0 -0
  60. {ominfra-0.0.0.dev190 → ominfra-0.0.0.dev192}/ominfra/manage/commands/types.py +0 -0
  61. {ominfra-0.0.0.dev190 → ominfra-0.0.0.dev192}/ominfra/manage/config.py +0 -0
  62. {ominfra-0.0.0.dev190 → ominfra-0.0.0.dev192}/ominfra/manage/deploy/__init__.py +0 -0
  63. {ominfra-0.0.0.dev190 → ominfra-0.0.0.dev192}/ominfra/manage/deploy/commands.py +0 -0
  64. {ominfra-0.0.0.dev190 → ominfra-0.0.0.dev192}/ominfra/manage/deploy/conf/__init__.py +0 -0
  65. {ominfra-0.0.0.dev190 → ominfra-0.0.0.dev192}/ominfra/manage/deploy/conf/inject.py +0 -0
  66. {ominfra-0.0.0.dev190 → ominfra-0.0.0.dev192}/ominfra/manage/deploy/conf/specs.py +0 -0
  67. {ominfra-0.0.0.dev190 → ominfra-0.0.0.dev192}/ominfra/manage/deploy/config.py +0 -0
  68. {ominfra-0.0.0.dev190 → ominfra-0.0.0.dev192}/ominfra/manage/deploy/inject.py +0 -0
  69. {ominfra-0.0.0.dev190 → ominfra-0.0.0.dev192}/ominfra/manage/deploy/inject_.py +0 -0
  70. {ominfra-0.0.0.dev190 → ominfra-0.0.0.dev192}/ominfra/manage/deploy/interp.py +0 -0
  71. {ominfra-0.0.0.dev190 → ominfra-0.0.0.dev192}/ominfra/manage/deploy/nginx.py +0 -0
  72. {ominfra-0.0.0.dev190 → ominfra-0.0.0.dev192}/ominfra/manage/deploy/paths/__init__.py +0 -0
  73. {ominfra-0.0.0.dev190 → ominfra-0.0.0.dev192}/ominfra/manage/deploy/paths/inject.py +0 -0
  74. {ominfra-0.0.0.dev190 → ominfra-0.0.0.dev192}/ominfra/manage/deploy/paths/manager.py +0 -0
  75. {ominfra-0.0.0.dev190 → ominfra-0.0.0.dev192}/ominfra/manage/deploy/paths/owners.py +0 -0
  76. {ominfra-0.0.0.dev190 → ominfra-0.0.0.dev192}/ominfra/manage/deploy/paths/paths.py +0 -0
  77. {ominfra-0.0.0.dev190 → ominfra-0.0.0.dev192}/ominfra/manage/deploy/paths/specs.py +0 -0
  78. {ominfra-0.0.0.dev190 → ominfra-0.0.0.dev192}/ominfra/manage/deploy/paths/types.py +0 -0
  79. {ominfra-0.0.0.dev190 → ominfra-0.0.0.dev192}/ominfra/manage/deploy/tmp.py +0 -0
  80. {ominfra-0.0.0.dev190 → ominfra-0.0.0.dev192}/ominfra/manage/deploy/types.py +0 -0
  81. {ominfra-0.0.0.dev190 → ominfra-0.0.0.dev192}/ominfra/manage/inject.py +0 -0
  82. {ominfra-0.0.0.dev190 → ominfra-0.0.0.dev192}/ominfra/manage/main.py +0 -0
  83. {ominfra-0.0.0.dev190 → ominfra-0.0.0.dev192}/ominfra/manage/marshal.py +0 -0
  84. {ominfra-0.0.0.dev190 → ominfra-0.0.0.dev192}/ominfra/manage/remote/__init__.py +0 -0
  85. {ominfra-0.0.0.dev190 → ominfra-0.0.0.dev192}/ominfra/manage/remote/_main.py +0 -0
  86. {ominfra-0.0.0.dev190 → ominfra-0.0.0.dev192}/ominfra/manage/remote/channel.py +0 -0
  87. {ominfra-0.0.0.dev190 → ominfra-0.0.0.dev192}/ominfra/manage/remote/config.py +0 -0
  88. {ominfra-0.0.0.dev190 → ominfra-0.0.0.dev192}/ominfra/manage/remote/connection.py +0 -0
  89. {ominfra-0.0.0.dev190 → ominfra-0.0.0.dev192}/ominfra/manage/remote/execution.py +0 -0
  90. {ominfra-0.0.0.dev190 → ominfra-0.0.0.dev192}/ominfra/manage/remote/inject.py +0 -0
  91. {ominfra-0.0.0.dev190 → ominfra-0.0.0.dev192}/ominfra/manage/remote/payload.py +0 -0
  92. {ominfra-0.0.0.dev190 → ominfra-0.0.0.dev192}/ominfra/manage/remote/spawning.py +0 -0
  93. {ominfra-0.0.0.dev190 → ominfra-0.0.0.dev192}/ominfra/manage/system/__init__.py +0 -0
  94. {ominfra-0.0.0.dev190 → ominfra-0.0.0.dev192}/ominfra/manage/system/commands.py +0 -0
  95. {ominfra-0.0.0.dev190 → ominfra-0.0.0.dev192}/ominfra/manage/system/config.py +0 -0
  96. {ominfra-0.0.0.dev190 → ominfra-0.0.0.dev192}/ominfra/manage/system/inject.py +0 -0
  97. {ominfra-0.0.0.dev190 → ominfra-0.0.0.dev192}/ominfra/manage/system/packages.py +0 -0
  98. {ominfra-0.0.0.dev190 → ominfra-0.0.0.dev192}/ominfra/manage/system/platforms.py +0 -0
  99. {ominfra-0.0.0.dev190 → ominfra-0.0.0.dev192}/ominfra/manage/targets/__init__.py +0 -0
  100. {ominfra-0.0.0.dev190 → ominfra-0.0.0.dev192}/ominfra/manage/targets/bestpython.py +0 -0
  101. {ominfra-0.0.0.dev190 → ominfra-0.0.0.dev192}/ominfra/manage/targets/bestpython.sh +0 -0
  102. {ominfra-0.0.0.dev190 → ominfra-0.0.0.dev192}/ominfra/manage/targets/connection.py +0 -0
  103. {ominfra-0.0.0.dev190 → ominfra-0.0.0.dev192}/ominfra/manage/targets/inject.py +0 -0
  104. {ominfra-0.0.0.dev190 → ominfra-0.0.0.dev192}/ominfra/manage/targets/targets.py +0 -0
  105. {ominfra-0.0.0.dev190 → ominfra-0.0.0.dev192}/ominfra/pyremote.py +0 -0
  106. {ominfra-0.0.0.dev190 → ominfra-0.0.0.dev192}/ominfra/scripts/__init__.py +0 -0
  107. {ominfra-0.0.0.dev190 → ominfra-0.0.0.dev192}/ominfra/scripts/journald2aws.py +0 -0
  108. {ominfra-0.0.0.dev190 → ominfra-0.0.0.dev192}/ominfra/ssh.py +0 -0
  109. {ominfra-0.0.0.dev190 → ominfra-0.0.0.dev192}/ominfra/supervisor/LICENSE.txt +0 -0
  110. {ominfra-0.0.0.dev190 → ominfra-0.0.0.dev192}/ominfra/supervisor/__init__.py +0 -0
  111. {ominfra-0.0.0.dev190 → ominfra-0.0.0.dev192}/ominfra/supervisor/__main__.py +0 -0
  112. {ominfra-0.0.0.dev190 → ominfra-0.0.0.dev192}/ominfra/supervisor/dispatchers.py +0 -0
  113. {ominfra-0.0.0.dev190 → ominfra-0.0.0.dev192}/ominfra/supervisor/dispatchersimpl.py +0 -0
  114. {ominfra-0.0.0.dev190 → ominfra-0.0.0.dev192}/ominfra/supervisor/events.py +0 -0
  115. {ominfra-0.0.0.dev190 → ominfra-0.0.0.dev192}/ominfra/supervisor/exceptions.py +0 -0
  116. {ominfra-0.0.0.dev190 → ominfra-0.0.0.dev192}/ominfra/supervisor/groups.py +0 -0
  117. {ominfra-0.0.0.dev190 → ominfra-0.0.0.dev192}/ominfra/supervisor/groupsimpl.py +0 -0
  118. {ominfra-0.0.0.dev190 → ominfra-0.0.0.dev192}/ominfra/supervisor/io.py +0 -0
  119. {ominfra-0.0.0.dev190 → ominfra-0.0.0.dev192}/ominfra/supervisor/main.py +0 -0
  120. {ominfra-0.0.0.dev190 → ominfra-0.0.0.dev192}/ominfra/supervisor/pipes.py +0 -0
  121. {ominfra-0.0.0.dev190 → ominfra-0.0.0.dev192}/ominfra/supervisor/privileges.py +0 -0
  122. {ominfra-0.0.0.dev190 → ominfra-0.0.0.dev192}/ominfra/supervisor/process.py +0 -0
  123. {ominfra-0.0.0.dev190 → ominfra-0.0.0.dev192}/ominfra/supervisor/processimpl.py +0 -0
  124. {ominfra-0.0.0.dev190 → ominfra-0.0.0.dev192}/ominfra/supervisor/setup.py +0 -0
  125. {ominfra-0.0.0.dev190 → ominfra-0.0.0.dev192}/ominfra/supervisor/setupimpl.py +0 -0
  126. {ominfra-0.0.0.dev190 → ominfra-0.0.0.dev192}/ominfra/supervisor/signals.py +0 -0
  127. {ominfra-0.0.0.dev190 → ominfra-0.0.0.dev192}/ominfra/supervisor/spawning.py +0 -0
  128. {ominfra-0.0.0.dev190 → ominfra-0.0.0.dev192}/ominfra/supervisor/spawningimpl.py +0 -0
  129. {ominfra-0.0.0.dev190 → ominfra-0.0.0.dev192}/ominfra/supervisor/states.py +0 -0
  130. {ominfra-0.0.0.dev190 → ominfra-0.0.0.dev192}/ominfra/supervisor/supervisor.py +0 -0
  131. {ominfra-0.0.0.dev190 → ominfra-0.0.0.dev192}/ominfra/supervisor/types.py +0 -0
  132. {ominfra-0.0.0.dev190 → ominfra-0.0.0.dev192}/ominfra/supervisor/utils/__init__.py +0 -0
  133. {ominfra-0.0.0.dev190 → ominfra-0.0.0.dev192}/ominfra/supervisor/utils/collections.py +0 -0
  134. {ominfra-0.0.0.dev190 → ominfra-0.0.0.dev192}/ominfra/supervisor/utils/diag.py +0 -0
  135. {ominfra-0.0.0.dev190 → ominfra-0.0.0.dev192}/ominfra/supervisor/utils/fds.py +0 -0
  136. {ominfra-0.0.0.dev190 → ominfra-0.0.0.dev192}/ominfra/supervisor/utils/fs.py +0 -0
  137. {ominfra-0.0.0.dev190 → ominfra-0.0.0.dev192}/ominfra/supervisor/utils/os.py +0 -0
  138. {ominfra-0.0.0.dev190 → ominfra-0.0.0.dev192}/ominfra/supervisor/utils/ostypes.py +0 -0
  139. {ominfra-0.0.0.dev190 → ominfra-0.0.0.dev192}/ominfra/supervisor/utils/signals.py +0 -0
  140. {ominfra-0.0.0.dev190 → ominfra-0.0.0.dev192}/ominfra/supervisor/utils/strings.py +0 -0
  141. {ominfra-0.0.0.dev190 → ominfra-0.0.0.dev192}/ominfra/supervisor/utils/users.py +0 -0
  142. {ominfra-0.0.0.dev190 → ominfra-0.0.0.dev192}/ominfra/tailscale/__init__.py +0 -0
  143. {ominfra-0.0.0.dev190 → ominfra-0.0.0.dev192}/ominfra/tailscale/api.py +0 -0
  144. {ominfra-0.0.0.dev190 → ominfra-0.0.0.dev192}/ominfra/tailscale/cli.py +0 -0
  145. {ominfra-0.0.0.dev190 → ominfra-0.0.0.dev192}/ominfra/threadworkers.py +0 -0
  146. {ominfra-0.0.0.dev190 → ominfra-0.0.0.dev192}/ominfra/tools/__init__.py +0 -0
  147. {ominfra-0.0.0.dev190 → ominfra-0.0.0.dev192}/ominfra/tools/listresources.py +0 -0
  148. {ominfra-0.0.0.dev190 → ominfra-0.0.0.dev192}/ominfra.egg-info/dependency_links.txt +0 -0
  149. {ominfra-0.0.0.dev190 → ominfra-0.0.0.dev192}/ominfra.egg-info/entry_points.txt +0 -0
  150. {ominfra-0.0.0.dev190 → ominfra-0.0.0.dev192}/ominfra.egg-info/top_level.txt +0 -0
  151. {ominfra-0.0.0.dev190 → ominfra-0.0.0.dev192}/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.dev192
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.dev192
16
+ Requires-Dist: omlish==0.0.0.dev192
17
+ Requires-Dist: omserv==0.0.0.dev192
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
@@ -70,21 +84,30 @@ class DeployAppManager(DeployPathOwner):
70
84
  spec: DeployAppSpec,
71
85
  home: DeployHome,
72
86
  tags: DeployTagMap,
87
+ *,
88
+ conf_string_ns: ta.Optional[ta.Mapping[str, ta.Any]] = None,
73
89
  ) -> PreparedApp:
74
90
  spec_json = json_dumps_pretty(self._msh.marshal_obj(spec))
75
91
 
76
92
  #
77
93
 
94
+ app_tags = tags.add(*self._make_tags(spec))
95
+
96
+ #
97
+
78
98
  check.non_empty_str(home)
79
99
 
80
- app_dir = os.path.join(home, self.APP_DIR.render(tags))
100
+ app_dir = os.path.join(home, self.APP_DIR.render(app_tags))
81
101
 
82
102
  os.makedirs(app_dir, exist_ok=True)
83
103
 
84
104
  #
85
105
 
86
106
  rkw: ta.Dict[str, ta.Any] = dict(
87
- app_dir=app_dir,
107
+ spec=spec,
108
+ tags=app_tags,
109
+
110
+ dir=app_dir,
88
111
  )
89
112
 
90
113
  #
@@ -119,11 +142,67 @@ class DeployAppManager(DeployPathOwner):
119
142
  if spec.conf is not None:
120
143
  conf_dir = os.path.join(app_dir, 'conf')
121
144
  rkw.update(conf_dir=conf_dir)
145
+
146
+ conf_ns: ta.Dict[str, ta.Any] = dict(
147
+ **(conf_string_ns or {}),
148
+ app=spec.app.s,
149
+ app_dir=app_dir.rstrip('/'),
150
+ )
151
+
122
152
  await self._conf.write_app_conf(
123
153
  spec.conf,
124
154
  conf_dir,
155
+ string_ns=conf_ns,
125
156
  )
126
157
 
127
158
  #
128
159
 
129
160
  return DeployAppManager.PreparedApp(**rkw)
161
+
162
+ async def prepare_app_link(
163
+ self,
164
+ tags: DeployTagMap,
165
+ app_dir: str,
166
+ ) -> PreparedApp:
167
+ spec_file = os.path.join(app_dir, 'spec.json')
168
+ with open(spec_file) as f: # noqa
169
+ spec_json = f.read()
170
+
171
+ spec: DeployAppSpec = self._msh.unmarshal_obj(json.loads(spec_json), DeployAppSpec)
172
+
173
+ #
174
+
175
+ app_tags = tags.add(*self._make_tags(spec))
176
+
177
+ #
178
+
179
+ rkw: ta.Dict[str, ta.Any] = dict(
180
+ spec=spec,
181
+ tags=app_tags,
182
+
183
+ dir=app_dir,
184
+ )
185
+
186
+ #
187
+
188
+ git_dir = os.path.join(app_dir, 'git')
189
+ check.state(os.path.isdir(git_dir))
190
+ rkw.update(git_dir=git_dir)
191
+
192
+ #
193
+
194
+ if spec.venv is not None:
195
+ venv_dir = os.path.join(app_dir, 'venv')
196
+ check.state(os.path.isdir(venv_dir))
197
+ rkw.update(venv_dir=venv_dir)
198
+
199
+ #
200
+
201
+ if spec.conf is not None:
202
+ conf_dir = os.path.join(app_dir, 'conf')
203
+ check.state(os.path.isdir(conf_dir))
204
+ rkw.update(conf_dir=conf_dir)
205
+
206
+ #
207
+
208
+ 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
 
@@ -80,11 +133,21 @@ class DeployConfManager:
80
133
  self,
81
134
  spec: DeployAppConfSpec,
82
135
  app_conf_dir: str,
136
+ *,
137
+ string_ns: ta.Optional[ta.Mapping[str, ta.Any]] = None,
83
138
  ) -> None:
139
+ process_str: ta.Any
140
+ if string_ns is not None:
141
+ def process_str(s: str) -> str:
142
+ return s.format(**string_ns)
143
+ else:
144
+ process_str = None
145
+
84
146
  for acf in spec.files or []:
85
147
  await self._write_app_conf_file(
86
148
  acf,
87
149
  app_conf_dir,
150
+ str_processor=process_str,
88
151
  )
89
152
 
90
153
  #
@@ -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,16 +20,17 @@ 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
 
28
30
  ##
29
31
 
30
32
 
31
- DEPLOY_TAG_DATETIME_FMT = '%Y%m%dT%H%M%SZ'
33
+ DEPLOY_TAG_DATETIME_FMT = '%Y-%m-%d-T-%H-%M-%S-%f-Z'
32
34
 
33
35
 
34
36
  DeployManagerUtcClock = ta.NewType('DeployManagerUtcClock', Func0[datetime.datetime])
@@ -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,37 @@ 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
+ ras: ta.Set[DeployApp] = set(self._spec.app_links.removed_apps)
187
+ check.empty(das & (las | ras))
188
+ check.empty(las & ras)
189
+
190
+ #
191
+
153
192
  self._paths.validate_deploy_paths()
154
193
 
155
194
  #
156
195
 
157
- os.makedirs(self.deploy_dir)
196
+ os.makedirs(self.dir)
158
197
 
159
198
  #
160
199
 
161
- spec_file = self.render_deploy_path(self._deploys.DEPLOY_SPEC_FILE)
200
+ spec_file = self.render_path(self._deploys.DEPLOY_SPEC_FILE)
162
201
  with open(spec_file, 'w') as f: # noqa
163
202
  f.write(spec_json)
164
203
 
165
204
  #
166
205
 
167
- deploying_link = self.render_deploy_path(self._deploys.DEPLOYING_DEPLOY_LINK)
206
+ deploying_link = self.render_path(self._deploys.DEPLOYING_DEPLOY_LINK)
207
+ current_link = self.render_path(self._deploys.CURRENT_DEPLOY_LINK)
208
+
209
+ #
210
+
168
211
  if os.path.exists(deploying_link):
169
212
  os.unlink(deploying_link)
170
213
  relative_symlink(
171
- self.deploy_dir,
214
+ self.dir,
172
215
  deploying_link,
173
216
  target_is_directory=True,
174
217
  make_dirs=True,
@@ -180,16 +223,30 @@ class DeployDriver:
180
223
  self._deploys.APPS_DEPLOY_DIR,
181
224
  self._deploys.CONFS_DEPLOY_DIR,
182
225
  ]:
183
- os.makedirs(self.render_deploy_path(md))
226
+ os.makedirs(self.render_path(md))
184
227
 
185
228
  #
186
229
 
230
+ if not self._spec.app_links.exclude_unspecified:
231
+ cad = abs_real_path(os.path.join(current_link, 'apps'))
232
+ if os.path.exists(cad):
233
+ for d in os.listdir(cad):
234
+ if (da := DeployApp(d)) not in das and da not in ras:
235
+ las.add(da)
236
+
237
+ for la in las:
238
+ await self._drive_app_link(
239
+ la,
240
+ current_link,
241
+ )
242
+
187
243
  for app in self._spec.apps:
188
- await self.drive_app_deploy(app)
244
+ await self._drive_app_deploy(
245
+ app,
246
+ )
189
247
 
190
248
  #
191
249
 
192
- current_link = self.render_deploy_path(self._deploys.CURRENT_DEPLOY_LINK)
193
250
  os.replace(deploying_link, current_link)
194
251
 
195
252
  #
@@ -197,31 +254,33 @@ class DeployDriver:
197
254
  await self._systemd.sync_systemd(
198
255
  self._spec.systemd,
199
256
  self._home,
200
- os.path.join(self.deploy_dir, 'conf', 'systemd'), # FIXME
257
+ os.path.join(self.dir, 'conf', 'systemd'), # FIXME
201
258
  )
202
259
 
203
- #
260
+ #
204
261
 
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
- )
262
+ self._deploys.make_home_current_link(self._home)
211
263
 
212
- #
264
+ #
265
+
266
+ async def _drive_app_deploy(self, app: DeployAppSpec) -> None:
267
+ current_deploy_link = os.path.join(self._home, self._deploys.CURRENT_DEPLOY_LINK.render())
213
268
 
214
- da = await self._apps.prepare_app(
269
+ pa = await self._apps.prepare_app(
215
270
  app,
216
271
  self._home,
217
- app_tags,
272
+ self.tags,
273
+ conf_string_ns=dict(
274
+ deploy_home=self._home,
275
+ current_deploy_link=current_deploy_link,
276
+ ),
218
277
  )
219
278
 
220
279
  #
221
280
 
222
- app_link = self.render_deploy_path(self._deploys.APP_DEPLOY_LINK, app_tags)
281
+ app_link = self.render_path(self._deploys.APP_DEPLOY_LINK, pa.tags)
223
282
  relative_symlink(
224
- da.app_dir,
283
+ pa.dir,
225
284
  app_link,
226
285
  target_is_directory=True,
227
286
  make_dirs=True,
@@ -229,11 +288,47 @@ class DeployDriver:
229
288
 
230
289
  #
231
290
 
232
- deploy_conf_dir = self.render_deploy_path(self._deploys.CONFS_DEPLOY_DIR)
233
- if app.conf is not None:
291
+ await self._drive_app_configure(pa)
292
+
293
+ async def _drive_app_link(
294
+ self,
295
+ app: DeployApp,
296
+ current_link: str,
297
+ ) -> None:
298
+ app_link = os.path.join(abs_real_path(current_link), 'apps', app.s)
299
+ check.state(os.path.islink(app_link))
300
+
301
+ app_dir = abs_real_path(app_link)
302
+ check.state(os.path.isdir(app_dir))
303
+
304
+ #
305
+
306
+ pa = await self._apps.prepare_app_link(
307
+ self.tags,
308
+ app_dir,
309
+ )
310
+
311
+ #
312
+
313
+ relative_symlink(
314
+ app_dir,
315
+ os.path.join(self.dir, 'apps', app.s),
316
+ target_is_directory=True,
317
+ )
318
+
319
+ #
320
+
321
+ await self._drive_app_configure(pa)
322
+
323
+ async def _drive_app_configure(
324
+ self,
325
+ pa: DeployAppManager.PreparedApp,
326
+ ) -> None:
327
+ deploy_conf_dir = self.render_path(self._deploys.CONFS_DEPLOY_DIR)
328
+ if pa.spec.conf is not None:
234
329
  await self._conf.link_app_conf(
235
- app.conf,
236
- app_tags,
237
- check.non_empty_str(da.conf_dir),
330
+ pa.spec.conf,
331
+ pa.tags,
332
+ check.non_empty_str(pa.conf_dir),
238
333
  deploy_conf_dir,
239
334
  )
@@ -91,7 +91,9 @@ class DeployGitManager(SingleDirDeployPathOwner):
91
91
 
92
92
  async def fetch(self, rev: DeployRev) -> None:
93
93
  await self.init()
94
- await self._call('git', 'fetch', '--depth=1', 'origin', rev)
94
+
95
+ # This fetch shouldn't be depth=1 - git doesn't reuse local data with shallow fetches.
96
+ await self._call('git', 'fetch', 'origin', rev)
95
97
 
96
98
  #
97
99
 
@@ -91,6 +91,15 @@ 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
+ removed_apps: ta.Sequence[DeployApp] = ()
99
+
100
+ exclude_unspecified: bool = False
101
+
102
+
94
103
  ##
95
104
 
96
105
 
@@ -109,6 +118,8 @@ class DeploySpec(DeploySpecKeyed[DeployKey]):
109
118
 
110
119
  apps: ta.Sequence[DeployAppSpec] = ()
111
120
 
121
+ app_links: DeployAppLinksSpec = DeployAppLinksSpec()
122
+
112
123
  systemd: ta.Optional[DeploySystemdSpec] = None
113
124
 
114
125
  def __post_init__(self) -> None: