mirror of
https://github.com/arcan1s/ahriman.git
synced 2025-07-13 22:15:48 +00:00
deprecate init/repo-init command
In current workflow you need to run setup to run init (because of repository name), but you need to run init before setup (because of repository tree rights). New solution just add `Repo.init()` method call to setup subcommand after config reload to make sure that repository name has been applied. In addition chown method as well as setuid method for check_output have been added.
This commit is contained in:
@ -85,7 +85,6 @@ def _parser() -> argparse.ArgumentParser:
|
||||
_set_repo_check_parser(subparsers)
|
||||
_set_repo_clean_parser(subparsers)
|
||||
_set_repo_config_parser(subparsers)
|
||||
_set_repo_init_parser(subparsers)
|
||||
_set_repo_rebuild_parser(subparsers)
|
||||
_set_repo_remove_unknown_parser(subparsers)
|
||||
_set_repo_report_parser(subparsers)
|
||||
@ -342,19 +341,6 @@ def _set_repo_config_parser(root: SubParserAction) -> argparse.ArgumentParser:
|
||||
return parser
|
||||
|
||||
|
||||
def _set_repo_init_parser(root: SubParserAction) -> argparse.ArgumentParser:
|
||||
"""
|
||||
add parser for repository init subcommand
|
||||
:param root: subparsers for the commands
|
||||
:return: created argument parser
|
||||
"""
|
||||
parser = root.add_parser("repo-init", aliases=["init"], help="create repository tree",
|
||||
description="create empty repository tree. Optional command for auto architecture support",
|
||||
formatter_class=_formatter)
|
||||
parser.set_defaults(handler=handlers.Init, no_report=True)
|
||||
return parser
|
||||
|
||||
|
||||
def _set_repo_rebuild_parser(root: SubParserAction) -> argparse.ArgumentParser:
|
||||
"""
|
||||
add parser for repository rebuild subcommand
|
||||
@ -406,7 +392,7 @@ def _set_repo_setup_parser(root: SubParserAction) -> argparse.ArgumentParser:
|
||||
:param root: subparsers for the commands
|
||||
:return: created argument parser
|
||||
"""
|
||||
parser = root.add_parser("repo-setup", aliases=["setup"], help="initial service configuration",
|
||||
parser = root.add_parser("repo-setup", aliases=["init", "repo-init", "setup"], help="initial service configuration",
|
||||
description="create initial service configuration, requires root",
|
||||
epilog="Create _minimal_ configuration for the service according to provided options.",
|
||||
formatter_class=_formatter)
|
||||
|
@ -22,7 +22,6 @@ from ahriman.application.handlers.handler import Handler
|
||||
from ahriman.application.handlers.add import Add
|
||||
from ahriman.application.handlers.clean import Clean
|
||||
from ahriman.application.handlers.dump import Dump
|
||||
from ahriman.application.handlers.init import Init
|
||||
from ahriman.application.handlers.key_import import KeyImport
|
||||
from ahriman.application.handlers.patch import Patch
|
||||
from ahriman.application.handlers.rebuild import Rebuild
|
||||
|
@ -27,7 +27,7 @@ from typing import List, Type
|
||||
|
||||
from ahriman.application.lock import Lock
|
||||
from ahriman.core.configuration import Configuration
|
||||
from ahriman.core.exceptions import MissingArchitecture, MultipleArchitecture
|
||||
from ahriman.core.exceptions import MissingArchitecture, MultipleArchitectures
|
||||
from ahriman.models.repository_paths import RepositoryPaths
|
||||
|
||||
|
||||
@ -95,7 +95,7 @@ class Handler:
|
||||
# actually we do not have to spawn another process if it is single-process application, do we?
|
||||
if len(architectures) > 1:
|
||||
if not cls.ALLOW_MULTI_ARCHITECTURE_RUN:
|
||||
raise MultipleArchitecture(args.command)
|
||||
raise MultipleArchitectures(args.command)
|
||||
|
||||
with Pool(len(architectures)) as pool:
|
||||
result = pool.starmap(
|
||||
|
@ -1,47 +0,0 @@
|
||||
#
|
||||
# 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 <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
import argparse
|
||||
|
||||
from typing import Type
|
||||
|
||||
from ahriman.application.application import Application
|
||||
from ahriman.application.handlers.handler import Handler
|
||||
from ahriman.core.configuration import Configuration
|
||||
|
||||
|
||||
class Init(Handler):
|
||||
"""
|
||||
repository init handler
|
||||
"""
|
||||
|
||||
ALLOW_AUTO_ARCHITECTURE_RUN = False
|
||||
|
||||
@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
|
||||
"""
|
||||
Application(architecture, configuration, no_report, unsafe).repository.repo.init()
|
@ -55,14 +55,19 @@ class Setup(Handler):
|
||||
:param no_report: force disable reporting
|
||||
:param unsafe: if set no user check will be performed before path creation
|
||||
"""
|
||||
Setup.configuration_create_ahriman(args, architecture, args.repository, configuration.include)
|
||||
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.configuration_create_devtools(args.build_command, architecture, args.from_configuration,
|
||||
args.no_multilib, args.repository, application.repository.paths)
|
||||
Setup.configuration_create_ahriman(args, architecture, args.repository, configuration.include)
|
||||
Setup.configuration_create_sudo(args.build_command, architecture)
|
||||
|
||||
application.repository.repo.init()
|
||||
|
||||
@staticmethod
|
||||
def build_command(prefix: str, architecture: str) -> Path:
|
||||
"""
|
||||
|
@ -32,6 +32,7 @@ from ahriman.core.exceptions import DuplicateRun
|
||||
from ahriman.core.status.client import Client
|
||||
from ahriman.core.util import check_user
|
||||
from ahriman.models.build_status import BuildStatusEnum
|
||||
from ahriman.models.repository_paths import RepositoryPaths
|
||||
|
||||
|
||||
class Lock:
|
||||
@ -40,7 +41,7 @@ class Lock:
|
||||
:ivar force: remove lock file on start if any
|
||||
:ivar path: path to lock file if any
|
||||
:ivar reporter: build status reporter instance
|
||||
:ivar root: repository root (i.e. ahriman home)
|
||||
:ivar paths: repository paths instance
|
||||
:ivar unsafe: skip user check
|
||||
"""
|
||||
|
||||
@ -55,7 +56,7 @@ class Lock:
|
||||
self.force = args.force
|
||||
self.unsafe = args.unsafe
|
||||
|
||||
self.root = Path(configuration.get("repository", "root"))
|
||||
self.paths = RepositoryPaths(configuration.getpath("repository", "root"), architecture)
|
||||
self.reporter = Client() if args.no_report else Client.load(configuration)
|
||||
|
||||
def __enter__(self) -> Lock:
|
||||
@ -103,7 +104,7 @@ class Lock:
|
||||
"""
|
||||
check if current user is actually owner of ahriman root
|
||||
"""
|
||||
check_user(self.root, self.unsafe)
|
||||
check_user(self.paths, self.unsafe)
|
||||
|
||||
def clear(self) -> None:
|
||||
"""
|
||||
|
@ -34,6 +34,7 @@ class Repo:
|
||||
:ivar name: repository name
|
||||
:ivar paths: repository paths instance
|
||||
:ivar sign_args: additional args which have to be used to sign repository archive
|
||||
:ivar uid: uid of the repository owner user
|
||||
"""
|
||||
|
||||
_check_output = check_output
|
||||
@ -48,6 +49,7 @@ class Repo:
|
||||
self.logger = logging.getLogger("build_details")
|
||||
self.name = name
|
||||
self.paths = paths
|
||||
self.uid, _ = paths.root_owner
|
||||
self.sign_args = sign_args
|
||||
|
||||
@property
|
||||
@ -66,7 +68,8 @@ class Repo:
|
||||
"repo-add", *self.sign_args, "-R", str(self.repo_path), str(path),
|
||||
exception=BuildFailed(path.name),
|
||||
cwd=self.paths.repository,
|
||||
logger=self.logger)
|
||||
logger=self.logger,
|
||||
user=self.uid)
|
||||
|
||||
def init(self) -> None:
|
||||
"""
|
||||
@ -76,7 +79,8 @@ class Repo:
|
||||
"repo-add", *self.sign_args, str(self.repo_path),
|
||||
exception=None,
|
||||
cwd=self.paths.repository,
|
||||
logger=self.logger)
|
||||
logger=self.logger,
|
||||
user=self.uid)
|
||||
|
||||
def remove(self, package: str, filename: Path) -> None:
|
||||
"""
|
||||
@ -93,4 +97,5 @@ class Repo:
|
||||
"repo-remove", *self.sign_args, str(self.repo_path), package,
|
||||
exception=BuildFailed(package),
|
||||
cwd=self.paths.repository,
|
||||
logger=self.logger)
|
||||
logger=self.logger,
|
||||
user=self.uid)
|
||||
|
@ -38,6 +38,7 @@ class Task:
|
||||
:ivar logger: class logger
|
||||
:ivar package: package definitions
|
||||
:ivar paths: repository paths instance
|
||||
:ivar uid: uid of the repository owner user
|
||||
"""
|
||||
|
||||
_check_output = check_output
|
||||
@ -53,6 +54,7 @@ class Task:
|
||||
self.build_logger = logging.getLogger("build_details")
|
||||
self.package = package
|
||||
self.paths = paths
|
||||
self.uid, _ = paths.root_owner
|
||||
|
||||
self.archbuild_flags = configuration.getlist("build", "archbuild_flags", fallback=[])
|
||||
self.build_command = configuration.get("build", "build_command")
|
||||
@ -74,7 +76,8 @@ class Task:
|
||||
*command,
|
||||
exception=BuildFailed(self.package.base),
|
||||
cwd=self.paths.sources_for(self.package.base),
|
||||
logger=self.build_logger)
|
||||
logger=self.build_logger,
|
||||
user=self.uid)
|
||||
|
||||
# well it is not actually correct, but we can deal with it
|
||||
packages = Task._check_output("makepkg", "--packagelist",
|
||||
|
@ -17,6 +17,7 @@
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
|
||||
@ -85,6 +86,20 @@ class InvalidOption(ValueError):
|
||||
ValueError.__init__(self, f"Invalid or unknown option value `{value}`")
|
||||
|
||||
|
||||
class InvalidPath(ValueError):
|
||||
"""
|
||||
exception which will be raised on path which is not belong to root directory
|
||||
"""
|
||||
|
||||
def __init__(self, path: Path, root: Path) -> None:
|
||||
"""
|
||||
default constructor
|
||||
:param path: path which raised an exception
|
||||
:param root: repository root (i.e. ahriman home)
|
||||
"""
|
||||
ValueError.__init__(self, f"Path `{path}` does not belong to repository root `{root}`")
|
||||
|
||||
|
||||
class InvalidPackageInfo(RuntimeError):
|
||||
"""
|
||||
exception which will be raised on package load errors
|
||||
@ -111,7 +126,7 @@ class MissingArchitecture(ValueError):
|
||||
ValueError.__init__(self, f"Architecture required for subcommand {command}, but missing")
|
||||
|
||||
|
||||
class MultipleArchitecture(ValueError):
|
||||
class MultipleArchitectures(ValueError):
|
||||
"""
|
||||
exception which will be raised if multiple architectures are not supported by the handler
|
||||
"""
|
||||
|
@ -62,7 +62,7 @@ class Properties:
|
||||
|
||||
self.paths = RepositoryPaths(configuration.getpath("repository", "root"), architecture)
|
||||
try:
|
||||
check_user(self.paths.root, unsafe)
|
||||
check_user(self.paths, unsafe)
|
||||
self.paths.tree_create()
|
||||
except UnsafeRun:
|
||||
self.logger.warning("root owner differs from the current user, skipping tree creation")
|
||||
|
@ -27,10 +27,11 @@ from pathlib import Path
|
||||
from typing import Any, Dict, Generator, Iterable, Optional, Union
|
||||
|
||||
from ahriman.core.exceptions import InvalidOption, UnsafeRun
|
||||
from ahriman.models.repository_paths import RepositoryPaths
|
||||
|
||||
|
||||
def check_output(*args: str, exception: Optional[Exception], cwd: Optional[Path] = None,
|
||||
input_data: Optional[str] = None, logger: Optional[Logger] = None) -> str:
|
||||
input_data: Optional[str] = None, logger: Optional[Logger] = None, user: Optional[int] = None) -> str:
|
||||
"""
|
||||
subprocess wrapper
|
||||
:param args: command line arguments
|
||||
@ -38,12 +39,15 @@ def check_output(*args: str, exception: Optional[Exception], cwd: Optional[Path]
|
||||
:param cwd: current working directory
|
||||
:param input_data: data which will be written to command stdin
|
||||
:param logger: logger to log command result if required
|
||||
:param user: run process as specified user
|
||||
:return: command output
|
||||
"""
|
||||
try:
|
||||
# universal_newlines is required to read input from string
|
||||
# FIXME additional workaround for linter and type check which do not know that user arg is supported
|
||||
# pylint: disable=unexpected-keyword-arg
|
||||
result: str = subprocess.check_output(args, cwd=cwd, input=input_data, stderr=subprocess.STDOUT,
|
||||
universal_newlines=True).strip()
|
||||
universal_newlines=True, user=user).strip() # type: ignore
|
||||
if logger is not None:
|
||||
for line in result.splitlines():
|
||||
logger.debug(line)
|
||||
@ -55,18 +59,18 @@ def check_output(*args: str, exception: Optional[Exception], cwd: Optional[Path]
|
||||
raise exception or e
|
||||
|
||||
|
||||
def check_user(root: Path, unsafe: bool) -> None:
|
||||
def check_user(paths: RepositoryPaths, unsafe: bool) -> None:
|
||||
"""
|
||||
check if current user is the owner of the root
|
||||
:param root: root directory (i.e. ahriman home)
|
||||
:param paths: repository paths object
|
||||
:param unsafe: if set no user check will be performed before path creation
|
||||
"""
|
||||
if not root.exists():
|
||||
if not paths.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
|
||||
root_uid, _ = paths.root_owner
|
||||
if current_uid != root_uid:
|
||||
raise UnsafeRun(current_uid, root_uid)
|
||||
|
||||
|
@ -19,11 +19,14 @@
|
||||
#
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
import shutil
|
||||
|
||||
from dataclasses import dataclass
|
||||
from pathlib import Path
|
||||
from typing import Set, Type
|
||||
from typing import Set, Tuple, Type
|
||||
|
||||
from ahriman.core.exceptions import InvalidPath
|
||||
|
||||
|
||||
@dataclass
|
||||
@ -80,6 +83,13 @@ class RepositoryPaths:
|
||||
"""
|
||||
return self.root / "repository" / self.architecture
|
||||
|
||||
@property
|
||||
def root_owner(self) -> Tuple[int, int]:
|
||||
"""
|
||||
:return: owner user and group of the root directory
|
||||
"""
|
||||
return self.owner(self.root)
|
||||
|
||||
@property
|
||||
def sources(self) -> Path:
|
||||
"""
|
||||
@ -101,6 +111,16 @@ class RepositoryPaths:
|
||||
if path.is_dir()
|
||||
}
|
||||
|
||||
@staticmethod
|
||||
def owner(path: Path) -> Tuple[int, int]:
|
||||
"""
|
||||
retrieve owner information by path
|
||||
:param path: path for which extract ids
|
||||
:return: owner user and group ids of the directory
|
||||
"""
|
||||
stat = path.stat()
|
||||
return stat.st_uid, stat.st_gid
|
||||
|
||||
def cache_for(self, package_base: str) -> Path:
|
||||
"""
|
||||
get path to cached PKGBUILD and package sources for the package base
|
||||
@ -109,6 +129,28 @@ class RepositoryPaths:
|
||||
"""
|
||||
return self.cache / package_base
|
||||
|
||||
def chown(self, path: Path) -> None:
|
||||
"""
|
||||
set owner of path recursively (from root) to root owner
|
||||
:param path: path to be chown
|
||||
"""
|
||||
def set_owner(current: Path) -> None:
|
||||
"""
|
||||
set owner to the specified path
|
||||
:param current: path to set
|
||||
"""
|
||||
uid, gid = self.owner(current)
|
||||
if uid == root_uid and gid == root_gid:
|
||||
return
|
||||
os.chown(current, root_uid, root_gid, follow_symlinks=False)
|
||||
|
||||
if self.root not in path.parents:
|
||||
raise InvalidPath(path, self.root)
|
||||
root_uid, root_gid = self.root_owner
|
||||
while path != self.root:
|
||||
set_owner(path)
|
||||
path = path.parent
|
||||
|
||||
def manual_for(self, package_base: str) -> Path:
|
||||
"""
|
||||
get manual path for specific package base
|
||||
@ -158,3 +200,4 @@ class RepositoryPaths:
|
||||
self.repository,
|
||||
self.sources):
|
||||
directory.mkdir(mode=0o755, parents=True, exist_ok=True)
|
||||
self.chown(directory)
|
||||
|
Reference in New Issue
Block a user