mirror of
https://github.com/kiviktnm/decman.git
synced 2026-09-19 12:08:28 +00:00
Implement systemd plugin
This commit is contained in:
@@ -4,6 +4,7 @@ import os
|
||||
import pty
|
||||
import pwd
|
||||
import select
|
||||
import shlex
|
||||
import shutil
|
||||
import struct
|
||||
import subprocess
|
||||
@@ -13,6 +14,7 @@ import tty
|
||||
import typing
|
||||
|
||||
import decman.core.error as errors
|
||||
import decman.core.output as output
|
||||
|
||||
|
||||
def get_user_info(user: str) -> tuple[int, int]:
|
||||
@@ -55,6 +57,8 @@ def pty_run(
|
||||
if not sys.stdin.isatty():
|
||||
raise OSError(errno.ENOTTY, "Stdin is not a TTY.")
|
||||
|
||||
output.print_debug(f"Running command '{shlex.join(command)}'")
|
||||
|
||||
env = _build_env(user=user, env_overrides=env_overrides, mimic_login=mimic_login)
|
||||
|
||||
pid, master_fd = pty.fork()
|
||||
@@ -90,6 +94,8 @@ def run(
|
||||
if not command:
|
||||
return 0, ""
|
||||
|
||||
output.print_debug(f"Running command '{shlex.join(command)}'")
|
||||
|
||||
env = _build_env(user=user, env_overrides=env_overrides, mimic_login=mimic_login)
|
||||
uid, gid = None, None
|
||||
|
||||
@@ -104,9 +110,9 @@ def run(
|
||||
except OSError as error:
|
||||
# Mirror PTY behavior: "<cmd>: <error>\n" and errno-based exit code
|
||||
msg = error.strerror or str(error)
|
||||
output = f"{command[0]}: {msg}\n"
|
||||
text_output = f"{command[0]}: {msg}\n"
|
||||
code = error.errno if error.errno and error.errno < 128 else 127
|
||||
return code, output
|
||||
return code, text_output
|
||||
|
||||
return process.returncode, stdout.decode("utf-8", errors="replace")
|
||||
|
||||
|
||||
@@ -133,6 +133,14 @@ def print_debug(msg: str):
|
||||
print(f"{_tag()} {_gray('DEBUG')}: {msg}")
|
||||
|
||||
|
||||
def print_command_output(msg: str):
|
||||
"""
|
||||
Prints command output without a DECMAN tag if debug messages are enabled.
|
||||
"""
|
||||
if config.debug_output:
|
||||
print(msg)
|
||||
|
||||
|
||||
# ─────────────────────────────
|
||||
# List printing
|
||||
# ─────────────────────────────
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import importlib.metadata as metadata
|
||||
import typing
|
||||
|
||||
import decman.core.module as module
|
||||
import decman.core.store as _store
|
||||
@@ -45,6 +46,24 @@ class Plugin:
|
||||
"""
|
||||
|
||||
|
||||
def run_method_with_attribute(mod: module.Module, attribute: str) -> typing.Any:
|
||||
"""
|
||||
Runs the method with the given attribute in the module and returns its returned value.
|
||||
Returns none if no such method is found.
|
||||
|
||||
Only the first found method with the attribute is ran.
|
||||
"""
|
||||
for name in dir(mod):
|
||||
attr = getattr(mod, name)
|
||||
if not callable(attr):
|
||||
continue
|
||||
func = getattr(attr, "__func__", attr)
|
||||
if getattr(func, attribute, False):
|
||||
return attr()
|
||||
|
||||
return None
|
||||
|
||||
|
||||
def available_plugins() -> dict[str, Plugin]:
|
||||
"""
|
||||
Returns all available plugins.
|
||||
|
||||
@@ -1,5 +1,296 @@
|
||||
import shutil
|
||||
|
||||
import decman.core.command as command
|
||||
import decman.core.module as module
|
||||
import decman.core.output as output
|
||||
import decman.core.store as _store
|
||||
import decman.plugins as plugins
|
||||
|
||||
|
||||
def units(fn):
|
||||
"""
|
||||
Annotate that this function returns a set of systemd unit names that should be enabled.
|
||||
"""
|
||||
fn.__systemd__units__ = True
|
||||
return fn
|
||||
|
||||
|
||||
def user_units(fn):
|
||||
"""
|
||||
Annotate that this function returns a dict of users and systemd user unit names that should be
|
||||
enabled.
|
||||
"""
|
||||
fn.__systemd__user__units__ = True
|
||||
return fn
|
||||
|
||||
|
||||
class SystemdCommands:
|
||||
"""
|
||||
Default commands for the Systemd plugin.
|
||||
"""
|
||||
|
||||
def enable_units(self, units: set[str]) -> list[str]:
|
||||
"""
|
||||
Running this command enables the given systemd units.
|
||||
"""
|
||||
return ["systemctl", "enable"] + list(units)
|
||||
|
||||
def disable_units(self, units: set[str]) -> list[str]:
|
||||
"""
|
||||
Running this command disables the given systemd units.
|
||||
"""
|
||||
return ["systemctl", "disable"] + list(units)
|
||||
|
||||
def enable_user_units(self, units: set[str], user: str) -> list[str]:
|
||||
"""
|
||||
Running this command enables the given systemd units for the user.
|
||||
"""
|
||||
return ["systemctl", "--user", "-M", f"{user}@", "enable"] + list(units)
|
||||
|
||||
def disable_user_units(self, units: set[str], user: str) -> list[str]:
|
||||
"""
|
||||
Running this command disables the given systemd units for the user.
|
||||
"""
|
||||
return ["systemctl", "--user", "-M", f"{user}@", "disable"] + list(units)
|
||||
|
||||
def daemon_reload(self) -> list[str]:
|
||||
"""
|
||||
Running this command reloads the systemd daemon.
|
||||
"""
|
||||
return ["systemctl", "daemon-reload"]
|
||||
|
||||
def user_daemon_reload(self, user: str) -> list[str]:
|
||||
"""
|
||||
Running this command reloads the systemd daemon for the given user.
|
||||
"""
|
||||
return ["systemctl", "--user", "-M", f"{user}@", "daemon-reload"]
|
||||
|
||||
|
||||
class Systemd(plugins.Plugin):
|
||||
NAME = "systemd"
|
||||
|
||||
def __init__(self) -> None:
|
||||
self.enabled_systemd_units: set[str] = set()
|
||||
self.enabled_systemd_user_units: dict[str, set[str]] = {}
|
||||
self.commands = SystemdCommands()
|
||||
|
||||
def available(self) -> bool:
|
||||
return shutil.which("systemctl") is not None
|
||||
|
||||
def process_modules(self, store: _store.Store, modules: set[module.Module]):
|
||||
# These store keys are used to track changes in modules.
|
||||
# This way when these change, module can be marked as changed
|
||||
store.ensure("systemd_units_for_module", {})
|
||||
store.ensure("systemd_user_units_for_module", {})
|
||||
|
||||
for mod in modules:
|
||||
store["systemd_units_for_module"].setdefault(mod.name, set())
|
||||
store["systemd_user_units_for_module"].setdefault(mod.name, {})
|
||||
|
||||
units = plugins.run_method_with_attribute(mod, "__systemd__units__") or set()
|
||||
user_units = plugins.run_method_with_attribute(mod, "__systemd__user__units__") or {}
|
||||
|
||||
if store["systemd_units_for_module"][mod.name] != units:
|
||||
mod._changed = True
|
||||
|
||||
if store["systemd_user_units_for_module"][mod.name] != user_units:
|
||||
mod._changed = True
|
||||
|
||||
self.enabled_systemd_units |= units
|
||||
for user, u_units in user_units.items():
|
||||
self.enabled_systemd_user_units.setdefault(user, set()).update(u_units)
|
||||
|
||||
store["systemd_units_for_module"][mod.name] = units
|
||||
store["systemd_user_units_for_module"][mod.name] = user_units
|
||||
|
||||
def apply(
|
||||
self, store: _store.Store, dry_run: bool = False, params: list[str] | None = None
|
||||
) -> bool:
|
||||
store.ensure("systemd_units", set())
|
||||
store.ensure("systemd_user_units", {})
|
||||
|
||||
units_to_enable = set()
|
||||
units_to_disable = set()
|
||||
user_units_to_enable: dict[str, set[str]] = {}
|
||||
user_units_to_disable: dict[str, set[str]] = {}
|
||||
|
||||
for unit in self.enabled_systemd_units:
|
||||
if unit not in store["systemd_units"]:
|
||||
units_to_enable.add(unit)
|
||||
|
||||
for unit in store["systemd_units"]:
|
||||
if unit not in self.enabled_systemd_units:
|
||||
units_to_disable.add(unit)
|
||||
|
||||
for user, units in self.enabled_systemd_user_units.items():
|
||||
store["systemd_user_units"].setdefault(user, set())
|
||||
user_units_to_enable.setdefault(user, set())
|
||||
|
||||
for unit in units:
|
||||
if unit not in store["systemd_user_units"][user]:
|
||||
user_units_to_enable[user].add(unit)
|
||||
|
||||
for user, units in store["systemd_user_units"].items():
|
||||
self.enabled_systemd_user_units.setdefault(user, set())
|
||||
user_units_to_disable.setdefault(user, set())
|
||||
|
||||
for unit in units:
|
||||
if unit not in self.enabled_systemd_user_units[user]:
|
||||
user_units_to_disable[user].add(unit)
|
||||
|
||||
output.print_info("Reloading systemd daemon.")
|
||||
if not dry_run:
|
||||
if not self.reload_daemon():
|
||||
return False
|
||||
|
||||
output.print_info("Reloading systemd daemon for users.")
|
||||
if not dry_run:
|
||||
for user in user_units_to_enable.keys() | user_units_to_disable.keys():
|
||||
if not self.reload_user_daemon(user):
|
||||
return False
|
||||
|
||||
output.print_list("Enabling systemd units:", list(units_to_enable))
|
||||
if not dry_run:
|
||||
if not self.enable_units(store, units_to_enable):
|
||||
return False
|
||||
|
||||
output.print_list("Disabling systemd units:", list(units_to_disable))
|
||||
if not dry_run:
|
||||
if not self.disable_units(store, units_to_disable):
|
||||
return False
|
||||
|
||||
for user, units in user_units_to_enable.items():
|
||||
output.print_list(f"Enabling systemd units for {user}:", list(units))
|
||||
if not dry_run:
|
||||
if not self.enable_user_units(store, units, user):
|
||||
return False
|
||||
|
||||
for user, units in user_units_to_disable.items():
|
||||
output.print_list(f"Disabling systemd units for {user}:", list(units))
|
||||
if not dry_run:
|
||||
if not self.disable_user_units(store, units, user):
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
def enable_units(self, store: _store.Store, units: set[str]) -> bool:
|
||||
"""
|
||||
Enables the given units.
|
||||
|
||||
Returns ``True`` if the operation was successful.
|
||||
"""
|
||||
if not units:
|
||||
return True
|
||||
|
||||
code, text = command.run(self.commands.enable_units(units))
|
||||
output.print_command_output(text)
|
||||
if code != 0:
|
||||
output.print_error(f"Failed to enable systemd units '{' '.join(units)}'.")
|
||||
output.print_error(f"Command exited with code: {code}")
|
||||
output.print_error(f"{text}")
|
||||
return False
|
||||
|
||||
store["systemd_units"] |= units
|
||||
|
||||
return True
|
||||
|
||||
def disable_units(self, store: _store.Store, units: set[str]) -> bool:
|
||||
"""
|
||||
Disables the given units.
|
||||
|
||||
Returns ``True`` if the operation was successful.
|
||||
"""
|
||||
if not units:
|
||||
return True
|
||||
|
||||
code, text = command.run(self.commands.disable_units(units))
|
||||
output.print_command_output(text)
|
||||
if code != 0:
|
||||
output.print_error(f"Failed to disable systemd units '{' '.join(units)}'.")
|
||||
output.print_error(f"Command exited with code: {code}")
|
||||
output.print_error(f"{text}")
|
||||
return False
|
||||
|
||||
store["systemd_units"] -= units
|
||||
|
||||
return True
|
||||
|
||||
def enable_user_units(self, store: _store.Store, units: set[str], user: str) -> bool:
|
||||
"""
|
||||
Enables the given units for the given user.
|
||||
|
||||
Returns ``True`` if the operation was successful.
|
||||
"""
|
||||
if not units:
|
||||
return True
|
||||
|
||||
code, text = command.run(self.commands.enable_user_units(units, user))
|
||||
output.print_command_output(text)
|
||||
if code != 0:
|
||||
output.print_error(
|
||||
f"Failed to enable systemd units '{' '.join(units)}' for user {user}."
|
||||
)
|
||||
output.print_error(f"Command exited with code: {code}")
|
||||
output.print_error(f"{text}")
|
||||
return False
|
||||
|
||||
store["systemd_user_units"].setdefault(user, set())
|
||||
store["systemd_user_units"][user] |= units
|
||||
|
||||
return True
|
||||
|
||||
def disable_user_units(self, store: _store.Store, units: set[str], user: str) -> bool:
|
||||
"""
|
||||
Disables the given units for the given user.
|
||||
|
||||
Returns ``True`` if the operation was successful.
|
||||
"""
|
||||
if not units:
|
||||
return True
|
||||
|
||||
code, text = command.run(self.commands.disable_user_units(units, user))
|
||||
output.print_command_output(text)
|
||||
if code != 0:
|
||||
output.print_error(
|
||||
f"Failed to disable systemd units '{' '.join(units)}' for user {user}."
|
||||
)
|
||||
output.print_error(f"Command exited with code: {code}")
|
||||
output.print_error(f"{text}")
|
||||
return False
|
||||
|
||||
store["systemd_user_units"].setdefault(user, set())
|
||||
store["systemd_user_units"][user] -= units
|
||||
|
||||
return True
|
||||
|
||||
def reload_user_daemon(self, user: str) -> bool:
|
||||
"""
|
||||
Reloads the user's systemd daemon.
|
||||
|
||||
Returns ``True`` if the operation was successful.
|
||||
"""
|
||||
|
||||
code, text = command.run(self.commands.user_daemon_reload(user))
|
||||
output.print_command_output(text)
|
||||
if code != 0:
|
||||
output.print_error(f"Failed to reload systemd daemon for {user}.")
|
||||
output.print_error(f"Command exited with code: {code}")
|
||||
output.print_error(f"{text}")
|
||||
return False
|
||||
return True
|
||||
|
||||
def reload_daemon(self) -> bool:
|
||||
"""
|
||||
Reloads the systemd daemon.
|
||||
|
||||
Returns ``True`` if the operation was successful.
|
||||
"""
|
||||
|
||||
code, text = command.run(self.commands.daemon_reload())
|
||||
output.print_command_output(text)
|
||||
if code != 0:
|
||||
output.print_error("Failed to reload systemd daemon.")
|
||||
output.print_error(f"Command exited with code: {code}")
|
||||
output.print_error(f"{text}")
|
||||
return False
|
||||
return True
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
from decman.core.module import Module
|
||||
from decman.plugins import run_method_with_attribute
|
||||
|
||||
|
||||
def mark(attr):
|
||||
attr.__flag__ = True
|
||||
return attr
|
||||
|
||||
|
||||
def test_runs_marked_method_and_returns_value():
|
||||
class M(Module):
|
||||
@mark
|
||||
def foo(self):
|
||||
return 123
|
||||
|
||||
m = M("m")
|
||||
assert run_method_with_attribute(m, "__flag__") == 123
|
||||
|
||||
|
||||
def test_returns_none_if_no_method_has_attribute():
|
||||
class M(Module):
|
||||
def foo(self):
|
||||
return 1
|
||||
|
||||
m = M("m")
|
||||
assert run_method_with_attribute(m, "__flag__") is None
|
||||
@@ -0,0 +1,366 @@
|
||||
import pytest
|
||||
|
||||
from decman.plugins import systemd as systemd_mod
|
||||
|
||||
|
||||
class DummyStore(dict):
|
||||
def ensure(self, key, default):
|
||||
if key not in self:
|
||||
self[key] = default
|
||||
|
||||
|
||||
class DummyModule:
|
||||
def __init__(self, name: str):
|
||||
self.name = name
|
||||
self._changed = False
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def store():
|
||||
return DummyStore()
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def systemd():
|
||||
return systemd_mod.Systemd()
|
||||
|
||||
|
||||
def test_units_decorator_sets_attribute():
|
||||
@systemd_mod.units
|
||||
def fn():
|
||||
pass
|
||||
|
||||
assert getattr(fn, "__systemd__units__", False) is True
|
||||
|
||||
|
||||
def test_user_units_decorator_sets_attribute():
|
||||
@systemd_mod.user_units
|
||||
def fn():
|
||||
pass
|
||||
|
||||
assert getattr(fn, "__systemd__user__units__", False) is True
|
||||
|
||||
|
||||
def test_available_true_if_systemctl_found(monkeypatch, systemd):
|
||||
called = {}
|
||||
|
||||
def fake_which(name):
|
||||
called["name"] = name
|
||||
return "/bin/systemctl"
|
||||
|
||||
monkeypatch.setattr(systemd_mod.shutil, "which", fake_which)
|
||||
assert systemd.available() is True
|
||||
assert called["name"] == "systemctl"
|
||||
|
||||
|
||||
def test_available_false_if_systemctl_missing(monkeypatch, systemd):
|
||||
monkeypatch.setattr(systemd_mod.shutil, "which", lambda name: None)
|
||||
assert systemd.available() is False
|
||||
|
||||
|
||||
def test_process_modules_marks_changed_and_updates_store(monkeypatch, store, systemd):
|
||||
# initial store empty; ensure keys will be created
|
||||
m1 = DummyModule("mod1")
|
||||
m2 = DummyModule("mod2")
|
||||
|
||||
def fake_run_method(mod, attr):
|
||||
if mod is m1 and attr == "__systemd__units__":
|
||||
return {"a.service"}
|
||||
if mod is m1 and attr == "__systemd__user__units__":
|
||||
return {"alice": {"u1.service"}}
|
||||
# m2 has no units
|
||||
return None
|
||||
|
||||
monkeypatch.setattr(systemd_mod.plugins, "run_method_with_attribute", fake_run_method)
|
||||
|
||||
systemd.process_modules(store, {m1, m2})
|
||||
|
||||
# m1 changed from default -> marked _changed
|
||||
assert m1._changed is True
|
||||
# m2 had no units
|
||||
assert m2._changed is False
|
||||
|
||||
# enabled units aggregated
|
||||
assert systemd.enabled_systemd_units == {"a.service"}
|
||||
assert systemd.enabled_systemd_user_units == {"alice": {"u1.service"}}
|
||||
|
||||
# store updated per module
|
||||
assert store["systemd_units_for_module"]["mod1"] == {"a.service"}
|
||||
assert store["systemd_user_units_for_module"]["mod1"] == {"alice": {"u1.service"}}
|
||||
assert store["systemd_units_for_module"]["mod2"] == set()
|
||||
assert store["systemd_user_units_for_module"]["mod2"] == {}
|
||||
|
||||
|
||||
def test_process_modules_no_change_second_run(monkeypatch, store, systemd):
|
||||
m1 = DummyModule("mod1")
|
||||
|
||||
def fake_run_method(mod, attr):
|
||||
if attr == "__systemd__units__":
|
||||
return {"a.service"}
|
||||
if attr == "__systemd__user__units__":
|
||||
return {"alice": {"u1.service"}}
|
||||
return None
|
||||
|
||||
monkeypatch.setattr(systemd_mod.plugins, "run_method_with_attribute", fake_run_method)
|
||||
|
||||
# first run populates store
|
||||
systemd.process_modules(store, {m1})
|
||||
m1._changed = False
|
||||
|
||||
# new instance (fresh per-process in real usage)
|
||||
systemd2 = systemd_mod.Systemd()
|
||||
monkeypatch.setattr(systemd_mod.plugins, "run_method_with_attribute", fake_run_method)
|
||||
|
||||
systemd2.process_modules(store, {m1})
|
||||
|
||||
# values in store are same -> _changed stays False
|
||||
assert m1._changed is False
|
||||
|
||||
|
||||
def test_apply_enables_and_disables_units_and_user_units(store):
|
||||
s = systemd_mod.Systemd()
|
||||
|
||||
# Current enabled according to modules
|
||||
s.enabled_systemd_units = {"new.service"}
|
||||
s.enabled_systemd_user_units = {"alice": {"newuser.service"}}
|
||||
|
||||
# Store says we had an old unit enabled before
|
||||
store["systemd_units"] = {"old.service"}
|
||||
store["systemd_user_units"] = {"alice": {"olduser.service"}}
|
||||
|
||||
calls = []
|
||||
|
||||
def fake_reload_daemon():
|
||||
calls.append(("reload_daemon",))
|
||||
return True
|
||||
|
||||
def fake_reload_user_daemon(user):
|
||||
calls.append(("reload_user_daemon", user))
|
||||
return True
|
||||
|
||||
def fake_enable_units(store_arg, units_arg):
|
||||
calls.append(("enable_units", frozenset(units_arg)))
|
||||
store_arg["systemd_units"] |= units_arg
|
||||
return True
|
||||
|
||||
def fake_disable_units(store_arg, units_arg):
|
||||
calls.append(("disable_units", frozenset(units_arg)))
|
||||
store_arg["systemd_units"] -= units_arg
|
||||
return True
|
||||
|
||||
def fake_enable_user_units(store_arg, units_arg, user):
|
||||
calls.append(("enable_user_units", user, frozenset(units_arg)))
|
||||
store_arg["systemd_user_units"].setdefault(user, set()).update(units_arg)
|
||||
return True
|
||||
|
||||
def fake_disable_user_units(store_arg, units_arg, user):
|
||||
calls.append(("disable_user_units", user, frozenset(units_arg)))
|
||||
store_arg["systemd_user_units"].setdefault(user, set()).difference_update(units_arg)
|
||||
return True
|
||||
|
||||
# patch instance methods (no self parameter expected)
|
||||
s.reload_daemon = fake_reload_daemon
|
||||
s.reload_user_daemon = fake_reload_user_daemon
|
||||
s.enable_units = fake_enable_units
|
||||
s.disable_units = fake_disable_units
|
||||
s.enable_user_units = fake_enable_user_units
|
||||
s.disable_user_units = fake_disable_user_units
|
||||
|
||||
result = s.apply(store, dry_run=False, params=None)
|
||||
assert result is True
|
||||
|
||||
# reloads called once
|
||||
assert ("reload_daemon",) in calls
|
||||
assert ("reload_user_daemon", "alice") in calls
|
||||
|
||||
# enable/disable correct units
|
||||
assert ("enable_units", frozenset({"new.service"})) in calls
|
||||
assert ("disable_units", frozenset({"old.service"})) in calls
|
||||
assert ("enable_user_units", "alice", frozenset({"newuser.service"})) in calls
|
||||
assert ("disable_user_units", "alice", frozenset({"olduser.service"})) in calls
|
||||
|
||||
# store reconciled
|
||||
assert store["systemd_units"] == {"new.service"}
|
||||
assert store["systemd_user_units"]["alice"] == {"newuser.service"}
|
||||
|
||||
|
||||
def test_apply_dry_run_does_not_mutate_store_or_call_commands(store):
|
||||
s = systemd_mod.Systemd()
|
||||
s.enabled_systemd_units = {"new.service"}
|
||||
s.enabled_systemd_user_units = {"alice": {"newuser.service"}}
|
||||
|
||||
store["systemd_units"] = {"old.service"}
|
||||
store["systemd_user_units"] = {"alice": {"olduser.service"}}
|
||||
|
||||
called = {"reload": False, "enable": False, "disable": False}
|
||||
|
||||
s.reload_daemon = lambda: called.__setitem__("reload", True) or True
|
||||
s.reload_user_daemon = lambda user: called.__setitem__("reload", True) or True
|
||||
s.enable_units = lambda st, u: called.__setitem__("enable", True) or True
|
||||
s.disable_units = lambda st, u: called.__setitem__("disable", True) or True
|
||||
s.enable_user_units = lambda st, u, user: called.__setitem__("enable", True) or True
|
||||
s.disable_user_units = lambda st, u, user: called.__setitem__("disable", True) or True
|
||||
|
||||
result = s.apply(store, dry_run=True, params=None)
|
||||
assert result is True
|
||||
|
||||
# no commands should be called
|
||||
assert called == {"reload": False, "enable": False, "disable": False}
|
||||
|
||||
# store unchanged
|
||||
assert store["systemd_units"] == {"old.service"}
|
||||
assert store["systemd_user_units"]["alice"] == {"olduser.service"}
|
||||
|
||||
|
||||
def test_enable_units_success(monkeypatch, store, systemd):
|
||||
store["systemd_units"] = {"old.service"}
|
||||
|
||||
def fake_run(cmd):
|
||||
assert cmd[0] == "systemctl"
|
||||
assert cmd[1] == "enable"
|
||||
assert "new.service" in cmd[2:]
|
||||
return 0, "ok"
|
||||
|
||||
monkeypatch.setattr(systemd_mod.command, "run", fake_run)
|
||||
|
||||
result = systemd.enable_units(store, {"new.service"})
|
||||
assert result is True
|
||||
assert store["systemd_units"] == {"old.service", "new.service"}
|
||||
|
||||
|
||||
def test_enable_units_failure_does_not_update_store(monkeypatch, store, systemd):
|
||||
store["systemd_units"] = {"old.service"}
|
||||
|
||||
def fake_run(cmd):
|
||||
return 1, "error"
|
||||
|
||||
monkeypatch.setattr(systemd_mod.command, "run", fake_run)
|
||||
|
||||
result = systemd.enable_units(store, {"new.service"})
|
||||
assert result is False
|
||||
# unchanged
|
||||
assert store["systemd_units"] == {"old.service"}
|
||||
|
||||
|
||||
def test_disable_units_success(monkeypatch, store, systemd):
|
||||
store["systemd_units"] = {"old.service", "new.service"}
|
||||
|
||||
def fake_run(cmd):
|
||||
assert cmd[0] == "systemctl"
|
||||
assert cmd[1] == "disable"
|
||||
assert "new.service" in cmd[2:]
|
||||
return 0, "ok"
|
||||
|
||||
monkeypatch.setattr(systemd_mod.command, "run", fake_run)
|
||||
|
||||
result = systemd.disable_units(store, {"new.service"})
|
||||
assert result is True
|
||||
assert store["systemd_units"] == {"old.service"}
|
||||
|
||||
|
||||
def test_disable_units_failure_does_not_update_store(monkeypatch, store, systemd):
|
||||
store["systemd_units"] = {"old.service", "new.service"}
|
||||
|
||||
def fake_run(cmd):
|
||||
return 1, "error"
|
||||
|
||||
monkeypatch.setattr(systemd_mod.command, "run", fake_run)
|
||||
|
||||
result = systemd.disable_units(store, {"new.service"})
|
||||
assert result is False
|
||||
assert store["systemd_units"] == {"old.service", "new.service"}
|
||||
|
||||
|
||||
def test_enable_user_units_success(monkeypatch, store, systemd):
|
||||
store["systemd_user_units"] = {"alice": {"olduser.service"}}
|
||||
|
||||
def fake_run(cmd):
|
||||
assert cmd[0] == "systemctl"
|
||||
assert "--user" in cmd
|
||||
assert "enable" in cmd
|
||||
assert "newuser.service" in cmd
|
||||
return 0, "ok"
|
||||
|
||||
monkeypatch.setattr(systemd_mod.command, "run", fake_run)
|
||||
|
||||
result = systemd.enable_user_units(store, {"newuser.service"}, "alice")
|
||||
assert result is True
|
||||
assert store["systemd_user_units"]["alice"] == {
|
||||
"olduser.service",
|
||||
"newuser.service",
|
||||
}
|
||||
|
||||
|
||||
def test_enable_user_units_failure_does_not_update_store(monkeypatch, store, systemd):
|
||||
store["systemd_user_units"] = {"alice": {"olduser.service"}}
|
||||
|
||||
def fake_run(cmd):
|
||||
return 1, "error"
|
||||
|
||||
monkeypatch.setattr(systemd_mod.command, "run", fake_run)
|
||||
|
||||
result = systemd.enable_user_units(store, {"newuser.service"}, "alice")
|
||||
assert result is False
|
||||
assert store["systemd_user_units"]["alice"] == {"olduser.service"}
|
||||
|
||||
|
||||
def test_disable_user_units_success(monkeypatch, store, systemd):
|
||||
store["systemd_user_units"] = {"alice": {"olduser.service", "newuser.service"}}
|
||||
|
||||
def fake_run(cmd):
|
||||
assert cmd[0] == "systemctl"
|
||||
assert "--user" in cmd
|
||||
assert "disable" in cmd
|
||||
assert "newuser.service" in cmd
|
||||
return 0, "ok"
|
||||
|
||||
monkeypatch.setattr(systemd_mod.command, "run", fake_run)
|
||||
|
||||
result = systemd.disable_user_units(store, {"newuser.service"}, "alice")
|
||||
assert result is True
|
||||
assert store["systemd_user_units"]["alice"] == {"olduser.service"}
|
||||
|
||||
|
||||
def test_disable_user_units_failure_does_not_update_store(monkeypatch, store, systemd):
|
||||
store["systemd_user_units"] = {"alice": {"olduser.service", "newuser.service"}}
|
||||
|
||||
def fake_run(cmd):
|
||||
return 1, "error"
|
||||
|
||||
monkeypatch.setattr(systemd_mod.command, "run", fake_run)
|
||||
|
||||
result = systemd.disable_user_units(store, {"newuser.service"}, "alice")
|
||||
assert result is False
|
||||
assert store["systemd_user_units"]["alice"] == {
|
||||
"olduser.service",
|
||||
"newuser.service",
|
||||
}
|
||||
|
||||
|
||||
def test_reload_daemon_uses_command_run(monkeypatch, systemd):
|
||||
called = {}
|
||||
|
||||
def fake_run(cmd):
|
||||
called["cmd"] = cmd
|
||||
return 0, "ok"
|
||||
|
||||
monkeypatch.setattr(systemd_mod.command, "run", fake_run)
|
||||
result = systemd.reload_daemon()
|
||||
assert result is True
|
||||
assert called["cmd"][:2] == ["systemctl", "daemon-reload"]
|
||||
|
||||
|
||||
def test_reload_user_daemon_uses_command_run(monkeypatch, systemd):
|
||||
called = {}
|
||||
|
||||
def fake_run(cmd):
|
||||
called["cmd"] = cmd
|
||||
return 0, "ok"
|
||||
|
||||
monkeypatch.setattr(systemd_mod.command, "run", fake_run)
|
||||
result = systemd.reload_user_daemon("alice")
|
||||
assert result is True
|
||||
cmd = called["cmd"]
|
||||
assert cmd[0] == "systemctl"
|
||||
assert "--user" in cmd
|
||||
assert "daemon-reload" in cmd
|
||||
Reference in New Issue
Block a user