ominfra 0.0.0.dev170__tar.gz → 0.0.0.dev171__tar.gz

Sign up to get free protection for your applications and to get access to all the features.
Files changed (141) hide show
  1. {ominfra-0.0.0.dev170/ominfra.egg-info → ominfra-0.0.0.dev171}/PKG-INFO +3 -3
  2. {ominfra-0.0.0.dev170 → ominfra-0.0.0.dev171}/ominfra/manage/deploy/apps.py +34 -1
  3. {ominfra-0.0.0.dev170 → ominfra-0.0.0.dev171}/ominfra/manage/remote/spawning.py +12 -4
  4. ominfra-0.0.0.dev171/ominfra/manage/targets/bestpython.py +29 -0
  5. ominfra-0.0.0.dev171/ominfra/manage/targets/bestpython.sh +21 -0
  6. {ominfra-0.0.0.dev170 → ominfra-0.0.0.dev171}/ominfra/manage/targets/connection.py +11 -3
  7. {ominfra-0.0.0.dev170 → ominfra-0.0.0.dev171}/ominfra/manage/targets/targets.py +5 -2
  8. {ominfra-0.0.0.dev170 → ominfra-0.0.0.dev171}/ominfra/pyremote.py +4 -2
  9. {ominfra-0.0.0.dev170 → ominfra-0.0.0.dev171}/ominfra/scripts/manage.py +140 -12
  10. {ominfra-0.0.0.dev170 → ominfra-0.0.0.dev171/ominfra.egg-info}/PKG-INFO +3 -3
  11. {ominfra-0.0.0.dev170 → ominfra-0.0.0.dev171}/ominfra.egg-info/SOURCES.txt +2 -0
  12. {ominfra-0.0.0.dev170 → ominfra-0.0.0.dev171}/ominfra.egg-info/requires.txt +2 -2
  13. {ominfra-0.0.0.dev170 → ominfra-0.0.0.dev171}/pyproject.toml +4 -3
  14. {ominfra-0.0.0.dev170 → ominfra-0.0.0.dev171}/LICENSE +0 -0
  15. {ominfra-0.0.0.dev170 → ominfra-0.0.0.dev171}/MANIFEST.in +0 -0
  16. {ominfra-0.0.0.dev170 → ominfra-0.0.0.dev171}/README.rst +0 -0
  17. {ominfra-0.0.0.dev170 → ominfra-0.0.0.dev171}/ominfra/.manifests.json +0 -0
  18. {ominfra-0.0.0.dev170 → ominfra-0.0.0.dev171}/ominfra/__about__.py +0 -0
  19. {ominfra-0.0.0.dev170 → ominfra-0.0.0.dev171}/ominfra/__init__.py +0 -0
  20. {ominfra-0.0.0.dev170 → ominfra-0.0.0.dev171}/ominfra/clouds/__init__.py +0 -0
  21. {ominfra-0.0.0.dev170 → ominfra-0.0.0.dev171}/ominfra/clouds/aws/__init__.py +0 -0
  22. {ominfra-0.0.0.dev170 → ominfra-0.0.0.dev171}/ominfra/clouds/aws/__main__.py +0 -0
  23. {ominfra-0.0.0.dev170 → ominfra-0.0.0.dev171}/ominfra/clouds/aws/auth.py +0 -0
  24. {ominfra-0.0.0.dev170 → ominfra-0.0.0.dev171}/ominfra/clouds/aws/cli.py +0 -0
  25. {ominfra-0.0.0.dev170 → ominfra-0.0.0.dev171}/ominfra/clouds/aws/dataclasses.py +0 -0
  26. {ominfra-0.0.0.dev170 → ominfra-0.0.0.dev171}/ominfra/clouds/aws/journald2aws/__init__.py +0 -0
  27. {ominfra-0.0.0.dev170 → ominfra-0.0.0.dev171}/ominfra/clouds/aws/journald2aws/__main__.py +0 -0
  28. {ominfra-0.0.0.dev170 → ominfra-0.0.0.dev171}/ominfra/clouds/aws/journald2aws/cursor.py +0 -0
  29. {ominfra-0.0.0.dev170 → ominfra-0.0.0.dev171}/ominfra/clouds/aws/journald2aws/driver.py +0 -0
  30. {ominfra-0.0.0.dev170 → ominfra-0.0.0.dev171}/ominfra/clouds/aws/journald2aws/main.py +0 -0
  31. {ominfra-0.0.0.dev170 → ominfra-0.0.0.dev171}/ominfra/clouds/aws/journald2aws/poster.py +0 -0
  32. {ominfra-0.0.0.dev170 → ominfra-0.0.0.dev171}/ominfra/clouds/aws/logs.py +0 -0
  33. {ominfra-0.0.0.dev170 → ominfra-0.0.0.dev171}/ominfra/clouds/aws/metadata.py +0 -0
  34. {ominfra-0.0.0.dev170 → ominfra-0.0.0.dev171}/ominfra/clouds/gcp/__init__.py +0 -0
  35. {ominfra-0.0.0.dev170 → ominfra-0.0.0.dev171}/ominfra/clouds/gcp/auth.py +0 -0
  36. {ominfra-0.0.0.dev170 → ominfra-0.0.0.dev171}/ominfra/cmds.py +0 -0
  37. {ominfra-0.0.0.dev170 → ominfra-0.0.0.dev171}/ominfra/configs.py +0 -0
  38. {ominfra-0.0.0.dev170 → ominfra-0.0.0.dev171}/ominfra/journald/__init__.py +0 -0
  39. {ominfra-0.0.0.dev170 → ominfra-0.0.0.dev171}/ominfra/journald/fields.py +0 -0
  40. {ominfra-0.0.0.dev170 → ominfra-0.0.0.dev171}/ominfra/journald/genmessages.py +0 -0
  41. {ominfra-0.0.0.dev170 → ominfra-0.0.0.dev171}/ominfra/journald/messages.py +0 -0
  42. {ominfra-0.0.0.dev170 → ominfra-0.0.0.dev171}/ominfra/journald/tailer.py +0 -0
  43. {ominfra-0.0.0.dev170 → ominfra-0.0.0.dev171}/ominfra/manage/__init__.py +0 -0
  44. {ominfra-0.0.0.dev170 → ominfra-0.0.0.dev171}/ominfra/manage/__main__.py +0 -0
  45. {ominfra-0.0.0.dev170 → ominfra-0.0.0.dev171}/ominfra/manage/bootstrap.py +0 -0
  46. {ominfra-0.0.0.dev170 → ominfra-0.0.0.dev171}/ominfra/manage/bootstrap_.py +0 -0
  47. {ominfra-0.0.0.dev170 → ominfra-0.0.0.dev171}/ominfra/manage/commands/__init__.py +0 -0
  48. {ominfra-0.0.0.dev170 → ominfra-0.0.0.dev171}/ominfra/manage/commands/base.py +0 -0
  49. {ominfra-0.0.0.dev170 → ominfra-0.0.0.dev171}/ominfra/manage/commands/inject.py +0 -0
  50. {ominfra-0.0.0.dev170 → ominfra-0.0.0.dev171}/ominfra/manage/commands/local.py +0 -0
  51. {ominfra-0.0.0.dev170 → ominfra-0.0.0.dev171}/ominfra/manage/commands/marshal.py +0 -0
  52. {ominfra-0.0.0.dev170 → ominfra-0.0.0.dev171}/ominfra/manage/commands/ping.py +0 -0
  53. {ominfra-0.0.0.dev170 → ominfra-0.0.0.dev171}/ominfra/manage/commands/subprocess.py +0 -0
  54. {ominfra-0.0.0.dev170 → ominfra-0.0.0.dev171}/ominfra/manage/commands/types.py +0 -0
  55. {ominfra-0.0.0.dev170 → ominfra-0.0.0.dev171}/ominfra/manage/config.py +0 -0
  56. {ominfra-0.0.0.dev170 → ominfra-0.0.0.dev171}/ominfra/manage/deploy/__init__.py +0 -0
  57. {ominfra-0.0.0.dev170 → ominfra-0.0.0.dev171}/ominfra/manage/deploy/commands.py +0 -0
  58. {ominfra-0.0.0.dev170 → ominfra-0.0.0.dev171}/ominfra/manage/deploy/conf.py +0 -0
  59. {ominfra-0.0.0.dev170 → ominfra-0.0.0.dev171}/ominfra/manage/deploy/config.py +0 -0
  60. {ominfra-0.0.0.dev170 → ominfra-0.0.0.dev171}/ominfra/manage/deploy/deploy.py +0 -0
  61. {ominfra-0.0.0.dev170 → ominfra-0.0.0.dev171}/ominfra/manage/deploy/git.py +0 -0
  62. {ominfra-0.0.0.dev170 → ominfra-0.0.0.dev171}/ominfra/manage/deploy/inject.py +0 -0
  63. {ominfra-0.0.0.dev170 → ominfra-0.0.0.dev171}/ominfra/manage/deploy/interp.py +0 -0
  64. {ominfra-0.0.0.dev170 → ominfra-0.0.0.dev171}/ominfra/manage/deploy/paths/__init__.py +0 -0
  65. {ominfra-0.0.0.dev170 → ominfra-0.0.0.dev171}/ominfra/manage/deploy/paths/inject.py +0 -0
  66. {ominfra-0.0.0.dev170 → ominfra-0.0.0.dev171}/ominfra/manage/deploy/paths/manager.py +0 -0
  67. {ominfra-0.0.0.dev170 → ominfra-0.0.0.dev171}/ominfra/manage/deploy/paths/owners.py +0 -0
  68. {ominfra-0.0.0.dev170 → ominfra-0.0.0.dev171}/ominfra/manage/deploy/paths/paths.py +0 -0
  69. {ominfra-0.0.0.dev170 → ominfra-0.0.0.dev171}/ominfra/manage/deploy/specs.py +0 -0
  70. {ominfra-0.0.0.dev170 → ominfra-0.0.0.dev171}/ominfra/manage/deploy/tmp.py +0 -0
  71. {ominfra-0.0.0.dev170 → ominfra-0.0.0.dev171}/ominfra/manage/deploy/types.py +0 -0
  72. {ominfra-0.0.0.dev170 → ominfra-0.0.0.dev171}/ominfra/manage/deploy/venvs.py +0 -0
  73. {ominfra-0.0.0.dev170 → ominfra-0.0.0.dev171}/ominfra/manage/inject.py +0 -0
  74. {ominfra-0.0.0.dev170 → ominfra-0.0.0.dev171}/ominfra/manage/main.py +0 -0
  75. {ominfra-0.0.0.dev170 → ominfra-0.0.0.dev171}/ominfra/manage/marshal.py +0 -0
  76. {ominfra-0.0.0.dev170 → ominfra-0.0.0.dev171}/ominfra/manage/remote/__init__.py +0 -0
  77. {ominfra-0.0.0.dev170 → ominfra-0.0.0.dev171}/ominfra/manage/remote/_main.py +0 -0
  78. {ominfra-0.0.0.dev170 → ominfra-0.0.0.dev171}/ominfra/manage/remote/channel.py +0 -0
  79. {ominfra-0.0.0.dev170 → ominfra-0.0.0.dev171}/ominfra/manage/remote/config.py +0 -0
  80. {ominfra-0.0.0.dev170 → ominfra-0.0.0.dev171}/ominfra/manage/remote/connection.py +0 -0
  81. {ominfra-0.0.0.dev170 → ominfra-0.0.0.dev171}/ominfra/manage/remote/execution.py +0 -0
  82. {ominfra-0.0.0.dev170 → ominfra-0.0.0.dev171}/ominfra/manage/remote/inject.py +0 -0
  83. {ominfra-0.0.0.dev170 → ominfra-0.0.0.dev171}/ominfra/manage/remote/payload.py +0 -0
  84. {ominfra-0.0.0.dev170 → ominfra-0.0.0.dev171}/ominfra/manage/system/__init__.py +0 -0
  85. {ominfra-0.0.0.dev170 → ominfra-0.0.0.dev171}/ominfra/manage/system/commands.py +0 -0
  86. {ominfra-0.0.0.dev170 → ominfra-0.0.0.dev171}/ominfra/manage/system/config.py +0 -0
  87. {ominfra-0.0.0.dev170 → ominfra-0.0.0.dev171}/ominfra/manage/system/inject.py +0 -0
  88. {ominfra-0.0.0.dev170 → ominfra-0.0.0.dev171}/ominfra/manage/system/packages.py +0 -0
  89. {ominfra-0.0.0.dev170 → ominfra-0.0.0.dev171}/ominfra/manage/system/platforms.py +0 -0
  90. {ominfra-0.0.0.dev170 → ominfra-0.0.0.dev171}/ominfra/manage/targets/__init__.py +0 -0
  91. {ominfra-0.0.0.dev170 → ominfra-0.0.0.dev171}/ominfra/manage/targets/inject.py +0 -0
  92. {ominfra-0.0.0.dev170 → ominfra-0.0.0.dev171}/ominfra/scripts/__init__.py +0 -0
  93. {ominfra-0.0.0.dev170 → ominfra-0.0.0.dev171}/ominfra/scripts/journald2aws.py +0 -0
  94. {ominfra-0.0.0.dev170 → ominfra-0.0.0.dev171}/ominfra/scripts/supervisor.py +0 -0
  95. {ominfra-0.0.0.dev170 → ominfra-0.0.0.dev171}/ominfra/ssh.py +0 -0
  96. {ominfra-0.0.0.dev170 → ominfra-0.0.0.dev171}/ominfra/supervisor/LICENSE.txt +0 -0
  97. {ominfra-0.0.0.dev170 → ominfra-0.0.0.dev171}/ominfra/supervisor/__init__.py +0 -0
  98. {ominfra-0.0.0.dev170 → ominfra-0.0.0.dev171}/ominfra/supervisor/__main__.py +0 -0
  99. {ominfra-0.0.0.dev170 → ominfra-0.0.0.dev171}/ominfra/supervisor/configs.py +0 -0
  100. {ominfra-0.0.0.dev170 → ominfra-0.0.0.dev171}/ominfra/supervisor/dispatchers.py +0 -0
  101. {ominfra-0.0.0.dev170 → ominfra-0.0.0.dev171}/ominfra/supervisor/dispatchersimpl.py +0 -0
  102. {ominfra-0.0.0.dev170 → ominfra-0.0.0.dev171}/ominfra/supervisor/events.py +0 -0
  103. {ominfra-0.0.0.dev170 → ominfra-0.0.0.dev171}/ominfra/supervisor/exceptions.py +0 -0
  104. {ominfra-0.0.0.dev170 → ominfra-0.0.0.dev171}/ominfra/supervisor/groups.py +0 -0
  105. {ominfra-0.0.0.dev170 → ominfra-0.0.0.dev171}/ominfra/supervisor/groupsimpl.py +0 -0
  106. {ominfra-0.0.0.dev170 → ominfra-0.0.0.dev171}/ominfra/supervisor/http.py +0 -0
  107. {ominfra-0.0.0.dev170 → ominfra-0.0.0.dev171}/ominfra/supervisor/inject.py +0 -0
  108. {ominfra-0.0.0.dev170 → ominfra-0.0.0.dev171}/ominfra/supervisor/io.py +0 -0
  109. {ominfra-0.0.0.dev170 → ominfra-0.0.0.dev171}/ominfra/supervisor/main.py +0 -0
  110. {ominfra-0.0.0.dev170 → ominfra-0.0.0.dev171}/ominfra/supervisor/pipes.py +0 -0
  111. {ominfra-0.0.0.dev170 → ominfra-0.0.0.dev171}/ominfra/supervisor/privileges.py +0 -0
  112. {ominfra-0.0.0.dev170 → ominfra-0.0.0.dev171}/ominfra/supervisor/process.py +0 -0
  113. {ominfra-0.0.0.dev170 → ominfra-0.0.0.dev171}/ominfra/supervisor/processimpl.py +0 -0
  114. {ominfra-0.0.0.dev170 → ominfra-0.0.0.dev171}/ominfra/supervisor/setup.py +0 -0
  115. {ominfra-0.0.0.dev170 → ominfra-0.0.0.dev171}/ominfra/supervisor/setupimpl.py +0 -0
  116. {ominfra-0.0.0.dev170 → ominfra-0.0.0.dev171}/ominfra/supervisor/signals.py +0 -0
  117. {ominfra-0.0.0.dev170 → ominfra-0.0.0.dev171}/ominfra/supervisor/spawning.py +0 -0
  118. {ominfra-0.0.0.dev170 → ominfra-0.0.0.dev171}/ominfra/supervisor/spawningimpl.py +0 -0
  119. {ominfra-0.0.0.dev170 → ominfra-0.0.0.dev171}/ominfra/supervisor/states.py +0 -0
  120. {ominfra-0.0.0.dev170 → ominfra-0.0.0.dev171}/ominfra/supervisor/supervisor.py +0 -0
  121. {ominfra-0.0.0.dev170 → ominfra-0.0.0.dev171}/ominfra/supervisor/types.py +0 -0
  122. {ominfra-0.0.0.dev170 → ominfra-0.0.0.dev171}/ominfra/supervisor/utils/__init__.py +0 -0
  123. {ominfra-0.0.0.dev170 → ominfra-0.0.0.dev171}/ominfra/supervisor/utils/collections.py +0 -0
  124. {ominfra-0.0.0.dev170 → ominfra-0.0.0.dev171}/ominfra/supervisor/utils/diag.py +0 -0
  125. {ominfra-0.0.0.dev170 → ominfra-0.0.0.dev171}/ominfra/supervisor/utils/fds.py +0 -0
  126. {ominfra-0.0.0.dev170 → ominfra-0.0.0.dev171}/ominfra/supervisor/utils/fs.py +0 -0
  127. {ominfra-0.0.0.dev170 → ominfra-0.0.0.dev171}/ominfra/supervisor/utils/os.py +0 -0
  128. {ominfra-0.0.0.dev170 → ominfra-0.0.0.dev171}/ominfra/supervisor/utils/ostypes.py +0 -0
  129. {ominfra-0.0.0.dev170 → ominfra-0.0.0.dev171}/ominfra/supervisor/utils/signals.py +0 -0
  130. {ominfra-0.0.0.dev170 → ominfra-0.0.0.dev171}/ominfra/supervisor/utils/strings.py +0 -0
  131. {ominfra-0.0.0.dev170 → ominfra-0.0.0.dev171}/ominfra/supervisor/utils/users.py +0 -0
  132. {ominfra-0.0.0.dev170 → ominfra-0.0.0.dev171}/ominfra/tailscale/__init__.py +0 -0
  133. {ominfra-0.0.0.dev170 → ominfra-0.0.0.dev171}/ominfra/tailscale/api.py +0 -0
  134. {ominfra-0.0.0.dev170 → ominfra-0.0.0.dev171}/ominfra/tailscale/cli.py +0 -0
  135. {ominfra-0.0.0.dev170 → ominfra-0.0.0.dev171}/ominfra/threadworkers.py +0 -0
  136. {ominfra-0.0.0.dev170 → ominfra-0.0.0.dev171}/ominfra/tools/__init__.py +0 -0
  137. {ominfra-0.0.0.dev170 → ominfra-0.0.0.dev171}/ominfra/tools/listresources.py +0 -0
  138. {ominfra-0.0.0.dev170 → ominfra-0.0.0.dev171}/ominfra.egg-info/dependency_links.txt +0 -0
  139. {ominfra-0.0.0.dev170 → ominfra-0.0.0.dev171}/ominfra.egg-info/entry_points.txt +0 -0
  140. {ominfra-0.0.0.dev170 → ominfra-0.0.0.dev171}/ominfra.egg-info/top_level.txt +0 -0
  141. {ominfra-0.0.0.dev170 → ominfra-0.0.0.dev171}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: ominfra
3
- Version: 0.0.0.dev170
3
+ Version: 0.0.0.dev171
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.dev170
16
- Requires-Dist: omlish==0.0.0.dev170
15
+ Requires-Dist: omdev==0.0.0.dev171
16
+ Requires-Dist: omlish==0.0.0.dev171
17
17
  Provides-Extra: all
18
18
  Requires-Dist: paramiko~=3.5; extra == "all"
19
19
  Requires-Dist: asyncssh~=2.18; extra == "all"
@@ -1,6 +1,7 @@
1
1
  # ruff: noqa: UP006 UP007
2
2
  import datetime
3
3
  import os.path
4
+ import shutil
4
5
  import typing as ta
5
6
 
6
7
  from omlish.lite.cached import cached_nullary
@@ -142,6 +143,39 @@ class DeployAppManager(DeployPathOwner):
142
143
 
143
144
  #
144
145
 
146
+ def mirror_symlinks(src: str, dst: str) -> None:
147
+ def mirror_link(lp: str) -> None:
148
+ check.state(os.path.islink(lp))
149
+ shutil.copy2(
150
+ lp,
151
+ os.path.join(dst, os.path.relpath(lp, src)),
152
+ follow_symlinks=False,
153
+ )
154
+
155
+ for dp, dns, fns in os.walk(src, followlinks=False):
156
+ for fn in fns:
157
+ mirror_link(os.path.join(dp, fn))
158
+
159
+ for dn in dns:
160
+ dp2 = os.path.join(dp, dn)
161
+ if os.path.islink(dp2):
162
+ mirror_link(dp2)
163
+ else:
164
+ os.makedirs(os.path.join(dst, os.path.relpath(dp2, src)))
165
+
166
+ current_link = os.path.join(deploy_home, 'deploys/current')
167
+ # if os.path.exists(current_link):
168
+ # mirror_symlinks(
169
+ # os.path.join(current_link, 'conf'),
170
+ # conf_tag_dir,
171
+ # )
172
+ # mirror_symlinks(
173
+ # os.path.join(current_link, 'apps'),
174
+ # os.path.join(deploy_dir, 'apps'),
175
+ # )
176
+
177
+ #
178
+
145
179
  git_dir = os.path.join(app_tag_dir, 'git')
146
180
  await self._git.checkout(
147
181
  spec.git,
@@ -171,5 +205,4 @@ class DeployAppManager(DeployPathOwner):
171
205
 
172
206
  #
173
207
 
174
- current_link = os.path.join(deploy_home, 'deploys/current')
175
208
  os.replace(deploying_link, current_link)
@@ -9,6 +9,7 @@ import typing as ta
9
9
 
10
10
  from omlish.asyncs.asyncio.subprocesses import asyncio_subprocesses
11
11
  from omlish.lite.check import check
12
+ from omlish.lite.shlex import shlex_maybe_quote
12
13
  from omlish.subprocesses import SUBPROCESS_CHANNEL_OPTION_VALUES
13
14
  from omlish.subprocesses import SubprocessChannelOption
14
15
 
@@ -22,11 +23,14 @@ class RemoteSpawning(abc.ABC):
22
23
  shell: ta.Optional[str] = None
23
24
  shell_quote: bool = False
24
25
 
25
- DEFAULT_PYTHON: ta.ClassVar[str] = 'python3'
26
- python: str = DEFAULT_PYTHON
26
+ DEFAULT_PYTHON: ta.ClassVar[ta.Sequence[str]] = ('python3',)
27
+ python: ta.Sequence[str] = DEFAULT_PYTHON
27
28
 
28
29
  stderr: ta.Optional[str] = None # SubprocessChannelOption
29
30
 
31
+ def __post_init__(self) -> None:
32
+ check.not_isinstance(self.python, str)
33
+
30
34
  @dc.dataclass(frozen=True)
31
35
  class Spawned:
32
36
  stdin: asyncio.StreamWriter
@@ -59,14 +63,18 @@ class SubprocessRemoteSpawning(RemoteSpawning):
59
63
  src: str,
60
64
  ) -> _PreparedCmd:
61
65
  if tgt.shell is not None:
62
- sh_src = f'{tgt.python} -c {shlex.quote(src)}'
66
+ sh_src = ' '.join([
67
+ *map(shlex_maybe_quote, tgt.python),
68
+ '-c',
69
+ shlex_maybe_quote(src),
70
+ ])
63
71
  if tgt.shell_quote:
64
72
  sh_src = shlex.quote(sh_src)
65
73
  sh_cmd = f'{tgt.shell} {sh_src}'
66
74
  return SubprocessRemoteSpawning._PreparedCmd([sh_cmd], shell=True)
67
75
 
68
76
  else:
69
- return SubprocessRemoteSpawning._PreparedCmd([tgt.python, '-c', src], shell=False)
77
+ return SubprocessRemoteSpawning._PreparedCmd([*tgt.python, '-c', src], shell=False)
70
78
 
71
79
  #
72
80
 
@@ -0,0 +1,29 @@
1
+ import io
2
+
3
+ from omlish.lite.cached import cached_nullary
4
+ from omlish.lite.resources import read_package_resource_text
5
+
6
+
7
+ BEST_PYTHON_SH = read_package_resource_text(__package__, 'bestpython.sh')
8
+
9
+
10
+ @cached_nullary
11
+ def get_best_python_sh() -> str:
12
+ buf = io.StringIO()
13
+
14
+ for l in BEST_PYTHON_SH.strip().splitlines():
15
+ if not (l := l.strip()):
16
+ continue
17
+
18
+ buf.write(l)
19
+
20
+ if l.split()[-1] not in ('do', 'then', 'else'):
21
+ buf.write(';')
22
+
23
+ buf.write(' ')
24
+
25
+ return buf.getvalue().strip(' ;')
26
+
27
+
28
+ if __name__ == '__main__':
29
+ print(__import__('shlex').quote(get_best_python_sh()))
@@ -0,0 +1,21 @@
1
+ bv=""
2
+ bx=""
3
+
4
+ for version in "" 3 3.{8..13}; do
5
+ x="python$version"
6
+ v=$($x -c "import sys; print(sys.version_info[:])" 2>/dev/null)
7
+ if [ $? -eq 0 ]; then
8
+ cv=$(echo $v | tr -d "(), ")
9
+ if [ -z "$bv" ] || [ "$cv" \> "$bv" ]; then
10
+ bv=$cv
11
+ bx=$x
12
+ fi
13
+ fi
14
+ done
15
+
16
+ if [ -z "$bx" ]; then
17
+ echo "no python" >&2
18
+ exit 1
19
+ fi
20
+
21
+ exec "$bx" "$@"
@@ -12,6 +12,7 @@ from ..commands.local import LocalCommandExecutor
12
12
  from ..remote.connection import InProcessRemoteExecutionConnector
