mirror of
https://github.com/arcan1s/ahriman.git
synced 2025-08-28 04:19:57 +00:00
add archive trigger
This commit is contained in:
@ -1,5 +1,6 @@
|
||||
[build]
|
||||
; List of well-known triggers. Used only for configuration purposes.
|
||||
triggers_known[] = ahriman.core.archive.ArchiveTrigger
|
||||
triggers_known[] = ahriman.core.distributed.WorkerLoaderTrigger
|
||||
triggers_known[] = ahriman.core.distributed.WorkerTrigger
|
||||
triggers_known[] = ahriman.core.support.KeyringTrigger
|
||||
|
@ -88,22 +88,24 @@ class Repo(LazyLogging):
|
||||
check_output("repo-add", *self.sign_args, str(self.repo_path),
|
||||
cwd=self.root, logger=self.logger, user=self.uid)
|
||||
|
||||
def remove(self, package: str, filename: Path) -> None:
|
||||
def remove(self, package_name: str | None, filename: Path) -> None:
|
||||
"""
|
||||
remove package from repository
|
||||
|
||||
Args:
|
||||
package(str): package name to remove
|
||||
package_name(str | None): package name to remove. If none set, it will be guessed from filename
|
||||
filename(Path): package filename to remove
|
||||
"""
|
||||
package_name = package_name or filename.name.rsplit("-", maxsplit=3)[0]
|
||||
|
||||
# remove package and signature (if any) from filesystem
|
||||
for full_path in self.root.glob(f"**/{filename.name}*"):
|
||||
full_path.unlink()
|
||||
|
||||
# remove package from registry
|
||||
check_output(
|
||||
"repo-remove", *self.sign_args, str(self.repo_path), package,
|
||||
exception=BuildError.from_process(package),
|
||||
"repo-remove", *self.sign_args, str(self.repo_path), package_name,
|
||||
exception=BuildError.from_process(package_name),
|
||||
cwd=self.root,
|
||||
logger=self.logger,
|
||||
user=self.uid,
|
||||
|
@ -17,3 +17,4 @@
|
||||
# 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 ahriman.core.archive.archive_trigger import ArchiveTrigger
|
||||
|
127
src/ahriman/core/archive/archive_tree.py
Normal file
127
src/ahriman/core/archive/archive_tree.py
Normal file
@ -0,0 +1,127 @@
|
||||
#
|
||||
# Copyright (c) 2021-2025 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 datetime
|
||||
|
||||
from pathlib import Path
|
||||
|
||||
from ahriman.core.alpm.repo import Repo
|
||||
from ahriman.core.log import LazyLogging
|
||||
from ahriman.core.utils import utcnow
|
||||
from ahriman.models.package import Package
|
||||
from ahriman.models.repository_paths import RepositoryPaths
|
||||
|
||||
|
||||
class ArchiveTree(LazyLogging):
|
||||
"""
|
||||
wrapper around archive tree
|
||||
|
||||
Attributes:
|
||||
paths(RepositoryPaths): repository paths instance
|
||||
repository_id(RepositoryId): repository unique identifier
|
||||
sign_args(list[str]): additional args which have to be used to sign repository archive
|
||||
"""
|
||||
|
||||
def __init__(self, repository_path: RepositoryPaths, sign_args: list[str]) -> None:
|
||||
"""
|
||||
Args:
|
||||
repository_path(RepositoryPaths): repository paths instance
|
||||
sign_args(list[str]): additional args which have to be used to sign repository archive
|
||||
"""
|
||||
self.paths = repository_path
|
||||
self.repository_id = repository_path.repository_id
|
||||
self.sign_args = sign_args
|
||||
|
||||
def repository_for(self, date: datetime.date | None = None) -> Path:
|
||||
"""
|
||||
get full path to repository at the specified date
|
||||
|
||||
Args:
|
||||
date(datetime.date | None, optional): date to generate path. If none supplied then today will be used
|
||||
|
||||
Returns:
|
||||
Path: path to the repository root
|
||||
"""
|
||||
date = date or utcnow().today()
|
||||
return (
|
||||
self.paths.archive
|
||||
/ "repos"
|
||||
/ date.strftime("%Y")
|
||||
/ date.strftime("%m")
|
||||
/ date.strftime("%d")
|
||||
/ self.repository_id.name
|
||||
/ self.repository_id.architecture
|
||||
)
|
||||
|
||||
def symlinks_create(self, packages: list[Package]) -> None:
|
||||
"""
|
||||
create symlinks for the specified packages in today's repository
|
||||
|
||||
Args:
|
||||
packages(list[Package]): list of packages to be updated
|
||||
"""
|
||||
root = self.repository_for()
|
||||
repo = Repo(self.repository_id.name, self.paths, self.sign_args, root)
|
||||
|
||||
for package in packages:
|
||||
archive = self.paths.archive_for(package.base)
|
||||
|
||||
for package_name, single in package.packages.items():
|
||||
if single.filename is None:
|
||||
self.logger.warning("received empty package filename for %s", package_name)
|
||||
continue
|
||||
|
||||
has_file = False
|
||||
for file in archive.glob(f"{single.filename}*"):
|
||||
if not (symlink := root / file.name).exists():
|
||||
has_file |= True
|
||||
symlink.symlink_to(file.relative_to(symlink.parent, walk_up=True))
|
||||
|
||||
if has_file:
|
||||
repo.add(root / single.filename)
|
||||
|
||||
def symlinks_fix(self) -> None:
|
||||
"""
|
||||
remove broken symlinks across all repositories
|
||||
"""
|
||||
for root, _, files in self.paths.archive.walk():
|
||||
*_, name, architecture = root.parts
|
||||
if self.repository_id.name != name or self.repository_id.architecture != architecture:
|
||||
continue # we only process same name repositories
|
||||
|
||||
for file in files:
|
||||
path = root / file
|
||||
if not path.is_symlink():
|
||||
continue # find symlinks only
|
||||
if path.exists():
|
||||
continue # filter out not broken symlinks
|
||||
|
||||
repo = Repo(self.repository_id.name, self.paths, self.sign_args, root)
|
||||
repo.remove(None, path)
|
||||
|
||||
def tree_create(self) -> None:
|
||||
"""
|
||||
create repository tree for current repository
|
||||
"""
|
||||
path = self.repository_for()
|
||||
if path.exists():
|
||||
return
|
||||
|
||||
with self.paths.preserve_owner(self.paths.archive):
|
||||
path.mkdir(0o755, parents=True)
|
@ -17,16 +17,17 @@
|
||||
# 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 ahriman.core import context
|
||||
from ahriman.core.archive.archive_tree import ArchiveTree
|
||||
from ahriman.core.configuration import Configuration
|
||||
from ahriman.core.sign.gpg import GPG
|
||||
from ahriman.core.triggers import Trigger
|
||||
from ahriman.models.package import Package
|
||||
from ahriman.models.repository_id import RepositoryId
|
||||
from ahriman.models.result import Result
|
||||
|
||||
|
||||
class ArchiveRotationTrigger(Trigger):
|
||||
class ArchiveTrigger(Trigger):
|
||||
"""
|
||||
archive repository extension
|
||||
|
||||
@ -44,9 +45,8 @@ class ArchiveRotationTrigger(Trigger):
|
||||
|
||||
self.paths = configuration.repository_paths
|
||||
|
||||
@property
|
||||
def repos_path(self) -> Path:
|
||||
return self.paths.archive / "repos"
|
||||
ctx = context.get()
|
||||
self.tree = ArchiveTree(self.paths, ctx.get(GPG).repository_sign_args)
|
||||
|
||||
def on_result(self, result: Result, packages: list[Package]) -> None:
|
||||
"""
|
||||
@ -56,10 +56,16 @@ class ArchiveRotationTrigger(Trigger):
|
||||
result(Result): build result
|
||||
packages(list[Package]): list of all available packages
|
||||
"""
|
||||
self.tree.symlinks_create(packages)
|
||||
|
||||
def on_start(self) -> None:
|
||||
"""
|
||||
trigger action which will be called at the start of the application
|
||||
"""
|
||||
with self.paths.preserve_owner(self.repos_path):
|
||||
self.repos_path.mkdir(mode=0o755, exist_ok=True)
|
||||
self.tree.tree_create()
|
||||
|
||||
def on_stop(self) -> None:
|
||||
"""
|
||||
trigger action which will be called before the stop of the application
|
||||
"""
|
||||
self.tree.symlinks_fix()
|
||||
|
@ -80,7 +80,7 @@ class Executor(PackageInfo, Cleaner):
|
||||
package_base(str): package base name
|
||||
"""
|
||||
if description.filename is None:
|
||||
self.logger.warning("received empty package name for base %s", package_base)
|
||||
self.logger.warning("received empty package filename for base %s", package_base)
|
||||
return # suppress type checking, it never can be none actually
|
||||
|
||||
if (safe := safe_filename(description.filename)) != description.filename:
|
||||
@ -161,7 +161,7 @@ class Executor(PackageInfo, Cleaner):
|
||||
packager_key(str | None): packager key identifier
|
||||
"""
|
||||
if filename is None:
|
||||
self.logger.warning("received empty package name for base %s", package_base)
|
||||
self.logger.warning("received empty package filename for base %s", package_base)
|
||||
return # suppress type checking, it never can be none actually
|
||||
|
||||
# in theory, it might be NOT packages directory, but we suppose it is
|
||||
|
@ -37,6 +37,7 @@ SUBPACKAGES = {
|
||||
"ahriman-triggers": [
|
||||
prefix / "share" / "ahriman" / "settings" / "ahriman.ini.d" / "00-triggers.ini",
|
||||
site_packages / "ahriman" / "application" / "handlers" / "triggers_support.py",
|
||||
site_packages / "ahriman" / "core" / "archive",
|
||||
site_packages / "ahriman" / "core" / "distributed",
|
||||
site_packages / "ahriman" / "core" / "support",
|
||||
],
|
||||
|
Reference in New Issue
Block a user