feat: add NetBox plugin store
This commit is contained in:
@@ -0,0 +1,69 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
import subprocess
|
||||
from dataclasses import dataclass
|
||||
from pathlib import Path
|
||||
from typing import Sequence
|
||||
|
||||
from .redaction import redact_text
|
||||
|
||||
|
||||
class CommandExecutionError(RuntimeError):
|
||||
def __init__(self, message: str, *, output: str = ""):
|
||||
super().__init__(message)
|
||||
self.output = redact_text(output)
|
||||
|
||||
|
||||
@dataclass(frozen=True, slots=True)
|
||||
class CommandResult:
|
||||
returncode: int
|
||||
stdout: str
|
||||
stderr: str
|
||||
|
||||
@property
|
||||
def output(self) -> str:
|
||||
return redact_text("\n".join(part for part in (self.stdout, self.stderr) if part))
|
||||
|
||||
|
||||
class SubprocessRunner:
|
||||
"""Injectable no-shell subprocess boundary."""
|
||||
|
||||
def run(
|
||||
self,
|
||||
argv: Sequence[str],
|
||||
*,
|
||||
timeout: int,
|
||||
cwd: Path | None = None,
|
||||
input_text: str | None = None,
|
||||
) -> CommandResult:
|
||||
if not argv or any(not isinstance(arg, str) or "\x00" in arg for arg in argv):
|
||||
raise CommandExecutionError("Refusing to execute invalid argv.")
|
||||
env = os.environ.copy()
|
||||
env.update({"PIP_NO_INPUT": "1", "PYTHONUNBUFFERED": "1"})
|
||||
try:
|
||||
completed = subprocess.run(
|
||||
list(argv),
|
||||
cwd=str(cwd) if cwd else None,
|
||||
env=env,
|
||||
shell=False,
|
||||
check=False,
|
||||
text=True,
|
||||
input=input_text,
|
||||
stdin=subprocess.DEVNULL if input_text is None else None,
|
||||
capture_output=True,
|
||||
timeout=timeout,
|
||||
)
|
||||
except subprocess.TimeoutExpired as exc:
|
||||
output = "\n".join(
|
||||
str(value) for value in (getattr(exc, "stdout", ""), getattr(exc, "stderr", "")) if value
|
||||
)
|
||||
raise CommandExecutionError("Command timed out.", output=output) from exc
|
||||
except OSError as exc:
|
||||
raise CommandExecutionError(f"Unable to start command: {type(exc).__name__}") from exc
|
||||
result = CommandResult(completed.returncode, completed.stdout or "", completed.stderr or "")
|
||||
if completed.returncode != 0:
|
||||
raise CommandExecutionError(
|
||||
f"Command failed with exit code {completed.returncode}.", output=result.output
|
||||
)
|
||||
return result
|
||||
Reference in New Issue
Block a user