summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorBernát Gábor <bgabor8@bloomberg.net>2021-08-02 16:28:20 +0100
committerGitHub <noreply@github.com>2021-08-02 16:28:20 +0100
commit5e33fda1a40ffb4973de3d607a572891eb3cb2d2 (patch)
treeff7eb657564d3be61bf77387fa460b6fdbf04139 /src
parent7e1950cc4521c3ccb9b7b0bb7a357bc024f51725 (diff)
downloadtox-git-5e33fda1a40ffb4973de3d607a572891eb3cb2d2.tar.gz
Add support for allowlist_externals (#2134)
* Add support for allowlist_externals Signed-off-by: Bernát Gábor <gaborjbernat@gmail.com> * Apply suggestions from code review Co-authored-by: Jürgen Gmach <juergen.gmach@googlemail.com> Signed-off-by: Bernát Gábor <bgabor8@bloomberg.net> Co-authored-by: Jürgen Gmach <juergen.gmach@googlemail.com>
Diffstat (limited to 'src')
-rw-r--r--src/tox/execute/local_sub_process/__init__.py15
-rw-r--r--src/tox/execute/request.py13
-rw-r--r--src/tox/tox_env/api.py14
-rw-r--r--src/tox/util/spinner.py2
4 files changed, 39 insertions, 5 deletions
diff --git a/src/tox/execute/local_sub_process/__init__.py b/src/tox/execute/local_sub_process/__init__.py
index 5fb21d4b..7b8177b3 100644
--- a/src/tox/execute/local_sub_process/__init__.py
+++ b/src/tox/execute/local_sub_process/__init__.py
@@ -1,4 +1,5 @@
"""Execute that runs on local file system via subprocess-es"""
+import fnmatch
import logging
import os
import shutil
@@ -8,6 +9,8 @@ from subprocess import DEVNULL, PIPE, TimeoutExpired
from types import TracebackType
from typing import TYPE_CHECKING, Any, Dict, Generator, List, Optional, Sequence, Tuple, Type
+from tox.tox_env.errors import Fail
+
from ..api import Execute, ExecuteInstance, ExecuteStatus
from ..request import ExecuteRequest, StdinSource
from ..stream import SyncWrite
@@ -176,10 +179,20 @@ class LocalSubProcessExecuteInstance(ExecuteInstance):
@property
def cmd(self) -> Sequence[str]:
if self._cmd is None:
- executable = shutil.which(self.request.cmd[0], path=self.request.env["PATH"])
+ base = self.request.cmd[0]
+ executable = shutil.which(base, path=self.request.env["PATH"])
if executable is None:
cmd = self.request.cmd # if failed to find leave as it is
else:
+ if self.request.allow is not None:
+ for allow in self.request.allow:
+ # 1. allow matches just the original name of the executable
+ # 2. allow matches the entire resolved path
+ if fnmatch.fnmatch(self.request.cmd[0], allow) or fnmatch.fnmatch(executable, allow):
+ break
+ else:
+ msg = f"{base} (resolves to {executable})" if base == executable else base
+ raise Fail(f"{msg} is not allowed, use allowlist_externals to allow it")
# else use expanded format
cmd = [executable, *self.request.cmd[1:]]
self._cmd = cmd
diff --git a/src/tox/execute/request.py b/src/tox/execute/request.py
index 8cccbf9a..91d2e996 100644
--- a/src/tox/execute/request.py
+++ b/src/tox/execute/request.py
@@ -2,7 +2,7 @@
import sys
from enum import Enum
from pathlib import Path
-from typing import Dict, List, Sequence, Union
+from typing import Dict, List, Optional, Sequence, Union
class StdinSource(Enum):
@@ -20,7 +20,13 @@ class ExecuteRequest:
"""Defines a commands execution request"""
def __init__(
- self, cmd: Sequence[Union[str, Path]], cwd: Path, env: Dict[str, str], stdin: StdinSource, run_id: str
+ self,
+ cmd: Sequence[Union[str, Path]],
+ cwd: Path,
+ env: Dict[str, str],
+ stdin: StdinSource,
+ run_id: str,
+ allow: Optional[List[str]] = None,
) -> None:
"""
Create a new execution request.
@@ -38,6 +44,9 @@ class ExecuteRequest:
self.env = env #: the environment variables to use
self.stdin = stdin #: the type of standard input interaction allowed
self.run_id = run_id #: an id to identify this run
+ if allow is not None and "*" in allow:
+ allow = None # if we allow everything we can just disable the check
+ self.allow = allow
@property
def shell_cmd(self) -> str:
diff --git a/src/tox/tox_env/api.py b/src/tox/tox_env/api.py
index 46709b23..83163df5 100644
--- a/src/tox/tox_env/api.py
+++ b/src/tox/tox_env/api.py
@@ -128,6 +128,12 @@ class ToxEnv(ABC):
default=False,
desc="always recreate virtual environment if this option is true, otherwise leave it up to tox",
)
+ self.conf.add_config(
+ keys=["allowlist_externals", "whitelist_externals"],
+ of_type=List[str],
+ default=[],
+ desc="external command glob to allow calling",
+ )
@property
def env_dir(self) -> Path:
@@ -293,6 +299,12 @@ class ToxEnv(ABC):
result = self._make_path()
self._env_vars["PATH"] = result
+ @property
+ def _allow_externals(self) -> List[str]:
+ result: List[str] = [f"{i}{os.sep}*" for i in self._paths]
+ result.extend(i.strip() for i in self.conf["allowlist_externals"])
+ return result
+
def _make_path(self) -> str:
values = dict.fromkeys(str(i) for i in self._paths)
values.update(dict.fromkeys(os.environ.get("PATH", "").split(os.pathsep)))
@@ -338,7 +350,7 @@ class ToxEnv(ABC):
cwd = self.core["tox_root"]
if show is None:
show = self.options.verbosity > 3
- request = ExecuteRequest(cmd, cwd, self._environment_variables, stdin, run_id)
+ request = ExecuteRequest(cmd, cwd, self._environment_variables, stdin, run_id, allow=self._allow_externals)
if _CWD == request.cwd:
repr_cwd = ""
else:
diff --git a/src/tox/util/spinner.py b/src/tox/util/spinner.py
index 78edc8fa..ca2dbb06 100644
--- a/src/tox/util/spinner.py
+++ b/src/tox/util/spinner.py
@@ -93,7 +93,7 @@ class Spinner:
self.render_frame()
self._stop_spinner = threading.Event()
self._spinner_thread = threading.Thread(target=self.render)
- self._spinner_thread.setDaemon(True)
+ self._spinner_thread.daemon = True
self._spinner_thread.start()
return self