13
13
  from ..remote.connection import PyremoteRemoteExecutionConnector
14
14
  from ..remote.spawning import RemoteSpawning
15
+ from .bestpython import get_best_python_sh
15
16
  from .targets import DockerManageTarget
16
17
  from .targets import InProcessManageTarget
17
18
  from .targets import LocalManageTarget
@@ -28,6 +29,13 @@ class ManageTargetConnector(abc.ABC):
28
29
  def connect(self, tgt: ManageTarget) -> ta.AsyncContextManager[CommandExecutor]:
29
30
  raise NotImplementedError
30
31
 
32
+ def _default_python(self, python: ta.Optional[ta.Sequence[str]]) -> ta.Sequence[str]:
33
+ check.not_isinstance(python, str)
34
+ if python is not None:
35
+ return python
36
+ else:
37
+ return ['sh', '-c', get_best_python_sh(), '--']
38
+
31
39
 
32
40
  ##
33
41
 
@@ -79,7 +87,7 @@ class LocalManageTargetConnector(ManageTargetConnector):
79
87
  elif isinstance(lmt, SubprocessManageTarget):
80
88
  async with self._pyremote_connector.connect(
81
89
  RemoteSpawning.Target(
82
- python=lmt.python,
90
+ python=self._default_python(lmt.python),
83
91
  ),
84
92
  self._bootstrap,
85
93
  ) as rce:
@@ -112,7 +120,7 @@ class DockerManageTargetConnector(ManageTargetConnector):
112
120
  async with self._pyremote_connector.connect(
113
121
  RemoteSpawning.Target(
114
122
  shell=' '.join(sh_parts),
115
- python=dmt.python,
123
+ python=self._default_python(dmt.python),
116
124
  ),
117
125
  self._bootstrap,
118
126
  ) as rce:
@@ -143,7 +151,7 @@ class SshManageTargetConnector(ManageTargetConnector):
143
151
  RemoteSpawning.Target(
144
152
  shell=' '.join(sh_parts),
145
153
  shell_quote=True,
146
- python=smt.python,
154
+ python=self._default_python(smt.python),
147
155
  ),
148
156
  self._bootstrap,
149
157
  ) as rce:
@@ -26,8 +26,11 @@ class ManageTarget(abc.ABC): # noqa
26
26
 
27
27
  @dc.dataclass(frozen=True)
28
28
  class PythonRemoteManageTarget:
29
- DEFAULT_PYTHON: ta.ClassVar[str] = 'python3'
30
- python: str = DEFAULT_PYTHON
29
+ DEFAULT_PYTHON: ta.ClassVar[ta.Optional[ta.Sequence[str]]] = None
30
+ python: ta.Optional[ta.Sequence[str]] = DEFAULT_PYTHON
31
+
32
+ def __post_init__(self) -> None:
33
+ check.not_isinstance(self.python, str)
31
34
 
32
35
 
33
36
  #
@@ -256,11 +256,13 @@ def pyremote_build_bootstrap_cmd(context_name: str) -> str:
256
256
 
257
257
  bs_z = zlib.compress(bs_src.encode('utf-8'), 9)
258
258
  bs_z85 = base64.b85encode(bs_z).replace(b'\n', b'')
259
+ if b'"' in bs_z85:
260
+ raise ValueError(bs_z85)
259
261
 
260
262
  stmts = [
261
263
  f'import {", ".join(_PYREMOTE_BOOTSTRAP_IMPORTS)}',
262
- f'exec(zlib.decompress(base64.b85decode({bs_z85!r})))',
263
- f'_pyremote_bootstrap_main({context_name!r})',
264
+ f'exec(zlib.decompress(base64.b85decode(b"{bs_z85.decode("ascii")}")))',
265
+ f'_pyremote_bootstrap_main("{context_name}")',
264
266
  ]
265
267
 
266
268
  cmd = '; '.join(stmts)
@@ -26,6 +26,7 @@ import fractions
26
26
  import functools
27
27
  import hashlib
28
28
  import inspect
29
+ import io
29
30
  import itertools
30
31
  import json
31
32
  import logging
@@ -1626,11 +1627,13 @@ def pyremote_build_bootstrap_cmd(context_name: str) -> str:
1626
1627
 
1627
1628
  bs_z = zlib.compress(bs_src.encode('utf-8'), 9)
1628
1629
  bs_z85 = base64.b85encode(bs_z).replace(b'\n', b'')
1630
+ if b'"' in bs_z85:
1631
+ raise ValueError(bs_z85)
1629
1632
 
1630
1633
  stmts = [
1631
1634
  f'import {", ".join(_PYREMOTE_BOOTSTRAP_IMPORTS)}',
1632
- f'exec(zlib.decompress(base64.b85decode({bs_z85!r})))',
1633
- f'_pyremote_bootstrap_main({context_name!r})',
1635
+ f'exec(zlib.decompress(base64.b85decode(b"{bs_z85.decode("ascii")}")))',
1636
+ f'_pyremote_bootstrap_main("{context_name}")',
1634
1637
  ]
1635
1638
 
1636
1639
  cmd = '; '.join(stmts)
@@ -2693,6 +2696,35 @@ def deep_subclasses(cls: ta.Type[T]) -> ta.Iterator[ta.Type[T]]:
2693
2696
  todo.extend(reversed(cur.__subclasses__()))
2694
2697
 
2695
2698
 
2699
+ ########################################
2700
+ # ../../../omlish/lite/resources.py
2701
+
2702
+
2703
+ def read_package_resource_binary(package: str, resource: str) -> bytes:
2704
+ import importlib.resources
2705
+ return importlib.resources.read_binary(package, resource)
2706
+
2707
+
2708
+ def read_package_resource_text(package: str, resource: str) -> str:
2709
+ import importlib.resources
2710
+ return importlib.resources.read_text(package, resource)
2711
+
2712
+
2713
+ ########################################
2714
+ # ../../../omlish/lite/shlex.py
2715
+
2716
+
2717
+ def shlex_needs_quote(s: str) -> bool:
2718
+ return bool(s) and len(list(shlex.shlex(s))) > 1
2719
+
2720
+
2721
+ def shlex_maybe_quote(s: str) -> str:
2722
+ if shlex_needs_quote(s):
2723
+ return shlex.quote(s)
2724
+ else:
2725
+ return s
2726
+
2727
+
2696
2728
  ########################################
2697
2729
  # ../../../omlish/lite/strings.py
