feat: add NetBox plugin store
This commit is contained in:
@@ -0,0 +1,70 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
import subprocess
|
||||
from dataclasses import dataclass
|
||||
from pathlib import Path
|
||||
from typing import Protocol, Sequence
|
||||
|
||||
from .config import Config
|
||||
from .errors import ExecutionError, ValidationError
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class CommandResult:
|
||||
argv: tuple[str, ...]
|
||||
stdout: str
|
||||
stderr: str
|
||||
|
||||
|
||||
class Runner(Protocol):
|
||||
def run(self, argv: Sequence[str]) -> CommandResult: ...
|
||||
|
||||
|
||||
class SubprocessRunner:
|
||||
MAX_CAPTURE_CHARS = 16_000
|
||||
|
||||
def __init__(self, config: Config):
|
||||
self.config = config
|
||||
|
||||
def run(self, argv: Sequence[str]) -> CommandResult:
|
||||
command = tuple(argv)
|
||||
if not command or not all(isinstance(item, str) and item for item in command):
|
||||
raise ValidationError("command argv must contain non-empty strings")
|
||||
if any("\x00" in item or "\r" in item or "\n" in item for item in command):
|
||||
raise ValidationError("command argv contains a forbidden control character")
|
||||
if not Path(command[0]).is_absolute():
|
||||
raise ValidationError("command executable must be an absolute path")
|
||||
environment = os.environ.copy()
|
||||
for key in tuple(environment):
|
||||
if key.startswith("PIP_") or key in {"PYTHONPATH", "PYTHONHOME"}:
|
||||
environment.pop(key, None)
|
||||
environment.update(
|
||||
{
|
||||
"PIP_DISABLE_PIP_VERSION_CHECK": "1",
|
||||
"PIP_NO_INPUT": "1",
|
||||
"PYTHONNOUSERSITE": "1",
|
||||
}
|
||||
)
|
||||
try:
|
||||
completed = subprocess.run(
|
||||
command,
|
||||
shell=False,
|
||||
stdin=subprocess.DEVNULL,
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE,
|
||||
text=True,
|
||||
errors="replace",
|
||||
timeout=self.config.commands.command_timeout_seconds,
|
||||
check=False,
|
||||
env=environment,
|
||||
)
|
||||
except (OSError, subprocess.TimeoutExpired) as exc:
|
||||
raise ExecutionError(f"command could not complete: {command[0]}") from exc
|
||||
stdout = completed.stdout[-self.MAX_CAPTURE_CHARS :]
|
||||
stderr = completed.stderr[-self.MAX_CAPTURE_CHARS :]
|
||||
if completed.returncode != 0:
|
||||
raise ExecutionError(
|
||||
f"command failed with exit code {completed.returncode}: {command[0]}"
|
||||
)
|
||||
return CommandResult(command, stdout, stderr)
|
||||
Reference in New Issue
Block a user