runspec-linux-core 0.6.1__tar.gz → 0.7.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {runspec_linux_core-0.6.1 → runspec_linux_core-0.7.0}/CHANGELOG.md +33 -0
- {runspec_linux_core-0.6.1 → runspec_linux_core-0.7.0}/PKG-INFO +1 -1
- {runspec_linux_core-0.6.1 → runspec_linux_core-0.7.0}/pyproject.toml +1 -1
- {runspec_linux_core-0.6.1 → runspec_linux_core-0.7.0}/runspec_linux_core/__init__.py +2 -1
- {runspec_linux_core-0.6.1 → runspec_linux_core-0.7.0}/runspec_linux_core/venvs.py +92 -12
- {runspec_linux_core-0.6.1 → runspec_linux_core-0.7.0}/tests/test_venvs.py +113 -4
- {runspec_linux_core-0.6.1 → runspec_linux_core-0.7.0}/.gitignore +0 -0
- {runspec_linux_core-0.6.1 → runspec_linux_core-0.7.0}/runspec_linux_core/_paths.py +0 -0
- {runspec_linux_core-0.6.1 → runspec_linux_core-0.7.0}/runspec_linux_core/become.py +0 -0
- {runspec_linux_core-0.6.1 → runspec_linux_core-0.7.0}/runspec_linux_core/commands.py +0 -0
- {runspec_linux_core-0.6.1 → runspec_linux_core-0.7.0}/runspec_linux_core/containers.py +0 -0
- {runspec_linux_core-0.6.1 → runspec_linux_core-0.7.0}/runspec_linux_core/cron.py +0 -0
- {runspec_linux_core-0.6.1 → runspec_linux_core-0.7.0}/runspec_linux_core/errors.py +0 -0
- {runspec_linux_core-0.6.1 → runspec_linux_core-0.7.0}/runspec_linux_core/files.py +0 -0
- {runspec_linux_core-0.6.1 → runspec_linux_core-0.7.0}/runspec_linux_core/investigate.py +0 -0
- {runspec_linux_core-0.6.1 → runspec_linux_core-0.7.0}/runspec_linux_core/logs.py +0 -0
- {runspec_linux_core-0.6.1 → runspec_linux_core-0.7.0}/runspec_linux_core/nc.py +0 -0
- {runspec_linux_core-0.6.1 → runspec_linux_core-0.7.0}/runspec_linux_core/network.py +0 -0
- {runspec_linux_core-0.6.1 → runspec_linux_core-0.7.0}/runspec_linux_core/packages.py +0 -0
- {runspec_linux_core-0.6.1 → runspec_linux_core-0.7.0}/runspec_linux_core/perf.py +0 -0
- {runspec_linux_core-0.6.1 → runspec_linux_core-0.7.0}/runspec_linux_core/power.py +0 -0
- {runspec_linux_core-0.6.1 → runspec_linux_core-0.7.0}/runspec_linux_core/security.py +0 -0
- {runspec_linux_core-0.6.1 → runspec_linux_core-0.7.0}/runspec_linux_core/services.py +0 -0
- {runspec_linux_core-0.6.1 → runspec_linux_core-0.7.0}/runspec_linux_core/sudoers.py +0 -0
- {runspec_linux_core-0.6.1 → runspec_linux_core-0.7.0}/runspec_linux_core/system.py +0 -0
- {runspec_linux_core-0.6.1 → runspec_linux_core-0.7.0}/tests/__init__.py +0 -0
- {runspec_linux_core-0.6.1 → runspec_linux_core-0.7.0}/tests/test_become.py +0 -0
- {runspec_linux_core-0.6.1 → runspec_linux_core-0.7.0}/tests/test_commands.py +0 -0
- {runspec_linux_core-0.6.1 → runspec_linux_core-0.7.0}/tests/test_containers_runas.py +0 -0
- {runspec_linux_core-0.6.1 → runspec_linux_core-0.7.0}/tests/test_cron.py +0 -0
- {runspec_linux_core-0.6.1 → runspec_linux_core-0.7.0}/tests/test_investigate.py +0 -0
- {runspec_linux_core-0.6.1 → runspec_linux_core-0.7.0}/tests/test_logs_services_runas.py +0 -0
- {runspec_linux_core-0.6.1 → runspec_linux_core-0.7.0}/tests/test_nc_send.py +0 -0
- {runspec_linux_core-0.6.1 → runspec_linux_core-0.7.0}/tests/test_network.py +0 -0
- {runspec_linux_core-0.6.1 → runspec_linux_core-0.7.0}/tests/test_packages.py +0 -0
- {runspec_linux_core-0.6.1 → runspec_linux_core-0.7.0}/tests/test_paths.py +0 -0
- {runspec_linux_core-0.6.1 → runspec_linux_core-0.7.0}/tests/test_perf.py +0 -0
- {runspec_linux_core-0.6.1 → runspec_linux_core-0.7.0}/tests/test_power.py +0 -0
- {runspec_linux_core-0.6.1 → runspec_linux_core-0.7.0}/tests/test_preconditions.py +0 -0
- {runspec_linux_core-0.6.1 → runspec_linux_core-0.7.0}/tests/test_sudoers.py +0 -0
|
@@ -1,5 +1,38 @@
|
|
|
1
1
|
# runspec-linux-core Changelog
|
|
2
2
|
|
|
3
|
+
## [0.7.0] — 2026-09-25
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
|
|
7
|
+
- **`share_venv_logs(venv, *, group=None, run_as=None, become_method="sudo")`** —
|
|
8
|
+
the repair path for a venv **not** built by `create-venv --group` (hand-made or
|
|
9
|
+
legacy). Sets `<venv>/logs` to `2775` (setgid + group-writable) so new files —
|
|
10
|
+
including the per-venv runs index `runspec_runs.db` and the audit-log files —
|
|
11
|
+
inherit the directory's group and can be written across a shared console group,
|
|
12
|
+
and, with `group`, `chgrp`s the `logs/` **directory** to it. The chgrp is on the
|
|
13
|
+
directory only (not `-R`): `logs/` already holds per-run files owned by *other*
|
|
14
|
+
users (a `run_as="root"` runnable leaves root-owned logs there) which a non-root
|
|
15
|
+
caller can't chgrp, and the setgid bit means only new files need the group. This
|
|
16
|
+
is exactly what `create_venv` does at build time, applied to an existing venv
|
|
17
|
+
without recreating it. Raises `ValueError` for a bad group name or a `<venv>`
|
|
18
|
+
that isn't a directory, `CommandError` on a failed step. Backs the new
|
|
19
|
+
`share-venv-logs` runnable in `runspec-linux`.
|
|
20
|
+
|
|
21
|
+
### Fixed
|
|
22
|
+
|
|
23
|
+
- **`install_into_venv` regression (0.6.1): `chmod: … Operation not permitted` on
|
|
24
|
+
a `--symlinks` venv's `bin/python*`.** 0.6.1 replaced the `chmod -R` normalise
|
|
25
|
+
sweep with `find … -exec chmod {} +` to prune the `logs/` dir. But `find -exec
|
|
26
|
+
chmod` **dereferences a symlink argument**, and a venv built with `--symlinks`
|
|
27
|
+
(what `create_venv` always does) has `bin/python`, `bin/python3`,
|
|
28
|
+
`bin/python3.X` pointing at the **root-owned system interpreter outside the
|
|
29
|
+
venv** — so the sweep tried to `chmod /usr/bin/python3.X` and failed for a
|
|
30
|
+
non-root caller (or, under sudo, would have silently repermissioned the system
|
|
31
|
+
Python). `chmod -R` skipped symlinks for free; the `find` sweep now prunes them
|
|
32
|
+
explicitly too (`-type l -prune`), matching the old behaviour. A symlink's own
|
|
33
|
+
mode is unused on Linux, so nothing is lost. The `logs/` prune from 0.6.1 is
|
|
34
|
+
unchanged. No API change.
|
|
35
|
+
|
|
3
36
|
## [0.6.1] — 2026-09-17
|
|
4
37
|
|
|
5
38
|
### Fixed
|
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "runspec-linux-core"
|
|
7
|
-
version = "0.
|
|
7
|
+
version = "0.7.0"
|
|
8
8
|
requires-python = ">=3.10"
|
|
9
9
|
description = "Pure-Python Linux system-admin helpers — the importable core behind runspec-linux (no runspec dependency, no runnables)"
|
|
10
10
|
dependencies = []
|
|
@@ -63,7 +63,7 @@ from runspec_linux_core.security import last_logins, who
|
|
|
63
63
|
from runspec_linux_core.services import check_service, list_services, restart_service
|
|
64
64
|
from runspec_linux_core.sudoers import enable_passwordless_sudo
|
|
65
65
|
from runspec_linux_core.system import check_memory, disk_usage, list_processes, system_info
|
|
66
|
-
from runspec_linux_core.venvs import configure_pip, create_venv, install_into_venv
|
|
66
|
+
from runspec_linux_core.venvs import configure_pip, create_venv, install_into_venv, share_venv_logs
|
|
67
67
|
|
|
68
68
|
__all__ = [
|
|
69
69
|
# errors
|
|
@@ -139,6 +139,7 @@ __all__ = [
|
|
|
139
139
|
"create_venv",
|
|
140
140
|
"install_into_venv",
|
|
141
141
|
"configure_pip",
|
|
142
|
+
"share_venv_logs",
|
|
142
143
|
# tcp
|
|
143
144
|
"nc_send",
|
|
144
145
|
# commands
|
|
@@ -115,6 +115,54 @@ def create_venv(
|
|
|
115
115
|
}
|
|
116
116
|
|
|
117
117
|
|
|
118
|
+
def share_venv_logs(
|
|
119
|
+
venv: str,
|
|
120
|
+
*,
|
|
121
|
+
group: str | None = None,
|
|
122
|
+
run_as: str | None = None,
|
|
123
|
+
become_method: str = "sudo",
|
|
124
|
+
) -> dict:
|
|
125
|
+
"""Make an existing venv's ``logs/`` dir shared — the repair path for a venv
|
|
126
|
+
**not** built by ``create-venv --group`` (hand-made or legacy).
|
|
127
|
+
|
|
128
|
+
Sets ``<venv>/logs`` to ``2775`` (setgid + group-writable) so new files —
|
|
129
|
+
including the per-venv runs index ``runspec_runs.db`` and the audit-log
|
|
130
|
+
files — inherit the directory's group and can be written by every console /
|
|
131
|
+
service account in a shared group. When ``group`` is given it also
|
|
132
|
+
``chgrp``s the ``logs/`` **directory** to it.
|
|
133
|
+
|
|
134
|
+
The chgrp is on the directory only, **not** ``-R``: ``logs/`` already holds
|
|
135
|
+
per-run files owned by *other* users (a ``run_as="root"`` runnable leaves
|
|
136
|
+
root-owned logs there), which a non-root caller can't chgrp — and the setgid
|
|
137
|
+
bit means only *new* files need the group anyway (the DB is created fresh).
|
|
138
|
+
``create-venv --group`` does exactly this at build time; this brings a
|
|
139
|
+
pre-existing venv up to the same state without recreating it. With
|
|
140
|
+
``run_as`` every step runs as that user (the venv's owner).
|
|
141
|
+
|
|
142
|
+
Raises ValueError if ``group`` is malformed or ``<venv>`` isn't an existing
|
|
143
|
+
directory, CommandError on a failed step.
|
|
144
|
+
"""
|
|
145
|
+
if group is not None:
|
|
146
|
+
_validate_name(group, "group")
|
|
147
|
+
if not _dir_exists_as(venv, run_as, become_method):
|
|
148
|
+
raise ValueError(f"not a directory: {venv!r} (point at an existing venv)")
|
|
149
|
+
|
|
150
|
+
logs_dir = os.path.join(venv, "logs")
|
|
151
|
+
_check(_run(build_become(["mkdir", "-p", logs_dir], run_as, become_method)), "logs dir", run_as)
|
|
152
|
+
if group:
|
|
153
|
+
_check(_run(build_become(["chgrp", group, logs_dir], run_as, become_method)), "chgrp logs", run_as)
|
|
154
|
+
_check(_run(build_become(["chmod", "2775", logs_dir], run_as, become_method)), "chmod logs", run_as)
|
|
155
|
+
|
|
156
|
+
return {
|
|
157
|
+
"venv": venv,
|
|
158
|
+
"logs_dir": logs_dir,
|
|
159
|
+
"group": group or "",
|
|
160
|
+
"run_as": run_as or "",
|
|
161
|
+
"mode": "2775",
|
|
162
|
+
"shared": True,
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
|
|
118
166
|
def install_into_venv(
|
|
119
167
|
venv: str,
|
|
120
168
|
packages: str = "",
|
|
@@ -140,11 +188,15 @@ def install_into_venv(
|
|
|
140
188
|
regardless of the caller's umask — a ``sudo`` upgrade under a PAM-set ``027``
|
|
141
189
|
login umask otherwise lands site-packages entries at ``750`` and hides the
|
|
142
190
|
packs from ``runspec local`` / ``importlib.metadata`` for anyone but the
|
|
143
|
-
owner. The sweep **prunes** ``<venv>/logs
|
|
144
|
-
|
|
145
|
-
leaves root-owned logs there
|
|
146
|
-
|
|
147
|
-
|
|
191
|
+
owner. The sweep **prunes** two things: ``<venv>/logs`` (the group-shared
|
|
192
|
+
audit dir, which holds per-run log files owned by other users — a
|
|
193
|
+
``run_as="root"`` runnable leaves root-owned logs there — so its ``2775``
|
|
194
|
+
setgid mode is left intact and restored afterwards as a guard), and
|
|
195
|
+
**symlinks** (a ``--symlinks`` venv's ``bin/python*`` point at the root-owned
|
|
196
|
+
system interpreter *outside* the venv; ``find -exec chmod`` would dereference
|
|
197
|
+
them and fail — or, under sudo, silently repermission the system Python —
|
|
198
|
+
where ``chmod -R`` skipped symlinks for free). A symlink's own mode is unused
|
|
199
|
+
on Linux, so pruning them changes nothing.
|
|
148
200
|
"""
|
|
149
201
|
from runspec_linux_core.packages import _validate_packages
|
|
150
202
|
|
|
@@ -179,17 +231,40 @@ def install_into_venv(
|
|
|
179
231
|
# Normalise pip's freshly-created site-packages entries to u=rwX,go=rX (the
|
|
180
232
|
# same step create_venv runs) so a sudo install under a restrictive login
|
|
181
233
|
# umask can't land them at 750 and hide the packs from `runspec local` /
|
|
182
|
-
# importlib.metadata.
|
|
183
|
-
# (2775) audit dir
|
|
184
|
-
# run_as="root" runnable
|
|
185
|
-
# logs there, so a
|
|
186
|
-
#
|
|
187
|
-
#
|
|
234
|
+
# importlib.metadata. Two subtrees are pruned:
|
|
235
|
+
# * <venv>/logs — the group-shared (2775) audit dir, which accrues per-run
|
|
236
|
+
# log files owned by *other* users (a run_as="root" runnable such as
|
|
237
|
+
# list-upgrades / upgrade-packages leaves root-owned logs there), so a
|
|
238
|
+
# non-root caller's chmod on them dies with "Operation not permitted".
|
|
239
|
+
# * symlinks (-type l) — a `--symlinks` venv's bin/python* point at the
|
|
240
|
+
# system interpreter (root-owned, *outside* the venv). `find -exec chmod`
|
|
241
|
+
# dereferences a symlink argument, so without this it would chmod the
|
|
242
|
+
# system python: "Operation not permitted" as a normal user, or a silent
|
|
243
|
+
# repermission of /usr/bin/python* under sudo. `chmod -R` skipped symlinks
|
|
244
|
+
# for free; the find sweep must skip them explicitly to match. A symlink's
|
|
245
|
+
# own mode is irrelevant on Linux, so nothing is lost.
|
|
246
|
+
# The logs dir keeps its 2775 mode (restored below as a belt-and-braces guard).
|
|
188
247
|
logs_dir = os.path.join(venv, "logs")
|
|
189
248
|
_check(
|
|
190
249
|
_run(
|
|
191
250
|
build_become(
|
|
192
|
-
[
|
|
251
|
+
[
|
|
252
|
+
"find",
|
|
253
|
+
venv,
|
|
254
|
+
"-path",
|
|
255
|
+
logs_dir,
|
|
256
|
+
"-prune",
|
|
257
|
+
"-o",
|
|
258
|
+
"-type",
|
|
259
|
+
"l",
|
|
260
|
+
"-prune",
|
|
261
|
+
"-o",
|
|
262
|
+
"-exec",
|
|
263
|
+
"chmod",
|
|
264
|
+
"u=rwX,go=rX",
|
|
265
|
+
"{}",
|
|
266
|
+
"+",
|
|
267
|
+
],
|
|
193
268
|
run_as,
|
|
194
269
|
become_method,
|
|
195
270
|
)
|
|
@@ -317,3 +392,8 @@ def configure_pip(
|
|
|
317
392
|
def _path_exists_as(path: str, run_as: str | None, become_method: str) -> bool:
|
|
318
393
|
"""True if ``path`` exists from the run-as user's view (``test -e``)."""
|
|
319
394
|
return _run(build_become(["test", "-e", path], run_as, become_method)).returncode == 0
|
|
395
|
+
|
|
396
|
+
|
|
397
|
+
def _dir_exists_as(path: str, run_as: str | None, become_method: str) -> bool:
|
|
398
|
+
"""True if ``path`` is a directory from the run-as user's view (``test -d``)."""
|
|
399
|
+
return _run(build_become(["test", "-d", path], run_as, become_method)).returncode == 0
|
|
@@ -101,6 +101,93 @@ def test_create_venv_raises_on_failure(monkeypatch: pytest.MonkeyPatch) -> None:
|
|
|
101
101
|
venvs.create_venv(sys.executable, "/opt/v1")
|
|
102
102
|
|
|
103
103
|
|
|
104
|
+
# ── share_venv_logs (repair a legacy venv's logs/ perms) ───────────────────────
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
def test_share_venv_logs_builds_commands(monkeypatch: pytest.MonkeyPatch) -> None:
|
|
108
|
+
calls = _record(monkeypatch) # default stub: `test -d` returns 0
|
|
109
|
+
|
|
110
|
+
result = venvs.share_venv_logs("/opt/v1")
|
|
111
|
+
|
|
112
|
+
# Verify the venv is a dir, then mkdir -p logs, then chmod 2775 logs. No chgrp
|
|
113
|
+
# without a group; the chmod is the last step.
|
|
114
|
+
assert calls == [
|
|
115
|
+
["test", "-d", "/opt/v1"],
|
|
116
|
+
["mkdir", "-p", "/opt/v1/logs"],
|
|
117
|
+
["chmod", "2775", "/opt/v1/logs"],
|
|
118
|
+
]
|
|
119
|
+
assert result == {
|
|
120
|
+
"venv": "/opt/v1",
|
|
121
|
+
"logs_dir": "/opt/v1/logs",
|
|
122
|
+
"group": "",
|
|
123
|
+
"run_as": "",
|
|
124
|
+
"mode": "2775",
|
|
125
|
+
"shared": True,
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
def test_share_venv_logs_with_group_chgrps_dir_only(monkeypatch: pytest.MonkeyPatch) -> None:
|
|
130
|
+
calls = _record(monkeypatch)
|
|
131
|
+
|
|
132
|
+
venvs.share_venv_logs("/opt/v1", group="consoles")
|
|
133
|
+
|
|
134
|
+
# chgrp targets the logs *directory* only — never `-R` (logs/ holds per-run
|
|
135
|
+
# files owned by other users a non-root caller can't chgrp; setgid covers new
|
|
136
|
+
# files). Order: test -d, mkdir, chgrp, chmod.
|
|
137
|
+
assert calls == [
|
|
138
|
+
["test", "-d", "/opt/v1"],
|
|
139
|
+
["mkdir", "-p", "/opt/v1/logs"],
|
|
140
|
+
["chgrp", "consoles", "/opt/v1/logs"],
|
|
141
|
+
["chmod", "2775", "/opt/v1/logs"],
|
|
142
|
+
]
|
|
143
|
+
assert not any(c[:2] == ["chgrp", "-R"] for c in calls)
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
def test_share_venv_logs_run_as_escalates_every_step(monkeypatch: pytest.MonkeyPatch) -> None:
|
|
147
|
+
calls = _record(monkeypatch)
|
|
148
|
+
|
|
149
|
+
venvs.share_venv_logs("/opt/v1", group="consoles", run_as="svc")
|
|
150
|
+
|
|
151
|
+
for cmd in calls:
|
|
152
|
+
assert cmd[:4] == ["sudo", "-n", "-u", "svc"]
|
|
153
|
+
assert ["sudo", "-n", "-u", "svc", "chgrp", "consoles", "/opt/v1/logs"] in calls
|
|
154
|
+
assert ["sudo", "-n", "-u", "svc", "chmod", "2775", "/opt/v1/logs"] in calls
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
def test_share_venv_logs_rejects_missing_venv(monkeypatch: pytest.MonkeyPatch) -> None:
|
|
158
|
+
calls: list[list[str]] = []
|
|
159
|
+
|
|
160
|
+
def fake_run(argv: list[str]) -> subprocess.CompletedProcess:
|
|
161
|
+
calls.append(argv)
|
|
162
|
+
return _completed(returncode=1) # `test -d` fails → not a directory
|
|
163
|
+
|
|
164
|
+
monkeypatch.setattr(venvs, "_run", fake_run)
|
|
165
|
+
|
|
166
|
+
with pytest.raises(ValueError):
|
|
167
|
+
venvs.share_venv_logs("/opt/nope")
|
|
168
|
+
# Bailed after the probe — no mkdir/chmod attempted.
|
|
169
|
+
assert calls == [["test", "-d", "/opt/nope"]]
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
def test_share_venv_logs_rejects_bad_group(monkeypatch: pytest.MonkeyPatch) -> None:
|
|
173
|
+
calls = _record(monkeypatch)
|
|
174
|
+
with pytest.raises(ValueError):
|
|
175
|
+
venvs.share_venv_logs("/opt/v1", group="bad group")
|
|
176
|
+
# Group is validated before any subprocess runs (not even the `test -d` probe).
|
|
177
|
+
assert calls == []
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
def test_share_venv_logs_raises_on_failure(monkeypatch: pytest.MonkeyPatch) -> None:
|
|
181
|
+
def fake_run(argv: list[str]) -> subprocess.CompletedProcess:
|
|
182
|
+
if argv[:2] == ["test", "-d"]:
|
|
183
|
+
return _completed(returncode=0) # dir exists…
|
|
184
|
+
return _completed("", returncode=1, stderr="boom") # …but a step fails
|
|
185
|
+
|
|
186
|
+
monkeypatch.setattr(venvs, "_run", fake_run)
|
|
187
|
+
with pytest.raises(CommandError):
|
|
188
|
+
venvs.share_venv_logs("/opt/v1")
|
|
189
|
+
|
|
190
|
+
|
|
104
191
|
# ── install_into_venv ─────────────────────────────────────────────────────────
|
|
105
192
|
|
|
106
193
|
|
|
@@ -148,9 +235,11 @@ def test_install_into_venv_normalises_perms(tmp_path, monkeypatch: pytest.Monkey
|
|
|
148
235
|
|
|
149
236
|
venvs.install_into_venv(venv, "httpx", upgrade=True, run_as="svc")
|
|
150
237
|
|
|
151
|
-
# The normalise sweep prunes <venv>/logs
|
|
152
|
-
#
|
|
153
|
-
#
|
|
238
|
+
# The normalise sweep prunes <venv>/logs (root-owned per-run logs a
|
|
239
|
+
# run_as="root" runnable leaves there) AND symlinks (a --symlinks venv's
|
|
240
|
+
# bin/python* point at the root-owned system interpreter; find -exec chmod
|
|
241
|
+
# would dereference and fail / repermission it). Otherwise: chmod "Operation
|
|
242
|
+
# not permitted", and the install reports failure.
|
|
154
243
|
assert [
|
|
155
244
|
"sudo",
|
|
156
245
|
"-n",
|
|
@@ -162,6 +251,10 @@ def test_install_into_venv_normalises_perms(tmp_path, monkeypatch: pytest.Monkey
|
|
|
162
251
|
f"{venv}/logs",
|
|
163
252
|
"-prune",
|
|
164
253
|
"-o",
|
|
254
|
+
"-type",
|
|
255
|
+
"l",
|
|
256
|
+
"-prune",
|
|
257
|
+
"-o",
|
|
165
258
|
"-exec",
|
|
166
259
|
"chmod",
|
|
167
260
|
"u=rwX,go=rX",
|
|
@@ -198,7 +291,23 @@ def test_install_into_venv_skips_logs_restore_when_absent(tmp_path, monkeypatch:
|
|
|
198
291
|
|
|
199
292
|
venvs.install_into_venv(venv, "httpx")
|
|
200
293
|
|
|
201
|
-
assert [
|
|
294
|
+
assert [
|
|
295
|
+
"find",
|
|
296
|
+
venv,
|
|
297
|
+
"-path",
|
|
298
|
+
f"{venv}/logs",
|
|
299
|
+
"-prune",
|
|
300
|
+
"-o",
|
|
301
|
+
"-type",
|
|
302
|
+
"l",
|
|
303
|
+
"-prune",
|
|
304
|
+
"-o",
|
|
305
|
+
"-exec",
|
|
306
|
+
"chmod",
|
|
307
|
+
"u=rwX,go=rX",
|
|
308
|
+
"{}",
|
|
309
|
+
"+",
|
|
310
|
+
] in calls
|
|
202
311
|
assert ["test", "-d", f"{venv}/logs"] in calls
|
|
203
312
|
assert not any(c[:2] == ["chmod", "2775"] for c in calls)
|
|
204
313
|
|
|
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
|