2698
2730
 
@@ -4280,6 +4312,53 @@ def detect_system_platform() -> Platform:
4280
4312
  return platform
4281
4313
 
4282
4314
 
4315
+ ########################################
4316
+ # ../targets/bestpython.py
4317
+
4318
+
4319
+ BEST_PYTHON_SH = """\
4320
+ bv=""
4321
+ bx=""
4322
+
4323
+ for version in "" 3 3.{8..13}; do
4324
+ x="python$version"
4325
+ v=$($x -c "import sys; print(sys.version_info[:])" 2>/dev/null)
4326
+ if [ $? -eq 0 ]; then
4327
+ cv=$(echo $v | tr -d "(), ")
4328
+ if [ -z "$bv" ] || [ "$cv" \\> "$bv" ]; then
4329
+ bv=$cv
4330
+ bx=$x
4331
+ fi
4332
+ fi
4333
+ done
4334
+
4335
+ if [ -z "$bx" ]; then
4336
+ echo "no python" >&2
4337
+ exit 1
4338
+ fi
4339
+
4340
+ exec "$bx" "$@"
4341
+ """ # noqa
4342
+
4343
+
4344
+ @cached_nullary
4345
+ def get_best_python_sh() -> str:
4346
+ buf = io.StringIO()
4347
+
4348
+ for l in BEST_PYTHON_SH.strip().splitlines():
4349
+ if not (l := l.strip()):
4350
+ continue
4351
+
4352
+ buf.write(l)
4353
+
4354
+ if l.split()[-1] not in ('do', 'then', 'else'):
4355
+ buf.write(';')
4356
+
4357
+ buf.write(' ')
4358
+
4359
+ return buf.getvalue().strip(' ;')
4360
+
4361
+
4283
4362
  ########################################
4284
4363
  # ../targets/targets.py
