Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

pass certain env vars during elevation #139

Merged
merged 6 commits into from
Jul 6, 2023
Merged
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
3 changes: 2 additions & 1 deletion menuinst/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@
import json
import os
import sys
from logging import getLogger as _getLogger, basicConfig as _basicConfig
from logging import basicConfig as _basicConfig
from logging import getLogger as _getLogger
from os import PathLike

try:
Expand Down
36 changes: 26 additions & 10 deletions menuinst/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -98,9 +98,9 @@ def slugify(text: str):
Remove characters that aren't alphanumerics, or hyphens.
Convert to lowercase. Also strip leading and trailing whitespace.
"""
text = normalize('NFKD', text).encode('ascii', 'ignore').decode('ascii')
text = re.sub(r'[^\w\s-]', '', text).strip().lower()
return re.sub(r'[_\-\s]+', '-', text)
text = normalize("NFKD", text).encode("ascii", "ignore").decode("ascii")
text = re.sub(r"[^\w\s-]", "", text).strip().lower()
return re.sub(r"[_\-\s]+", "-", text)


def indent_xml_tree(elem, level=0):
Expand Down Expand Up @@ -247,11 +247,11 @@ def deep_update(mapping: Mapping, *updating_mappings: Iterable[Mapping]) -> Mapp


def user_is_admin() -> bool:
if os.name == 'nt':
if os.name == "nt":
from .platforms.win_utils.win_elevate import isUserAdmin

return bool(isUserAdmin())
elif os.name == 'posix':
elif os.name == "posix":
# Check for root on Linux, macOS and other posix systems
return os.getuid() == 0
else:
Expand All @@ -262,11 +262,11 @@ def run_as_admin(argv: Sequence[str]) -> int:
"""
Rerun this command in a new process with admin permissions.
"""
if os.name == 'nt':
if os.name == "nt":
from .platforms.win_utils.win_elevate import runAsAdmin

return runAsAdmin(argv)
elif os.name == 'posix':
elif os.name == "posix":
return subprocess.call(["sudo", *argv])
else:
raise RuntimeError(f"Unsupported operating system: {os.name}")
Expand All @@ -279,7 +279,7 @@ def python_executable(base_prefix: Optional[os.PathLike] = None) -> Sequence[str
# in these cases, we prefer using the base env python to
# avoid a 2nd decompression + hacky console, so we try that
# first; otherwise, we use 'conda-standalone.exe python'
if getattr(sys, 'frozen', False):
if getattr(sys, "frozen", False):
if os.name == "nt":
base_prefix_python = base_prefix / "python.exe"
else:
Expand Down Expand Up @@ -341,11 +341,19 @@ def wrapper_elevate(
)
else:
import_func = f"from {func.__module__} import {func.__name__};"
env_vars = ";".join(
[
f"os.environ.setdefault('{k}', '{v}')"
for (k, v) in os.environ.items()
if k.startswith(("CONDA_", "CONSTRUCTOR_", "MENUINST_"))
]
)
cmd = [
*python_executable(),
"-c",
f"import os;"
f"os.environ.setdefault('_MENUINST_RECURSING', '1');"
f"{env_vars};"
f"{import_func}"
f"{func.__name__}("
f"*{args!r},"
Expand Down Expand Up @@ -376,7 +384,7 @@ def wrapper_elevate(
return wrapper_elevate


def _test_elevation(base_prefix: Optional[os.PathLike] = None, _mode: str = "user"):
def _test_elevation(base_prefix: Optional[os.PathLike] = None, _mode: _UserOrSystem = "user"):
if os.name == "nt":
if base_prefix:
output = os.path.join(base_prefix, "_test_output.txt")
Expand All @@ -385,7 +393,15 @@ def _test_elevation(base_prefix: Optional[os.PathLike] = None, _mode: str = "use
out = open(output, "a")
else:
out = sys.stdout
print("user_is_admin():", user_is_admin(), "_mode:", _mode, file=out)
print(
"user_is_admin():",
user_is_admin(),
"env_var:",
os.environ.get("MENUINST_TEST", "N/A"),
"_mode:",
_mode,
file=out,
)
if os.name == "nt":
out.close()

Expand Down
2 changes: 1 addition & 1 deletion news/138-elevation
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
### Enhancements

* Enable automatic elevation on Unix platforms too, and add tests. (#137 via #138)
* Enable automatic elevation on Unix platforms too, and add tests. (#137 via #138, #139)

### Bug fixes

Expand Down
17 changes: 10 additions & 7 deletions tests/test_elevation.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@


def test_elevation(tmp_path, capfd):
os.environ["MENUINST_TEST"] = "TEST"
if os.name == "nt":
on_ci = os.environ.get("CI")
is_admin = user_is_admin()
Expand All @@ -14,25 +15,27 @@ def test_elevation(tmp_path, capfd):
_test_elevation(str(tmp_path))
output = (tmp_path / "_test_output.txt").read_text().strip()
if on_ci:
assert output.endswith("_mode: user")
assert output.endswith("env_var: TEST _mode: user")
else:
assert output.endswith("user_is_admin(): False _mode: user")
assert output.endswith("user_is_admin(): False env_var: TEST _mode: user")

elevate_as_needed(_test_elevation)(base_prefix=str(tmp_path))
output = (tmp_path / "_test_output.txt").read_text().strip()
if on_ci:
assert output.endswith("_mode: system")
assert output.endswith("env_var: TEST _mode: system")
else:
assert output.endswith("user_is_admin(): True _mode: system")
assert output.endswith("user_is_admin(): True env_var: TEST _mode: system")
else:
assert not user_is_admin() # We need to start as a non-root user

_test_elevation(str(tmp_path))
assert capfd.readouterr().out.strip() == "user_is_admin(): False _mode: user"
assert capfd.readouterr().out.strip() == "user_is_admin(): False env_var: TEST _mode: user"

elevate_as_needed(_test_elevation)(base_prefix=str(tmp_path))
assert capfd.readouterr().out.strip() == "user_is_admin(): True _mode: system"
assert (
capfd.readouterr().out.strip() == "user_is_admin(): True env_var: TEST _mode: system"
)

(tmp_path / ".nonadmin").touch()
elevate_as_needed(_test_elevation)(base_prefix=str(tmp_path))
assert capfd.readouterr().out.strip() == "user_is_admin(): False _mode: user"
assert capfd.readouterr().out.strip() == "user_is_admin(): False env_var: TEST _mode: user"
Loading