Compare commits

...

2 Commits

Author SHA1 Message Date
d98211e5e5 add possibility to run full update
In case if packages are not set from web, the spawner will run full
repository update
2022-10-31 02:41:24 +02:00
b97c8928e1 add daemon subcommand
This command emulates default systemd timer and can be useful in docker
container in order to run 24/7
2022-10-31 01:38:01 +02:00
11 changed files with 155 additions and 26 deletions

View File

@ -27,7 +27,7 @@
<button id="add-btn" class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#add-form" hidden> <button id="add-btn" class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#add-form" hidden>
<i class="bi bi-plus"></i> add <i class="bi bi-plus"></i> add
</button> </button>
<button id="update-btn" class="btn btn-secondary" onclick="updatePackages()" disabled hidden> <button id="update-btn" class="btn btn-secondary" onclick="updatePackages()" hidden>
<i class="bi bi-play"></i> update <i class="bi bi-play"></i> update
</button> </button>
<button id="remove-btn" class="btn btn-danger" onclick="removePackages()" disabled hidden> <button id="remove-btn" class="btn btn-danger" onclick="removePackages()" disabled hidden>

View File

@ -7,7 +7,6 @@
table.on("check.bs.table uncheck.bs.table check-all.bs.table uncheck-all.bs.table", table.on("check.bs.table uncheck.bs.table check-all.bs.table uncheck-all.bs.table",
() => { () => {
removeButton.prop("disabled", !table.bootstrapTable("getSelections").length); removeButton.prop("disabled", !table.bootstrapTable("getSelections").length);
updateButton.prop("disabled", !table.bootstrapTable("getSelections").length);
}); });
const architectureBadge = $("#badge-architecture"); const architectureBadge = $("#badge-architecture");
@ -16,8 +15,6 @@
const versionBadge = $("#badge-version"); const versionBadge = $("#badge-version");
function doPackageAction(uri, packages) { function doPackageAction(uri, packages) {
if (packages.length === 0)
return;
$.ajax({ $.ajax({
url: uri, url: uri,
data: JSON.stringify({packages: packages}), data: JSON.stringify({packages: packages}),

View File

@ -82,6 +82,7 @@ def _parser() -> argparse.ArgumentParser:
subparsers = parser.add_subparsers(title="command", help="command to run", dest="command", required=True) subparsers = parser.add_subparsers(title="command", help="command to run", dest="command", required=True)
_set_aur_search_parser(subparsers) _set_aur_search_parser(subparsers)
_set_daemon_parser(subparsers)
_set_help_parser(subparsers) _set_help_parser(subparsers)
_set_help_commands_unsafe_parser(subparsers) _set_help_commands_unsafe_parser(subparsers)
_set_key_import_parser(subparsers) _set_key_import_parser(subparsers)
@ -141,6 +142,28 @@ def _set_aur_search_parser(root: SubParserAction) -> argparse.ArgumentParser:
return parser 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: def _set_help_parser(root: SubParserAction) -> argparse.ArgumentParser:
""" """
add parser for listing help subcommand add parser for listing help subcommand

View File

@ -22,6 +22,7 @@ from ahriman.application.handlers.handler import Handler
from ahriman.application.handlers.add import Add from ahriman.application.handlers.add import Add
from ahriman.application.handlers.backup import Backup from ahriman.application.handlers.backup import Backup
from ahriman.application.handlers.clean import Clean 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.dump import Dump
from ahriman.application.handlers.help import Help from ahriman.application.handlers.help import Help
from ahriman.application.handlers.key_import import KeyImport from ahriman.application.handlers.key_import import KeyImport

View File

@ -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 <http://www.gnu.org/licenses/>.
#
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()

View File

@ -86,10 +86,12 @@ class Spawn(Thread, LazyLogging):
packages(Iterable[str]): packages list to add packages(Iterable[str]): packages list to add
now(bool): build packages now now(bool): build packages now
""" """
if not packages:
return self.spawn_process("repo-update")
kwargs = {"source": PackageSource.AUR.value} # avoid abusing by building non-aur packages kwargs = {"source": PackageSource.AUR.value} # avoid abusing by building non-aur packages
if now: if now:
kwargs["now"] = "" kwargs["now"] = ""
self.spawn_process("add", *packages, **kwargs) return self.spawn_process("package-add", *packages, **kwargs)
def packages_remove(self, packages: Iterable[str]) -> None: def packages_remove(self, packages: Iterable[str]) -> None:
""" """
@ -98,7 +100,7 @@ class Spawn(Thread, LazyLogging):
Args: Args:
packages(Iterable[str]): packages list to remove packages(Iterable[str]): packages list to remove
""" """
self.spawn_process("remove", *packages) self.spawn_process("package-remove", *packages)
def spawn_process(self, command: str, *args: str, **kwargs: str) -> None: def spawn_process(self, command: str, *args: str, **kwargs: str) -> None:
""" """

View File

@ -17,7 +17,7 @@
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
# #
from aiohttp.web import HTTPBadRequest, HTTPFound from aiohttp.web import HTTPFound
from ahriman.models.user_access import UserAccess from ahriman.models.user_access import UserAccess
from ahriman.web.views.base import BaseView from ahriman.web.views.base import BaseView
@ -47,11 +47,8 @@ class AddView(BaseView):
HTTPBadRequest: if bad data is supplied HTTPBadRequest: if bad data is supplied
HTTPFound: in case of success response HTTPFound: in case of success response
""" """
try: data = await self.extract_data(["packages"])
data = await self.extract_data(["packages"]) packages = data.get("packages", [])
packages = data["packages"]
except Exception as e:
raise HTTPBadRequest(reason=str(e))
self.spawner.packages_add(packages, now=True) self.spawner.packages_add(packages, now=True)

View File

@ -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()

View File

@ -65,6 +65,26 @@ def test_subparsers_aur_search_architecture(parser: argparse.ArgumentParser) ->
assert args.architecture == [""] 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: def test_subparsers_help(parser: argparse.ArgumentParser) -> None:
""" """
help command must imply architecture list, lock, no-report, quiet, unsafe and parser help command must imply architecture list, lock, no-report, quiet, unsafe and parser

View File

@ -42,7 +42,7 @@ def test_packages_add(spawner: Spawn, mocker: MockerFixture) -> None:
""" """
spawn_mock = mocker.patch("ahriman.core.spawn.Spawn.spawn_process") spawn_mock = mocker.patch("ahriman.core.spawn.Spawn.spawn_process")
spawner.packages_add(["ahriman", "linux"], now=False) spawner.packages_add(["ahriman", "linux"], now=False)
spawn_mock.assert_called_once_with("add", "ahriman", "linux", source="aur") spawn_mock.assert_called_once_with("package-add", "ahriman", "linux", source="aur")
def test_packages_add_with_build(spawner: Spawn, mocker: MockerFixture) -> None: def test_packages_add_with_build(spawner: Spawn, mocker: MockerFixture) -> None:
@ -51,7 +51,16 @@ def test_packages_add_with_build(spawner: Spawn, mocker: MockerFixture) -> None:
""" """
spawn_mock = mocker.patch("ahriman.core.spawn.Spawn.spawn_process") spawn_mock = mocker.patch("ahriman.core.spawn.Spawn.spawn_process")
spawner.packages_add(["ahriman", "linux"], now=True) spawner.packages_add(["ahriman", "linux"], now=True)
spawn_mock.assert_called_once_with("add", "ahriman", "linux", source="aur", now="") spawn_mock.assert_called_once_with("package-add", "ahriman", "linux", source="aur", now="")
def test_packages_add_update(spawner: Spawn, mocker: MockerFixture) -> None:
"""
must call repo update
"""
spawn_mock = mocker.patch("ahriman.core.spawn.Spawn.spawn_process")
spawner.packages_add([], now=False)
spawn_mock.assert_called_once_with("repo-update")
def test_packages_remove(spawner: Spawn, mocker: MockerFixture) -> None: def test_packages_remove(spawner: Spawn, mocker: MockerFixture) -> None:
@ -60,7 +69,7 @@ def test_packages_remove(spawner: Spawn, mocker: MockerFixture) -> None:
""" """
spawn_mock = mocker.patch("ahriman.core.spawn.Spawn.spawn_process") spawn_mock = mocker.patch("ahriman.core.spawn.Spawn.spawn_process")
spawner.packages_remove(["ahriman", "linux"]) spawner.packages_remove(["ahriman", "linux"])
spawn_mock.assert_called_once_with("remove", "ahriman", "linux") spawn_mock.assert_called_once_with("package-remove", "ahriman", "linux")
def test_spawn_process(spawner: Spawn, mocker: MockerFixture) -> None: def test_spawn_process(spawner: Spawn, mocker: MockerFixture) -> None:

View File

@ -27,17 +27,6 @@ async def test_post(client: TestClient, mocker: MockerFixture) -> None:
add_mock.assert_called_once_with(["ahriman"], now=True) add_mock.assert_called_once_with(["ahriman"], now=True)
async def test_post_exception(client: TestClient, mocker: MockerFixture) -> None:
"""
must raise exception on missing packages payload
"""
add_mock = mocker.patch("ahriman.core.spawn.Spawn.packages_add")
response = await client.post("/api/v1/service/add")
assert response.status == 400
add_mock.assert_not_called()
async def test_post_update(client: TestClient, mocker: MockerFixture) -> None: async def test_post_update(client: TestClient, mocker: MockerFixture) -> None:
""" """
must call post request correctly for alias must call post request correctly for alias