From 43c553a3dbd1cc4bf817075867b84eb600dbd302 Mon Sep 17 00:00:00 2001 From: Evgeniy Alekseev Date: Sat, 5 Nov 2022 03:58:01 +0200 Subject: [PATCH] improve repo-setup command * Move devtools executable to ahriman home, because we don't really need to use executable inside root * Use named sudoers file instead of single file. It will allow easily to remove file as well as use setup command for several repositories/architectures --- src/ahriman/application/handlers/setup.py | 43 ++++++++++------- src/ahriman/core/lazy_logging.py | 2 +- .../handlers/test_handler_setup.py | 48 +++++++++++-------- 3 files changed, 55 insertions(+), 38 deletions(-) diff --git a/src/ahriman/application/handlers/setup.py b/src/ahriman/application/handlers/setup.py index 6d2424b9..565afaee 100644 --- a/src/ahriman/application/handlers/setup.py +++ b/src/ahriman/application/handlers/setup.py @@ -34,17 +34,15 @@ class Setup(Handler): Attributes: ARCHBUILD_COMMAND_PATH(Path): (class attribute) default devtools command - BIN_DIR_PATH(Path): (class attribute) directory for custom binaries MIRRORLIST_PATH(Path): (class attribute) path to pacman default mirrorlist (used by multilib repository) - SUDOERS_PATH(Path): (class attribute) path to sudoers.d include configuration + SUDOERS_DIR_PATH(Path): (class attribute) path to sudoers.d includes directory """ ALLOW_AUTO_ARCHITECTURE_RUN = False ARCHBUILD_COMMAND_PATH = Path("/usr/bin/archbuild") - BIN_DIR_PATH = Path("/usr/local/bin") MIRRORLIST_PATH = Path("/etc/pacman.d/mirrorlist") - SUDOERS_PATH = Path("/etc/sudoers.d/ahriman") + SUDOERS_DIR_PATH = Path("/etc/sudoers.d") @classmethod def run(cls: Type[Handler], args: argparse.Namespace, architecture: str, @@ -59,36 +57,38 @@ class Setup(Handler): no_report(bool): force disable reporting unsafe(bool): if set no user check will be performed before path creation """ - Setup.configuration_create_ahriman(args, architecture, args.repository, configuration.include) + Setup.configuration_create_ahriman(args, architecture, args.repository, configuration.include, + configuration.repository_paths) configuration.reload() application = Application(architecture, configuration, no_report, unsafe) Setup.configuration_create_makepkg(args.packager, application.repository.paths) - Setup.executable_create(args.build_command, architecture) + Setup.executable_create(application.repository.paths, args.build_command, architecture) Setup.configuration_create_devtools(args.build_command, architecture, args.from_configuration, args.no_multilib, args.repository, application.repository.paths) - Setup.configuration_create_sudo(args.build_command, architecture) + Setup.configuration_create_sudo(application.repository.paths, args.build_command, architecture) application.repository.repo.init() @staticmethod - def build_command(prefix: str, architecture: str) -> Path: + def build_command(root: Path, prefix: str, architecture: str) -> Path: """ generate build command name Args: + root(Path): root directory for the build command (must be root of the reporitory) prefix(str): command prefix in {prefix}-{architecture}-build architecture(str): repository architecture Returns: Path: valid devtools command name """ - return Setup.BIN_DIR_PATH / f"{prefix}-{architecture}-build" + return root / f"{prefix}-{architecture}-build" @staticmethod def configuration_create_ahriman(args: argparse.Namespace, architecture: str, repository: str, - include_path: Path) -> None: + include_path: Path, paths: RepositoryPaths) -> None: """ create service specific configuration @@ -97,11 +97,13 @@ class Setup(Handler): architecture(str): repository architecture repository(str): repository name include_path(Path): path to directory with configuration includes + paths(RepositoryPaths): repository paths instance """ configuration = Configuration() section = Configuration.section_name("build", architecture) - configuration.set_option(section, "build_command", str(Setup.build_command(args.build_command, architecture))) + build_command = Setup.build_command(paths.root, args.build_command, architecture) + configuration.set_option(section, "build_command", str(build_command)) 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}") @@ -125,6 +127,9 @@ class Setup(Handler): """ create configuration for devtools based on ``source`` configuration + Note: + devtools does not allow to specify the pacman configuration, thus we still have to use configuration in /usr + Args: prefix(str): command prefix in {prefix}-{architecture}-build architecture(str): repository architecture @@ -171,27 +176,31 @@ class Setup(Handler): (paths.root / ".makepkg.conf").write_text(f"PACKAGER='{packager}'\n", encoding="utf8") @staticmethod - def configuration_create_sudo(prefix: str, architecture: str) -> None: + def configuration_create_sudo(paths: RepositoryPaths, prefix: str, architecture: str) -> None: """ create configuration to run build command with sudo without password Args: + paths(RepositoryPaths): repository paths instance prefix(str): command prefix in {prefix}-{architecture}-build architecture(str): repository architecture """ - command = Setup.build_command(prefix, architecture) - Setup.SUDOERS_PATH.write_text(f"ahriman ALL=(ALL) NOPASSWD: {command} *\n", encoding="utf8") - Setup.SUDOERS_PATH.chmod(0o400) # security! + command = Setup.build_command(paths.root, prefix, architecture) + sudoers_file = Setup.build_command(Setup.SUDOERS_DIR_PATH, prefix, architecture) + sudoers_file.write_text(f"ahriman ALL=(ALL) NOPASSWD: {command} *\n", encoding="utf8") + sudoers_file.chmod(0o400) # security! @staticmethod - def executable_create(prefix: str, architecture: str) -> None: + def executable_create(paths: RepositoryPaths, prefix: str, architecture: str) -> None: """ create executable for the service Args: + paths(RepositoryPaths): repository paths instance prefix(str): command prefix in {prefix}-{architecture}-build architecture(str): repository architecture """ - command = Setup.build_command(prefix, architecture) + command = Setup.build_command(paths.root, prefix, architecture) command.unlink(missing_ok=True) command.symlink_to(Setup.ARCHBUILD_COMMAND_PATH) + paths.chown(command) # we would like to keep owner inside ahriman's home diff --git a/src/ahriman/core/lazy_logging.py b/src/ahriman/core/lazy_logging.py index 96d330d4..e1c51b42 100644 --- a/src/ahriman/core/lazy_logging.py +++ b/src/ahriman/core/lazy_logging.py @@ -61,4 +61,4 @@ class LazyLogging: """ clazz = self.__class__ prefix = "" if clazz.__module__ is None else f"{clazz.__module__}." - return f"{prefix}{self.__class__.__qualname__}" + return f"{prefix}{clazz.__qualname__}" diff --git a/tests/ahriman/application/handlers/test_handler_setup.py b/tests/ahriman/application/handlers/test_handler_setup.py index 596e10c6..7bf661ee 100644 --- a/tests/ahriman/application/handlers/test_handler_setup.py +++ b/tests/ahriman/application/handlers/test_handler_setup.py @@ -33,7 +33,8 @@ def _default_args(args: argparse.Namespace) -> argparse.Namespace: return args -def test_run(args: argparse.Namespace, configuration: Configuration, mocker: MockerFixture) -> None: +def test_run(args: argparse.Namespace, configuration: Configuration, repository_paths: RepositoryPaths, + mocker: MockerFixture) -> None: """ must run command """ @@ -45,15 +46,15 @@ def test_run(args: argparse.Namespace, configuration: Configuration, mocker: Moc sudo_configuration_mock = mocker.patch("ahriman.application.handlers.Setup.configuration_create_sudo") executable_mock = mocker.patch("ahriman.application.handlers.Setup.executable_create") init_mock = mocker.patch("ahriman.core.alpm.repo.Repo.init") - paths = RepositoryPaths(configuration.getpath("repository", "root"), "x86_64") 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) - makepkg_configuration_mock.assert_called_once_with(args.packager, paths) - sudo_configuration_mock.assert_called_once_with(args.build_command, "x86_64") - executable_mock.assert_called_once_with(args.build_command, "x86_64") + ahriman_configuration_mock.assert_called_once_with( + args, "x86_64", args.repository, configuration.include, repository_paths) + devtools_configuration_mock.assert_called_once_with( + args.build_command, "x86_64", args.from_configuration, args.no_multilib, args.repository, repository_paths) + makepkg_configuration_mock.assert_called_once_with(args.packager, repository_paths) + sudo_configuration_mock.assert_called_once_with(repository_paths, args.build_command, "x86_64") + executable_mock.assert_called_once_with(repository_paths, args.build_command, "x86_64") init_mock.assert_called_once() @@ -62,11 +63,15 @@ def test_build_command(args: argparse.Namespace) -> None: must generate correct build command name """ args = _default_args(args) - assert Setup.build_command(args.build_command, "x86_64").name == f"{args.build_command}-x86_64-build" + path = Path("local") + + build_command = Setup.build_command(path, args.build_command, "x86_64") + assert build_command.name == f"{args.build_command}-x86_64-build" + assert build_command.parent == path def test_configuration_create_ahriman(args: argparse.Namespace, configuration: Configuration, - mocker: MockerFixture) -> None: + repository_paths: RepositoryPaths, mocker: MockerFixture) -> None: """ must create configuration for the service """ @@ -74,9 +79,9 @@ def test_configuration_create_ahriman(args: argparse.Namespace, configuration: C mocker.patch("pathlib.Path.open") set_option_mock = mocker.patch("ahriman.core.configuration.Configuration.set_option") write_mock = mocker.patch("ahriman.core.configuration.Configuration.write") + command = Setup.build_command(repository_paths.root, args.build_command, "x86_64") - command = Setup.build_command(args.build_command, "x86_64") - Setup.configuration_create_ahriman(args, "x86_64", args.repository, configuration.include) + Setup.configuration_create_ahriman(args, "x86_64", args.repository, configuration.include, repository_paths) set_option_mock.assert_has_calls([ mock.call(Configuration.section_name("build", "x86_64"), "build_command", str(command)), mock.call("repository", "name", args.repository), @@ -136,7 +141,8 @@ def test_configuration_create_makepkg(args: argparse.Namespace, repository_paths write_text_mock.assert_called_once_with(pytest.helpers.anyvar(str, True), encoding="utf8") -def test_configuration_create_sudo(args: argparse.Namespace, mocker: MockerFixture) -> None: +def test_configuration_create_sudo(args: argparse.Namespace, repository_paths: RepositoryPaths, + mocker: MockerFixture) -> None: """ must create sudo configuration """ @@ -144,22 +150,24 @@ def test_configuration_create_sudo(args: argparse.Namespace, mocker: MockerFixtu chmod_text_mock = mocker.patch("pathlib.Path.chmod") write_text_mock = mocker.patch("pathlib.Path.write_text") - Setup.configuration_create_sudo(args.build_command, "x86_64") + Setup.configuration_create_sudo(repository_paths, args.build_command, "x86_64") chmod_text_mock.assert_called_once_with(0o400) write_text_mock.assert_called_once_with(pytest.helpers.anyvar(str, True), encoding="utf8") -def test_executable_create(args: argparse.Namespace, mocker: MockerFixture) -> None: +def test_executable_create(args: argparse.Namespace, repository_paths: RepositoryPaths, mocker: MockerFixture) -> None: """ must create executable """ args = _default_args(args) - symlink_text_mock = mocker.patch("pathlib.Path.symlink_to") - unlink_text_mock = mocker.patch("pathlib.Path.unlink") + chown_mock = mocker.patch("ahriman.models.repository_paths.RepositoryPaths.chown") + symlink_mock = mocker.patch("pathlib.Path.symlink_to") + unlink_mock = mocker.patch("pathlib.Path.unlink") - Setup.executable_create(args.build_command, "x86_64") - symlink_text_mock.assert_called_once_with(Setup.ARCHBUILD_COMMAND_PATH) - unlink_text_mock.assert_called_once_with(missing_ok=True) + Setup.executable_create(repository_paths, args.build_command, "x86_64") + chown_mock.assert_called_once_with(Setup.build_command(repository_paths.root, args.build_command, "x86_64")) + symlink_mock.assert_called_once_with(Setup.ARCHBUILD_COMMAND_PATH) + unlink_mock.assert_called_once_with(missing_ok=True) def test_disallow_auto_architecture_run() -> None: