From b97c8928e14f279700a4e7154d4406709cd31487 Mon Sep 17 00:00:00 2001 From: Evgeniy Alekseev Date: Mon, 31 Oct 2022 01:38:01 +0200 Subject: [PATCH] add daemon subcommand This command emulates default systemd timer and can be useful in docker container in order to run 24/7 --- src/ahriman/application/ahriman.py | 23 +++++++++ src/ahriman/application/handlers/__init__.py | 1 + src/ahriman/application/handlers/daemon.py | 51 +++++++++++++++++++ .../handlers/test_handler_daemon.py | 40 +++++++++++++++ tests/ahriman/application/test_ahriman.py | 20 ++++++++ 5 files changed, 135 insertions(+) create mode 100644 src/ahriman/application/handlers/daemon.py create mode 100644 tests/ahriman/application/handlers/test_handler_daemon.py diff --git a/src/ahriman/application/ahriman.py b/src/ahriman/application/ahriman.py index 7861c971..d36ff462 100644 --- a/src/ahriman/application/ahriman.py +++ b/src/ahriman/application/ahriman.py @@ -82,6 +82,7 @@ def _parser() -> argparse.ArgumentParser: subparsers = parser.add_subparsers(title="command", help="command to run", dest="command", required=True) _set_aur_search_parser(subparsers) + _set_daemon_parser(subparsers) _set_help_parser(subparsers) _set_help_commands_unsafe_parser(subparsers) _set_key_import_parser(subparsers) @@ -141,6 +142,28 @@ def _set_aur_search_parser(root: SubParserAction) -> argparse.ArgumentParser: return parser +def _set_daemon_parser(root: SubParserAction) -> argparse.ArgumentParser: + """ + add parser for daemon subcommand + + Args: + root(SubParserAction): subparsers for the commands + + Returns: + argparse.ArgumentParser: created argument parser + """ + parser = root.add_parser("daemon", help="run application as daemon", + description="start process which periodically will run update process", + formatter_class=_formatter) + parser.add_argument("-i", "--interval", help="interval between runs in seconds", type=int, default=60 * 60 * 12) + parser.add_argument("--no-aur", help="do not check for AUR updates. Implies --no-vcs", action="store_true") + parser.add_argument("--no-local", help="do not check local packages for updates", action="store_true") + parser.add_argument("--no-manual", help="do not include manual updates", action="store_true") + parser.add_argument("--no-vcs", help="do not check VCS packages", action="store_true") + parser.set_defaults(handler=handlers.Daemon, dry_run=False, exit_code=False, package=[]) + return parser + + def _set_help_parser(root: SubParserAction) -> argparse.ArgumentParser: """ add parser for listing help subcommand diff --git a/src/ahriman/application/handlers/__init__.py b/src/ahriman/application/handlers/__init__.py index 6c72ad46..ebba3604 100644 --- a/src/ahriman/application/handlers/__init__.py +++ b/src/ahriman/application/handlers/__init__.py @@ -22,6 +22,7 @@ from ahriman.application.handlers.handler import Handler from ahriman.application.handlers.add import Add from ahriman.application.handlers.backup import Backup from ahriman.application.handlers.clean import Clean +from ahriman.application.handlers.daemon import Daemon from ahriman.application.handlers.dump import Dump from ahriman.application.handlers.help import Help from ahriman.application.handlers.key_import import KeyImport diff --git a/src/ahriman/application/handlers/daemon.py b/src/ahriman/application/handlers/daemon.py new file mode 100644 index 00000000..ac96d95d --- /dev/null +++ b/src/ahriman/application/handlers/daemon.py @@ -0,0 +1,51 @@ +# +# Copyright (c) 2021-2022 ahriman team. +# +# This file is part of ahriman +# (see https://github.com/arcan1s/ahriman). +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +# +import argparse +import threading + +from typing import Type + +from ahriman.application.handlers import Handler +from ahriman.core.configuration import Configuration + + +class Daemon(Handler): + """ + daemon packages handler + """ + + @classmethod + def run(cls: Type[Handler], args: argparse.Namespace, architecture: str, + configuration: Configuration, no_report: bool, unsafe: bool) -> None: + """ + callback for command line + + Args: + args(argparse.Namespace): command line args + architecture(str): repository architecture + configuration(Configuration): configuration instance + no_report(bool): force disable reporting + unsafe(bool): if set no user check will be performed before path creation + """ + from ahriman.application.handlers import Update + Update.run(args, architecture, configuration, no_report, unsafe) + timer = threading.Timer(args.interval, Daemon.run, (args, architecture, configuration, no_report, unsafe)) + timer.start() + timer.join() diff --git a/tests/ahriman/application/handlers/test_handler_daemon.py b/tests/ahriman/application/handlers/test_handler_daemon.py new file mode 100644 index 00000000..dd02ae6a --- /dev/null +++ b/tests/ahriman/application/handlers/test_handler_daemon.py @@ -0,0 +1,40 @@ +import argparse + +from pytest_mock import MockerFixture + +from ahriman.application.handlers import Daemon +from ahriman.core.configuration import Configuration + + +def _default_args(args: argparse.Namespace) -> argparse.Namespace: + """ + default arguments for these test cases + + Args: + args(argparse.Namespace): command line arguments fixture + + Returns: + argparse.Namespace: generated arguments for these test cases + """ + args.interval = 60 * 60 * 12 + args.no_aur = False + args.no_local = False + args.no_manual = False + args.no_vcs = False + return args + + +def test_run(args: argparse.Namespace, configuration: Configuration, mocker: MockerFixture) -> None: + """ + must run command + """ + args = _default_args(args) + run_mock = mocker.patch("ahriman.application.handlers.Update.run") + start_mock = mocker.patch("threading.Timer.start") + join_mock = mocker.patch("threading.Timer.join") + + Daemon.run(args, "x86_64", configuration, True, False) + Daemon._SHOULD_RUN = False + run_mock.assert_called_once_with(args, "x86_64", configuration, True, False) + start_mock.assert_called_once_with() + join_mock.assert_called_once_with() diff --git a/tests/ahriman/application/test_ahriman.py b/tests/ahriman/application/test_ahriman.py index e2d607bd..eee20540 100644 --- a/tests/ahriman/application/test_ahriman.py +++ b/tests/ahriman/application/test_ahriman.py @@ -65,6 +65,26 @@ def test_subparsers_aur_search_architecture(parser: argparse.ArgumentParser) -> assert args.architecture == [""] +def test_subparsers_daemon(parser: argparse.ArgumentParser) -> None: + """ + daemon command must imply dry run, exit code and package + """ + args = parser.parse_args(["daemon"]) + assert not args.dry_run + assert not args.exit_code + assert args.package == [] + + +def test_subparsers_daemon_option_interval(parser: argparse.ArgumentParser) -> None: + """ + daemon command must convert interval option to int instance + """ + args = parser.parse_args(["daemon"]) + assert isinstance(args.interval, int) + args = parser.parse_args(["daemon", "--interval", "10"]) + assert isinstance(args.interval, int) + + def test_subparsers_help(parser: argparse.ArgumentParser) -> None: """ help command must imply architecture list, lock, no-report, quiet, unsafe and parser