left4me/l4d2host/service_control.py

85 lines
2.5 KiB
Python

import subprocess
from typing import Callable, Iterator, Sequence
from l4d2host.process import CommandResult, run_command
SYSTEMCTL_HELPER = "/usr/local/libexec/left4me/left4me-systemctl"
JOURNALCTL_HELPER = "/usr/local/libexec/left4me/left4me-journalctl"
def systemctl_command(action: str, name: str) -> list[str]:
return ["sudo", "-n", SYSTEMCTL_HELPER, action, name]
def journalctl_command(name: str, lines: int = 200, follow: bool = True) -> list[str]:
follow_arg = "--follow" if follow else "--no-follow"
return ["sudo", "-n", JOURNALCTL_HELPER, name, "--lines", str(lines), follow_arg]
def start_service(
name: str,
*,
on_stdout: Callable[[str], None] | None = None,
on_stderr: Callable[[str], None] | None = None,
passthrough: bool = False,
should_cancel: Callable[[], bool] | None = None,
) -> CommandResult:
return run_command(
systemctl_command("start", name),
on_stdout=on_stdout,
on_stderr=on_stderr,
passthrough=passthrough,
should_cancel=should_cancel,
)
def stop_service(
name: str,
*,
on_stdout: Callable[[str], None] | None = None,
on_stderr: Callable[[str], None] | None = None,
passthrough: bool = False,
should_cancel: Callable[[], bool] | None = None,
) -> CommandResult:
return run_command(
systemctl_command("stop", name),
on_stdout=on_stdout,
on_stderr=on_stderr,
passthrough=passthrough,
should_cancel=should_cancel,
)
def show_service(name: str) -> CommandResult:
return run_command(systemctl_command("show", name))
def stream_command(cmd: Sequence[str]) -> Iterator[str]:
command = list(cmd)
proc = subprocess.Popen(
command,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
text=True,
bufsize=1,
)
try:
if proc.stdout is None:
return
for raw in iter(proc.stdout.readline, ""):
yield raw.rstrip("\n")
returncode = proc.wait()
if returncode is None:
returncode = proc.poll()
stderr = proc.stderr.read() if proc.stderr is not None else ""
if returncode:
raise subprocess.CalledProcessError(returncode=returncode, cmd=command, stderr=stderr)
finally:
if proc.poll() is None:
proc.terminate()
proc.wait(timeout=2)
def stream_journal(name: str, *, lines: int = 200, follow: bool = True) -> Iterator[str]:
return stream_command(journalctl_command(name, lines=lines, follow=follow))