4285
4364
  """
@@ -4303,8 +4382,11 @@ class ManageTarget(abc.ABC): # noqa
4303
4382
 
4304
4383
  @dc.dataclass(frozen=True)
4305
4384
  class PythonRemoteManageTarget:
4306
- DEFAULT_PYTHON: ta.ClassVar[str] = 'python3'
4307
- python: str = DEFAULT_PYTHON
4385
+ DEFAULT_PYTHON: ta.ClassVar[ta.Optional[ta.Sequence[str]]] = None
4386
+ python: ta.Optional[ta.Sequence[str]] = DEFAULT_PYTHON
4387
+
4388
+ def __post_init__(self) -> None:
4389
+ check.not_isinstance(self.python, str)
4308
4390
 
4309
4391
 
4310
4392
  #
@@ -8852,11 +8934,14 @@ class RemoteSpawning(abc.ABC):
8852
8934
  shell: ta.Optional[str] = None
8853
8935
  shell_quote: bool = False
8854
8936
 
8855
- DEFAULT_PYTHON: ta.ClassVar[str] = 'python3'
8856
- python: str = DEFAULT_PYTHON
8937
+ DEFAULT_PYTHON: ta.ClassVar[ta.Sequence[str]] = ('python3',)
8938
+ python: ta.Sequence[str] = DEFAULT_PYTHON
8857
8939
 
8858
8940
  stderr: ta.Optional[str] = None # SubprocessChannelOption
8859
8941
 
8942
+ def __post_init__(self) -> None:
8943
+ check.not_isinstance(self.python, str)
8944
+
8860
8945
  @dc.dataclass(frozen=True)
8861
8946
  class Spawned:
8862
8947
  stdin: asyncio.StreamWriter
@@ -8889,14 +8974,18 @@ class SubprocessRemoteSpawning(RemoteSpawning):
8889
8974
  src: str,
8890
8975
  ) -> _PreparedCmd:
8891
8976
  if tgt.shell is not None:
8892
- sh_src = f'{tgt.python} -c {shlex.quote(src)}'
8977
+ sh_src = ' '.join([
8978
+ *map(shlex_maybe_quote, tgt.python),
8979
+ '-c',
8980
+ shlex_maybe_quote(src),
8981
+ ])
8893
8982
  if tgt.shell_quote:
8894
8983
  sh_src = shlex.quote(sh_src)
8895
8984
  sh_cmd = f'{tgt.shell} {sh_src}'
8896
8985
  return SubprocessRemoteSpawning._PreparedCmd([sh_cmd], shell=True)
8897
8986
 
8898
8987
  else:
8899
- return SubprocessRemoteSpawning._PreparedCmd([tgt.python, '-c', src], shell=False)
8988
+ return SubprocessRemoteSpawning._PreparedCmd([*tgt.python, '-c', src], shell=False)
8900
8989
 
8901
8990
  #
8902
8991
 
@@ -9348,6 +9437,39 @@ class DeployAppManager(DeployPathOwner):
9348
9437
 
9349
9438
  #
9350
9439
 
9440
+ def mirror_symlinks(src: str, dst: str) -> None:
9441
+ def mirror_link(lp: str) -> None:
9442
+ check.state(os.path.islink(lp))
9443
+ shutil.copy2(
9444
+ lp,
9445
+ os.path.join(dst, os.path.relpath(lp, src)),
9446
+ follow_symlinks=False,
9447
+ )
9448
+
9449
+ for dp, dns, fns in os.walk(src, followlinks=False):
9450
+ for fn in fns:
9451
+ mirror_link(os.path.join(dp, fn))
9452
+
9453
+ for dn in dns:
9454
+ dp2 = os.path.join(dp, dn)
9455
+ if os.path.islink(dp2):
9456
+ mirror_link(dp2)
9457
+ else:
9458
+ os.makedirs(os.path.join(dst, os.path.relpath(dp2, src)))
9459
+
9460
+ current_link = os.path.join(deploy_home, 'deploys/current')
9461
+ # if os.path.exists(current_link):
9462
+ # mirror_symlinks(
9463
+ # os.path.join(current_link, 'conf'),
9464
+ # conf_tag_dir,
9465
+ # )
9466
+ # mirror_symlinks(
9467
+ # os.path.join(current_link, 'apps'),
9468
+ # os.path.join(deploy_dir, 'apps'),
9469
+ # )
9470
+
9471
+ #
9472
+
9351
9473
  git_dir = os.path.join(app_tag_dir, 'git')
9352
9474
  await self._git.checkout(
9353
9475
  spec.git,
@@ -9377,7 +9499,6 @@ class DeployAppManager(DeployPathOwner):
9377
9499
 
9378
9500
  #
9379
9501
 
9380
- current_link = os.path.join(deploy_home, 'deploys/current')
9381
9502
  os.replace(deploying_link, current_link)
9382
9503
 
9383
9504
 
@@ -10225,6 +10346,13 @@ class ManageTargetConnector(abc.ABC):
10225
10346
  def connect(self, tgt: ManageTarget) -> ta.AsyncContextManager[CommandExecutor]:
10226
10347
  raise NotImplementedError
10227
10348
 
10349
+ def _default_python(self, python: ta.Optional[ta.Sequence[str]]) -> ta.Sequence[str]:
10350
+ check.not_isinstance(python, str)
10351
+ if python is not None:
10352
+ return python
10353
+ else:
10354
+ return ['sh', '-c', get_best_python_sh(), '--']
10355
+
10228
10356
 
10229
10357
  ##
10230
10358
 
@@ -10276,7 +10404,7 @@ class LocalManageTargetConnector(ManageTargetConnector):
10276
10404
  elif isinstance(lmt, SubprocessManageTarget):
10277
10405
  async with self._pyremote_connector.connect(
10278
10406
  RemoteSpawning.Target(
10279
- python=lmt.python,
10407
+ python=self._default_python(lmt.python),
10280
10408
  ),
10281
10409
  self._bootstrap,
10282
10410
  ) as rce:
@@ -10309,7 +10437,7 @@ class DockerManageTargetConnector(ManageTargetConnector):
10309
10437
  async with self._pyremote_connector.connect(
10310
10438
  RemoteSpawning.Target(
10311
10439
  shell=' '.join(sh_parts),
10312
- python=dmt.python,
10440
+ python=self._default_python(dmt.python),
10313
10441
  ),
10314
10442
  self._bootstrap,
10315
10443
  ) as rce:
@@ -10340,7 +10468,7 @@ class SshManageTargetConnector(ManageTargetConnector):
10340
10468
  RemoteSpawning.Target(
10341
10469
  shell=' '.join(sh_parts),
10342
10470
  shell_quote=True,
10343
- python=smt.python,
10471
+ python=self._default_python(smt.python),
10344
10472
  ),
10345
10473
  self._bootstrap,
10346
10474
  ) as rce:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: ominfra
3
- Version: 0.0.0.dev170
3
+ Version: 0.0.0.dev171
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.dev170
16
- Requires-Dist: omlish==0.0.0.dev170
15
+ Requires-Dist: omdev==0.0.0.dev171
16
+ Requires-Dist: omlish==0.0.0.dev171
17
17
  Provides-Extra: all
18
18
  Requires-Dist: paramiko~=3.5; extra == "all"
19
19
  Requires-Dist: asyncssh~=2.18; extra == "all"
@@ -87,6 +87,8 @@ ominfra/manage/system/inject.py
87
87
  ominfra/manage/system/packages.py
88
88
  ominfra/manage/system/platforms.py
89
89
  ominfra/manage/targets/__init__.py
90
+ ominfra/manage/targets/bestpython.py
91
+ ominfra/manage/targets/bestpython.sh
90
92
  ominfra/manage/targets/connection.py
91
93
  ominfra/manage/targets/inject.py
92
94
  ominfra/manage/targets/targets.py
@@ -1,5 +1,5 @@
1
- omdev==0.0.0.dev170
2
- omlish==0.0.0.dev170
1
+ omdev==0.0.0.dev171
2
+ omlish==0.0.0.dev171
3
3
 
4
4
  [all]
5
5
  paramiko~=3.5
@@ -12,7 +12,7 @@ authors = [
12
12
  urls = {source = 'https://github.com/wrmsr/omlish'}
13
13
  license = {text = 'BSD-3-Clause'}
14
14
  requires-python = '>=3.12'
15
- version = '0.0.0.dev170'
15
+ version = '0.0.0.dev171'
16
16
  classifiers = [
17
17
  'License :: OSI Approved :: BSD License',
18
18
  'Development Status :: 2 - Pre-Alpha',
@@ -22,8 +22,8 @@ classifiers = [
22
22
  ]
23
23
  description = 'ominfra'
24
24
  dependencies = [
25
- 'omdev == 0.0.0.dev170',
26
- 'omlish == 0.0.0.dev170',
25
+ 'omdev == 0.0.0.dev171',
26
+ 'omlish == 0.0.0.dev171',
27
27
  ]
28
28
 
29
29
  [project.optional-dependencies]
@@ -61,4 +61,5 @@ exclude = [
61
61
  '.manifests.json',
62
62
  'LICENSE',
63
63
  'LICENSE.txt',
64
+ 'manage/targets/bestpython.sh',
64
65
  ]
File without changes
File without changes