ominfra 0.0.0.dev169__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.
- {ominfra-0.0.0.dev169/ominfra.egg-info → ominfra-0.0.0.dev171}/PKG-INFO +3 -3
- {ominfra-0.0.0.dev169 → ominfra-0.0.0.dev171}/ominfra/manage/deploy/apps.py +34 -1
- {ominfra-0.0.0.dev169 → ominfra-0.0.0.dev171}/ominfra/manage/remote/spawning.py +12 -4
- ominfra-0.0.0.dev171/ominfra/manage/targets/bestpython.py +29 -0
- ominfra-0.0.0.dev171/ominfra/manage/targets/bestpython.sh +21 -0
- {ominfra-0.0.0.dev169 → ominfra-0.0.0.dev171}/ominfra/manage/targets/connection.py +11 -3
- {ominfra-0.0.0.dev169 → ominfra-0.0.0.dev171}/ominfra/manage/targets/targets.py +5 -2
- {ominfra-0.0.0.dev169 → ominfra-0.0.0.dev171}/ominfra/pyremote.py +4 -2
- {ominfra-0.0.0.dev169 → ominfra-0.0.0.dev171}/ominfra/scripts/manage.py +140 -12
- {ominfra-0.0.0.dev169 → ominfra-0.0.0.dev171/ominfra.egg-info}/PKG-INFO +3 -3
- {ominfra-0.0.0.dev169 → ominfra-0.0.0.dev171}/ominfra.egg-info/SOURCES.txt +2 -0
- {ominfra-0.0.0.dev169 → ominfra-0.0.0.dev171}/ominfra.egg-info/requires.txt +2 -2
- {ominfra-0.0.0.dev169 → ominfra-0.0.0.dev171}/pyproject.toml +4 -3
- {ominfra-0.0.0.dev169 → ominfra-0.0.0.dev171}/LICENSE +0 -0
- {ominfra-0.0.0.dev169 → ominfra-0.0.0.dev171}/MANIFEST.in +0 -0
- {ominfra-0.0.0.dev169 → ominfra-0.0.0.dev171}/README.rst +0 -0
- {ominfra-0.0.0.dev169 → ominfra-0.0.0.dev171}/ominfra/.manifests.json +0 -0
- {ominfra-0.0.0.dev169 → ominfra-0.0.0.dev171}/ominfra/__about__.py +0 -0
- {ominfra-0.0.0.dev169 → ominfra-0.0.0.dev171}/ominfra/__init__.py +0 -0
- {ominfra-0.0.0.dev169 → ominfra-0.0.0.dev171}/ominfra/clouds/__init__.py +0 -0
- {ominfra-0.0.0.dev169 → ominfra-0.0.0.dev171}/ominfra/clouds/aws/__init__.py +0 -0
- {ominfra-0.0.0.dev169 → ominfra-0.0.0.dev171}/ominfra/clouds/aws/__main__.py +0 -0
- {ominfra-0.0.0.dev169 → ominfra-0.0.0.dev171}/ominfra/clouds/aws/auth.py +0 -0
- {ominfra-0.0.0.dev169 → ominfra-0.0.0.dev171}/ominfra/clouds/aws/cli.py +0 -0
- {ominfra-0.0.0.dev169 → ominfra-0.0.0.dev171}/ominfra/clouds/aws/dataclasses.py +0 -0
- {ominfra-0.0.0.dev169 → ominfra-0.0.0.dev171}/ominfra/clouds/aws/journald2aws/__init__.py +0 -0
- {ominfra-0.0.0.dev169 → ominfra-0.0.0.dev171}/ominfra/clouds/aws/journald2aws/__main__.py +0 -0
- {ominfra-0.0.0.dev169 → ominfra-0.0.0.dev171}/ominfra/clouds/aws/journald2aws/cursor.py +0 -0
- {ominfra-0.0.0.dev169 → ominfra-0.0.0.dev171}/ominfra/clouds/aws/journald2aws/driver.py +0 -0
- {ominfra-0.0.0.dev169 → ominfra-0.0.0.dev171}/ominfra/clouds/aws/journald2aws/main.py +0 -0
- {ominfra-0.0.0.dev169 → ominfra-0.0.0.dev171}/ominfra/clouds/aws/journald2aws/poster.py +0 -0
- {ominfra-0.0.0.dev169 → ominfra-0.0.0.dev171}/ominfra/clouds/aws/logs.py +0 -0
- {ominfra-0.0.0.dev169 → ominfra-0.0.0.dev171}/ominfra/clouds/aws/metadata.py +0 -0
- {ominfra-0.0.0.dev169 → ominfra-0.0.0.dev171}/ominfra/clouds/gcp/__init__.py +0 -0
- {ominfra-0.0.0.dev169 → ominfra-0.0.0.dev171}/ominfra/clouds/gcp/auth.py +0 -0
- {ominfra-0.0.0.dev169 → ominfra-0.0.0.dev171}/ominfra/cmds.py +0 -0
- {ominfra-0.0.0.dev169 → ominfra-0.0.0.dev171}/ominfra/configs.py +0 -0
- {ominfra-0.0.0.dev169 → ominfra-0.0.0.dev171}/ominfra/journald/__init__.py +0 -0
- {ominfra-0.0.0.dev169 → ominfra-0.0.0.dev171}/ominfra/journald/fields.py +0 -0
- {ominfra-0.0.0.dev169 → ominfra-0.0.0.dev171}/ominfra/journald/genmessages.py +0 -0
- {ominfra-0.0.0.dev169 → ominfra-0.0.0.dev171}/ominfra/journald/messages.py +0 -0
- {ominfra-0.0.0.dev169 → ominfra-0.0.0.dev171}/ominfra/journald/tailer.py +0 -0
- {ominfra-0.0.0.dev169 → ominfra-0.0.0.dev171}/ominfra/manage/__init__.py +0 -0
- {ominfra-0.0.0.dev169 → ominfra-0.0.0.dev171}/ominfra/manage/__main__.py +0 -0
- {ominfra-0.0.0.dev169 → ominfra-0.0.0.dev171}/ominfra/manage/bootstrap.py +0 -0
- {ominfra-0.0.0.dev169 → ominfra-0.0.0.dev171}/ominfra/manage/bootstrap_.py +0 -0
- {ominfra-0.0.0.dev169 → ominfra-0.0.0.dev171}/ominfra/manage/commands/__init__.py +0 -0
- {ominfra-0.0.0.dev169 → ominfra-0.0.0.dev171}/ominfra/manage/commands/base.py +0 -0
- {ominfra-0.0.0.dev169 → ominfra-0.0.0.dev171}/ominfra/manage/commands/inject.py +0 -0
- {ominfra-0.0.0.dev169 → ominfra-0.0.0.dev171}/ominfra/manage/commands/local.py +0 -0
- {ominfra-0.0.0.dev169 → ominfra-0.0.0.dev171}/ominfra/manage/commands/marshal.py +0 -0
- {ominfra-0.0.0.dev169 → ominfra-0.0.0.dev171}/ominfra/manage/commands/ping.py +0 -0
- {ominfra-0.0.0.dev169 → ominfra-0.0.0.dev171}/ominfra/manage/commands/subprocess.py +0 -0
- {ominfra-0.0.0.dev169 → ominfra-0.0.0.dev171}/ominfra/manage/commands/types.py +0 -0
- {ominfra-0.0.0.dev169 → ominfra-0.0.0.dev171}/ominfra/manage/config.py +0 -0
- {ominfra-0.0.0.dev169 → ominfra-0.0.0.dev171}/ominfra/manage/deploy/__init__.py +0 -0
- {ominfra-0.0.0.dev169 → ominfra-0.0.0.dev171}/ominfra/manage/deploy/commands.py +0 -0
- {ominfra-0.0.0.dev169 → ominfra-0.0.0.dev171}/ominfra/manage/deploy/conf.py +0 -0
- {ominfra-0.0.0.dev169 → ominfra-0.0.0.dev171}/ominfra/manage/deploy/config.py +0 -0
- {ominfra-0.0.0.dev169 → ominfra-0.0.0.dev171}/ominfra/manage/deploy/deploy.py +0 -0
- {ominfra-0.0.0.dev169 → ominfra-0.0.0.dev171}/ominfra/manage/deploy/git.py +0 -0
- {ominfra-0.0.0.dev169 → ominfra-0.0.0.dev171}/ominfra/manage/deploy/inject.py +0 -0
- {ominfra-0.0.0.dev169 → ominfra-0.0.0.dev171}/ominfra/manage/deploy/interp.py +0 -0
- {ominfra-0.0.0.dev169 → ominfra-0.0.0.dev171}/ominfra/manage/deploy/paths/__init__.py +0 -0
- {ominfra-0.0.0.dev169 → ominfra-0.0.0.dev171}/ominfra/manage/deploy/paths/inject.py +0 -0
- {ominfra-0.0.0.dev169 → ominfra-0.0.0.dev171}/ominfra/manage/deploy/paths/manager.py +0 -0
- {ominfra-0.0.0.dev169 → ominfra-0.0.0.dev171}/ominfra/manage/deploy/paths/owners.py +0 -0
- {ominfra-0.0.0.dev169 → ominfra-0.0.0.dev171}/ominfra/manage/deploy/paths/paths.py +0 -0
- {ominfra-0.0.0.dev169 → ominfra-0.0.0.dev171}/ominfra/manage/deploy/specs.py +0 -0
- {ominfra-0.0.0.dev169 → ominfra-0.0.0.dev171}/ominfra/manage/deploy/tmp.py +0 -0
- {ominfra-0.0.0.dev169 → ominfra-0.0.0.dev171}/ominfra/manage/deploy/types.py +0 -0
- {ominfra-0.0.0.dev169 → ominfra-0.0.0.dev171}/ominfra/manage/deploy/venvs.py +0 -0
- {ominfra-0.0.0.dev169 → ominfra-0.0.0.dev171}/ominfra/manage/inject.py +0 -0
- {ominfra-0.0.0.dev169 → ominfra-0.0.0.dev171}/ominfra/manage/main.py +0 -0
- {ominfra-0.0.0.dev169 → ominfra-0.0.0.dev171}/ominfra/manage/marshal.py +0 -0
- {ominfra-0.0.0.dev169 → ominfra-0.0.0.dev171}/ominfra/manage/remote/__init__.py +0 -0
- {ominfra-0.0.0.dev169 → ominfra-0.0.0.dev171}/ominfra/manage/remote/_main.py +0 -0
- {ominfra-0.0.0.dev169 → ominfra-0.0.0.dev171}/ominfra/manage/remote/channel.py +0 -0
- {ominfra-0.0.0.dev169 → ominfra-0.0.0.dev171}/ominfra/manage/remote/config.py +0 -0
- {ominfra-0.0.0.dev169 → ominfra-0.0.0.dev171}/ominfra/manage/remote/connection.py +0 -0
- {ominfra-0.0.0.dev169 → ominfra-0.0.0.dev171}/ominfra/manage/remote/execution.py +0 -0
- {ominfra-0.0.0.dev169 → ominfra-0.0.0.dev171}/ominfra/manage/remote/inject.py +0 -0
- {ominfra-0.0.0.dev169 → ominfra-0.0.0.dev171}/ominfra/manage/remote/payload.py +0 -0
- {ominfra-0.0.0.dev169 → ominfra-0.0.0.dev171}/ominfra/manage/system/__init__.py +0 -0
- {ominfra-0.0.0.dev169 → ominfra-0.0.0.dev171}/ominfra/manage/system/commands.py +0 -0
- {ominfra-0.0.0.dev169 → ominfra-0.0.0.dev171}/ominfra/manage/system/config.py +0 -0
- {ominfra-0.0.0.dev169 → ominfra-0.0.0.dev171}/ominfra/manage/system/inject.py +0 -0
- {ominfra-0.0.0.dev169 → ominfra-0.0.0.dev171}/ominfra/manage/system/packages.py +0 -0
- {ominfra-0.0.0.dev169 → ominfra-0.0.0.dev171}/ominfra/manage/system/platforms.py +0 -0
- {ominfra-0.0.0.dev169 → ominfra-0.0.0.dev171}/ominfra/manage/targets/__init__.py +0 -0
- {ominfra-0.0.0.dev169 → ominfra-0.0.0.dev171}/ominfra/manage/targets/inject.py +0 -0
- {ominfra-0.0.0.dev169 → ominfra-0.0.0.dev171}/ominfra/scripts/__init__.py +0 -0
- {ominfra-0.0.0.dev169 → ominfra-0.0.0.dev171}/ominfra/scripts/journald2aws.py +0 -0
- {ominfra-0.0.0.dev169 → ominfra-0.0.0.dev171}/ominfra/scripts/supervisor.py +0 -0
- {ominfra-0.0.0.dev169 → ominfra-0.0.0.dev171}/ominfra/ssh.py +0 -0
- {ominfra-0.0.0.dev169 → ominfra-0.0.0.dev171}/ominfra/supervisor/LICENSE.txt +0 -0
- {ominfra-0.0.0.dev169 → ominfra-0.0.0.dev171}/ominfra/supervisor/__init__.py +0 -0
- {ominfra-0.0.0.dev169 → ominfra-0.0.0.dev171}/ominfra/supervisor/__main__.py +0 -0
- {ominfra-0.0.0.dev169 → ominfra-0.0.0.dev171}/ominfra/supervisor/configs.py +0 -0
- {ominfra-0.0.0.dev169 → ominfra-0.0.0.dev171}/ominfra/supervisor/dispatchers.py +0 -0
- {ominfra-0.0.0.dev169 → ominfra-0.0.0.dev171}/ominfra/supervisor/dispatchersimpl.py +0 -0
- {ominfra-0.0.0.dev169 → ominfra-0.0.0.dev171}/ominfra/supervisor/events.py +0 -0
- {ominfra-0.0.0.dev169 → ominfra-0.0.0.dev171}/ominfra/supervisor/exceptions.py +0 -0
- {ominfra-0.0.0.dev169 → ominfra-0.0.0.dev171}/ominfra/supervisor/groups.py +0 -0
- {ominfra-0.0.0.dev169 → ominfra-0.0.0.dev171}/ominfra/supervisor/groupsimpl.py +0 -0
- {ominfra-0.0.0.dev169 → ominfra-0.0.0.dev171}/ominfra/supervisor/http.py +0 -0
- {ominfra-0.0.0.dev169 → ominfra-0.0.0.dev171}/ominfra/supervisor/inject.py +0 -0
- {ominfra-0.0.0.dev169 → ominfra-0.0.0.dev171}/ominfra/supervisor/io.py +0 -0
- {ominfra-0.0.0.dev169 → ominfra-0.0.0.dev171}/ominfra/supervisor/main.py +0 -0
- {ominfra-0.0.0.dev169 → ominfra-0.0.0.dev171}/ominfra/supervisor/pipes.py +0 -0
- {ominfra-0.0.0.dev169 → ominfra-0.0.0.dev171}/ominfra/supervisor/privileges.py +0 -0
- {ominfra-0.0.0.dev169 → ominfra-0.0.0.dev171}/ominfra/supervisor/process.py +0 -0
- {ominfra-0.0.0.dev169 → ominfra-0.0.0.dev171}/ominfra/supervisor/processimpl.py +0 -0
- {ominfra-0.0.0.dev169 → ominfra-0.0.0.dev171}/ominfra/supervisor/setup.py +0 -0
- {ominfra-0.0.0.dev169 → ominfra-0.0.0.dev171}/ominfra/supervisor/setupimpl.py +0 -0
- {ominfra-0.0.0.dev169 → ominfra-0.0.0.dev171}/ominfra/supervisor/signals.py +0 -0
- {ominfra-0.0.0.dev169 → ominfra-0.0.0.dev171}/ominfra/supervisor/spawning.py +0 -0
- {ominfra-0.0.0.dev169 → ominfra-0.0.0.dev171}/ominfra/supervisor/spawningimpl.py +0 -0
- {ominfra-0.0.0.dev169 → ominfra-0.0.0.dev171}/ominfra/supervisor/states.py +0 -0
- {ominfra-0.0.0.dev169 → ominfra-0.0.0.dev171}/ominfra/supervisor/supervisor.py +0 -0
- {ominfra-0.0.0.dev169 → ominfra-0.0.0.dev171}/ominfra/supervisor/types.py +0 -0
- {ominfra-0.0.0.dev169 → ominfra-0.0.0.dev171}/ominfra/supervisor/utils/__init__.py +0 -0
- {ominfra-0.0.0.dev169 → ominfra-0.0.0.dev171}/ominfra/supervisor/utils/collections.py +0 -0
- {ominfra-0.0.0.dev169 → ominfra-0.0.0.dev171}/ominfra/supervisor/utils/diag.py +0 -0
- {ominfra-0.0.0.dev169 → ominfra-0.0.0.dev171}/ominfra/supervisor/utils/fds.py +0 -0
- {ominfra-0.0.0.dev169 → ominfra-0.0.0.dev171}/ominfra/supervisor/utils/fs.py +0 -0
- {ominfra-0.0.0.dev169 → ominfra-0.0.0.dev171}/ominfra/supervisor/utils/os.py +0 -0
- {ominfra-0.0.0.dev169 → ominfra-0.0.0.dev171}/ominfra/supervisor/utils/ostypes.py +0 -0
- {ominfra-0.0.0.dev169 → ominfra-0.0.0.dev171}/ominfra/supervisor/utils/signals.py +0 -0
- {ominfra-0.0.0.dev169 → ominfra-0.0.0.dev171}/ominfra/supervisor/utils/strings.py +0 -0
- {ominfra-0.0.0.dev169 → ominfra-0.0.0.dev171}/ominfra/supervisor/utils/users.py +0 -0
- {ominfra-0.0.0.dev169 → ominfra-0.0.0.dev171}/ominfra/tailscale/__init__.py +0 -0
- {ominfra-0.0.0.dev169 → ominfra-0.0.0.dev171}/ominfra/tailscale/api.py +0 -0
- {ominfra-0.0.0.dev169 → ominfra-0.0.0.dev171}/ominfra/tailscale/cli.py +0 -0
- {ominfra-0.0.0.dev169 → ominfra-0.0.0.dev171}/ominfra/threadworkers.py +0 -0
- {ominfra-0.0.0.dev169 → ominfra-0.0.0.dev171}/ominfra/tools/__init__.py +0 -0
- {ominfra-0.0.0.dev169 → ominfra-0.0.0.dev171}/ominfra/tools/listresources.py +0 -0
- {ominfra-0.0.0.dev169 → ominfra-0.0.0.dev171}/ominfra.egg-info/dependency_links.txt +0 -0
- {ominfra-0.0.0.dev169 → ominfra-0.0.0.dev171}/ominfra.egg-info/entry_points.txt +0 -0
- {ominfra-0.0.0.dev169 → ominfra-0.0.0.dev171}/ominfra.egg-info/top_level.txt +0 -0
- {ominfra-0.0.0.dev169 → 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.
|
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.
|
16
|
-
Requires-Dist: omlish==0.0.0.
|
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 =
|
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] =
|
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
|
263
|
-
f'_pyremote_bootstrap_main({context_name
|
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
|
1633
|
-
f'_pyremote_bootstrap_main({context_name
|
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] =
|
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 =
|
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.
|
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.
|
16
|
-
Requires-Dist: omlish==0.0.0.
|
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
|
@@ -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.
|
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.
|
26
|
-
'omlish == 0.0.0.
|
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
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|