feat: add ability to run multiple commands on success

This commit is contained in:
Evgenii Alekseev 2023-10-19 04:34:49 +03:00
parent 6bd1636bfa
commit 16ad96d8c6
5 changed files with 183 additions and 0 deletions

View File

@ -129,6 +129,7 @@ def _parser() -> argparse.ArgumentParser:
_set_service_config_validate_parser(subparsers) _set_service_config_validate_parser(subparsers)
_set_service_key_import_parser(subparsers) _set_service_key_import_parser(subparsers)
_set_service_repositories(subparsers) _set_service_repositories(subparsers)
_set_service_run(subparsers)
_set_service_setup_parser(subparsers) _set_service_setup_parser(subparsers)
_set_service_shell_parser(subparsers) _set_service_shell_parser(subparsers)
_set_service_tree_migrate_parser(subparsers) _set_service_tree_migrate_parser(subparsers)
@ -902,6 +903,27 @@ def _set_service_repositories(root: SubParserAction) -> argparse.ArgumentParser:
return parser return parser
def _set_service_run(root: SubParserAction) -> argparse.ArgumentParser:
"""
add parser for multicommand
Args:
root(SubParserAction): subparsers for the commands
Returns:
argparse.ArgumentParser: created argument parser
"""
parser = root.add_parser("service-run", aliases=["run"], help="run multiple commands",
description="run multiple commands on success run of the previous command",
epilog="Commands must be quoted by using usual bash rules. Processes will be spawned "
"under the same user as this command",
formatter_class=_formatter)
parser.add_argument("command", help="command to be run (quoted) without ``ahriman``", nargs="+")
parser.set_defaults(handler=handlers.Run, architecture="", lock=None, report=False, repository="",
unsafe=True, parser=_parser)
return parser
def _set_service_setup_parser(root: SubParserAction) -> argparse.ArgumentParser: def _set_service_setup_parser(root: SubParserAction) -> argparse.ArgumentParser:
""" """
add parser for setup subcommand add parser for setup subcommand

View File

@ -32,6 +32,7 @@ from ahriman.application.handlers.remove import Remove
from ahriman.application.handlers.remove_unknown import RemoveUnknown from ahriman.application.handlers.remove_unknown import RemoveUnknown
from ahriman.application.handlers.repositories import Repositories from ahriman.application.handlers.repositories import Repositories
from ahriman.application.handlers.restore import Restore from ahriman.application.handlers.restore import Restore
from ahriman.application.handlers.run import Run
from ahriman.application.handlers.search import Search from ahriman.application.handlers.search import Search
from ahriman.application.handlers.service_updates import ServiceUpdates from ahriman.application.handlers.service_updates import ServiceUpdates
from ahriman.application.handlers.setup import Setup from ahriman.application.handlers.setup import Setup

View File

@ -0,0 +1,66 @@
#
# Copyright (c) 2021-2023 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 shlex
from ahriman.application.handlers import Handler
from ahriman.core.configuration import Configuration
from ahriman.models.repository_id import RepositoryId
class Run(Handler):
"""
multicommand handler
"""
ALLOW_MULTI_ARCHITECTURE_RUN = False # system-wide action
@classmethod
def run(cls, args: argparse.Namespace, repository_id: RepositoryId, configuration: Configuration, *,
report: bool) -> None:
"""
callback for command line
Args:
args(argparse.Namespace): command line args
repository_id(RepositoryId): repository unique identifier
configuration(Configuration): configuration instance
report(bool): force enable or disable reporting
"""
parser = args.parser()
for command in args.command:
status = Run.run_command(shlex.split(command), parser)
Run.check_if_empty(True, not status)
@staticmethod
def run_command(command: list[str], parser: argparse.ArgumentParser) -> bool:
"""
run command specified by the argument
Args:
command(list[str]): command to run
parser(argparse.ArgumentParser): generated argument parser
Returns:
bool: status of the command
"""
args = parser.parse_args(command)
handler: Handler = args.handler
return handler.execute(args) == 0

View File

@ -0,0 +1,66 @@
import argparse
import pytest
from pytest_mock import MockerFixture
from ahriman.application.ahriman import _parser
from ahriman.application.handlers import Run
from ahriman.core.configuration import Configuration
from ahriman.core.exceptions import ExitCode
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.command = ["help"]
args.parser = _parser
return args
def test_run(args: argparse.Namespace, configuration: Configuration, mocker: MockerFixture) -> None:
"""
must run command
"""
args = _default_args(args)
application_mock = mocker.patch("ahriman.application.handlers.Run.run_command")
_, repository_id = configuration.check_loaded()
Run.run(args, repository_id, configuration, report=False)
application_mock.assert_called_once_with(["help"], pytest.helpers.anyvar(int))
def test_run_failed(args: argparse.Namespace, configuration: Configuration, mocker: MockerFixture) -> None:
"""
must run commands until success
"""
args = _default_args(args)
args.command = ["help", "config"]
application_mock = mocker.patch("ahriman.application.handlers.Run.run_command", return_value=False)
_, repository_id = configuration.check_loaded()
with pytest.raises(ExitCode):
Run.run(args, repository_id, configuration, report=False)
application_mock.assert_called_once_with(["help"], pytest.helpers.anyvar(int))
def test_run_command(mocker: MockerFixture) -> None:
"""
must correctly run external command
"""
execute_mock = mocker.patch("ahriman.application.handlers.Help.execute")
Run.run_command(["help"], _parser())
execute_mock.assert_called_once_with(pytest.helpers.anyvar(int))
def test_disallow_multi_architecture_run() -> None:
"""
must not allow multi architecture run
"""
assert not Run.ALLOW_MULTI_ARCHITECTURE_RUN

View File

@ -1060,6 +1060,34 @@ def test_subparsers_service_repositories_option_repository(parser: argparse.Argu
assert args.repository == "" assert args.repository == ""
def test_subparsers_service_run(parser: argparse.ArgumentParser) -> None:
"""
service-run command must imply architecture, lock, report, repository and parser
"""
args = parser.parse_args(["service-run", "help"])
assert args.architecture == ""
assert args.lock is None
assert not args.report
assert args.repository == ""
assert args.parser is not None and args.parser()
def test_subparsers_service_run_option_architecture(parser: argparse.ArgumentParser) -> None:
"""
service-run command must correctly parse architecture list
"""
args = parser.parse_args(["-a", "x86_64", "service-run", "help"])
assert args.architecture == ""
def test_subparsers_service_run_option_repository(parser: argparse.ArgumentParser) -> None:
"""
service-run command must correctly parse repository list
"""
args = parser.parse_args(["-r", "repo", "service-run", "help"])
assert args.repository == ""
def test_subparsers_service_setup(parser: argparse.ArgumentParser) -> None: def test_subparsers_service_setup(parser: argparse.ArgumentParser) -> None:
""" """
service-setup command must imply lock, quiet, report and unsafe service-setup command must imply lock, quiet, report and unsafe