Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 6 additions & 7 deletions src/dda/tools/uv.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,16 +39,15 @@ def execution_context(self, command: list[str]) -> Generator[ExecutionContext, N

import shutil

from dda.utils.fs import Path
from dda.utils.fs import Path, temp_directory

path = Path(self.path)
safe_path = path.with_stem(f"{path.stem}-{path.id}")
shutil.copy2(self.path, safe_path)

try:
safe_name = path.with_stem(f"{path.stem}-{path.id}").name
with temp_directory() as temp_dir:
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Avoid executing uv from default temp mount

execution_context now always runs uv from a file copied into temp_directory(), which uses the system temp location by default. In hardened Linux environments where that temp filesystem is mounted noexec, launching this copied binary fails with EACCES/permission denied, so all uv-backed commands break even though the installed uv is valid. The previous implementation executed from the original install filesystem, so this is a regression for those hosts; pick an exec-capable writable directory (or fallback strategy) instead of unconditionally using the default temp dir.

Useful? React with 👍 / 👎.

# Always use a temporary directory to avoid permission issues.
safe_path = temp_dir / safe_name
shutil.copy2(self.path, safe_path)
yield ExecutionContext(command=[str(safe_path), *command], env_vars={})
finally:
safe_path.unlink()

@cached_property
def path(self) -> str | None:
Expand Down
68 changes: 36 additions & 32 deletions tests/cli/inv/test_inv.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

import subprocess
import sys
from pathlib import Path
from unittest import mock

import pytest
Expand Down Expand Up @@ -33,38 +34,41 @@ def test_default(dda, helpers, temp_dir, uv_on_path, mocker):
),
)

expected_path = str(uv_on_path.with_stem(f"{uv_on_path.stem}-{uv_on_path.id}"))
assert subprocess_run.call_args_list == [
mock.call(
[
expected_path,
"venv",
str(temp_dir / "data" / "venvs" / "legacy"),
"--seed",
"--python",
sys.executable,
],
encoding="utf-8",
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
),
mock.call(
[
expected_path,
"sync",
"--frozen",
"--no-install-project",
"--inexact",
"--only-group",
"legacy-tasks",
],
encoding="utf-8",
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
cwd=mock.ANY,
env=mock.ANY,
),
]
expected_name = uv_on_path.with_stem(f"{uv_on_path.stem}-{uv_on_path.id}").name
first_call, second_call = subprocess_run.call_args_list

assert Path(first_call.args[0][0]).name == expected_name
assert first_call == mock.call(
[
mock.ANY,
"venv",
str(temp_dir / "data" / "venvs" / "legacy"),
"--seed",
"--python",
sys.executable,
],
encoding="utf-8",
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
)

assert Path(second_call.args[0][0]).name == expected_name
assert second_call == mock.call(
[
mock.ANY,
"sync",
"--frozen",
"--no-install-project",
"--inexact",
"--only-group",
"legacy-tasks",
],
encoding="utf-8",
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
cwd=mock.ANY,
env=mock.ANY,
)
assert exit_with.call_args_list == [
mock.call(
[
Expand Down
36 changes: 19 additions & 17 deletions tests/cli/test_dynamic.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import os
import subprocess
import sys
from pathlib import Path
from unittest import mock


Expand Down Expand Up @@ -108,20 +109,21 @@ def cmd(app):
),
)

expected_path = str(uv_on_path.with_stem(f"{uv_on_path.stem}-{uv_on_path.id}"))
assert subprocess_run.call_args_list == [
mock.call(
[
expected_path,
"pip",
"install",
"--python",
sys.executable,
"-r",
mocker.ANY,
],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
encoding="utf-8",
),
]
expected_name = uv_on_path.with_stem(f"{uv_on_path.stem}-{uv_on_path.id}").name
(call,) = subprocess_run.call_args_list

assert Path(call.args[0][0]).name == expected_name
assert call == mock.call(
[
mocker.ANY,
"pip",
"install",
"--python",
sys.executable,
"-r",
mocker.ANY,
],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
encoding="utf-8",
)
Loading