diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 00000000..fe574c66 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,51 @@ +FROM archlinux:base-devel + +# image configuration +ENV AHRIMAN_ARCHITECTURE="x86_64" +ENV AHRIMAN_DEBUG="" +ENV AHRIMAN_FORCE_ROOT="" +ENV AHRIMAN_OUTPUT="syslog" +ENV AHRIMAN_PACKAGER="ahriman bot " +ENV AHRIMAN_PORT="" +ENV AHRIMAN_REPOSITORY="aur-clone" +ENV AHRIMAN_REPOSITORY_ROOT="/var/lib/ahriman/ahriman" +ENV AHRIMAN_USER="ahriman" + +# install environment +## install git which is required for AUR interaction and go for yay +RUN pacman --noconfirm -Syu git go +## create build user +RUN useradd -m -d /home/build -s /usr/bin/nologin build && \ + echo "build ALL=(ALL) NOPASSWD: ALL" > /etc/sudoers.d/build +## install AUR helper +RUN YAY_DIR="$(runuser -u build -- mktemp -d)" && \ + git clone https://aur.archlinux.org/yay.git "$YAY_DIR" && \ + cd "$YAY_DIR" && \ + runuser -u build -- makepkg --noconfirm --install && \ + cd - && rm -r "$YAY_DIR" +## install package dependencies +RUN runuser -u build -- yay --noconfirm -Sy devtools git pyalpm python-inflection python-passlib python-srcinfo && \ + runuser -u build -- yay --noconfirm -Sy python-pip && \ + runuser -u build -- yay --noconfirm -Sy breezy darcs mercurial python-aioauth-client python-aiohttp \ + python-aiohttp-debugtoolbar python-aiohttp-jinja2 python-aiohttp-security \ + python-aiohttp-session python-boto3 python-cryptography python-jinja \ + rsync subversion + +# install ahriman +## copy tree +COPY --chown=build . "/home/build/ahriman" +## create package archive and install it +RUN cd /home/build/ahriman && \ + make VERSION="$(git describe --tags --abbrev=0)" archlinux && \ + cp ./*-src.tar.xz package/archlinux && \ + cd package/archlinux && \ + runuser -u build -- makepkg --noconfirm --install --skipchecksums && \ + cd - && rm -r /home/build/ahriman + +VOLUME ["/var/lib/ahriman"] + +# minimal runtime ahriman setup +COPY "docker/entrypoint.sh" "/usr/local/bin/entrypoint" +ENTRYPOINT ["entrypoint"] +# default command +CMD ["repo-update"] diff --git a/docker/entrypoint.sh b/docker/entrypoint.sh new file mode 100755 index 00000000..e24c7cdb --- /dev/null +++ b/docker/entrypoint.sh @@ -0,0 +1,50 @@ +#!/bin/bash + +set -e +[ -n "$AHRIMAN_DEBUG" ] && set -x + +# configuration tune +sed -i "s|root = /var/lib/ahriman|root = $AHRIMAN_REPOSITORY_ROOT|g" "/etc/ahriman.ini" +sed -i "s|host = 127.0.0.1|host = 0.0.0.0|g" "/etc/ahriman.ini" +sed -i "s|handlers = syslog_handler|handlers = ${AHRIMAN_OUTPUT}_handler|g" "/etc/ahriman.ini.d/logging.ini" + +AHRIMAN_DEFAULT_ARGS=("-a" "$AHRIMAN_ARCHITECTURE") +if [[ "$AHRIMAN_OUTPUT" == "syslog" ]]; then + if [ ! -e "/dev/log" ]; then + # by default ahriman uses syslog which is not available inside container + # to make noise less we force quiet mode in case if /dev/log was not mounted + AHRIMAN_DEFAULT_ARGS+=("-q") + fi +fi + +# create repository root inside the [[mounted]] directory and set correct ownership +[ -d "$AHRIMAN_REPOSITORY_ROOT" ] || mkdir "$AHRIMAN_REPOSITORY_ROOT" +chown "$AHRIMAN_USER":"$AHRIMAN_USER" "$AHRIMAN_REPOSITORY_ROOT" +# run initial setup +sudo -u "$AHRIMAN_USER" -- ahriman "${AHRIMAN_DEFAULT_ARGS[@]}" repo-init + +# run built-in setup command +AHRIMAN_SETUP_ARGS=("--build-as-user" "$AHRIMAN_USER") +AHRIMAN_SETUP_ARGS+=("--packager" "$AHRIMAN_PACKAGER") +AHRIMAN_SETUP_ARGS+=("--repository" "$AHRIMAN_REPOSITORY") +if [ -n "$AHRIMAN_PORT" ]; then + # in addition it must be handled in docker run command + AHRIMAN_SETUP_ARGS+=("--web-port" "$AHRIMAN_PORT") +fi +ahriman "${AHRIMAN_DEFAULT_ARGS[@]}" repo-setup "${AHRIMAN_SETUP_ARGS[@]}" + +# refresh database +runuser -u build -- yay --noconfirm -Syy &> /dev/null +# create machine-id which is required by build tools +systemd-machine-id-setup &> /dev/null + +# if AHRIMAN_FORCE_ROOT is set or command is unsafe we can run without sudo +# otherwise we prepend executable by sudo command +if [ -n "$AHRIMAN_FORCE_ROOT" ]; then + AHRIMAN_EXECUTABLE=("ahriman") +elif ahriman help-commands-unsafe | grep -Fxq "$1"; then + AHRIMAN_EXECUTABLE=("ahriman") +else + AHRIMAN_EXECUTABLE=("sudo" "-u" "$AHRIMAN_USER" "--" "ahriman") +fi +exec "${AHRIMAN_EXECUTABLE[@]}" "${AHRIMAN_DEFAULT_ARGS[@]}" "$@" diff --git a/docs/faq.md b/docs/faq.md index 079de039..d1d41a03 100644 --- a/docs/faq.md +++ b/docs/faq.md @@ -198,6 +198,66 @@ server { } ``` +## Docker image + +We provide official images which can be found under `arcan1s/ahriman` repository. Docker image is being updated on each master commit as well as on each version. If you would like to use last (probably unstable build) you can use `latest` tag; otherwise you can use any version tag available. + +The default action (in case if no arguments provided) is `repo-update`. Basically the idea is to run container, e.g.: + +```shell +docker run --privileged -v /path/to/local/repo:/var/lib/ahriman arcan1s/ahriman:latest +``` + +`--privileged` flag is required to make mount possible inside container. In addition, you can pass own configuration overrides by using the same `-v` flag, e.g.: + +```shell +docker run -v /path/to/local/repo:/var/lib/ahriman -v /etc/ahriman.ini:/etc/ahriman.ini.d/10-overrides.ini arcan1s/ahriman:latest +``` + +By default, it runs `repo-update`, but it can be overwritten to any other command you would like to, e.g.: + +```shell +docker run arcan1s/ahriman:latest package-add ahriman --now +``` + +For more details please refer to docker FAQ. + +### Environment variables + +The following environment variables are supported: + +* `AHRIMAN_ARCHITECTURE` - architecture of the repository, default is `x86_64`. +* `AHRIMAN_DEBUG` - if set all commands will be logged to console. +* `AHRIMAN_FORCE_ROOT` - force run ahriman as root instead of guessing by subcommand. +* `AHRIMAN_OUTPUT` - controls logging handler, e.g. `syslog`, `console`. The name must be found in logging configuration. Note that if `syslog` (the default) handler is used you will need to mount `/dev/log` inside container because it is not available there. +* `AHRIMAN_PACKAGER` - packager name from which packages will be built, default is `ahriman bot `. +* `AHRIMAN_PORT` - HTTP server port if any, default is empty. +* `AHRIMAN_REPOSITORY` - repository name, default is `aur-clone`. +* `AHRIMAN_REPOSITORY_ROOT` - repository root. Because of filesystem rights it is required to override default repository root. By default, it uses `ahriman` directory inside ahriman's home, which can be passed as mount volume. +* `AHRIMAN_USER` - ahriman user, usually must not be overwritten, default is `ahriman`. + +You can pass any of these variables by using `-e` argument, e.g.: + +```shell +docker run -e AHRIMAN_PORT=8080 arcan1s/ahriman:latest +``` + +### Working with web service + +Well for that you would need to have web container instance running forever; it can be achieved by the following command: + +```shell +docker run -p 8080:8080 -e AHRIMAN_PORT=8080 -v /path/to/local/repo:/var/lib/ahriman arcan1s/ahriman:latest +``` + +Note about `AHRIMAN_PORT` environment variable which is required in order to enable web service. An additional port bind by `-p 8080:8080` is required to pass docker port outside of container. + +For every next container run use arguments `-e AHRIMAN_PORT=8080 --net=host`, e.g.: + +```shell +docker run --privileged -e AHRIMAN_PORT=8080 --net=host -v /path/to/local/repo:/var/lib/ahriman arcan1s/ahriman:latest +``` + ## Remote synchronization ### Wait I would like to use the repository from another server diff --git a/src/ahriman/application/ahriman.py b/src/ahriman/application/ahriman.py index 412acef5..a7fcab50 100644 --- a/src/ahriman/application/ahriman.py +++ b/src/ahriman/application/ahriman.py @@ -72,6 +72,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_help_commands_unsafe(subparsers) _set_key_import_parser(subparsers) _set_package_add_parser(subparsers) _set_package_remove_parser(subparsers) @@ -118,6 +119,19 @@ def _set_aur_search_parser(root: SubParserAction) -> argparse.ArgumentParser: return parser +def _set_help_commands_unsafe(root: SubParserAction) -> argparse.ArgumentParser: + """ + add parser for listing unsafe commands + :param root: subparsers for the commands + :return: created argument parser + """ + parser = root.add_parser("help-commands-unsafe", help="list unsafe commands", + description="list unsafe commands as defined in default args", formatter_class=_formatter) + parser.set_defaults(handler=handlers.UnsafeCommands, architecture=[""], lock=None, no_report=True, quiet=True, + unsafe=True, parser=_parser) + return parser + + def _set_key_import_parser(root: SubParserAction) -> argparse.ArgumentParser: """ add parser for key import subcommand @@ -396,6 +410,7 @@ def _set_repo_setup_parser(root: SubParserAction) -> argparse.ArgumentParser: description="create initial service configuration, requires root", epilog="Create _minimal_ configuration for the service according to provided options.", formatter_class=_formatter) + parser.add_argument("--build-as-user", help="force makepkg user to the specific one") parser.add_argument("--build-command", help="build command prefix", default="ahriman") parser.add_argument("--from-configuration", help="path to default devtools pacman configuration", type=Path, default=Path("/usr/share/devtools/pacman-extra.conf")) diff --git a/src/ahriman/application/application/properties.py b/src/ahriman/application/application/properties.py index 549b3b52..7d6d039e 100644 --- a/src/ahriman/application/application/properties.py +++ b/src/ahriman/application/application/properties.py @@ -32,14 +32,15 @@ class Properties: :ivar repository: repository instance """ - def __init__(self, architecture: str, configuration: Configuration, no_report: bool) -> None: + def __init__(self, architecture: str, configuration: Configuration, no_report: bool, unsafe: bool) -> None: """ default constructor :param architecture: repository architecture :param configuration: configuration instance :param no_report: force disable reporting + :param unsafe: if set no user check will be performed before path creation """ self.logger = logging.getLogger("root") self.configuration = configuration self.architecture = architecture - self.repository = Repository(architecture, configuration, no_report) + self.repository = Repository(architecture, configuration, no_report, unsafe) diff --git a/src/ahriman/application/handlers/__init__.py b/src/ahriman/application/handlers/__init__.py index 3aaf517d..bc61bba5 100644 --- a/src/ahriman/application/handlers/__init__.py +++ b/src/ahriman/application/handlers/__init__.py @@ -35,6 +35,7 @@ from ahriman.application.handlers.sign import Sign from ahriman.application.handlers.status import Status from ahriman.application.handlers.status_update import StatusUpdate from ahriman.application.handlers.sync import Sync +from ahriman.application.handlers.unsafe_commands import UnsafeCommands from ahriman.application.handlers.update import Update from ahriman.application.handlers.user import User from ahriman.application.handlers.web import Web diff --git a/src/ahriman/application/handlers/add.py b/src/ahriman/application/handlers/add.py index 75f75a12..7f565de4 100644 --- a/src/ahriman/application/handlers/add.py +++ b/src/ahriman/application/handlers/add.py @@ -33,15 +33,16 @@ class Add(Handler): @classmethod def run(cls: Type[Handler], args: argparse.Namespace, architecture: str, - configuration: Configuration, no_report: bool) -> None: + configuration: Configuration, no_report: bool, unsafe: bool) -> None: """ callback for command line :param args: command line args :param architecture: repository architecture :param configuration: configuration instance :param no_report: force disable reporting + :param unsafe: if set no user check will be performed before path creation """ - application = Application(architecture, configuration, no_report) + application = Application(architecture, configuration, no_report, unsafe) application.add(args.package, args.source, args.without_dependencies) if not args.now: return diff --git a/src/ahriman/application/handlers/clean.py b/src/ahriman/application/handlers/clean.py index be2ffe0c..7a438dd6 100644 --- a/src/ahriman/application/handlers/clean.py +++ b/src/ahriman/application/handlers/clean.py @@ -33,13 +33,14 @@ class Clean(Handler): @classmethod def run(cls: Type[Handler], args: argparse.Namespace, architecture: str, - configuration: Configuration, no_report: bool) -> None: + configuration: Configuration, no_report: bool, unsafe: bool) -> None: """ callback for command line :param args: command line args :param architecture: repository architecture :param configuration: configuration instance :param no_report: force disable reporting + :param unsafe: if set no user check will be performed before path creation """ - Application(architecture, configuration, no_report).clean( + Application(architecture, configuration, no_report, unsafe).clean( args.build, args.cache, args.chroot, args.manual, args.packages, args.patches) diff --git a/src/ahriman/application/handlers/dump.py b/src/ahriman/application/handlers/dump.py index 42ae504e..fd4f4b91 100644 --- a/src/ahriman/application/handlers/dump.py +++ b/src/ahriman/application/handlers/dump.py @@ -35,13 +35,14 @@ class Dump(Handler): @classmethod def run(cls: Type[Handler], args: argparse.Namespace, architecture: str, - configuration: Configuration, no_report: bool) -> None: + configuration: Configuration, no_report: bool, unsafe: bool) -> None: """ callback for command line :param args: command line args :param architecture: repository architecture :param configuration: configuration instance :param no_report: force disable reporting + :param unsafe: if set no user check will be performed before path creation """ dump = configuration.dump() for section, values in sorted(dump.items()): diff --git a/src/ahriman/application/handlers/handler.py b/src/ahriman/application/handlers/handler.py index a7e3deef..69ffa296 100644 --- a/src/ahriman/application/handlers/handler.py +++ b/src/ahriman/application/handlers/handler.py @@ -76,7 +76,7 @@ class Handler: try: configuration = Configuration.from_path(args.configuration, architecture, args.quiet) with Lock(args, architecture, configuration): - cls.run(args, architecture, configuration, args.no_report) + cls.run(args, architecture, configuration, args.no_report, args.unsafe) return True except Exception: # we are basically always want to print error to stderr instead of default logger @@ -107,12 +107,13 @@ class Handler: @classmethod def run(cls: Type[Handler], args: argparse.Namespace, architecture: str, - configuration: Configuration, no_report: bool) -> None: + configuration: Configuration, no_report: bool, unsafe: bool) -> None: """ callback for command line :param args: command line args :param architecture: repository architecture :param configuration: configuration instance :param no_report: force disable reporting + :param unsafe: if set no user check will be performed before path creation """ raise NotImplementedError diff --git a/src/ahriman/application/handlers/init.py b/src/ahriman/application/handlers/init.py index 13cc3a3e..2b7a9bb0 100644 --- a/src/ahriman/application/handlers/init.py +++ b/src/ahriman/application/handlers/init.py @@ -35,12 +35,13 @@ class Init(Handler): @classmethod def run(cls: Type[Handler], args: argparse.Namespace, architecture: str, - configuration: Configuration, no_report: bool) -> None: + configuration: Configuration, no_report: bool, unsafe: bool) -> None: """ callback for command line :param args: command line args :param architecture: repository architecture :param configuration: configuration instance :param no_report: force disable reporting + :param unsafe: if set no user check will be performed before path creation """ - Application(architecture, configuration, no_report).repository.repo.init() + Application(architecture, configuration, no_report, unsafe).repository.repo.init() diff --git a/src/ahriman/application/handlers/key_import.py b/src/ahriman/application/handlers/key_import.py index 9aa343e5..f58630e4 100644 --- a/src/ahriman/application/handlers/key_import.py +++ b/src/ahriman/application/handlers/key_import.py @@ -35,12 +35,14 @@ class KeyImport(Handler): @classmethod def run(cls: Type[Handler], args: argparse.Namespace, architecture: str, - configuration: Configuration, no_report: bool) -> None: + configuration: Configuration, no_report: bool, unsafe: bool) -> None: """ callback for command line :param args: command line args :param architecture: repository architecture :param configuration: configuration instance :param no_report: force disable reporting + :param unsafe: if set no user check will be performed before path creation """ - Application(architecture, configuration, no_report).repository.sign.key_import(args.key_server, args.key) + Application(architecture, configuration, no_report, unsafe).repository.sign.key_import( + args.key_server, args.key) diff --git a/src/ahriman/application/handlers/patch.py b/src/ahriman/application/handlers/patch.py index 2872d5ec..a2d6d5b9 100644 --- a/src/ahriman/application/handlers/patch.py +++ b/src/ahriman/application/handlers/patch.py @@ -41,15 +41,16 @@ class Patch(Handler): @classmethod def run(cls: Type[Handler], args: argparse.Namespace, architecture: str, - configuration: Configuration, no_report: bool) -> None: + configuration: Configuration, no_report: bool, unsafe: bool) -> None: """ callback for command line :param args: command line args :param architecture: repository architecture :param configuration: configuration instance :param no_report: force disable reporting + :param unsafe: if set no user check will be performed before path creation """ - application = Application(architecture, configuration, no_report) + application = Application(architecture, configuration, no_report, unsafe) if args.action == Action.List: Patch.patch_set_list(application, args.package) diff --git a/src/ahriman/application/handlers/rebuild.py b/src/ahriman/application/handlers/rebuild.py index fc293923..09f9e9ac 100644 --- a/src/ahriman/application/handlers/rebuild.py +++ b/src/ahriman/application/handlers/rebuild.py @@ -34,17 +34,18 @@ class Rebuild(Handler): @classmethod def run(cls: Type[Handler], args: argparse.Namespace, architecture: str, - configuration: Configuration, no_report: bool) -> None: + configuration: Configuration, no_report: bool, unsafe: bool) -> None: """ callback for command line :param args: command line args :param architecture: repository architecture :param configuration: configuration instance :param no_report: force disable reporting + :param unsafe: if set no user check will be performed before path creation """ depends_on = set(args.depends_on) if args.depends_on else None - application = Application(architecture, configuration, no_report) + application = Application(architecture, configuration, no_report, unsafe) updates = application.repository.packages_depends_on(depends_on) if args.dry_run: for package in updates: diff --git a/src/ahriman/application/handlers/remove.py b/src/ahriman/application/handlers/remove.py index 7a403312..1e7b444d 100644 --- a/src/ahriman/application/handlers/remove.py +++ b/src/ahriman/application/handlers/remove.py @@ -33,12 +33,13 @@ class Remove(Handler): @classmethod def run(cls: Type[Handler], args: argparse.Namespace, architecture: str, - configuration: Configuration, no_report: bool) -> None: + configuration: Configuration, no_report: bool, unsafe: bool) -> None: """ callback for command line :param args: command line args :param architecture: repository architecture :param configuration: configuration instance :param no_report: force disable reporting + :param unsafe: if set no user check will be performed before path creation """ - Application(architecture, configuration, no_report).remove(args.package) + Application(architecture, configuration, no_report, unsafe).remove(args.package) diff --git a/src/ahriman/application/handlers/remove_unknown.py b/src/ahriman/application/handlers/remove_unknown.py index 6af7d682..4912e17e 100644 --- a/src/ahriman/application/handlers/remove_unknown.py +++ b/src/ahriman/application/handlers/remove_unknown.py @@ -34,15 +34,16 @@ class RemoveUnknown(Handler): @classmethod def run(cls: Type[Handler], args: argparse.Namespace, architecture: str, - configuration: Configuration, no_report: bool) -> None: + configuration: Configuration, no_report: bool, unsafe: bool) -> None: """ callback for command line :param args: command line args :param architecture: repository architecture :param configuration: configuration instance :param no_report: force disable reporting + :param unsafe: if set no user check will be performed before path creation """ - application = Application(architecture, configuration, no_report) + application = Application(architecture, configuration, no_report, unsafe) unknown_packages = application.unknown() if args.dry_run: for package in sorted(unknown_packages): diff --git a/src/ahriman/application/handlers/report.py b/src/ahriman/application/handlers/report.py index 6bbb8bac..9c10c3b8 100644 --- a/src/ahriman/application/handlers/report.py +++ b/src/ahriman/application/handlers/report.py @@ -33,12 +33,13 @@ class Report(Handler): @classmethod def run(cls: Type[Handler], args: argparse.Namespace, architecture: str, - configuration: Configuration, no_report: bool) -> None: + configuration: Configuration, no_report: bool, unsafe: bool) -> None: """ callback for command line :param args: command line args :param architecture: repository architecture :param configuration: configuration instance :param no_report: force disable reporting + :param unsafe: if set no user check will be performed before path creation """ - Application(architecture, configuration, no_report).report(args.target, []) + Application(architecture, configuration, no_report, unsafe).report(args.target, []) diff --git a/src/ahriman/application/handlers/search.py b/src/ahriman/application/handlers/search.py index ee99d2d1..0bd22c94 100644 --- a/src/ahriman/application/handlers/search.py +++ b/src/ahriman/application/handlers/search.py @@ -42,13 +42,14 @@ class Search(Handler): @classmethod def run(cls: Type[Handler], args: argparse.Namespace, architecture: str, - configuration: Configuration, no_report: bool) -> None: + configuration: Configuration, no_report: bool, unsafe: bool) -> None: """ callback for command line :param args: command line args :param architecture: repository architecture :param configuration: configuration instance :param no_report: force disable reporting + :param unsafe: if set no user check will be performed before path creation """ packages_list = AUR.multisearch(*args.search) for package in Search.sort(packages_list, args.sort_by): diff --git a/src/ahriman/application/handlers/setup.py b/src/ahriman/application/handlers/setup.py index 098a022b..2f6d7d52 100644 --- a/src/ahriman/application/handlers/setup.py +++ b/src/ahriman/application/handlers/setup.py @@ -46,15 +46,16 @@ class Setup(Handler): @classmethod def run(cls: Type[Handler], args: argparse.Namespace, architecture: str, - configuration: Configuration, no_report: bool) -> None: + configuration: Configuration, no_report: bool, unsafe: bool) -> None: """ callback for command line :param args: command line args :param architecture: repository architecture :param configuration: configuration instance :param no_report: force disable reporting + :param unsafe: if set no user check will be performed before path creation """ - application = Application(architecture, configuration, no_report) + application = Application(architecture, configuration, no_report, unsafe) Setup.configuration_create_makepkg(args.packager, application.repository.paths) Setup.executable_create(args.build_command, architecture) Setup.configuration_create_devtools(args.build_command, architecture, args.from_configuration, @@ -87,6 +88,8 @@ class Setup(Handler): section = Configuration.section_name("build", architecture) configuration.set_option(section, "build_command", str(Setup.build_command(args.build_command, architecture))) configuration.set_option("repository", "name", repository) + if args.build_as_user is not None: + configuration.set_option(section, "makechrootpkg_flags", f"-U {args.build_as_user}") if args.sign_key is not None: section = Configuration.section_name("sign", architecture) diff --git a/src/ahriman/application/handlers/sign.py b/src/ahriman/application/handlers/sign.py index 66d1b957..bb7a33b0 100644 --- a/src/ahriman/application/handlers/sign.py +++ b/src/ahriman/application/handlers/sign.py @@ -33,12 +33,13 @@ class Sign(Handler): @classmethod def run(cls: Type[Handler], args: argparse.Namespace, architecture: str, - configuration: Configuration, no_report: bool) -> None: + configuration: Configuration, no_report: bool, unsafe: bool) -> None: """ callback for command line :param args: command line args :param architecture: repository architecture :param configuration: configuration instance :param no_report: force disable reporting + :param unsafe: if set no user check will be performed before path creation """ - Application(architecture, configuration, no_report).sign(args.package) + Application(architecture, configuration, no_report, unsafe).sign(args.package) diff --git a/src/ahriman/application/handlers/status.py b/src/ahriman/application/handlers/status.py index 31b8c435..60a29643 100644 --- a/src/ahriman/application/handlers/status.py +++ b/src/ahriman/application/handlers/status.py @@ -39,16 +39,17 @@ class Status(Handler): @classmethod def run(cls: Type[Handler], args: argparse.Namespace, architecture: str, - configuration: Configuration, no_report: bool) -> None: + configuration: Configuration, no_report: bool, unsafe: bool) -> None: """ callback for command line :param args: command line args :param architecture: repository architecture :param configuration: configuration instance :param no_report: force disable reporting + :param unsafe: if set no user check will be performed before path creation """ # we are using reporter here - client = Application(architecture, configuration, no_report=False).repository.reporter + client = Application(architecture, configuration, no_report=False, unsafe=unsafe).repository.reporter if args.ahriman: ahriman = client.get_self() StatusPrinter(ahriman).print(args.info) diff --git a/src/ahriman/application/handlers/status_update.py b/src/ahriman/application/handlers/status_update.py index a9e9e4af..b769b477 100644 --- a/src/ahriman/application/handlers/status_update.py +++ b/src/ahriman/application/handlers/status_update.py @@ -36,16 +36,17 @@ class StatusUpdate(Handler): @classmethod def run(cls: Type[Handler], args: argparse.Namespace, architecture: str, - configuration: Configuration, no_report: bool) -> None: + configuration: Configuration, no_report: bool, unsafe: bool) -> None: """ callback for command line :param args: command line args :param architecture: repository architecture :param configuration: configuration instance :param no_report: force disable reporting + :param unsafe: if set no user check will be performed before path creation """ # we are using reporter here - client = Application(architecture, configuration, no_report=False).repository.reporter + client = Application(architecture, configuration, no_report=False, unsafe=unsafe).repository.reporter if args.action == Action.Update and args.package: # update packages statuses diff --git a/src/ahriman/application/handlers/sync.py b/src/ahriman/application/handlers/sync.py index 51af527e..95ea585d 100644 --- a/src/ahriman/application/handlers/sync.py +++ b/src/ahriman/application/handlers/sync.py @@ -33,12 +33,13 @@ class Sync(Handler): @classmethod def run(cls: Type[Handler], args: argparse.Namespace, architecture: str, - configuration: Configuration, no_report: bool) -> None: + configuration: Configuration, no_report: bool, unsafe: bool) -> None: """ callback for command line :param args: command line args :param architecture: repository architecture :param configuration: configuration instance :param no_report: force disable reporting + :param unsafe: if set no user check will be performed before path creation """ - Application(architecture, configuration, no_report).sync(args.target, []) + Application(architecture, configuration, no_report, unsafe).sync(args.target, []) diff --git a/src/ahriman/application/handlers/unsafe_commands.py b/src/ahriman/application/handlers/unsafe_commands.py new file mode 100644 index 00000000..8e7b04ba --- /dev/null +++ b/src/ahriman/application/handlers/unsafe_commands.py @@ -0,0 +1,58 @@ +# +# 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 List, Type + +from ahriman.application.formatters.string_printer import StringPrinter +from ahriman.application.handlers.handler import Handler +from ahriman.core.configuration import Configuration + + +class UnsafeCommands(Handler): + """ + unsafe command help parser + """ + + @classmethod + def run(cls: Type[Handler], args: argparse.Namespace, architecture: str, + configuration: Configuration, no_report: bool, unsafe: bool) -> None: + """ + callback for command line + :param args: command line args + :param architecture: repository architecture + :param configuration: configuration instance + :param no_report: force disable reporting + :param unsafe: if set no user check will be performed before path creation + """ + unsafe_commands = UnsafeCommands.get_unsafe_commands(args.parser()) + for command in unsafe_commands: + StringPrinter(command).print(verbose=True) + + @staticmethod + def get_unsafe_commands(parser: argparse.ArgumentParser) -> List[str]: + """ + extract unsafe commands from argument parser + :param parser: generated argument parser + :return: list of commands with default unsafe flag + """ + # pylint: disable=protected-access + subparser = next(action for action in parser._actions if isinstance(action, argparse._SubParsersAction)) + return [action_name for action_name, action in subparser.choices.items() if action.get_default("unsafe")] diff --git a/src/ahriman/application/handlers/update.py b/src/ahriman/application/handlers/update.py index 7d6fca90..f3ab6e0a 100644 --- a/src/ahriman/application/handlers/update.py +++ b/src/ahriman/application/handlers/update.py @@ -33,15 +33,16 @@ class Update(Handler): @classmethod def run(cls: Type[Handler], args: argparse.Namespace, architecture: str, - configuration: Configuration, no_report: bool) -> None: + configuration: Configuration, no_report: bool, unsafe: bool) -> None: """ callback for command line :param args: command line args :param architecture: repository architecture :param configuration: configuration instance :param no_report: force disable reporting + :param unsafe: if set no user check will be performed before path creation """ - application = Application(architecture, configuration, no_report) + application = Application(architecture, configuration, no_report, unsafe) packages = application.updates(args.package, args.no_aur, args.no_local, args.no_manual, args.no_vcs, Update.log_fn(application, args.dry_run)) if args.dry_run: diff --git a/src/ahriman/application/handlers/user.py b/src/ahriman/application/handlers/user.py index 7ecfa8b4..12ca508d 100644 --- a/src/ahriman/application/handlers/user.py +++ b/src/ahriman/application/handlers/user.py @@ -40,13 +40,14 @@ class User(Handler): @classmethod def run(cls: Type[Handler], args: argparse.Namespace, architecture: str, - configuration: Configuration, no_report: bool) -> None: + configuration: Configuration, no_report: bool, unsafe: bool) -> None: """ callback for command line :param args: command line args :param architecture: repository architecture :param configuration: configuration instance :param no_report: force disable reporting + :param unsafe: if set no user check will be performed before path creation """ salt = User.get_salt(configuration) user = User.user_create(args) @@ -58,7 +59,7 @@ class User(Handler): User.configuration_write(auth_configuration, args.secure) if not args.no_reload: - client = Application(architecture, configuration, no_report=False).repository.reporter + client = Application(architecture, configuration, no_report=False, unsafe=unsafe).repository.reporter client.reload_auth() @staticmethod diff --git a/src/ahriman/application/handlers/web.py b/src/ahriman/application/handlers/web.py index 2d729662..b59cbc60 100644 --- a/src/ahriman/application/handlers/web.py +++ b/src/ahriman/application/handlers/web.py @@ -36,13 +36,14 @@ class Web(Handler): @classmethod def run(cls: Type[Handler], args: argparse.Namespace, architecture: str, - configuration: Configuration, no_report: bool) -> None: + configuration: Configuration, no_report: bool, unsafe: bool) -> None: """ callback for command line :param args: command line args :param architecture: repository architecture :param configuration: configuration instance :param no_report: force disable reporting + :param unsafe: if set no user check will be performed before path creation """ # we are using local import for optional dependencies from ahriman.web.web import run_server, setup_service diff --git a/src/ahriman/application/lock.py b/src/ahriman/application/lock.py index c92a3338..4c72425b 100644 --- a/src/ahriman/application/lock.py +++ b/src/ahriman/application/lock.py @@ -103,9 +103,7 @@ class Lock: """ check if current user is actually owner of ahriman root """ - if self.unsafe: - return - check_user(self.root) + check_user(self.root, self.unsafe) def clear(self) -> None: """ diff --git a/src/ahriman/core/repository/properties.py b/src/ahriman/core/repository/properties.py index 25a9acca..3edbe82d 100644 --- a/src/ahriman/core/repository/properties.py +++ b/src/ahriman/core/repository/properties.py @@ -45,12 +45,13 @@ class Properties: :ivar sign: GPG wrapper instance """ - def __init__(self, architecture: str, configuration: Configuration, no_report: bool) -> None: + def __init__(self, architecture: str, configuration: Configuration, no_report: bool, unsafe: bool) -> None: """ default constructor :param architecture: repository architecture :param configuration: configuration instance :param no_report: force disable reporting + :param unsafe: if set no user check will be performed before path creation """ self.logger = logging.getLogger("root") self.architecture = architecture @@ -61,7 +62,7 @@ class Properties: self.paths = RepositoryPaths(configuration.getpath("repository", "root"), architecture) try: - check_user(self.paths.root) + check_user(self.paths.root, unsafe) self.paths.tree_create() except UnsafeRun: self.logger.warning("root owner differs from the current user, skipping tree creation") diff --git a/src/ahriman/core/status/watcher.py b/src/ahriman/core/status/watcher.py index d3c767ab..f918b72c 100644 --- a/src/ahriman/core/status/watcher.py +++ b/src/ahriman/core/status/watcher.py @@ -49,7 +49,7 @@ class Watcher: self.logger = logging.getLogger("http") self.architecture = architecture - self.repository = Repository(architecture, configuration, no_report=True) + self.repository = Repository(architecture, configuration, no_report=True, unsafe=False) self.known: Dict[str, Tuple[Package, BuildStatus]] = {} self.status = BuildStatus() diff --git a/src/ahriman/core/util.py b/src/ahriman/core/util.py index bed5aa65..f14dd7d4 100644 --- a/src/ahriman/core/util.py +++ b/src/ahriman/core/util.py @@ -55,13 +55,16 @@ def check_output(*args: str, exception: Optional[Exception], cwd: Optional[Path] raise exception or e -def check_user(root: Path) -> None: +def check_user(root: Path, unsafe: bool) -> None: """ check if current user is the owner of the root :param root: root directory (i.e. ahriman home) + :param unsafe: if set no user check will be performed before path creation """ if not root.exists(): return # no directory found, skip check + if unsafe: + return # unsafe flag is enabled, no check performed current_uid = os.getuid() root_uid = root.stat().st_uid if current_uid != root_uid: diff --git a/tests/ahriman/application/application/conftest.py b/tests/ahriman/application/application/conftest.py index 67110ab1..3cf080f2 100644 --- a/tests/ahriman/application/application/conftest.py +++ b/tests/ahriman/application/application/conftest.py @@ -17,7 +17,7 @@ def application_packages(configuration: Configuration, mocker: MockerFixture) -> :return: application test instance """ mocker.patch("ahriman.models.repository_paths.RepositoryPaths.tree_create") - return Packages("x86_64", configuration, no_report=True) + return Packages("x86_64", configuration, no_report=True, unsafe=False) @pytest.fixture @@ -29,7 +29,7 @@ def application_properties(configuration: Configuration, mocker: MockerFixture) :return: application test instance """ mocker.patch("ahriman.models.repository_paths.RepositoryPaths.tree_create") - return Properties("x86_64", configuration, no_report=True) + return Properties("x86_64", configuration, no_report=True, unsafe=False) @pytest.fixture @@ -41,4 +41,4 @@ def application_repository(configuration: Configuration, mocker: MockerFixture) :return: application test instance """ mocker.patch("ahriman.models.repository_paths.RepositoryPaths.tree_create") - return Repository("x86_64", configuration, no_report=True) + return Repository("x86_64", configuration, no_report=True, unsafe=False) diff --git a/tests/ahriman/application/conftest.py b/tests/ahriman/application/conftest.py index 33ea7c4d..b8083459 100644 --- a/tests/ahriman/application/conftest.py +++ b/tests/ahriman/application/conftest.py @@ -18,7 +18,7 @@ def application(configuration: Configuration, mocker: MockerFixture) -> Applicat :return: application test instance """ mocker.patch("ahriman.models.repository_paths.RepositoryPaths.tree_create") - return Application("x86_64", configuration, no_report=True) + return Application("x86_64", configuration, no_report=True, unsafe=False) @pytest.fixture diff --git a/tests/ahriman/application/handlers/test_handler.py b/tests/ahriman/application/handlers/test_handler.py index 94c67edb..8acd1217 100644 --- a/tests/ahriman/application/handlers/test_handler.py +++ b/tests/ahriman/application/handlers/test_handler.py @@ -114,4 +114,4 @@ def test_run(args: argparse.Namespace, configuration: Configuration) -> None: must raise NotImplemented for missing method """ with pytest.raises(NotImplementedError): - Handler.run(args, "x86_64", configuration, True) + Handler.run(args, "x86_64", configuration, True, True) diff --git a/tests/ahriman/application/handlers/test_handler_add.py b/tests/ahriman/application/handlers/test_handler_add.py index 77249c28..3123771c 100644 --- a/tests/ahriman/application/handlers/test_handler_add.py +++ b/tests/ahriman/application/handlers/test_handler_add.py @@ -30,7 +30,7 @@ def test_run(args: argparse.Namespace, configuration: Configuration, mocker: Moc mocker.patch("ahriman.models.repository_paths.RepositoryPaths.tree_create") application_mock = mocker.patch("ahriman.application.application.Application.add") - Add.run(args, "x86_64", configuration, True) + Add.run(args, "x86_64", configuration, True, False) application_mock.assert_called_once_with(args.package, args.source, args.without_dependencies) @@ -46,6 +46,6 @@ def test_run_with_updates(args: argparse.Namespace, configuration: Configuration application_mock = mocker.patch("ahriman.application.application.Application.update") updates_mock = mocker.patch("ahriman.application.application.Application.updates", return_value=[package_ahriman]) - Add.run(args, "x86_64", configuration, True) + Add.run(args, "x86_64", configuration, True, False) updates_mock.assert_called_once_with(args.package, True, True, False, True, pytest.helpers.anyvar(int)) application_mock.assert_called_once_with([package_ahriman]) diff --git a/tests/ahriman/application/handlers/test_handler_clean.py b/tests/ahriman/application/handlers/test_handler_clean.py index fe113165..3ae41265 100644 --- a/tests/ahriman/application/handlers/test_handler_clean.py +++ b/tests/ahriman/application/handlers/test_handler_clean.py @@ -29,5 +29,5 @@ def test_run(args: argparse.Namespace, configuration: Configuration, mocker: Moc mocker.patch("ahriman.models.repository_paths.RepositoryPaths.tree_create") application_mock = mocker.patch("ahriman.application.application.Application.clean") - Clean.run(args, "x86_64", configuration, True) + Clean.run(args, "x86_64", configuration, True, False) application_mock.assert_called_once_with(False, False, False, False, False, False) diff --git a/tests/ahriman/application/handlers/test_handler_dump.py b/tests/ahriman/application/handlers/test_handler_dump.py index ccf34db8..f04bbf2a 100644 --- a/tests/ahriman/application/handlers/test_handler_dump.py +++ b/tests/ahriman/application/handlers/test_handler_dump.py @@ -15,7 +15,7 @@ def test_run(args: argparse.Namespace, configuration: Configuration, mocker: Moc application_mock = mocker.patch("ahriman.core.configuration.Configuration.dump", return_value=configuration.dump()) - Dump.run(args, "x86_64", configuration, True) + Dump.run(args, "x86_64", configuration, True, False) application_mock.assert_called_once_with() print_mock.assert_called() diff --git a/tests/ahriman/application/handlers/test_handler_init.py b/tests/ahriman/application/handlers/test_handler_init.py index c635a2df..2737f89e 100644 --- a/tests/ahriman/application/handlers/test_handler_init.py +++ b/tests/ahriman/application/handlers/test_handler_init.py @@ -14,7 +14,7 @@ def test_run(args: argparse.Namespace, configuration: Configuration, mocker: Moc tree_create_mock = mocker.patch("ahriman.models.repository_paths.RepositoryPaths.tree_create") init_mock = mocker.patch("ahriman.core.alpm.repo.Repo.init") - Init.run(args, "x86_64", configuration, True) + Init.run(args, "x86_64", configuration, True, False) tree_create_mock.assert_called_once_with() init_mock.assert_called_once_with() diff --git a/tests/ahriman/application/handlers/test_handler_key_import.py b/tests/ahriman/application/handlers/test_handler_key_import.py index 9490ce0e..a51863ea 100644 --- a/tests/ahriman/application/handlers/test_handler_key_import.py +++ b/tests/ahriman/application/handlers/test_handler_key_import.py @@ -25,7 +25,7 @@ def test_run(args: argparse.Namespace, configuration: Configuration, mocker: Moc mocker.patch("ahriman.models.repository_paths.RepositoryPaths.tree_create") application_mock = mocker.patch("ahriman.core.sign.gpg.GPG.key_import") - KeyImport.run(args, "x86_64", configuration, True) + KeyImport.run(args, "x86_64", configuration, True, False) application_mock.assert_called_once_with(args.key_server, args.key) diff --git a/tests/ahriman/application/handlers/test_handler_patch.py b/tests/ahriman/application/handlers/test_handler_patch.py index 8447186d..65b02ff9 100644 --- a/tests/ahriman/application/handlers/test_handler_patch.py +++ b/tests/ahriman/application/handlers/test_handler_patch.py @@ -32,7 +32,7 @@ def test_run(args: argparse.Namespace, configuration: Configuration, mocker: Moc mocker.patch("ahriman.models.repository_paths.RepositoryPaths.tree_create") application_mock = mocker.patch("ahriman.application.handlers.patch.Patch.patch_set_create") - Patch.run(args, "x86_64", configuration, True) + Patch.run(args, "x86_64", configuration, True, False) application_mock.assert_called_once_with(pytest.helpers.anyvar(int), args.package, args.track) @@ -45,7 +45,7 @@ def test_run_list(args: argparse.Namespace, configuration: Configuration, mocker mocker.patch("ahriman.models.repository_paths.RepositoryPaths.tree_create") application_mock = mocker.patch("ahriman.application.handlers.patch.Patch.patch_set_list") - Patch.run(args, "x86_64", configuration, True) + Patch.run(args, "x86_64", configuration, True, False) application_mock.assert_called_once_with(pytest.helpers.anyvar(int), args.package) @@ -58,7 +58,7 @@ def test_run_remove(args: argparse.Namespace, configuration: Configuration, mock mocker.patch("ahriman.models.repository_paths.RepositoryPaths.tree_create") application_mock = mocker.patch("ahriman.application.handlers.patch.Patch.patch_set_remove") - Patch.run(args, "x86_64", configuration, True) + Patch.run(args, "x86_64", configuration, True, False) application_mock.assert_called_once_with(pytest.helpers.anyvar(int), args.package) diff --git a/tests/ahriman/application/handlers/test_handler_rebuild.py b/tests/ahriman/application/handlers/test_handler_rebuild.py index 06d5baac..6cd13889 100644 --- a/tests/ahriman/application/handlers/test_handler_rebuild.py +++ b/tests/ahriman/application/handlers/test_handler_rebuild.py @@ -29,7 +29,7 @@ def test_run(args: argparse.Namespace, package_ahriman: Package, return_value=[package_ahriman]) application_mock = mocker.patch("ahriman.application.application.Application.update") - Rebuild.run(args, "x86_64", configuration, True) + Rebuild.run(args, "x86_64", configuration, True, False) application_packages_mock.assert_called_once_with(None) application_mock.assert_called_once_with([package_ahriman]) @@ -45,7 +45,7 @@ def test_run_dry_run(args: argparse.Namespace, configuration: Configuration, mocker.patch("ahriman.core.repository.repository.Repository.packages_depends_on", return_value=[package_ahriman]) application_mock = mocker.patch("ahriman.application.application.Application.update") - Rebuild.run(args, "x86_64", configuration, True) + Rebuild.run(args, "x86_64", configuration, True, False) application_mock.assert_not_called() @@ -59,7 +59,7 @@ def test_run_filter(args: argparse.Namespace, configuration: Configuration, mock mocker.patch("ahriman.models.repository_paths.RepositoryPaths.tree_create") application_packages_mock = mocker.patch("ahriman.core.repository.repository.Repository.packages_depends_on") - Rebuild.run(args, "x86_64", configuration, True) + Rebuild.run(args, "x86_64", configuration, True, False) application_packages_mock.assert_called_once_with({"python-aur"}) @@ -72,5 +72,5 @@ def test_run_without_filter(args: argparse.Namespace, configuration: Configurati mocker.patch("ahriman.models.repository_paths.RepositoryPaths.tree_create") application_packages_mock = mocker.patch("ahriman.core.repository.repository.Repository.packages_depends_on") - Rebuild.run(args, "x86_64", configuration, True) + Rebuild.run(args, "x86_64", configuration, True, False) application_packages_mock.assert_called_once_with(None) diff --git a/tests/ahriman/application/handlers/test_handler_remove.py b/tests/ahriman/application/handlers/test_handler_remove.py index d537add1..3963821b 100644 --- a/tests/ahriman/application/handlers/test_handler_remove.py +++ b/tests/ahriman/application/handlers/test_handler_remove.py @@ -24,5 +24,5 @@ def test_run(args: argparse.Namespace, configuration: Configuration, mocker: Moc mocker.patch("ahriman.models.repository_paths.RepositoryPaths.tree_create") application_mock = mocker.patch("ahriman.application.application.Application.remove") - Remove.run(args, "x86_64", configuration, True) + Remove.run(args, "x86_64", configuration, True, False) application_mock.assert_called_once_with([]) diff --git a/tests/ahriman/application/handlers/test_handler_remove_unknown.py b/tests/ahriman/application/handlers/test_handler_remove_unknown.py index 3ca1ba4d..e3271b44 100644 --- a/tests/ahriman/application/handlers/test_handler_remove_unknown.py +++ b/tests/ahriman/application/handlers/test_handler_remove_unknown.py @@ -29,7 +29,7 @@ def test_run(args: argparse.Namespace, package_ahriman: Package, return_value=[package_ahriman]) remove_mock = mocker.patch("ahriman.application.application.Application.remove") - RemoveUnknown.run(args, "x86_64", configuration, True) + RemoveUnknown.run(args, "x86_64", configuration, True, False) application_mock.assert_called_once_with() remove_mock.assert_called_once_with([package_ahriman]) @@ -47,7 +47,7 @@ def test_run_dry_run(args: argparse.Namespace, configuration: Configuration, pac remove_mock = mocker.patch("ahriman.application.application.Application.remove") print_mock = mocker.patch("ahriman.application.formatters.printer.Printer.print") - RemoveUnknown.run(args, "x86_64", configuration, True) + RemoveUnknown.run(args, "x86_64", configuration, True, False) application_mock.assert_called_once_with() remove_mock.assert_not_called() print_mock.assert_called_once_with(False) @@ -67,7 +67,7 @@ def test_run_dry_run_verbose(args: argparse.Namespace, configuration: Configurat remove_mock = mocker.patch("ahriman.application.application.Application.remove") print_mock = mocker.patch("ahriman.application.formatters.printer.Printer.print") - RemoveUnknown.run(args, "x86_64", configuration, True) + RemoveUnknown.run(args, "x86_64", configuration, True, False) application_mock.assert_called_once_with() remove_mock.assert_not_called() print_mock.assert_called_once_with(True) diff --git a/tests/ahriman/application/handlers/test_handler_report.py b/tests/ahriman/application/handlers/test_handler_report.py index a0f7d73d..e44fd7cd 100644 --- a/tests/ahriman/application/handlers/test_handler_report.py +++ b/tests/ahriman/application/handlers/test_handler_report.py @@ -24,5 +24,5 @@ def test_run(args: argparse.Namespace, configuration: Configuration, mocker: Moc mocker.patch("ahriman.models.repository_paths.RepositoryPaths.tree_create") application_mock = mocker.patch("ahriman.application.application.Application.report") - Report.run(args, "x86_64", configuration, True) + Report.run(args, "x86_64", configuration, True, False) application_mock.assert_called_once_with(args.target, []) diff --git a/tests/ahriman/application/handlers/test_handler_search.py b/tests/ahriman/application/handlers/test_handler_search.py index e2f086f4..1d04cc7b 100644 --- a/tests/ahriman/application/handlers/test_handler_search.py +++ b/tests/ahriman/application/handlers/test_handler_search.py @@ -31,7 +31,7 @@ def test_run(args: argparse.Namespace, configuration: Configuration, aur_package search_mock = mocker.patch("ahriman.core.alpm.aur.AUR.multisearch", return_value=[aur_package_ahriman]) print_mock = mocker.patch("ahriman.application.formatters.printer.Printer.print") - Search.run(args, "x86_64", configuration, True) + Search.run(args, "x86_64", configuration, True, False) search_mock.assert_called_once_with("ahriman") print_mock.assert_called_once_with(False) @@ -45,7 +45,7 @@ def test_run_sort(args: argparse.Namespace, configuration: Configuration, aur_pa mocker.patch("ahriman.core.alpm.aur.AUR.multisearch", return_value=[aur_package_ahriman]) sort_mock = mocker.patch("ahriman.application.handlers.search.Search.sort") - Search.run(args, "x86_64", configuration, True) + Search.run(args, "x86_64", configuration, True, False) sort_mock.assert_called_once_with([aur_package_ahriman], "name") @@ -59,7 +59,7 @@ def test_run_sort_by(args: argparse.Namespace, configuration: Configuration, aur mocker.patch("ahriman.core.alpm.aur.AUR.multisearch", return_value=[aur_package_ahriman]) sort_mock = mocker.patch("ahriman.application.handlers.search.Search.sort") - Search.run(args, "x86_64", configuration, True) + Search.run(args, "x86_64", configuration, True, False) sort_mock.assert_called_once_with([aur_package_ahriman], "field") diff --git a/tests/ahriman/application/handlers/test_handler_setup.py b/tests/ahriman/application/handlers/test_handler_setup.py index b2b2cbfd..e2213fe7 100644 --- a/tests/ahriman/application/handlers/test_handler_setup.py +++ b/tests/ahriman/application/handlers/test_handler_setup.py @@ -17,6 +17,7 @@ def _default_args(args: argparse.Namespace) -> argparse.Namespace: :param args: command line arguments fixture :return: generated arguments for these test cases """ + args.build_as_user = "ahriman" args.build_command = "ahriman" args.from_configuration = Path("/usr/share/devtools/pacman-extra.conf") args.no_multilib = False @@ -41,7 +42,7 @@ def test_run(args: argparse.Namespace, configuration: Configuration, mocker: Moc executable_mock = mocker.patch("ahriman.application.handlers.setup.Setup.executable_create") paths = RepositoryPaths(configuration.getpath("repository", "root"), "x86_64") - Setup.run(args, "x86_64", configuration, True) + Setup.run(args, "x86_64", configuration, True, False) ahriman_configuration_mock.assert_called_once_with(args, "x86_64", args.repository, configuration.include) devtools_configuration_mock.assert_called_once_with(args.build_command, "x86_64", args.from_configuration, args.no_multilib, args.repository, paths) @@ -73,6 +74,7 @@ def test_configuration_create_ahriman(args: argparse.Namespace, configuration: C set_option_mock.assert_has_calls([ mock.call(Configuration.section_name("build", "x86_64"), "build_command", str(command)), mock.call("repository", "name", args.repository), + mock.call(Configuration.section_name("build", "x86_64"), "makechrootpkg_flags", f"-U {args.build_as_user}"), mock.call(Configuration.section_name("sign", "x86_64"), "target", " ".join([target.name.lower() for target in args.sign_target])), mock.call(Configuration.section_name("sign", "x86_64"), "key", args.sign_key), diff --git a/tests/ahriman/application/handlers/test_handler_sign.py b/tests/ahriman/application/handlers/test_handler_sign.py index 263b3351..a8e24a06 100644 --- a/tests/ahriman/application/handlers/test_handler_sign.py +++ b/tests/ahriman/application/handlers/test_handler_sign.py @@ -24,5 +24,5 @@ def test_run(args: argparse.Namespace, configuration: Configuration, mocker: Moc mocker.patch("ahriman.models.repository_paths.RepositoryPaths.tree_create") application_mock = mocker.patch("ahriman.application.application.Application.sign") - Sign.run(args, "x86_64", configuration, True) + Sign.run(args, "x86_64", configuration, True, False) application_mock.assert_called_once_with([]) diff --git a/tests/ahriman/application/handlers/test_handler_status.py b/tests/ahriman/application/handlers/test_handler_status.py index edace7e9..1d13cadd 100644 --- a/tests/ahriman/application/handlers/test_handler_status.py +++ b/tests/ahriman/application/handlers/test_handler_status.py @@ -35,7 +35,7 @@ def test_run(args: argparse.Namespace, configuration: Configuration, package_ahr (package_python_schedule, BuildStatus(BuildStatusEnum.Failed))]) print_mock = mocker.patch("ahriman.application.formatters.printer.Printer.print") - Status.run(args, "x86_64", configuration, True) + Status.run(args, "x86_64", configuration, True, False) application_mock.assert_called_once_with() packages_mock.assert_called_once_with(None) print_mock.assert_has_calls([mock.call(False) for _ in range(3)]) @@ -53,7 +53,7 @@ def test_run_verbose(args: argparse.Namespace, configuration: Configuration, pac return_value=[(package_ahriman, BuildStatus(BuildStatusEnum.Success))]) print_mock = mocker.patch("ahriman.application.formatters.printer.Printer.print") - Status.run(args, "x86_64", configuration, True) + Status.run(args, "x86_64", configuration, True, False) print_mock.assert_has_calls([mock.call(True) for _ in range(2)]) @@ -68,7 +68,7 @@ def test_run_with_package_filter(args: argparse.Namespace, configuration: Config packages_mock = mocker.patch("ahriman.core.status.client.Client.get", return_value=[(package_ahriman, BuildStatus(BuildStatusEnum.Success))]) - Status.run(args, "x86_64", configuration, True) + Status.run(args, "x86_64", configuration, True, False) packages_mock.assert_called_once_with(package_ahriman.base) @@ -85,7 +85,7 @@ def test_run_by_status(args: argparse.Namespace, configuration: Configuration, p mocker.patch("ahriman.models.repository_paths.RepositoryPaths.tree_create") print_mock = mocker.patch("ahriman.application.formatters.printer.Printer.print") - Status.run(args, "x86_64", configuration, True) + Status.run(args, "x86_64", configuration, True, False) print_mock.assert_has_calls([mock.call(False) for _ in range(2)]) @@ -97,7 +97,7 @@ def test_imply_with_report(args: argparse.Namespace, configuration: Configuratio mocker.patch("ahriman.models.repository_paths.RepositoryPaths.tree_create") load_mock = mocker.patch("ahriman.core.status.client.Client.load") - Status.run(args, "x86_64", configuration, True) + Status.run(args, "x86_64", configuration, True, False) load_mock.assert_called_once_with(configuration) diff --git a/tests/ahriman/application/handlers/test_handler_status_update.py b/tests/ahriman/application/handlers/test_handler_status_update.py index 3ff1b72d..df1587a9 100644 --- a/tests/ahriman/application/handlers/test_handler_status_update.py +++ b/tests/ahriman/application/handlers/test_handler_status_update.py @@ -29,7 +29,7 @@ def test_run(args: argparse.Namespace, configuration: Configuration, mocker: Moc mocker.patch("ahriman.models.repository_paths.RepositoryPaths.tree_create") update_self_mock = mocker.patch("ahriman.core.status.client.Client.update_self") - StatusUpdate.run(args, "x86_64", configuration, True) + StatusUpdate.run(args, "x86_64", configuration, True, False) update_self_mock.assert_called_once_with(args.status) @@ -43,7 +43,7 @@ def test_run_packages(args: argparse.Namespace, configuration: Configuration, pa mocker.patch("ahriman.models.repository_paths.RepositoryPaths.tree_create") update_mock = mocker.patch("ahriman.core.status.client.Client.update") - StatusUpdate.run(args, "x86_64", configuration, True) + StatusUpdate.run(args, "x86_64", configuration, True, False) update_mock.assert_called_once_with(package_ahriman.base, args.status) @@ -58,7 +58,7 @@ def test_run_remove(args: argparse.Namespace, configuration: Configuration, pack mocker.patch("ahriman.models.repository_paths.RepositoryPaths.tree_create") update_mock = mocker.patch("ahriman.core.status.client.Client.remove") - StatusUpdate.run(args, "x86_64", configuration, True) + StatusUpdate.run(args, "x86_64", configuration, True, False) update_mock.assert_called_once_with(package_ahriman.base) @@ -70,7 +70,7 @@ def test_imply_with_report(args: argparse.Namespace, configuration: Configuratio mocker.patch("ahriman.models.repository_paths.RepositoryPaths.tree_create") load_mock = mocker.patch("ahriman.core.status.client.Client.load") - StatusUpdate.run(args, "x86_64", configuration, True) + StatusUpdate.run(args, "x86_64", configuration, True, False) load_mock.assert_called_once_with(configuration) diff --git a/tests/ahriman/application/handlers/test_handler_sync.py b/tests/ahriman/application/handlers/test_handler_sync.py index f021ec9c..c5221a0b 100644 --- a/tests/ahriman/application/handlers/test_handler_sync.py +++ b/tests/ahriman/application/handlers/test_handler_sync.py @@ -24,5 +24,5 @@ def test_run(args: argparse.Namespace, configuration: Configuration, mocker: Moc mocker.patch("ahriman.models.repository_paths.RepositoryPaths.tree_create") application_mock = mocker.patch("ahriman.application.application.Application.sync") - Sync.run(args, "x86_64", configuration, True) + Sync.run(args, "x86_64", configuration, True, False) application_mock.assert_called_once_with(args.target, []) diff --git a/tests/ahriman/application/handlers/test_handler_update.py b/tests/ahriman/application/handlers/test_handler_update.py index 36625e12..586be0ea 100644 --- a/tests/ahriman/application/handlers/test_handler_update.py +++ b/tests/ahriman/application/handlers/test_handler_update.py @@ -34,7 +34,7 @@ def test_run(args: argparse.Namespace, package_ahriman: Package, application_mock = mocker.patch("ahriman.application.application.Application.update") updates_mock = mocker.patch("ahriman.application.application.Application.updates", return_value=[package_ahriman]) - Update.run(args, "x86_64", configuration, True) + Update.run(args, "x86_64", configuration, True, False) application_mock.assert_called_once_with([package_ahriman]) updates_mock.assert_called_once_with(args.package, args.no_aur, args.no_local, args.no_manual, args.no_vcs, pytest.helpers.anyvar(int)) @@ -49,7 +49,7 @@ def test_run_dry_run(args: argparse.Namespace, configuration: Configuration, moc mocker.patch("ahriman.models.repository_paths.RepositoryPaths.tree_create") updates_mock = mocker.patch("ahriman.application.application.Application.updates") - Update.run(args, "x86_64", configuration, True) + Update.run(args, "x86_64", configuration, True, False) updates_mock.assert_called_once_with(args.package, args.no_aur, args.no_local, args.no_manual, args.no_vcs, pytest.helpers.anyvar(int)) diff --git a/tests/ahriman/application/handlers/test_handler_user.py b/tests/ahriman/application/handlers/test_handler_user.py index b9151f4c..4b566e44 100644 --- a/tests/ahriman/application/handlers/test_handler_user.py +++ b/tests/ahriman/application/handlers/test_handler_user.py @@ -41,7 +41,7 @@ def test_run(args: argparse.Namespace, configuration: Configuration, mocker: Moc get_salt_mock = mocker.patch("ahriman.application.handlers.User.get_salt") reload_mock = mocker.patch("ahriman.core.status.client.Client.reload_auth") - User.run(args, "x86_64", configuration, True) + User.run(args, "x86_64", configuration, True, False) get_auth_configuration_mock.assert_called_once_with(configuration.include) create_configuration_mock.assert_called_once_with( pytest.helpers.anyvar(int), pytest.helpers.anyvar(int), pytest.helpers.anyvar(int), args.as_service) @@ -63,7 +63,7 @@ def test_run_remove(args: argparse.Namespace, configuration: Configuration, mock write_configuration_mock = mocker.patch("ahriman.application.handlers.User.configuration_write") reload_mock = mocker.patch("ahriman.core.status.client.Client.reload_auth") - User.run(args, "x86_64", configuration, True) + User.run(args, "x86_64", configuration, True, False) get_auth_configuration_mock.assert_called_once_with(configuration.include) create_configuration_mock.assert_not_called() write_configuration_mock.assert_called_once_with(pytest.helpers.anyvar(int), args.secure) @@ -81,7 +81,7 @@ def test_run_no_reload(args: argparse.Namespace, configuration: Configuration, m mocker.patch("ahriman.application.handlers.User.configuration_write") reload_mock = mocker.patch("ahriman.core.status.client.Client.reload_auth") - User.run(args, "x86_64", configuration, True) + User.run(args, "x86_64", configuration, True, False) reload_mock.assert_not_called() diff --git a/tests/ahriman/application/handlers/test_handler_web.py b/tests/ahriman/application/handlers/test_handler_web.py index f6db060f..f85bc28f 100644 --- a/tests/ahriman/application/handlers/test_handler_web.py +++ b/tests/ahriman/application/handlers/test_handler_web.py @@ -27,7 +27,7 @@ def test_run(args: argparse.Namespace, configuration: Configuration, mocker: Moc setup_mock = mocker.patch("ahriman.web.web.setup_service") run_mock = mocker.patch("ahriman.web.web.run_server") - Web.run(args, "x86_64", configuration, True) + Web.run(args, "x86_64", configuration, True, False) setup_mock.assert_called_once_with("x86_64", configuration, pytest.helpers.anyvar(int)) run_mock.assert_called_once_with(pytest.helpers.anyvar(int)) diff --git a/tests/ahriman/application/handlers/test_unsafe_commands.py b/tests/ahriman/application/handlers/test_unsafe_commands.py new file mode 100644 index 00000000..4976953b --- /dev/null +++ b/tests/ahriman/application/handlers/test_unsafe_commands.py @@ -0,0 +1,33 @@ +import argparse +import pytest + +from pytest_mock import MockerFixture + +from ahriman.application.ahriman import _parser +from ahriman.application.handlers import UnsafeCommands +from ahriman.core.configuration import Configuration + + +def test_run(args: argparse.Namespace, configuration: Configuration, mocker: MockerFixture) -> None: + """ + must run command + """ + args.parser = _parser + commands_mock = mocker.patch("ahriman.application.handlers.UnsafeCommands.get_unsafe_commands", + return_value=["command"]) + print_mock = mocker.patch("ahriman.application.formatters.printer.Printer.print") + + UnsafeCommands.run(args, "x86_64", configuration, True, False) + commands_mock.assert_called_once_with(pytest.helpers.anyvar(int)) + print_mock.assert_called_once_with(verbose=True) + + +def test_get_unsafe_commands() -> None: + """ + must return unsafe commands + """ + parser = _parser() + subparser = next(action for action in parser._actions if isinstance(action, argparse._SubParsersAction)) + commands = UnsafeCommands.get_unsafe_commands(parser) + for command in commands: + assert subparser.choices[command].get_default("unsafe") diff --git a/tests/ahriman/application/test_ahriman.py b/tests/ahriman/application/test_ahriman.py index d2c2fbb2..f8b7b19a 100644 --- a/tests/ahriman/application/test_ahriman.py +++ b/tests/ahriman/application/test_ahriman.py @@ -65,6 +65,27 @@ def test_subparsers_aur_search_architecture(parser: argparse.ArgumentParser) -> assert args.architecture == [""] +def test_subparsers_help_commands_unsafe(parser: argparse.ArgumentParser) -> None: + """ + help-commands-unsafe command must imply architecture list, lock, no-report, quiet, unsafe and parser + """ + args = parser.parse_args(["help-commands-unsafe"]) + assert args.architecture == [""] + assert args.lock is None + assert args.no_report + assert args.quiet + assert args.unsafe + assert args.parser is not None and args.parser() + + +def test_subparsers_help_commands_unsafe_architecture(parser: argparse.ArgumentParser) -> None: + """ + help-ommands-unsafe command must correctly parse architecture list + """ + args = parser.parse_args(["-a", "x86_64", "help-commands-unsafe"]) + assert args.architecture == [""] + + def test_subparsers_key_import(parser: argparse.ArgumentParser) -> None: """ key-import command must imply architecture list, lock and no-report diff --git a/tests/ahriman/application/test_lock.py b/tests/ahriman/application/test_lock.py index 2e04b465..61cc79b9 100644 --- a/tests/ahriman/application/test_lock.py +++ b/tests/ahriman/application/test_lock.py @@ -82,7 +82,7 @@ def test_check_user(lock: Lock, mocker: MockerFixture) -> None: """ check_user_patch = mocker.patch("ahriman.application.lock.check_user") lock.check_user() - check_user_patch.assert_called_once_with(lock.root) + check_user_patch.assert_called_once_with(lock.root, False) def test_check_user_exception(lock: Lock, mocker: MockerFixture) -> None: diff --git a/tests/ahriman/core/repository/conftest.py b/tests/ahriman/core/repository/conftest.py index b5bda167..b3902a28 100644 --- a/tests/ahriman/core/repository/conftest.py +++ b/tests/ahriman/core/repository/conftest.py @@ -19,7 +19,7 @@ def cleaner(configuration: Configuration, mocker: MockerFixture) -> Cleaner: :return: cleaner test instance """ mocker.patch("ahriman.models.repository_paths.RepositoryPaths.tree_create") - return Cleaner("x86_64", configuration, no_report=True) + return Cleaner("x86_64", configuration, no_report=True, unsafe=False) @pytest.fixture @@ -36,7 +36,7 @@ def executor(configuration: Configuration, mocker: MockerFixture) -> Executor: mocker.patch("ahriman.core.repository.cleaner.Cleaner.clear_manual") mocker.patch("ahriman.core.repository.cleaner.Cleaner.clear_packages") mocker.patch("ahriman.models.repository_paths.RepositoryPaths.tree_create") - return Executor("x86_64", configuration, no_report=True) + return Executor("x86_64", configuration, no_report=True, unsafe=False) @pytest.fixture @@ -48,7 +48,7 @@ def repository(configuration: Configuration, mocker: MockerFixture) -> Repositor :return: repository test instance """ mocker.patch("ahriman.models.repository_paths.RepositoryPaths.tree_create") - return Repository("x86_64", configuration, no_report=True) + return Repository("x86_64", configuration, no_report=True, unsafe=False) @pytest.fixture @@ -58,7 +58,7 @@ def properties(configuration: Configuration) -> Properties: :param configuration: configuration fixture :return: properties test instance """ - return Properties("x86_64", configuration, no_report=True) + return Properties("x86_64", configuration, no_report=True, unsafe=False) @pytest.fixture @@ -75,4 +75,4 @@ def update_handler(configuration: Configuration, mocker: MockerFixture) -> Updat mocker.patch("ahriman.core.repository.cleaner.Cleaner.clear_manual") mocker.patch("ahriman.core.repository.cleaner.Cleaner.clear_packages") mocker.patch("ahriman.models.repository_paths.RepositoryPaths.tree_create") - return UpdateHandler("x86_64", configuration, no_report=True) + return UpdateHandler("x86_64", configuration, no_report=True, unsafe=False) diff --git a/tests/ahriman/core/repository/test_properties.py b/tests/ahriman/core/repository/test_properties.py index 4099d95e..743542e5 100644 --- a/tests/ahriman/core/repository/test_properties.py +++ b/tests/ahriman/core/repository/test_properties.py @@ -12,7 +12,7 @@ def test_create_tree_on_load(configuration: Configuration, mocker: MockerFixture """ mocker.patch("ahriman.core.repository.properties.check_user") tree_create_mock = mocker.patch("ahriman.models.repository_paths.RepositoryPaths.tree_create") - Properties("x86_64", configuration, True) + Properties("x86_64", configuration, True, False) tree_create_mock.assert_called_once_with() @@ -23,7 +23,7 @@ def test_create_tree_on_load_unsafe(configuration: Configuration, mocker: Mocker """ mocker.patch("ahriman.core.repository.properties.check_user", side_effect=UnsafeRun(0, 1)) tree_create_mock = mocker.patch("ahriman.models.repository_paths.RepositoryPaths.tree_create") - Properties("x86_64", configuration, True) + Properties("x86_64", configuration, True, False) tree_create_mock.assert_not_called() @@ -34,7 +34,7 @@ def test_create_dummy_report_client(configuration: Configuration, mocker: Mocker """ mocker.patch("ahriman.models.repository_paths.RepositoryPaths.tree_create") load_mock = mocker.patch("ahriman.core.status.client.Client.load") - properties = Properties("x86_64", configuration, True) + properties = Properties("x86_64", configuration, True, False) load_mock.assert_not_called() assert not isinstance(properties.reporter, WebClient) @@ -46,6 +46,6 @@ def test_create_full_report_client(configuration: Configuration, mocker: MockerF """ mocker.patch("ahriman.models.repository_paths.RepositoryPaths.tree_create") load_mock = mocker.patch("ahriman.core.status.client.Client.load") - Properties("x86_64", configuration, False) + Properties("x86_64", configuration, False, False) load_mock.assert_called_once_with(configuration) diff --git a/tests/ahriman/core/test_util.py b/tests/ahriman/core/test_util.py index 698affa8..dedc77da 100644 --- a/tests/ahriman/core/test_util.py +++ b/tests/ahriman/core/test_util.py @@ -58,7 +58,7 @@ def test_check_user(mocker: MockerFixture) -> None: """ cwd = Path.cwd() mocker.patch("os.getuid", return_value=cwd.stat().st_uid) - check_user(cwd) + check_user(cwd, False) def test_check_user_no_directory(mocker: MockerFixture) -> None: @@ -66,7 +66,7 @@ def test_check_user_no_directory(mocker: MockerFixture) -> None: must not fail in case if no directory found """ mocker.patch("pathlib.Path.exists", return_value=False) - check_user(Path.cwd()) + check_user(Path.cwd(), False) def test_check_user_exception(mocker: MockerFixture) -> None: @@ -77,7 +77,16 @@ def test_check_user_exception(mocker: MockerFixture) -> None: mocker.patch("os.getuid", return_value=cwd.stat().st_uid + 1) with pytest.raises(UnsafeRun): - check_user(cwd) + check_user(cwd, False) + + +def test_check_unsafe(mocker: MockerFixture) -> None: + """ + must skip check if unsafe flag is set + """ + cwd = Path.cwd() + mocker.patch("os.getuid", return_value=cwd.stat().st_uid + 1) + check_user(cwd, True) def test_filter_json(package_ahriman: Package) -> None: