*: Use privileged decorator for service actions

Tests:

- DONE: Unit tests work
- DONE: Transmission
  - DONE: Enabling/disabling an app with a daemon works: transmission
  - DONE: Showing the status of whether the app is enabled with daemon
    is-enabled works.
  - DONE: A message is shown if app is enabled and service is not running
  - DONE: Service is stopped and re-started during backup
  - DONE: Adding user to share group during initial setup restarts the service
- Not tested: Enabling/disabling a service with alias works (no such apps)
- DONE: Restarting/try-restarting a service works
- DONE: Masking/unmasking works
  - DONE: rsyslog is masked after initial setup
  - DONE: systemd-journald is try-restarted during initial setup
- DONE: Avahi, email, security initial setup works
  - DONE: Fail2ban is unmasked and enabled
- DONE: Enabling/disabling fail2ban is security app works
- DONE: Enabling/disabling password authentication in SSH works
- ?? Let's encrypt
  - Services are try-restarted during certificate setup, obtain, renew
- Not tested: upgrade pagekite from version 1

Signed-off-by: Sunil Mohan Adapa <sunil@medhas.org>
Reviewed-by: James Valleroy <jvalleroy@mailbox.org>
This commit is contained in:
Sunil Mohan Adapa 2022-09-03 08:25:57 -07:00 committed by James Valleroy
parent 1dcbfce713
commit 222563a482
No known key found for this signature in database
GPG Key ID: 77C0C75E7B650808
16 changed files with 229 additions and 181 deletions

View File

@ -1,130 +0,0 @@
#!/usr/bin/python3
# SPDX-License-Identifier: AGPL-3.0-or-later
"""
Wrapper to list and handle system services
"""
import argparse
import os
from plinth import action_utils
from plinth import app as app_module
from plinth import cfg, module_loader
from plinth.daemon import Daemon, RelatedDaemon
cfg.read()
module_config_path = os.path.join(cfg.config_dir, 'modules-enabled')
def add_service_action(subparsers, action, help):
parser = subparsers.add_parser(action, help=help)
parser.add_argument('service', help='name of the service')
def parse_arguments():
"""Return parsed command line arguments as dictionary."""
parser = argparse.ArgumentParser()
subparsers = parser.add_subparsers(dest='subcommand', help='Sub command')
add_service_action(subparsers, 'start', 'start a service')
add_service_action(subparsers, 'stop', 'stop a service')
add_service_action(subparsers, 'enable', 'enable a service')
add_service_action(subparsers, 'disable', 'disable a service')
add_service_action(subparsers, 'restart', 'restart a service')
add_service_action(subparsers, 'try-restart',
'restart a service if running')
add_service_action(subparsers, 'reload', 'reload a service')
add_service_action(subparsers, 'is-running', 'status of a service')
add_service_action(subparsers, 'is-enabled', 'status a service')
add_service_action(subparsers, 'mask', 'unmask a service')
add_service_action(subparsers, 'unmask', 'unmask a service')
subparsers.required = True
return parser.parse_args()
def subcommand_start(arguments):
action_utils.service_start(arguments.service)
def subcommand_stop(arguments):
action_utils.service_stop(arguments.service)
def subcommand_enable(arguments):
action_utils.service_enable(arguments.service)
def subcommand_disable(arguments):
action_utils.service_disable(arguments.service)
def subcommand_restart(arguments):
action_utils.service_restart(arguments.service)
def subcommand_try_restart(arguments):
action_utils.service_try_restart(arguments.service)
def subcommand_reload(arguments):
action_utils.service_reload(arguments.service)
def subcommand_mask(arguments):
action_utils.service_mask(arguments.service)
def subcommand_unmask(arguments):
action_utils.service_unmask(arguments.service)
def subcommand_is_enabled(arguments):
print(action_utils.service_is_enabled(arguments.service))
def subcommand_is_running(arguments):
print(action_utils.service_is_running(arguments.service))
def _get_managed_services():
"""Get a set of all services managed by FreedomBox."""
services = set()
module_loader.load_modules()
app_module.apps_init()
for app in app_module.App.list():
components = app.get_components_of_type(Daemon)
for component in components:
services.add(component.unit)
if component.alias:
services.add(component.alias)
components = app.get_components_of_type(RelatedDaemon)
for component in components:
services.add(component.unit)
return services
def _assert_service_is_managed_by_plinth(service_name):
managed_services = _get_managed_services()
if service_name not in managed_services:
msg = ("The service '%s' is not managed by FreedomBox. Access is only "
"permitted for services listed in the 'managed_services' "
"variable of any FreedomBox app.") % service_name
raise ValueError(msg)
def main():
"""Parse arguments and perform all duties."""
arguments = parse_arguments()
subcommand = arguments.subcommand.replace('-', '_')
subcommand_method = globals()['subcommand_' + subcommand]
if hasattr(arguments, 'service'):
_assert_service_is_managed_by_plinth(arguments.service)
subcommand_method(arguments)
if __name__ == '__main__':
main()

View File

@ -1,7 +1,5 @@
# SPDX-License-Identifier: AGPL-3.0-or-later # SPDX-License-Identifier: AGPL-3.0-or-later
""" """Component for managing a background daemon or any systemd unit."""
Component for managing a background daemon or any systemd unit.
"""
import socket import socket
import subprocess import subprocess
@ -11,7 +9,7 @@ from django.utils.text import format_lazy
from django.utils.translation import gettext as _ from django.utils.translation import gettext as _
from django.utils.translation import gettext_lazy from django.utils.translation import gettext_lazy
from plinth import action_utils, actions, app from plinth import action_utils, app
class Daemon(app.LeaderComponent): class Daemon(app.LeaderComponent):
@ -70,15 +68,17 @@ class Daemon(app.LeaderComponent):
def enable(self): def enable(self):
"""Run operations to enable the daemon/unit.""" """Run operations to enable the daemon/unit."""
actions.superuser_run('service', ['enable', self.unit]) from plinth.privileged import service as service_privileged
service_privileged.enable(self.unit)
if self.alias: if self.alias:
actions.superuser_run('service', ['enable', self.alias]) service_privileged.enable(self.alias)
def disable(self): def disable(self):
"""Run operations to disable the daemon/unit.""" """Run operations to disable the daemon/unit."""
actions.superuser_run('service', ['disable', self.unit]) from plinth.privileged import service as service_privileged
service_privileged.disable(self.unit)
if self.alias: if self.alias:
actions.superuser_run('service', ['disable', self.alias]) service_privileged.disable(self.alias)
def is_running(self): def is_running(self):
"""Return whether the daemon/unit is running.""" """Return whether the daemon/unit is running."""

View File

@ -1,11 +1,8 @@
# SPDX-License-Identifier: AGPL-3.0-or-later # SPDX-License-Identifier: AGPL-3.0-or-later
""" """FreedomBox app for service discovery."""
FreedomBox app for service discovery.
"""
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from plinth import actions
from plinth import app as app_module from plinth import app as app_module
from plinth import cfg, menu from plinth import cfg, menu
from plinth.daemon import Daemon from plinth.daemon import Daemon
@ -14,6 +11,7 @@ from plinth.modules.config import get_hostname
from plinth.modules.firewall.components import Firewall from plinth.modules.firewall.components import Firewall
from plinth.modules.names.components import DomainType from plinth.modules.names.components import DomainType
from plinth.package import Packages from plinth.package import Packages
from plinth.privileged import service as service_privileged
from plinth.signals import domain_added, domain_removed, post_hostname_change from plinth.signals import domain_added, domain_removed, post_hostname_change
from plinth.utils import format_lazy from plinth.utils import format_lazy
@ -90,7 +88,7 @@ class AvahiApp(app_module.App):
# Reload avahi-daemon now that first-run does not reboot. After # Reload avahi-daemon now that first-run does not reboot. After
# performing FreedomBox Service (Plinth) package installation, new # performing FreedomBox Service (Plinth) package installation, new
# Avahi files will be available and require restart. # Avahi files will be available and require restart.
actions.superuser_run('service', ['reload', 'avahi-daemon']) service_privileged.reload('avahi-daemon')
self.enable() self.enable()

