From 2937f556a8dd707e713e201544ba12d77110ad01 Mon Sep 17 00:00:00 2001 From: Evgeniy Alekseev Date: Sun, 28 Mar 2021 16:20:47 +0300 Subject: [PATCH] add sign command (#7) --- src/ahriman/application/ahriman.py | 4 ++ src/ahriman/application/application.py | 32 +++++++++++-- src/ahriman/application/handlers/__init__.py | 1 + src/ahriman/application/handlers/sign.py | 42 ++++++++++++++++ .../application/handlers/test_handler_sign.py | 18 +++++++ tests/ahriman/application/test_application.py | 48 ++++++++++++++++++- 6 files changed, 138 insertions(+), 7 deletions(-) create mode 100644 src/ahriman/application/handlers/sign.py create mode 100644 tests/ahriman/application/handlers/test_handler_sign.py diff --git a/src/ahriman/application/ahriman.py b/src/ahriman/application/ahriman.py index 4fc5d79f..218b04f9 100644 --- a/src/ahriman/application/ahriman.py +++ b/src/ahriman/application/ahriman.py @@ -81,6 +81,10 @@ def _parser() -> argparse.ArgumentParser: report_parser.add_argument("target", help="target to generate report", nargs="*") report_parser.set_defaults(handler=handlers.Report) + sign_parser = subparsers.add_parser("sign", description="(re-)sign packages and repository database") + sign_parser.add_argument("package", help="sign only specified packages", nargs="*") + sign_parser.set_defaults(handler=handlers.Sign) + status_parser = subparsers.add_parser("status", description="request status of the package") status_parser.add_argument("--ahriman", help="get service status itself", action="store_true") status_parser.add_argument("package", help="filter status by package base", nargs="*") diff --git a/src/ahriman/application/application.py b/src/ahriman/application/application.py index 8869e2e7..60646318 100644 --- a/src/ahriman/application/application.py +++ b/src/ahriman/application/application.py @@ -21,7 +21,7 @@ import logging import shutil from pathlib import Path -from typing import Callable, Iterable, List, Optional, Set +from typing import Callable, Iterable, List, Set from ahriman.core.build_tools.task import Task from ahriman.core.configuration import Configuration @@ -67,8 +67,8 @@ class Application: """ generate report and sync to remote server """ - self.report() - self.sync() + self.report([]) + self.sync([]) def get_updates(self, filter_packages: List[str], no_aur: bool, no_manual: bool, no_vcs: bool, log_fn: Callable[[str], None]) -> List[Package]: @@ -162,7 +162,7 @@ class Application: self.repository.process_remove(names) self._finalize() - def report(self, target: Optional[Iterable[str]] = None) -> None: + def report(self, target: Iterable[str]) -> None: """ generate report :param target: list of targets to run (e.g. html) @@ -170,7 +170,29 @@ class Application: targets = target or None self.repository.process_report(targets) - def sync(self, target: Optional[Iterable[str]] = None) -> None: + def sign(self, packages: Iterable[str]) -> None: + """ + sign packages and repository + :param packages: only sign specified packages + """ + # copy to prebuilt directory + for package in self.repository.packages(): + # no one requested this package + if packages and package.base not in packages: + continue + for archive in package.packages.values(): + if archive.filepath is None: + continue # avoid mypy warning + src = self.repository.paths.repository / archive.filepath + dst = self.repository.paths.packages / archive.filepath + shutil.copy(src, dst) + # run generic update function + self.update([]) + # sign repository database if set + self.repository.sign.sign_repository(self.repository.repo.repo_path) + self._finalize() + + def sync(self, target: Iterable[str]) -> None: """ sync to remote server :param target: list of targets to run (e.g. s3) diff --git a/src/ahriman/application/handlers/__init__.py b/src/ahriman/application/handlers/__init__.py index 7422bb01..7691c927 100644 --- a/src/ahriman/application/handlers/__init__.py +++ b/src/ahriman/application/handlers/__init__.py @@ -25,6 +25,7 @@ from ahriman.application.handlers.dump import Dump from ahriman.application.handlers.rebuild import Rebuild from ahriman.application.handlers.remove import Remove from ahriman.application.handlers.report import Report +from ahriman.application.handlers.sign import Sign from ahriman.application.handlers.status import Status from ahriman.application.handlers.sync import Sync from ahriman.application.handlers.update import Update diff --git a/src/ahriman/application/handlers/sign.py b/src/ahriman/application/handlers/sign.py new file mode 100644 index 00000000..5cc60d5c --- /dev/null +++ b/src/ahriman/application/handlers/sign.py @@ -0,0 +1,42 @@ +# +# Copyright (c) 2021 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 + +from typing import Type + +from ahriman.application.application import Application +from ahriman.application.handlers.handler import Handler +from ahriman.core.configuration import Configuration + + +class Sign(Handler): + """ + (re-)sign handler + """ + + @classmethod + def run(cls: Type[Handler], args: argparse.Namespace, architecture: str, config: Configuration) -> None: + """ + callback for command line + :param args: command line args + :param architecture: repository architecture + :param config: configuration instance + """ + Application(architecture, config).sign(args.package) diff --git a/tests/ahriman/application/handlers/test_handler_sign.py b/tests/ahriman/application/handlers/test_handler_sign.py new file mode 100644 index 00000000..38f906f4 --- /dev/null +++ b/tests/ahriman/application/handlers/test_handler_sign.py @@ -0,0 +1,18 @@ +import argparse + +from pytest_mock import MockerFixture + +from ahriman.application.handlers import Sign +from ahriman.core.configuration import Configuration + + +def test_run(args: argparse.Namespace, configuration: Configuration, mocker: MockerFixture) -> None: + """ + must run command + """ + args.package = [] + mocker.patch("pathlib.Path.mkdir") + application_mock = mocker.patch("ahriman.application.application.Application.sign") + + Sign.run(args, "x86_64", configuration) + application_mock.assert_called_once() diff --git a/tests/ahriman/application/test_application.py b/tests/ahriman/application/test_application.py index 752538de..59e0f4fa 100644 --- a/tests/ahriman/application/test_application.py +++ b/tests/ahriman/application/test_application.py @@ -1,3 +1,6 @@ +import pytest + +from pathlib import Path from pytest_mock import MockerFixture from unittest import mock @@ -205,16 +208,57 @@ def test_report(application: Application, mocker: MockerFixture) -> None: must generate report """ executor_mock = mocker.patch("ahriman.core.repository.executor.Executor.process_report") - application.report(None) + application.report([]) executor_mock.assert_called_once() +def test_sign(application: Application, package_ahriman: Package, package_python_schedule: Package, + mocker: MockerFixture) -> None: + """ + must sign world + """ + mocker.patch("ahriman.core.repository.repository.Repository.packages", + return_value=[package_ahriman, package_python_schedule]) + copy_mock = mocker.patch("shutil.copy") + update_mock = mocker.patch("ahriman.application.application.Application.update") + sign_repository_mock = mocker.patch("ahriman.core.sign.gpg.GPG.sign_repository") + finalize_mock = mocker.patch("ahriman.application.application.Application._finalize") + + application.sign([]) + copy_mock.assert_has_calls([ + mock.call(pytest.helpers.anyvar(str), pytest.helpers.anyvar(str)), + mock.call(pytest.helpers.anyvar(str), pytest.helpers.anyvar(str)) + ]) + update_mock.assert_called_with([]) + sign_repository_mock.assert_called_once() + finalize_mock.assert_called_once() + + +def test_sign_specific(application: Application, package_ahriman: Package, package_python_schedule: Package, + mocker: MockerFixture) -> None: + """ + must sign only specified packages + """ + mocker.patch("ahriman.core.repository.repository.Repository.packages", + return_value=[package_ahriman, package_python_schedule]) + copy_mock = mocker.patch("shutil.copy") + update_mock = mocker.patch("ahriman.application.application.Application.update") + sign_repository_mock = mocker.patch("ahriman.core.sign.gpg.GPG.sign_repository") + finalize_mock = mocker.patch("ahriman.application.application.Application._finalize") + + application.sign([package_ahriman.base]) + copy_mock.assert_called_once() + update_mock.assert_called_with([]) + sign_repository_mock.assert_called_once() + finalize_mock.assert_called_once() + + def test_sync(application: Application, mocker: MockerFixture) -> None: """ must sync to remote """ executor_mock = mocker.patch("ahriman.core.repository.executor.Executor.process_sync") - application.sync(None) + application.sync([]) executor_mock.assert_called_once()