mirror of
https://github.com/arcan1s/ahriman.git
synced 2026-08-12 03:39:54 +00:00
feat: get rid of sudoers modification during setup command
This commit introduces small shell wrapper, which calls archbuild mimicing symlinked command. It allows to distribute static sudoers file. Additionally build_command option has been renamed to devtools_wrapper in order to allow to keep backward compatibility
This commit is contained in:
Executable
+33
@@ -0,0 +1,33 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
usage() {
|
||||
local cmd="${0##*/}"
|
||||
echo "Usage: $cmd [options] -- [archbuild args]"
|
||||
echo " -r <repository> Repository name"
|
||||
echo " -a <architecture> Repository architecture"
|
||||
exit 1
|
||||
}
|
||||
|
||||
repository=
|
||||
architecture=
|
||||
|
||||
while getopts ":r:a:" arg; do
|
||||
case "$arg" in
|
||||
r) repository="$OPTARG" ;;
|
||||
a) architecture="$OPTARG" ;;
|
||||
*) usage ;;
|
||||
esac
|
||||
done
|
||||
|
||||
if [[ -z $repository || -z $architecture ]]; then
|
||||
usage
|
||||
fi
|
||||
|
||||
source "/usr/share/devtools/lib/archroot.sh"
|
||||
check_root "SOURCE_DATE_EPOCH,SRCDEST,SRCPKGDEST,PKGDEST,LOGDEST,NPROC,MAKEFLAGS,PACKAGER,GNUPGHOME" "${BASH_SOURCE[0]}" "$@"
|
||||
|
||||
exec bash -c '
|
||||
source "$1" "${@:2}"
|
||||
' "${repository}-${architecture}-build" "archbuild" "${@:$OPTIND}"
|
||||
@@ -35,7 +35,7 @@ retry_backoff = 1.0
|
||||
; List of additional flags passed to archbuild command.
|
||||
;archbuild_flags =
|
||||
; Path to build command.
|
||||
;build_command =
|
||||
devtools_wrapper = ahriman-archbuild
|
||||
; List of packages to be ignored during automatic updates.
|
||||
;ignore_packages =
|
||||
; Include debug packages.
|
||||
|
||||
@@ -57,6 +57,7 @@ packages = [
|
||||
]
|
||||
|
||||
[tool.hatch.build.targets.wheel.shared-data]
|
||||
"package/bin" = "bin"
|
||||
"package/lib" = "lib"
|
||||
"package/share" = "share"
|
||||
|
||||
|
||||
@@ -40,16 +40,12 @@ class Setup(Handler):
|
||||
setup handler
|
||||
|
||||
Attributes:
|
||||
ARCHBUILD_COMMAND_PATH(Path): (class attribute) default devtools command
|
||||
MIRRORLIST_PATH(Path): (class attribute) path to pacman default mirrorlist (used by multilib repository)
|
||||
SUDOERS_DIR_PATH(Path): (class attribute) path to sudoers.d includes directory
|
||||
"""
|
||||
|
||||
ALLOW_MULTI_ARCHITECTURE_RUN = False # conflicting io
|
||||
|
||||
ARCHBUILD_COMMAND_PATH: ClassVar[Path] = Path("/") / "usr" / "bin" / "archbuild"
|
||||
MIRRORLIST_PATH: ClassVar[Path] = Path("/") / "etc" / "pacman.d" / "mirrorlist"
|
||||
SUDOERS_DIR_PATH: ClassVar[Path] = Path("/") / "etc" / "sudoers.d"
|
||||
|
||||
@classmethod
|
||||
def run(cls, args: argparse.Namespace, repository_id: RepositoryId, configuration: Configuration, *,
|
||||
@@ -74,11 +70,9 @@ class Setup(Handler):
|
||||
|
||||
# basically we create configuration here as root, but it is ok, because those files are only used for reading
|
||||
Setup.configuration_create_makepkg(args.packager, args.makeflags_jobs, application.repository.paths)
|
||||
Setup.executable_create(application.repository.paths, repository_id)
|
||||
repository_server = f"file://{application.repository.paths.repository}" if args.server is None else args.server
|
||||
Setup.configuration_create_devtools(
|
||||
repository_id, args.from_configuration, args.mirror, args.multilib, repository_server)
|
||||
Setup.configuration_create_sudo(application.repository.paths, repository_id)
|
||||
|
||||
# finish initialization
|
||||
with application.repository.paths.preserve_owner():
|
||||
@@ -124,20 +118,6 @@ class Setup(Handler):
|
||||
parser.set_defaults(lock=None, quiet=True, report=False, unsafe=True)
|
||||
return parser
|
||||
|
||||
@staticmethod
|
||||
def build_command(root: Path, repository_id: RepositoryId) -> Path:
|
||||
"""
|
||||
generate build command name
|
||||
|
||||
Args:
|
||||
root(Path): root directory for the build command (must be root of the repository)
|
||||
repository_id(RepositoryId): repository unique identifier
|
||||
|
||||
Returns:
|
||||
Path: valid devtools command name
|
||||
"""
|
||||
return root / f"{repository_id.name}-{repository_id.architecture}-build"
|
||||
|
||||
@staticmethod
|
||||
def configuration_create_ahriman(args: argparse.Namespace, repository_id: RepositoryId,
|
||||
root: Configuration) -> None:
|
||||
@@ -152,8 +132,6 @@ class Setup(Handler):
|
||||
configuration = Configuration()
|
||||
|
||||
section = Configuration.section_name("build", repository_id.name, repository_id.architecture)
|
||||
build_command = Setup.build_command(root.repository_paths.root, repository_id)
|
||||
configuration.set_option(section, "build_command", str(build_command))
|
||||
configuration.set_option("repository", "name", repository_id.name) # backward compatibility for docker
|
||||
if args.build_as_user is not None:
|
||||
configuration.set_option(section, "makechrootpkg_flags", f"-U {args.build_as_user}")
|
||||
@@ -257,31 +235,4 @@ class Setup(Handler):
|
||||
home_dir = Path(getpwuid(uid).pw_dir)
|
||||
(home_dir / ".makepkg.conf").write_text(content, encoding="utf8")
|
||||
|
||||
@staticmethod
|
||||
def configuration_create_sudo(paths: RepositoryPaths, repository_id: RepositoryId) -> None:
|
||||
"""
|
||||
create configuration to run build command with sudo without password
|
||||
|
||||
Args:
|
||||
paths(RepositoryPaths): repository paths instance
|
||||
repository_id(RepositoryId): repository unique identifier
|
||||
"""
|
||||
command = Setup.build_command(paths.root, repository_id)
|
||||
sudoers_file = Setup.build_command(Setup.SUDOERS_DIR_PATH, repository_id)
|
||||
sudoers_file.write_text(f"ahriman ALL=(ALL) NOPASSWD:SETENV: {command} *\n", encoding="utf8")
|
||||
sudoers_file.chmod(0o400) # security!
|
||||
|
||||
@staticmethod
|
||||
def executable_create(paths: RepositoryPaths, repository_id: RepositoryId) -> None:
|
||||
"""
|
||||
create executable for the service
|
||||
|
||||
Args:
|
||||
paths(RepositoryPaths): repository paths instance
|
||||
repository_id(RepositoryId): repository unique identifier
|
||||
"""
|
||||
command = Setup.build_command(paths.root, repository_id)
|
||||
command.unlink(missing_ok=True)
|
||||
command.symlink_to(Setup.ARCHBUILD_COMMAND_PATH)
|
||||
|
||||
arguments = [_set_service_setup_parser]
|
||||
|
||||
@@ -56,7 +56,7 @@ class PackageVersion(LazyLogging):
|
||||
|
||||
_, repository_id = configuration.check_loaded()
|
||||
paths = configuration.repository_paths
|
||||
task = Task(self.package, configuration, repository_id.architecture, paths)
|
||||
task = Task(self.package, configuration, repository_id, paths)
|
||||
|
||||
try:
|
||||
# create fresh chroot environment, fetch sources and - automagically - update PKGBUILD
|
||||
|
||||
@@ -27,6 +27,7 @@ from ahriman.core.log import LazyLogging
|
||||
from ahriman.core.utils import check_output, package_like
|
||||
from ahriman.models.package import Package
|
||||
from ahriman.models.pkgbuild_patch import PkgbuildPatch
|
||||
from ahriman.models.repository_id import RepositoryId
|
||||
from ahriman.models.repository_paths import RepositoryPaths
|
||||
|
||||
|
||||
@@ -36,32 +37,33 @@ class Task(LazyLogging):
|
||||
|
||||
Attributes:
|
||||
archbuild_flags(list[str]): command flags for archbuild command
|
||||
architecture(str): repository architecture
|
||||
build_command(str): build command
|
||||
build_command(list[str]): build command
|
||||
include_debug_packages(bool): whether to include debug packages or not
|
||||
makechrootpkg_flags(list[str]): command flags for makechrootpkg command
|
||||
makepkg_flags(list[str]): command flags for makepkg command
|
||||
package(Package): package definitions
|
||||
paths(RepositoryPaths): repository paths instance
|
||||
repository_id(RepositoryId): repository unique identifier
|
||||
uid(int): uid of the repository owner user
|
||||
"""
|
||||
|
||||
def __init__(self, package: Package, configuration: Configuration, architecture: str,
|
||||
def __init__(self, package: Package, configuration: Configuration, repository_id: RepositoryId,
|
||||
paths: RepositoryPaths) -> None:
|
||||
"""
|
||||
Args:
|
||||
package(Package): package definitions
|
||||
configuration(Configuration): configuration instance
|
||||
architecture(str): repository architecture
|
||||
repository_id(RepositoryId): repository unique identifier
|
||||
paths(RepositoryPaths): repository paths instance
|
||||
"""
|
||||
self.package = package
|
||||
self.paths = paths
|
||||
self.uid, _ = paths.root_owner
|
||||
self.architecture = architecture
|
||||
self.repository_id = repository_id
|
||||
|
||||
self.archbuild_flags = configuration.getlist("build", "archbuild_flags", fallback=[])
|
||||
self.build_command = configuration.get("build", "build_command")
|
||||
self.build_command = configuration.getlist("build", "devtools_wrapper")
|
||||
self._legacy_build_command = configuration.getlist("build", "build_command", fallback=[])
|
||||
self.include_debug_packages = configuration.getboolean("build", "include_debug_packages", fallback=True)
|
||||
self.makepkg_flags = configuration.getlist("build", "makepkg_flags", fallback=[])
|
||||
self.makechrootpkg_flags = configuration.getlist("build", "makechrootpkg_flags", fallback=[])
|
||||
@@ -108,12 +110,16 @@ class Task(LazyLogging):
|
||||
Returns:
|
||||
list[Path]: paths of produced packages
|
||||
"""
|
||||
command = [self.build_command, "-r", str(self.paths.chroot)]
|
||||
command.extend(self.archbuild_flags)
|
||||
command.extend(["--", "-D", str(self.paths.archive)] + self.makechrootpkg_flags)
|
||||
command.extend(["--"] + self.makepkg_flags)
|
||||
command = self._legacy_build_command[:]
|
||||
if not command:
|
||||
command = self.build_command + ["-r", self.repository_id.name, "-a", self.repository_id.architecture, "--"]
|
||||
|
||||
command.extend(["-r", str(self.paths.chroot)] + self.archbuild_flags) # archbuild flags
|
||||
command.extend(["--", "-D", str(self.paths.archive)] + self.makechrootpkg_flags) # makechrootpkg flags
|
||||
command.extend(["--"] + self.makepkg_flags) # makepkg flags
|
||||
if dry_run:
|
||||
command.extend(["--nobuild"])
|
||||
|
||||
self.logger.info("using %s for %s", command, self.package.base)
|
||||
|
||||
environment: dict[str, str] = {
|
||||
@@ -156,7 +162,7 @@ class Task(LazyLogging):
|
||||
return last_commit_sha
|
||||
|
||||
# load fresh package
|
||||
loaded_package = Package.from_build(sources_dir, self.architecture, None)
|
||||
loaded_package = Package.from_build(sources_dir, self.repository_id.architecture, None)
|
||||
if (pkgrel := loaded_package.next_pkgrel(local_version)) is not None:
|
||||
self.logger.info("package %s is the same as in repo, bumping pkgrel to %s", self.package.base, pkgrel)
|
||||
patch = PkgbuildPatch("pkgrel", pkgrel)
|
||||
|
||||
@@ -190,8 +190,20 @@ CONFIGURATION_SCHEMA: ConfigurationSchema = {
|
||||
},
|
||||
},
|
||||
"build_command": {
|
||||
"type": "string",
|
||||
"required": True,
|
||||
"type": "list",
|
||||
"coerce": "list",
|
||||
"schema": {
|
||||
"type": "string",
|
||||
"empty": False,
|
||||
},
|
||||
},
|
||||
"devtools_wrapper": {
|
||||
"type": "list",
|
||||
"coerce": "list",
|
||||
"schema": {
|
||||
"type": "string",
|
||||
"empty": False,
|
||||
},
|
||||
"empty": False,
|
||||
},
|
||||
"ignore_packages": {
|
||||
|
||||
@@ -73,7 +73,7 @@ class Executor(PackageInfo, Cleaner):
|
||||
"""
|
||||
self.reporter.set_building(package.base)
|
||||
|
||||
task = Task(package, self.configuration, self.repository_id.architecture, self.paths)
|
||||
task = Task(package, self.configuration, self.repository_id, self.paths)
|
||||
patches = self.reporter.package_patches_get(package.base, None)
|
||||
commit_sha = task.init(path, patches, local_version)
|
||||
|
||||
|
||||
@@ -55,8 +55,6 @@ def test_run(args: argparse.Namespace, configuration: Configuration, repository:
|
||||
ahriman_configuration_mock = mocker.patch("ahriman.application.handlers.setup.Setup.configuration_create_ahriman")
|
||||
devtools_configuration_mock = mocker.patch("ahriman.application.handlers.setup.Setup.configuration_create_devtools")
|
||||
makepkg_configuration_mock = mocker.patch("ahriman.application.handlers.setup.Setup.configuration_create_makepkg")
|
||||
sudo_configuration_mock = mocker.patch("ahriman.application.handlers.setup.Setup.configuration_create_sudo")
|
||||
executable_mock = mocker.patch("ahriman.application.handlers.setup.Setup.executable_create")
|
||||
init_mock = mocker.patch("ahriman.core.alpm.repo.Repo.init")
|
||||
owner_guard_mock = mocker.patch("ahriman.models.repository_paths.RepositoryPaths.preserve_owner")
|
||||
|
||||
@@ -67,8 +65,6 @@ def test_run(args: argparse.Namespace, configuration: Configuration, repository:
|
||||
devtools_configuration_mock.assert_called_once_with(
|
||||
repository_id, args.from_configuration, args.mirror, args.multilib, f"file://{repository_paths.repository}")
|
||||
makepkg_configuration_mock.assert_called_once_with(args.packager, args.makeflags_jobs, repository_paths)
|
||||
sudo_configuration_mock.assert_called_once_with(repository_paths, repository_id)
|
||||
executable_mock.assert_called_once_with(repository_paths, repository_id)
|
||||
init_mock.assert_called_once_with()
|
||||
|
||||
|
||||
@@ -102,8 +98,6 @@ def test_run_with_server(args: argparse.Namespace, configuration: Configuration,
|
||||
mocker.patch("ahriman.core.repository.Repository.load", return_value=repository)
|
||||
mocker.patch("ahriman.application.handlers.setup.Setup.configuration_create_ahriman")
|
||||
mocker.patch("ahriman.application.handlers.setup.Setup.configuration_create_makepkg")
|
||||
mocker.patch("ahriman.application.handlers.setup.Setup.configuration_create_sudo")
|
||||
mocker.patch("ahriman.application.handlers.setup.Setup.executable_create")
|
||||
mocker.patch("ahriman.core.alpm.repo.Repo.init")
|
||||
devtools_configuration_mock = mocker.patch("ahriman.application.handlers.setup.Setup.configuration_create_devtools")
|
||||
|
||||
@@ -113,17 +107,6 @@ def test_run_with_server(args: argparse.Namespace, configuration: Configuration,
|
||||
repository_id, args.from_configuration, args.mirror, args.multilib, "server")
|
||||
|
||||
|
||||
def test_build_command(repository_id: RepositoryId) -> None:
|
||||
"""
|
||||
must generate correct build command name
|
||||
"""
|
||||
path = Path("local")
|
||||
|
||||
build_command = Setup.build_command(path, repository_id)
|
||||
assert build_command.name == f"{repository_id.name}-{repository_id.architecture}-build"
|
||||
assert build_command.parent == path
|
||||
|
||||
|
||||
def test_configuration_create_ahriman(args: argparse.Namespace, configuration: Configuration,
|
||||
repository_paths: RepositoryPaths, mocker: MockerFixture) -> None:
|
||||
"""
|
||||
@@ -135,12 +118,9 @@ def test_configuration_create_ahriman(args: argparse.Namespace, configuration: C
|
||||
write_mock = mocker.patch("ahriman.core.configuration.Configuration.write")
|
||||
remove_mock = mocker.patch("pathlib.Path.unlink", autospec=True)
|
||||
_, repository_id = configuration.check_loaded()
|
||||
command = Setup.build_command(repository_paths.root, repository_id)
|
||||
|
||||
Setup.configuration_create_ahriman(args, repository_id, configuration)
|
||||
set_option_mock.assert_has_calls([
|
||||
MockCall(Configuration.section_name("build", repository_id.name, repository_id.architecture), "build_command",
|
||||
str(command)),
|
||||
MockCall("repository", "name", repository_id.name),
|
||||
MockCall(Configuration.section_name("build", repository_id.name, repository_id.architecture),
|
||||
"makechrootpkg_flags", f"-U {args.build_as_user}"),
|
||||
@@ -251,34 +231,6 @@ def test_configuration_create_makepkg(args: argparse.Namespace, repository_paths
|
||||
Path("home") / ".makepkg.conf", pytest.helpers.anyvar(str, True), encoding="utf8")
|
||||
|
||||
|
||||
def test_configuration_create_sudo(configuration: Configuration, repository_paths: RepositoryPaths,
|
||||
mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must create sudo configuration
|
||||
"""
|
||||
chmod_text_mock = mocker.patch("pathlib.Path.chmod")
|
||||
write_text_mock = mocker.patch("pathlib.Path.write_text")
|
||||
|
||||
_, repository_id = configuration.check_loaded()
|
||||
Setup.configuration_create_sudo(repository_paths, repository_id)
|
||||
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(configuration: Configuration, repository_paths: RepositoryPaths,
|
||||
mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must create executable
|
||||
"""
|
||||
symlink_mock = mocker.patch("pathlib.Path.symlink_to")
|
||||
unlink_mock = mocker.patch("pathlib.Path.unlink")
|
||||
|
||||
_, repository_id = configuration.check_loaded()
|
||||
Setup.executable_create(repository_paths, repository_id)
|
||||
symlink_mock.assert_called_once_with(Setup.ARCHBUILD_COMMAND_PATH)
|
||||
unlink_mock.assert_called_once_with(missing_ok=True)
|
||||
|
||||
|
||||
def test_disallow_multi_architecture_run() -> None:
|
||||
"""
|
||||
must not allow multi architecture run
|
||||
|
||||
@@ -63,7 +63,7 @@ def test_run_default(args: argparse.Namespace, configuration: Configuration) ->
|
||||
|
||||
default = Configuration.from_path(Configuration.SYSTEM_CONFIGURATION_PATH, repository_id)
|
||||
# copy autogenerated values
|
||||
for section, key in (("build", "build_command"), ("repository", "root")):
|
||||
for section, key in (("repository", "root"),):
|
||||
value = configuration.get(section, key)
|
||||
default.set_option(section, key, value)
|
||||
|
||||
|
||||
@@ -53,8 +53,9 @@ def test_build(task_ahriman: Task, mocker: MockerFixture) -> None:
|
||||
|
||||
assert task_ahriman.build(local) == [task_ahriman.package.base]
|
||||
check_output_mock.assert_called_once_with(
|
||||
"extra-x86_64-build",
|
||||
"-r", str(task_ahriman.paths.chroot),
|
||||
"ahriman-archbuild",
|
||||
"-r", task_ahriman.repository_id.name, "-a", task_ahriman.repository_id.architecture,
|
||||
"--", "-r", str(task_ahriman.paths.chroot),
|
||||
"--", "-D", str(task_ahriman.paths.archive),
|
||||
"--", "--skippgpcheck",
|
||||
exception=pytest.helpers.anyvar(int),
|
||||
@@ -79,8 +80,9 @@ def test_build_environment(task_ahriman: Task, mocker: MockerFixture) -> None:
|
||||
|
||||
task_ahriman.build(local, **environment, empty=None)
|
||||
check_output_mock.assert_called_once_with(
|
||||
"extra-x86_64-build",
|
||||
"-r", str(task_ahriman.paths.chroot),
|
||||
"ahriman-archbuild",
|
||||
"-r", task_ahriman.repository_id.name, "-a", task_ahriman.repository_id.architecture,
|
||||
"--", "-r", str(task_ahriman.paths.chroot),
|
||||
"--", "-D", str(task_ahriman.paths.archive),
|
||||
"--", "--skippgpcheck",
|
||||
exception=pytest.helpers.anyvar(int),
|
||||
@@ -101,12 +103,37 @@ def test_build_dry_run(task_ahriman: Task, mocker: MockerFixture) -> None:
|
||||
check_output_mock = mocker.patch("ahriman.core.build_tools.task.check_output")
|
||||
|
||||
assert task_ahriman.build(local, dry_run=True) == [task_ahriman.package.base]
|
||||
check_output_mock.assert_called_once_with(
|
||||
"ahriman-archbuild",
|
||||
"-r", task_ahriman.repository_id.name, "-a", task_ahriman.repository_id.architecture,
|
||||
"--", "-r", str(task_ahriman.paths.chroot),
|
||||
"--", "-D", str(task_ahriman.paths.archive),
|
||||
"--", "--skippgpcheck",
|
||||
"--nobuild",
|
||||
exception=pytest.helpers.anyvar(int),
|
||||
cwd=local,
|
||||
logger=task_ahriman.logger,
|
||||
user=task_ahriman.uid,
|
||||
environment={},
|
||||
)
|
||||
|
||||
|
||||
def test_build_legacy_subcommand(task_ahriman: Task, mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must build package by using legacy subcommand if set
|
||||
"""
|
||||
local = Path("local")
|
||||
mocker.patch("pathlib.Path.iterdir", return_value=["file"])
|
||||
mocker.patch("ahriman.core.build_tools.task.Task._package_archives", return_value=[task_ahriman.package.base])
|
||||
check_output_mock = mocker.patch("ahriman.core.build_tools.task.check_output")
|
||||
task_ahriman._legacy_build_command = ["extra-x86_64-build"]
|
||||
|
||||
task_ahriman.build(local)
|
||||
check_output_mock.assert_called_once_with(
|
||||
"extra-x86_64-build",
|
||||
"-r", str(task_ahriman.paths.chroot),
|
||||
"--", "-D", str(task_ahriman.paths.archive),
|
||||
"--", "--skippgpcheck",
|
||||
"--nobuild",
|
||||
exception=pytest.helpers.anyvar(int),
|
||||
cwd=local,
|
||||
logger=task_ahriman.logger,
|
||||
|
||||
@@ -76,4 +76,5 @@ def task_ahriman(package_ahriman: Package, configuration: Configuration, reposit
|
||||
Returns:
|
||||
Task: built task test instance
|
||||
"""
|
||||
return Task(package_ahriman, configuration, "x86_64", repository_paths)
|
||||
_, repository_id = configuration.check_loaded()
|
||||
return Task(package_ahriman, configuration, repository_id, repository_paths)
|
||||
|
||||
Reference in New Issue
Block a user