View File

@ -12,10 +12,11 @@ TODO:
import logging import logging
from plinth import action_utils, actions from plinth import action_utils
from plinth import app as app_module from plinth import app as app_module
from plinth import setup from plinth import setup
from plinth.modules.apache import privileged as apache_privileged from plinth.modules.apache import privileged as apache_privileged
from plinth.privileged import service as service_privileged
from .components import BackupRestore from .components import BackupRestore
@ -318,12 +319,12 @@ class SystemServiceHandler(ServiceHandler):
"""Stop the service.""" """Stop the service."""
self.was_running = action_utils.service_is_running(self.service) self.was_running = action_utils.service_is_running(self.service)
if self.was_running: if self.was_running:
actions.superuser_run('service', ['stop', self.service]) service_privileged.stop(self.service)
def restart(self): def restart(self):
"""Restart the service if it was earlier running.""" """Restart the service if it was earlier running."""
if self.was_running: if self.was_running:
actions.superuser_run('service', ['start', self.service]) service_privileged.start(self.service)
class ApacheServiceHandler(ServiceHandler): class ApacheServiceHandler(ServiceHandler):

View File

@ -13,6 +13,7 @@ from plinth.modules.apache import (get_users_with_website, user_of_uws_url,
uws_url_of_user) uws_url_of_user)
from plinth.modules.names.components import DomainType from plinth.modules.names.components import DomainType
from plinth.package import Packages from plinth.package import Packages
from plinth.privileged import service as service_privileged
from plinth.signals import domain_added from plinth.signals import domain_added
from . import privileged from . import privileged
@ -82,14 +83,14 @@ class ConfigApp(app_module.App):
# systemd-journald is socket activated, it may not be running and it # systemd-journald is socket activated, it may not be running and it
# does not support reload. # does not support reload.
actions.superuser_run('service', ['try-restart', 'systemd-journald']) service_privileged.try_restart('systemd-journald')
# rsyslog when enabled, is activated by syslog.socket (shipped by # rsyslog when enabled, is activated by syslog.socket (shipped by
# systemd). See: # systemd). See:
# https://www.freedesktop.org/wiki/Software/systemd/syslog/ . # https://www.freedesktop.org/wiki/Software/systemd/syslog/ .
actions.superuser_run('service', ['disable', 'rsyslog']) service_privileged.disable('rsyslog')
# Ensure that rsyslog is not started by something else as it is # Ensure that rsyslog is not started by something else as it is
# installed by default on Debian systems. # installed by default on Debian systems.
actions.superuser_run('service', ['mask', 'rsyslog']) service_privileged.mask('rsyslog')
def get_domainname(): def get_domainname():

View File

@ -7,7 +7,7 @@ from django.urls import reverse_lazy
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
import plinth.app import plinth.app
from plinth import actions, cfg, frontpage, menu from plinth import cfg, frontpage, menu
from plinth.daemon import Daemon from plinth.daemon import Daemon
from plinth.modules.apache.components import Webserver from plinth.modules.apache.components import Webserver
from plinth.modules.backups.components import BackupRestore from plinth.modules.backups.components import BackupRestore
@ -15,6 +15,7 @@ from plinth.modules.config import get_domainname
from plinth.modules.firewall.components import Firewall from plinth.modules.firewall.components import Firewall
from plinth.modules.letsencrypt.components import LetsEncrypt from plinth.modules.letsencrypt.components import LetsEncrypt
from plinth.package import Packages, uninstall from plinth.package import Packages, uninstall
from plinth.privileged import service as service_privileged
from plinth.signals import domain_added, domain_removed from plinth.signals import domain_added, domain_removed
from plinth.utils import format_lazy from plinth.utils import format_lazy
@ -189,9 +190,9 @@ class EmailApp(plinth.app.App):
privileged.setup_spam() privileged.setup_spam()
# Restart daemons # Restart daemons
actions.superuser_run('service', ['try-restart', 'postfix']) service_privileged.try_restart('postfix')
actions.superuser_run('service', ['try-restart', 'dovecot']) service_privileged.try_restart('dovecot')
actions.superuser_run('service', ['try-restart', 'rspamd']) service_privileged.try_restart('rspamd')
# Expose to public internet # Expose to public internet
if old_version == 0: if old_version == 0:

View File

@ -5,8 +5,9 @@ import logging
import pathlib import pathlib
import threading import threading
from plinth import actions, app from plinth import app
from plinth.modules.names.components import DomainName from plinth.modules.names.components import DomainName
from plinth.privileged import service as service_privileged
from . import privileged from . import privileged
@ -168,7 +169,7 @@ class LetsEncrypt(app.FollowerComponent):
self._copy_self_signed_certificates([domain]) self._copy_self_signed_certificates([domain])
for daemon in self.daemons: for daemon in self.daemons:
actions.superuser_run('service', ['try-restart', daemon]) service_privileged.try_restart(daemon)
def get_status(self): def get_status(self):
"""Return the status of certificates for all interested domains. """Return the status of certificates for all interested domains.
@ -213,7 +214,7 @@ class LetsEncrypt(app.FollowerComponent):
self._copy_letsencrypt_certificates(interested_domains, lineage) self._copy_letsencrypt_certificates(interested_domains, lineage)
for daemon in self.daemons: for daemon in self.daemons:
actions.superuser_run('service', ['try-restart', daemon]) service_privileged.try_restart(daemon)
def on_certificate_renewed(self, domains, lineage): def on_certificate_renewed(self, domains, lineage):
"""Handle event when a certificate is renewed. """Handle event when a certificate is renewed.
@ -247,7 +248,7 @@ class LetsEncrypt(app.FollowerComponent):
self._copy_self_signed_certificates(interested_domains) self._copy_self_signed_certificates(interested_domains)
for daemon in self.daemons: for daemon in self.daemons:
actions.superuser_run('service', ['try-restart', daemon]) service_privileged.try_restart(daemon)
def on_certificate_deleted(self, domains, lineage): def on_certificate_deleted(self, domains, lineage):
"""Handle event when a certificate is deleted. """Handle event when a certificate is deleted.

View File

@ -9,6 +9,7 @@ from plinth.daemon import Daemon
from plinth.modules.backups.components import BackupRestore from plinth.modules.backups.components import BackupRestore
from plinth.modules.names.components import DomainType from plinth.modules.names.components import DomainType
from plinth.package import Packages from plinth.package import Packages
from plinth.privileged import service as service_privileged
from plinth.utils import format_lazy from plinth.utils import format_lazy
from . import manifest, utils from . import manifest, utils
@ -103,5 +104,4 @@ class PagekiteApp(app_module.App):
self.enable() self.enable()
if old_version == 1: if old_version == 1:
actions.superuser_run('service', service_privileged.try_restart(PagekiteApp.DAEMON)
['try-restart', PagekiteApp.DAEMON])

View File

@ -7,12 +7,12 @@ from collections import defaultdict
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from plinth import actions
from plinth import app as app_module from plinth import app as app_module
from plinth import menu from plinth import menu
from plinth.daemon import Daemon, RelatedDaemon from plinth.daemon import Daemon, RelatedDaemon
from plinth.modules.backups.components import BackupRestore from plinth.modules.backups.components import BackupRestore
from plinth.package import Packages from plinth.package import Packages
from plinth.privileged import service as service_privileged
from . import manifest, privileged from . import manifest, privileged
@ -55,7 +55,7 @@ class SecurityApp(app_module.App):
if not old_version: if not old_version:
enable_fail2ban() enable_fail2ban()
actions.superuser_run('service', ['reload', 'fail2ban']) service_privileged.reload('fail2ban')
# Migrate to new config file. # Migrate to new config file.
enabled = privileged.get_restricted_access_enabled() enabled = privileged.get_restricted_access_enabled()
@ -66,8 +66,8 @@ class SecurityApp(app_module.App):
def enable_fail2ban(): def enable_fail2ban():
"""Unmask, enable and run the fail2ban service.""" """Unmask, enable and run the fail2ban service."""
actions.superuser_run('service', ['unmask', 'fail2ban']) service_privileged.unmask('fail2ban')
actions.superuser_run('service', ['enable', 'fail2ban']) service_privileged.enable('fail2ban')
def set_restricted_access(enabled): def set_restricted_access(enabled):

View File

@ -5,9 +5,10 @@ from django.contrib import messages
from django.template.response import TemplateResponse from django.template.response import TemplateResponse
from django.utils.translation import gettext as _ from django.utils.translation import gettext as _
from plinth import action_utils, actions from plinth import action_utils
from plinth.modules import security from plinth.modules import security
from plinth.modules.upgrades import is_backports_requested from plinth.modules.upgrades import is_backports_requested
from plinth.privileged import service as service_privileged
from plinth.views import AppView from plinth.views import AppView
from . import privileged from . import privileged
@ -63,9 +64,9 @@ def _apply_changes(request, old_status, new_status):
if old_status['fail2ban_enabled'] != new_status['fail2ban_enabled']: if old_status['fail2ban_enabled'] != new_status['fail2ban_enabled']:
if new_status['fail2ban_enabled']: if new_status['fail2ban_enabled']:
actions.superuser_run('service', ['enable', 'fail2ban']) service_privileged.enable('fail2ban')
else: else:
actions.superuser_run('service', ['disable', 'fail2ban']) service_privileged.disable('fail2ban')
def report(request): def report(request):

View File

@ -4,8 +4,8 @@
from django.contrib import messages from django.contrib import messages
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from plinth import actions
from plinth.modules import ssh from plinth.modules import ssh
from plinth.privileged import service as service_privileged
from plinth.views import AppView from plinth.views import AppView
from . import privileged from . import privileged
@ -48,7 +48,7 @@ class SshAppView(AppView):
if passwd_auth_changed: if passwd_auth_changed:
privileged.set_password_authentication( privileged.set_password_authentication(
not new_config['password_auth_disabled']) not new_config['password_auth_disabled'])
actions.superuser_run('service', ['reload', 'ssh']) service_privileged.reload('ssh')
messages.success(self.request, _('Configuration updated')) messages.success(self.request, _('Configuration updated'))
return super().form_valid(form) return super().form_valid(form)

View File

@ -7,11 +7,11 @@ import subprocess
from django.utils.text import format_lazy from django.utils.text import format_lazy
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from plinth import actions
from plinth import app as app_module from plinth import app as app_module
from plinth import cfg, menu from plinth import cfg, menu
from plinth.daemon import Daemon from plinth.daemon import Daemon
from plinth.package import Packages from plinth.package import Packages
from plinth.privileged import service as service_privileged
from . import privileged from . import privileged
from .components import UsersAndGroups from .components import UsersAndGroups
@ -131,4 +131,4 @@ def add_user_to_share_group(username, service=None):
if username not in group_members: if username not in group_members:
privileged.add_user_to_group(username, 'freedombox-share') privileged.add_user_to_group(username, 'freedombox-share')
if service: if service:
actions.superuser_run('service', ['try-restart', service]) service_privileged.try_restart(service)

View File

@ -10,6 +10,7 @@ from plinth.modules.apache.components import Webserver
from plinth.modules.backups.components import BackupRestore from plinth.modules.backups.components import BackupRestore
from plinth.modules.firewall.components import Firewall from plinth.modules.firewall.components import Firewall
from plinth.package import Packages from plinth.package import Packages
from plinth.privileged import service as service_privileged
from plinth.utils import format_lazy from plinth.utils import format_lazy
from . import manifest, privileged from . import manifest, privileged
@ -106,7 +107,7 @@ class WordPressApp(app_module.App):
self.enable() self.enable()
elif old_version < 3: elif old_version < 3:
# Apply changes to Apache configuration from v2 to v3. # Apply changes to Apache configuration from v2 to v3.
actions.superuser_run('service', ['reload', 'apache2']) service_privileged.reload('apache2')
class WordPressBackupRestore(BackupRestore): class WordPressBackupRestore(BackupRestore):

View File

@ -0,0 +1,10 @@
# SPDX-License-Identifier: AGPL-3.0-or-later
"""Package holding all the privileged actions outside of apps."""
from .service import (disable, enable, is_enabled, is_running, mask, reload,
restart, start, stop, try_restart, unmask)
__all__ = [
'disable', 'enable', 'is_enabled', 'is_running', 'mask', 'reload',
'restart', 'start', 'stop', 'try_restart', 'unmask'
]

View File

@ -0,0 +1,118 @@
# SPDX-License-Identifier: AGPL-3.0-or-later
"""List and handle system services."""
import os
from plinth import action_utils
from plinth import app as app_module
from plinth import cfg, module_loader
from plinth.actions import privileged
from plinth.daemon import Daemon, RelatedDaemon
cfg.read()
module_config_path = os.path.join(cfg.config_dir, 'modules-enabled')
@privileged
def start(service: str):
"""Start a service."""
_assert_service_is_managed_by_plinth(service)
action_utils.service_start(service)
@privileged
def stop(service: str):
"""Stop a running service."""
_assert_service_is_managed_by_plinth(service)
action_utils.service_stop(service)
@privileged
def enable(service: str):
"""Enable a service so that it start on system boot."""
_assert_service_is_managed_by_plinth(service)
action_utils.service_enable(service)
@privileged
def disable(service: str):
"""Disable a service so that it does not start on system boot."""
_assert_service_is_managed_by_plinth(service)
action_utils.service_disable(service)
@privileged
def restart(service: str):
"""Restart a service."""
_assert_service_is_managed_by_plinth(service)
action_utils.service_restart(service)
@privileged
def try_restart(service: str):
"""Restart a service if it is running."""
_assert_service_is_managed_by_plinth(service)
action_utils.service_try_restart(service)
@privileged
def reload(service: str):
"""Reload a service."""
_assert_service_is_managed_by_plinth(service)
action_utils.service_reload(service)
@privileged
def mask(service: str):
"""Mask a service."""
_assert_service_is_managed_by_plinth(service)
action_utils.service_mask(service)
@privileged
def unmask(service: str):
"""Unmask a service."""
_assert_service_is_managed_by_plinth(service)
action_utils.service_unmask(service)
@privileged
def is_enabled(service: str) -> bool:
"""Return whether a service is enabled."""
_assert_service_is_managed_by_plinth(service)
return action_utils.service_is_enabled(service)
@privileged
def is_running(service: str) -> bool:
"""Return whether a service is running."""
_assert_service_is_managed_by_plinth(service)
return action_utils.service_is_running(service)
def _get_managed_services():
"""Get a set of all services managed by FreedomBox."""
services = set()
module_loader.load_modules()
app_module.apps_init()
for app in app_module.App.list():
components = app.get_components_of_type(Daemon)
for component in components:
services.add(component.unit)
if component.alias:
services.add(component.alias)
components = app.get_components_of_type(RelatedDaemon)
for component in components:
services.add(component.unit)
return services
def _assert_service_is_managed_by_plinth(service_name):
managed_services = _get_managed_services()
if service_name not in managed_services:
msg = ("The service '%s' is not managed by FreedomBox. Access is only "
"permitted for services listed in the 'managed_services' "
"variable of any FreedomBox app.") % service_name
raise ValueError(msg)

View File

@ -4,14 +4,23 @@ Test module for component managing system daemons and other systemd units.
""" """
import socket import socket
import subprocess
from unittest.mock import Mock, call, patch from unittest.mock import Mock, call, patch
import pytest import pytest
from plinth.app import App, FollowerComponent from plinth.app import App, FollowerComponent, Info
from plinth.daemon import (Daemon, RelatedDaemon, app_is_running, from plinth.daemon import (Daemon, RelatedDaemon, app_is_running,
diagnose_netcat, diagnose_port_listening) diagnose_netcat, diagnose_port_listening)
privileged_modules_to_mock = ['plinth.privileged.service']
class AppTest(App):
"""Test application that contains a daemon."""
app_id = 'test-app'
@pytest.fixture(name='daemon') @pytest.fixture(name='daemon')
def fixture_daemon(): def fixture_daemon():
@ -19,6 +28,17 @@ def fixture_daemon():
return Daemon('test-daemon', 'test-unit') return Daemon('test-daemon', 'test-unit')
@pytest.fixture(name='app_list')
def fixture_app_list(daemon):
"""A list of apps on which tests are to be run."""
app1 = AppTest()
app1.add(Info('test-app', 1))
app1.add(daemon)
with patch('plinth.app.App.list') as app_list:
app_list.return_value = [app1]
yield app_list
def test_initialization(): def test_initialization():
"""Test that component is initialized properly.""" """Test that component is initialized properly."""
with pytest.raises(ValueError): with pytest.raises(ValueError):
@ -56,25 +76,51 @@ def test_is_enabled(service_is_enabled, daemon):
service_is_enabled.assert_has_calls([call('test-unit', strict_check=True)]) service_is_enabled.assert_has_calls([call('test-unit', strict_check=True)])
@patch('plinth.actions.superuser_run') @patch('subprocess.run')
def test_enable(superuser_run, daemon): @patch('subprocess.call')
def test_enable(subprocess_call, subprocess_run, app_list, mock_privileged,
daemon):
"""Test that enabling the daemon works.""" """Test that enabling the daemon works."""
daemon.enable() daemon.enable()
superuser_run.assert_has_calls([call('service', ['enable', 'test-unit'])]) subprocess_call.assert_has_calls(
[call(['systemctl', 'enable', 'test-unit'])])
subprocess_run.assert_any_call(['systemctl', 'start', 'test-unit'],
stdout=subprocess.DEVNULL, check=False)
subprocess_call.reset_mock()
daemon.alias = 'test-unit-2' daemon.alias = 'test-unit-2'
daemon.enable() daemon.enable()
superuser_run.assert_has_calls([ subprocess_call.assert_has_calls([
call('service', ['enable', 'test-unit']), call(['systemctl', 'enable', 'test-unit']),
call('service', ['enable', 'test-unit-2']) call(['systemctl', 'enable', 'test-unit-2'])
]) ])
subprocess_run.assert_any_call(['systemctl', 'start', 'test-unit'],
stdout=subprocess.DEVNULL, check=False)
subprocess_run.assert_any_call(['systemctl', 'start', 'test-unit-2'],
stdout=subprocess.DEVNULL, check=False)
@patch('plinth.actions.superuser_run') @patch('subprocess.run')
def test_disable(superuser_run, daemon): @patch('subprocess.call')
def test_disable(subprocess_call, subprocess_run, mock_privileged, daemon):
"""Test that disabling the daemon works.""" """Test that disabling the daemon works."""
daemon.disable() daemon.disable()
superuser_run.assert_has_calls([call('service', ['disable', 'test-unit'])]) subprocess_call.assert_has_calls(
[call(['systemctl', 'disable', 'test-unit'])])
subprocess_run.assert_any_call(['systemctl', 'stop', 'test-unit'],
stdout=subprocess.DEVNULL, check=False)
subprocess_call.reset_mock()
daemon.alias = 'test-unit-2'
daemon.disable()
subprocess_call.assert_has_calls([
call(['systemctl', 'disable', 'test-unit']),
call(['systemctl', 'disable', 'test-unit-2'])
])
subprocess_run.assert_any_call(['systemctl', 'stop', 'test-unit'],
stdout=subprocess.DEVNULL, check=False)
subprocess_run.assert_any_call(['systemctl', 'stop', 'test-unit-2'],
stdout=subprocess.DEVNULL, check=False)
@patch('plinth.action_utils.service_is_running') @patch('plinth.action_utils.service_is_running')