Compare commits

...

26 Commits

Author SHA1 Message Date
bb01f72931 fix sttyle 2026-01-16 09:23:13 +02:00
e2812c071b remove generators 2026-01-16 09:22:36 +02:00
9ccff5657b simplify symlionk creation 2026-01-16 09:20:38 +02:00
4dd578ce10 drop excess REQUIRES_REPOSITORY 2026-01-16 09:20:38 +02:00
49e558e4e6 support requires repostory flag 2026-01-16 09:20:38 +02:00
80ed47b4ed gpg loader fix 2026-01-16 09:20:38 +02:00
5ee949b5ec regenerate docs 2026-01-16 09:20:38 +02:00
61057c9f91 add archive trigger 2026-01-16 09:20:38 +02:00
8142a3d797 add archive trigger 2026-01-16 09:20:38 +02:00
aae536204e lookup through archive packages before build 2026-01-16 09:20:38 +02:00
27f5a5f5d1 use generic packages tree for all repos 2026-01-16 09:20:37 +02:00
918273c1bb implement atomic_move method, move files only with lock 2026-01-16 09:20:21 +02:00
c651db85ee write tests to support new changes 2026-01-16 09:20:18 +02:00
afd62e88f6 store built packages in archive tree instead of repository 2026-01-16 09:18:02 +02:00
4fa5d55317 type: replace generator return type with iterator 2026-01-15 15:22:03 +02:00
a7fa3b90e4 type: fix typing in some modules 2026-01-14 14:28:31 +02:00
ce07cda8ab fix: pass underlying exception on extensionerror 2026-01-14 14:06:34 +02:00
00c4f32294 fix: correct exception type on repository id comparison 2026-01-14 13:59:07 +02:00
49cf91ea52 chore: copyright update 2026-01-08 02:50:37 +02:00
4a8430dc67 build: allow to rebuild images manually 2026-01-08 02:45:31 +02:00
46af782db2 build: drop shtab wrapper after their release 2025-11-18 20:47:20 +02:00
6443e02352 type: use as keyword in case match 2025-10-26 09:36:54 +02:00
999ad39d6f feat: add trigger loader guard 2025-09-17 14:45:09 +03:00
dfab5f56b2 feat: use atexit instead of del for triggers 2025-08-11 14:53:10 +03:00
10798b9ba3 fix: correctly process trigger repo specific settings in validator (see #154) 2025-08-01 16:53:15 +03:00
358e3dc4d2 feat: expose repository name and architecure in configuration if available
In some cases there are reference to current repository settings. In
order to handle it correctly two ro options have been added

Related to #154
2025-07-31 14:14:22 +03:00
380 changed files with 2213 additions and 735 deletions

View File

@ -7,6 +7,7 @@ on:
tags: tags:
- '*' - '*'
- '!*rc*' - '!*rc*'
workflow_dispatch:
permissions: permissions:
contents: read contents: read

View File

@ -165,6 +165,11 @@ Again, the most checks can be performed by `tox` command, though some additional
# Blank line again and package imports # Blank line again and package imports
from ahriman.core.configuration import Configuration from ahriman.core.configuration import Configuration
# Multiline import example
from ahriman.core.database.operations import (
AuthOperations,
BuildOperations,
)
``` ```
* One file should define only one class, exception is class satellites in case if file length remains less than 400 lines. * One file should define only one class, exception is class satellites in case if file length remains less than 400 lines.
@ -215,6 +220,7 @@ Again, the most checks can be performed by `tox` command, though some additional
* It is allowed to change web API to add new fields or remove optional ones. However, in case of model changes, new API version must be introduced. * It is allowed to change web API to add new fields or remove optional ones. However, in case of model changes, new API version must be introduced.
* On the other hand, it is allowed to change method signatures, however, it is recommended to add new parameters as optional if possible. Deprecated API can be dropped during major release. * On the other hand, it is allowed to change method signatures, however, it is recommended to add new parameters as optional if possible. Deprecated API can be dropped during major release.
* Enumerations (`Enum` classes) are allowed and recommended. However, it is recommended to use `StrEnum` class if there are from/to string conversions and `IntEnum` otherwise. * Enumerations (`Enum` classes) are allowed and recommended. However, it is recommended to use `StrEnum` class if there are from/to string conversions and `IntEnum` otherwise.
* `Generator` return type is not allowed. Generator functions must return generic `Iterator` object. Documentation should be described as `Yields`, however, because of pylint checks. Unfortunately, `Iterable` return type is not available for generators also, because of specific `contextlib.contextmanager` case.
### Other checks ### Other checks

View File

@ -0,0 +1,29 @@
ahriman.core.archive package
============================
Submodules
----------
ahriman.core.archive.archive\_tree module
-----------------------------------------
.. automodule:: ahriman.core.archive.archive_tree
:members:
:no-undoc-members:
:show-inheritance:
ahriman.core.archive.archive\_trigger module
--------------------------------------------
.. automodule:: ahriman.core.archive.archive_trigger
:members:
:no-undoc-members:
:show-inheritance:
Module contents
---------------
.. automodule:: ahriman.core.archive
:members:
:no-undoc-members:
:show-inheritance:

View File

@ -132,6 +132,14 @@ ahriman.core.database.migrations.m015\_logs\_process\_id module
:no-undoc-members: :no-undoc-members:
:show-inheritance: :show-inheritance:
ahriman.core.database.migrations.m016\_archive module
-----------------------------------------------------
.. automodule:: ahriman.core.database.migrations.m016_archive
:members:
:no-undoc-members:
:show-inheritance:
Module contents Module contents
--------------- ---------------

View File

@ -4,6 +4,14 @@ ahriman.core.housekeeping package
Submodules Submodules
---------- ----------
ahriman.core.housekeeping.archive\_rotation\_trigger module
-----------------------------------------------------------
.. automodule:: ahriman.core.housekeeping.archive_rotation_trigger
:members:
:no-undoc-members:
:show-inheritance:
ahriman.core.housekeeping.logs\_rotation\_trigger module ahriman.core.housekeeping.logs\_rotation\_trigger module
-------------------------------------------------------- --------------------------------------------------------

View File

@ -8,6 +8,7 @@ Subpackages
:maxdepth: 4 :maxdepth: 4
ahriman.core.alpm ahriman.core.alpm
ahriman.core.archive
ahriman.core.auth ahriman.core.auth
ahriman.core.build_tools ahriman.core.build_tools
ahriman.core.configuration ahriman.core.configuration

View File

@ -97,6 +97,13 @@ libalpm and AUR related configuration. Group name can refer to architecture, e.g
* ``sync_files_database`` - download files database from mirror, boolean, required. * ``sync_files_database`` - download files database from mirror, boolean, required.
* ``use_ahriman_cache`` - use local pacman package cache instead of system one, boolean, required. With this option enabled you might want to refresh database periodically (available as additional flag for some subcommands). If set to ``no``, databases must be synchronized manually. * ``use_ahriman_cache`` - use local pacman package cache instead of system one, boolean, required. With this option enabled you might want to refresh database periodically (available as additional flag for some subcommands). If set to ``no``, databases must be synchronized manually.
``archive`` group
-----------------
Describes settings for packages archives management extensions.
* ``keep_built_packages`` - keep this amount of built packages with different versions, integer, required. ``0`` (or negative number) will effectively disable archives removal.
``auth`` group ``auth`` group
-------------- --------------
@ -139,6 +146,8 @@ Build related configuration. Group name can refer to architecture, e.g. ``build:
Base repository settings. Base repository settings.
* ``architecture`` - repository architecture, string. This field is read-only and generated automatically from run options if possible.
* ``name`` - repository name, string. This field is read-only and generated automatically from run options if possible.
* ``root`` - root path for application, string, required. * ``root`` - root path for application, string, required.
``sign:*`` groups ``sign:*`` groups

View File

@ -44,9 +44,11 @@ triggers[] = ahriman.core.report.ReportTrigger
triggers[] = ahriman.core.upload.UploadTrigger triggers[] = ahriman.core.upload.UploadTrigger
triggers[] = ahriman.core.gitremote.RemotePushTrigger triggers[] = ahriman.core.gitremote.RemotePushTrigger
triggers[] = ahriman.core.housekeeping.LogsRotationTrigger triggers[] = ahriman.core.housekeeping.LogsRotationTrigger
triggers[] = ahriman.core.housekeeping.ArchiveRotationTrigger
; List of well-known triggers. Used only for configuration purposes. ; List of well-known triggers. Used only for configuration purposes.
triggers_known[] = ahriman.core.gitremote.RemotePullTrigger triggers_known[] = ahriman.core.gitremote.RemotePullTrigger
triggers_known[] = ahriman.core.gitremote.RemotePushTrigger triggers_known[] = ahriman.core.gitremote.RemotePushTrigger
triggers_known[] = ahriman.core.housekeeping.ArchiveRotationTrigger
triggers_known[] = ahriman.core.housekeeping.LogsRotationTrigger triggers_known[] = ahriman.core.housekeeping.LogsRotationTrigger
triggers_known[] = ahriman.core.report.ReportTrigger triggers_known[] = ahriman.core.report.ReportTrigger
triggers_known[] = ahriman.core.upload.UploadTrigger triggers_known[] = ahriman.core.upload.UploadTrigger

View File

@ -1,3 +1,7 @@
[archive]
; Keep amount of last built packages in archive. 0 means keep all packages
keep_built_packages = 1
[logs-rotation] [logs-rotation]
; Keep last build logs for each package ; Keep last build logs for each package
keep_last_logs = 5 keep_last_logs = 5

View File

@ -1,5 +1,6 @@
[build] [build]
; List of well-known triggers. Used only for configuration purposes. ; 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.WorkerLoaderTrigger
triggers_known[] = ahriman.core.distributed.WorkerTrigger triggers_known[] = ahriman.core.distributed.WorkerTrigger
triggers_known[] = ahriman.core.support.KeyringTrigger triggers_known[] = ahriman.core.support.KeyringTrigger

View File

@ -1,5 +1,5 @@
# #
# Copyright (c) 2021-2025 ahriman team. # Copyright (c) 2021-2026 ahriman team.
# #
# This file is part of ahriman # This file is part of ahriman
# (see https://github.com/arcan1s/ahriman). # (see https://github.com/arcan1s/ahriman).

View File

@ -1,5 +1,5 @@
# #
# Copyright (c) 2021-2025 ahriman team. # Copyright (c) 2021-2026 ahriman team.
# #
# This file is part of ahriman # This file is part of ahriman
# (see https://github.com/arcan1s/ahriman). # (see https://github.com/arcan1s/ahriman).

View File

@ -1,5 +1,5 @@
# #
# Copyright (c) 2021-2025 ahriman team. # Copyright (c) 2021-2026 ahriman team.
# #
# This file is part of ahriman # This file is part of ahriman
# (see https://github.com/arcan1s/ahriman). # (see https://github.com/arcan1s/ahriman).

View File

@ -1,5 +1,5 @@
# #
# Copyright (c) 2021-2025 ahriman team. # Copyright (c) 2021-2026 ahriman team.
# #
# This file is part of ahriman # This file is part of ahriman
# (see https://github.com/arcan1s/ahriman). # (see https://github.com/arcan1s/ahriman).

View File

@ -1,5 +1,5 @@
# #
# Copyright (c) 2021-2025 ahriman team. # Copyright (c) 2021-2026 ahriman team.
# #
# This file is part of ahriman # This file is part of ahriman
# (see https://github.com/arcan1s/ahriman). # (see https://github.com/arcan1s/ahriman).

View File

@ -1,5 +1,5 @@
# #
# Copyright (c) 2021-2025 ahriman team. # Copyright (c) 2021-2026 ahriman team.
# #
# This file is part of ahriman # This file is part of ahriman
# (see https://github.com/arcan1s/ahriman). # (see https://github.com/arcan1s/ahriman).

View File

@ -1,5 +1,5 @@
# #
# Copyright (c) 2021-2025 ahriman team. # Copyright (c) 2021-2026 ahriman team.
# #
# This file is part of ahriman # This file is part of ahriman
# (see https://github.com/arcan1s/ahriman). # (see https://github.com/arcan1s/ahriman).

View File

@ -1,5 +1,5 @@
# #
# Copyright (c) 2021-2025 ahriman team. # Copyright (c) 2021-2026 ahriman team.
# #
# This file is part of ahriman # This file is part of ahriman
# (see https://github.com/arcan1s/ahriman). # (see https://github.com/arcan1s/ahriman).

View File

@ -1,5 +1,5 @@
# #
# Copyright (c) 2021-2025 ahriman team. # Copyright (c) 2021-2026 ahriman team.
# #
# This file is part of ahriman # This file is part of ahriman
# (see https://github.com/arcan1s/ahriman). # (see https://github.com/arcan1s/ahriman).

View File

@ -1,5 +1,5 @@
# #
# Copyright (c) 2021-2025 ahriman team. # Copyright (c) 2021-2026 ahriman team.
# #
# This file is part of ahriman # This file is part of ahriman
# (see https://github.com/arcan1s/ahriman). # (see https://github.com/arcan1s/ahriman).

View File

@ -1,5 +1,5 @@
# #
# Copyright (c) 2021-2025 ahriman team. # Copyright (c) 2021-2026 ahriman team.
# #
# This file is part of ahriman # This file is part of ahriman
# (see https://github.com/arcan1s/ahriman). # (see https://github.com/arcan1s/ahriman).

View File

@ -1,5 +1,5 @@
# #
# Copyright (c) 2021-2025 ahriman team. # Copyright (c) 2021-2026 ahriman team.
# #
# This file is part of ahriman # This file is part of ahriman
# (see https://github.com/arcan1s/ahriman). # (see https://github.com/arcan1s/ahriman).

View File

@ -1,5 +1,5 @@
# #
# Copyright (c) 2021-2025 ahriman team. # Copyright (c) 2021-2026 ahriman team.
# #
# This file is part of ahriman # This file is part of ahriman
# (see https://github.com/arcan1s/ahriman). # (see https://github.com/arcan1s/ahriman).

View File

@ -1,5 +1,5 @@
# #
# Copyright (c) 2021-2025 ahriman team. # Copyright (c) 2021-2026 ahriman team.
# #
# This file is part of ahriman # This file is part of ahriman
# (see https://github.com/arcan1s/ahriman). # (see https://github.com/arcan1s/ahriman).

View File

@ -1,5 +1,5 @@
# #
# Copyright (c) 2021-2025 ahriman team. # Copyright (c) 2021-2026 ahriman team.
# #
# This file is part of ahriman # This file is part of ahriman
# (see https://github.com/arcan1s/ahriman). # (see https://github.com/arcan1s/ahriman).

View File

@ -1,5 +1,5 @@
# #
# Copyright (c) 2021-2025 ahriman team. # Copyright (c) 2021-2026 ahriman team.
# #
# This file is part of ahriman # This file is part of ahriman
# (see https://github.com/arcan1s/ahriman). # (see https://github.com/arcan1s/ahriman).

View File

@ -1,5 +1,5 @@
# #
# Copyright (c) 2021-2025 ahriman team. # Copyright (c) 2021-2026 ahriman team.
# #
# This file is part of ahriman # This file is part of ahriman
# (see https://github.com/arcan1s/ahriman). # (see https://github.com/arcan1s/ahriman).

View File

@ -1,5 +1,5 @@
# #
# Copyright (c) 2021-2025 ahriman team. # Copyright (c) 2021-2026 ahriman team.
# #
# This file is part of ahriman # This file is part of ahriman
# (see https://github.com/arcan1s/ahriman). # (see https://github.com/arcan1s/ahriman).

View File

@ -1,5 +1,5 @@
# #
# Copyright (c) 2021-2025 ahriman team. # Copyright (c) 2021-2026 ahriman team.
# #
# This file is part of ahriman # This file is part of ahriman
# (see https://github.com/arcan1s/ahriman). # (see https://github.com/arcan1s/ahriman).

View File

@ -1,5 +1,5 @@
# #
# Copyright (c) 2021-2025 ahriman team. # Copyright (c) 2021-2026 ahriman team.
# #
# This file is part of ahriman # This file is part of ahriman
# (see https://github.com/arcan1s/ahriman). # (see https://github.com/arcan1s/ahriman).

View File

@ -1,5 +1,5 @@
# #
# Copyright (c) 2021-2025 ahriman team. # Copyright (c) 2021-2026 ahriman team.
# #
# This file is part of ahriman # This file is part of ahriman
# (see https://github.com/arcan1s/ahriman). # (see https://github.com/arcan1s/ahriman).

View File

@ -1,5 +1,5 @@
# #
# Copyright (c) 2021-2025 ahriman team. # Copyright (c) 2021-2026 ahriman team.
# #
# This file is part of ahriman # This file is part of ahriman
# (see https://github.com/arcan1s/ahriman). # (see https://github.com/arcan1s/ahriman).

View File

@ -1,5 +1,5 @@
# #
# Copyright (c) 2021-2025 ahriman team. # Copyright (c) 2021-2026 ahriman team.
# #
# This file is part of ahriman # This file is part of ahriman
# (see https://github.com/arcan1s/ahriman). # (see https://github.com/arcan1s/ahriman).

View File

@ -1,5 +1,5 @@
# #
# Copyright (c) 2021-2025 ahriman team. # Copyright (c) 2021-2026 ahriman team.
# #
# This file is part of ahriman # This file is part of ahriman
# (see https://github.com/arcan1s/ahriman). # (see https://github.com/arcan1s/ahriman).

View File

@ -1,5 +1,5 @@
# #
# Copyright (c) 2021-2025 ahriman team. # Copyright (c) 2021-2026 ahriman team.
# #
# This file is part of ahriman # This file is part of ahriman
# (see https://github.com/arcan1s/ahriman). # (see https://github.com/arcan1s/ahriman).

View File

@ -1,5 +1,5 @@
# #
# Copyright (c) 2021-2025 ahriman team. # Copyright (c) 2021-2026 ahriman team.
# #
# This file is part of ahriman # This file is part of ahriman
# (see https://github.com/arcan1s/ahriman). # (see https://github.com/arcan1s/ahriman).

View File

@ -1,5 +1,5 @@
# #
# Copyright (c) 2021-2025 ahriman team. # Copyright (c) 2021-2026 ahriman team.
# #
# This file is part of ahriman # This file is part of ahriman
# (see https://github.com/arcan1s/ahriman). # (see https://github.com/arcan1s/ahriman).

View File

@ -1,5 +1,5 @@
# #
# Copyright (c) 2021-2025 ahriman team. # Copyright (c) 2021-2026 ahriman team.
# #
# This file is part of ahriman # This file is part of ahriman
# (see https://github.com/arcan1s/ahriman). # (see https://github.com/arcan1s/ahriman).

View File

@ -1,5 +1,5 @@
# #
# Copyright (c) 2021-2025 ahriman team. # Copyright (c) 2021-2026 ahriman team.
# #
# This file is part of ahriman # This file is part of ahriman
# (see https://github.com/arcan1s/ahriman). # (see https://github.com/arcan1s/ahriman).

View File

@ -1,5 +1,5 @@
# #
# Copyright (c) 2021-2025 ahriman team. # Copyright (c) 2021-2026 ahriman team.
# #
# This file is part of ahriman # This file is part of ahriman
# (see https://github.com/arcan1s/ahriman). # (see https://github.com/arcan1s/ahriman).

View File

@ -1,5 +1,5 @@
# #
# Copyright (c) 2021-2025 ahriman team. # Copyright (c) 2021-2026 ahriman team.
# #
# This file is part of ahriman # This file is part of ahriman
# (see https://github.com/arcan1s/ahriman). # (see https://github.com/arcan1s/ahriman).

View File

@ -1,5 +1,5 @@
# #
# Copyright (c) 2021-2025 ahriman team. # Copyright (c) 2021-2026 ahriman team.
# #
# This file is part of ahriman # This file is part of ahriman
# (see https://github.com/arcan1s/ahriman). # (see https://github.com/arcan1s/ahriman).

View File

@ -1,5 +1,5 @@
# #
# Copyright (c) 2021-2025 ahriman team. # Copyright (c) 2021-2026 ahriman team.
# #
# This file is part of ahriman # This file is part of ahriman
# (see https://github.com/arcan1s/ahriman). # (see https://github.com/arcan1s/ahriman).

View File

@ -1,5 +1,5 @@
# #
# Copyright (c) 2021-2025 ahriman team. # Copyright (c) 2021-2026 ahriman team.
# #
# This file is part of ahriman # This file is part of ahriman
# (see https://github.com/arcan1s/ahriman). # (see https://github.com/arcan1s/ahriman).

View File

@ -1,5 +1,5 @@
# #
# Copyright (c) 2021-2025 ahriman team. # Copyright (c) 2021-2026 ahriman team.
# #
# This file is part of ahriman # This file is part of ahriman
# (see https://github.com/arcan1s/ahriman). # (see https://github.com/arcan1s/ahriman).

View File

@ -1,5 +1,5 @@
# #
# Copyright (c) 2021-2025 ahriman team. # Copyright (c) 2021-2026 ahriman team.
# #
# This file is part of ahriman # This file is part of ahriman
# (see https://github.com/arcan1s/ahriman). # (see https://github.com/arcan1s/ahriman).

View File

@ -1,5 +1,5 @@
# #
# Copyright (c) 2021-2025 ahriman team. # Copyright (c) 2021-2026 ahriman team.
# #
# This file is part of ahriman # This file is part of ahriman
# (see https://github.com/arcan1s/ahriman). # (see https://github.com/arcan1s/ahriman).

View File

@ -1,5 +1,5 @@
# #
# Copyright (c) 2021-2025 ahriman team. # Copyright (c) 2021-2026 ahriman team.
# #
# This file is part of ahriman # This file is part of ahriman
# (see https://github.com/arcan1s/ahriman). # (see https://github.com/arcan1s/ahriman).

View File

@ -1,5 +1,5 @@
# #
# Copyright (c) 2021-2025 ahriman team. # Copyright (c) 2021-2026 ahriman team.
# #
# This file is part of ahriman # This file is part of ahriman
# (see https://github.com/arcan1s/ahriman). # (see https://github.com/arcan1s/ahriman).
@ -66,7 +66,7 @@ class Status(Handler):
Status.check_status(args.exit_code, packages) Status.check_status(args.exit_code, packages)
comparator: Callable[[tuple[Package, BuildStatus]], Comparable] = lambda item: item[0].base comparator: Callable[[tuple[Package, BuildStatus]], Comparable] = lambda item: item[0].base
filter_fn: Callable[[tuple[Package, BuildStatus]], bool] =\ filter_fn: Callable[[tuple[Package, BuildStatus]], bool] = \
lambda item: args.status is None or item[1].status == args.status lambda item: args.status is None or item[1].status == args.status
for package, package_status in sorted(filter(filter_fn, packages), key=comparator): for package, package_status in sorted(filter(filter_fn, packages), key=comparator):
PackagePrinter(package, package_status)(verbose=args.info) PackagePrinter(package, package_status)(verbose=args.info)

View File

@ -1,5 +1,5 @@
# #
# Copyright (c) 2021-2025 ahriman team. # Copyright (c) 2021-2026 ahriman team.
# #
# This file is part of ahriman # This file is part of ahriman
# (see https://github.com/arcan1s/ahriman). # (see https://github.com/arcan1s/ahriman).

View File

@ -1,5 +1,5 @@
# #
# Copyright (c) 2021-2025 ahriman team. # Copyright (c) 2021-2026 ahriman team.
# #
# This file is part of ahriman # This file is part of ahriman
# (see https://github.com/arcan1s/ahriman). # (see https://github.com/arcan1s/ahriman).

View File

@ -1,5 +1,5 @@
# #
# Copyright (c) 2021-2025 ahriman team. # Copyright (c) 2021-2026 ahriman team.
# #
# This file is part of ahriman # This file is part of ahriman
# (see https://github.com/arcan1s/ahriman). # (see https://github.com/arcan1s/ahriman).
@ -21,6 +21,7 @@ import argparse
from ahriman.application.handlers.handler import Handler, SubParserAction from ahriman.application.handlers.handler import Handler, SubParserAction
from ahriman.core.configuration import Configuration from ahriman.core.configuration import Configuration
from ahriman.core.utils import walk
from ahriman.models.repository_id import RepositoryId from ahriman.models.repository_id import RepositoryId
from ahriman.models.repository_paths import RepositoryPaths from ahriman.models.repository_paths import RepositoryPaths
@ -49,6 +50,7 @@ class TreeMigrate(Handler):
target_tree.tree_create() target_tree.tree_create()
# perform migration # perform migration
TreeMigrate.tree_move(current_tree, target_tree) TreeMigrate.tree_move(current_tree, target_tree)
TreeMigrate.fix_symlinks(target_tree)
@staticmethod @staticmethod
def _set_service_tree_migrate_parser(root: SubParserAction) -> argparse.ArgumentParser: def _set_service_tree_migrate_parser(root: SubParserAction) -> argparse.ArgumentParser:
@ -66,6 +68,22 @@ class TreeMigrate(Handler):
parser.set_defaults(lock=None, quiet=True, report=False) parser.set_defaults(lock=None, quiet=True, report=False)
return parser return parser
@staticmethod
def fix_symlinks(paths: RepositoryPaths) -> None:
"""
fix packages archives symlinks
Args:
paths(RepositoryPaths): new repository paths
"""
archives = {path.name: path for path in walk(paths.archive)}
for symlink in walk(paths.repository):
if symlink.exists(): # no need to check for symlinks as we have just walked through the tree
continue
if (source_archive := archives.get(symlink.name)) is not None:
symlink.unlink()
symlink.symlink_to(source_archive.relative_to(symlink.parent, walk_up=True))
@staticmethod @staticmethod
def tree_move(from_tree: RepositoryPaths, to_tree: RepositoryPaths) -> None: def tree_move(from_tree: RepositoryPaths, to_tree: RepositoryPaths) -> None:
""" """

View File

@ -1,5 +1,5 @@
# #
# Copyright (c) 2021-2025 ahriman team. # Copyright (c) 2021-2026 ahriman team.
# #
# This file is part of ahriman # This file is part of ahriman
# (see https://github.com/arcan1s/ahriman). # (see https://github.com/arcan1s/ahriman).

View File

@ -1,5 +1,5 @@
# #
# Copyright (c) 2021-2025 ahriman team. # Copyright (c) 2021-2026 ahriman team.
# #
# This file is part of ahriman # This file is part of ahriman
# (see https://github.com/arcan1s/ahriman). # (see https://github.com/arcan1s/ahriman).

View File

@ -1,5 +1,5 @@
# #
# Copyright (c) 2021-2025 ahriman team. # Copyright (c) 2021-2026 ahriman team.
# #
# This file is part of ahriman # This file is part of ahriman
# (see https://github.com/arcan1s/ahriman). # (see https://github.com/arcan1s/ahriman).

View File

@ -1,5 +1,5 @@
# #
# Copyright (c) 2021-2025 ahriman team. # Copyright (c) 2021-2026 ahriman team.
# #
# This file is part of ahriman # This file is part of ahriman
# (see https://github.com/arcan1s/ahriman). # (see https://github.com/arcan1s/ahriman).

View File

@ -1,5 +1,5 @@
# #
# Copyright (c) 2021-2025 ahriman team. # Copyright (c) 2021-2026 ahriman team.
# #
# This file is part of ahriman # This file is part of ahriman
# (see https://github.com/arcan1s/ahriman). # (see https://github.com/arcan1s/ahriman).

View File

@ -1,5 +1,5 @@
# #
# Copyright (c) 2021-2025 ahriman team. # Copyright (c) 2021-2026 ahriman team.
# #
# This file is part of ahriman # This file is part of ahriman
# (see https://github.com/arcan1s/ahriman). # (see https://github.com/arcan1s/ahriman).
@ -52,7 +52,7 @@ class Validate(Handler):
""" """
from ahriman.core.configuration.validator import Validator from ahriman.core.configuration.validator import Validator
schema = Validate.schema(repository_id, configuration) schema = Validate.schema(configuration)
validator = Validator(configuration=configuration, schema=schema) validator = Validator(configuration=configuration, schema=schema)
if validator.validate(configuration.dump()): if validator.validate(configuration.dump()):
@ -83,12 +83,11 @@ class Validate(Handler):
return parser return parser
@staticmethod @staticmethod
def schema(repository_id: RepositoryId, configuration: Configuration) -> ConfigurationSchema: def schema(configuration: Configuration) -> ConfigurationSchema:
""" """
get schema with triggers get schema with triggers
Args: Args:
repository_id(RepositoryId): repository unique identifier
configuration(Configuration): configuration instance configuration(Configuration): configuration instance
Returns: Returns:
@ -107,12 +106,12 @@ class Validate(Handler):
continue continue
# default settings if any # default settings if any
for schema_name, schema in trigger_class.configuration_schema(repository_id, None).items(): for schema_name, schema in trigger_class.configuration_schema(None).items():
erased = Validate.schema_erase_required(copy.deepcopy(schema)) erased = Validate.schema_erase_required(copy.deepcopy(schema))
root[schema_name] = Validate.schema_merge(root.get(schema_name, {}), erased) root[schema_name] = Validate.schema_merge(root.get(schema_name, {}), erased)
# settings according to enabled triggers # settings according to enabled triggers
for schema_name, schema in trigger_class.configuration_schema(repository_id, configuration).items(): for schema_name, schema in trigger_class.configuration_schema(configuration).items():
root[schema_name] = Validate.schema_merge(root.get(schema_name, {}), copy.deepcopy(schema)) root[schema_name] = Validate.schema_merge(root.get(schema_name, {}), copy.deepcopy(schema))
return root return root

View File

@ -1,5 +1,5 @@
# #
# Copyright (c) 2021-2025 ahriman team. # Copyright (c) 2021-2026 ahriman team.
# #
# This file is part of ahriman # This file is part of ahriman
# (see https://github.com/arcan1s/ahriman). # (see https://github.com/arcan1s/ahriman).
@ -21,7 +21,7 @@ import argparse
import re import re
import sys import sys
from collections.abc import Generator from collections.abc import Iterator
from importlib import metadata from importlib import metadata
from typing import ClassVar from typing import ClassVar
@ -77,7 +77,7 @@ class Versions(Handler):
return parser return parser
@staticmethod @staticmethod
def package_dependencies(root: str) -> Generator[tuple[str, str], None, None]: def package_dependencies(root: str) -> Iterator[tuple[str, str]]:
""" """
extract list of ahriman package dependencies installed into system with their versions extract list of ahriman package dependencies installed into system with their versions
@ -87,7 +87,7 @@ class Versions(Handler):
Yields: Yields:
tuple[str, str]: map of installed dependency to its version tuple[str, str]: map of installed dependency to its version
""" """
def dependencies_by_key(key: str) -> Generator[str, None, None]: def dependencies_by_key(key: str) -> Iterator[str]:
# in importlib it returns requires in the following format # in importlib it returns requires in the following format
# ["pytest (>=3.0.0) ; extra == 'test'", "pytest-cov ; extra == 'test'"] # ["pytest (>=3.0.0) ; extra == 'test'", "pytest-cov ; extra == 'test'"]
try: try:

View File

@ -1,5 +1,5 @@
# #
# Copyright (c) 2021-2025 ahriman team. # Copyright (c) 2021-2026 ahriman team.
# #
# This file is part of ahriman # This file is part of ahriman
# (see https://github.com/arcan1s/ahriman). # (see https://github.com/arcan1s/ahriman).
@ -19,7 +19,7 @@
# #
import argparse import argparse
from collections.abc import Generator from collections.abc import Iterator
from pathlib import Path from pathlib import Path
from ahriman.application.handlers.handler import Handler, SubParserAction from ahriman.application.handlers.handler import Handler, SubParserAction
@ -86,7 +86,7 @@ class Web(Handler):
return parser return parser
@staticmethod @staticmethod
def extract_arguments(args: argparse.Namespace, configuration: Configuration) -> Generator[str, None, None]: def extract_arguments(args: argparse.Namespace, configuration: Configuration) -> Iterator[str]:
""" """
extract list of arguments used for current command, except for command specific ones extract list of arguments used for current command, except for command specific ones

View File

@ -1,5 +1,5 @@
# #
# Copyright (c) 2021-2025 ahriman team. # Copyright (c) 2021-2026 ahriman team.
# #
# This file is part of ahriman # This file is part of ahriman
# (see https://github.com/arcan1s/ahriman). # (see https://github.com/arcan1s/ahriman).

View File

@ -1,5 +1,5 @@
# #
# Copyright (c) 2021-2025 ahriman team. # Copyright (c) 2021-2026 ahriman team.
# #
# This file is part of ahriman # This file is part of ahriman
# (see https://github.com/arcan1s/ahriman). # (see https://github.com/arcan1s/ahriman).

View File

@ -1,5 +1,5 @@
# #
# Copyright (c) 2021-2025 ahriman team. # Copyright (c) 2021-2026 ahriman team.
# #
# This file is part of ahriman # This file is part of ahriman
# (see https://github.com/arcan1s/ahriman). # (see https://github.com/arcan1s/ahriman).

View File

@ -1,5 +1,5 @@
# #
# Copyright (c) 2021-2025 ahriman team. # Copyright (c) 2021-2026 ahriman team.
# #
# This file is part of ahriman # This file is part of ahriman
# (see https://github.com/arcan1s/ahriman). # (see https://github.com/arcan1s/ahriman).

View File

@ -1,5 +1,5 @@
# #
# Copyright (c) 2021-2025 ahriman team. # Copyright (c) 2021-2026 ahriman team.
# #
# This file is part of ahriman # This file is part of ahriman
# (see https://github.com/arcan1s/ahriman). # (see https://github.com/arcan1s/ahriman).

View File

@ -1,5 +1,5 @@
# #
# Copyright (c) 2021-2025 ahriman team. # Copyright (c) 2021-2026 ahriman team.
# #
# This file is part of ahriman # This file is part of ahriman
# (see https://github.com/arcan1s/ahriman). # (see https://github.com/arcan1s/ahriman).
@ -21,7 +21,7 @@ import itertools
import shutil import shutil
import tarfile import tarfile
from collections.abc import Generator, Iterable from collections.abc import Iterable, Iterator
from functools import cached_property from functools import cached_property
from pathlib import Path from pathlib import Path
from pyalpm import DB, Handle, Package, SIG_DATABASE_OPTIONAL, SIG_PACKAGE_OPTIONAL # type: ignore[import-not-found] from pyalpm import DB, Handle, Package, SIG_DATABASE_OPTIONAL, SIG_PACKAGE_OPTIONAL # type: ignore[import-not-found]
@ -188,7 +188,7 @@ class Pacman(LazyLogging):
Returns: Returns:
dict[str, set[str]]: map of package name to its list of files dict[str, set[str]]: map of package name to its list of files
""" """
def extract(tar: tarfile.TarFile, versions: dict[str, str]) -> Generator[tuple[str, set[str]], None, None]: def extract(tar: tarfile.TarFile, versions: dict[str, str]) -> Iterator[tuple[str, set[str]]]:
for package_name, version in versions.items(): for package_name, version in versions.items():
path = Path(f"{package_name}-{version}") / "files" path = Path(f"{package_name}-{version}") / "files"
try: try:
@ -223,7 +223,7 @@ class Pacman(LazyLogging):
return result return result
def package(self, package_name: str) -> Generator[Package, None, None]: def package(self, package_name: str) -> Iterator[Package]:
""" """
retrieve list of the packages from the repository by name retrieve list of the packages from the repository by name
@ -256,7 +256,7 @@ class Pacman(LazyLogging):
return result return result
def provided_by(self, package_name: str) -> Generator[Package, None, None]: def provided_by(self, package_name: str) -> Iterator[Package]:
""" """
search through databases and emit packages which provides the ``package_name`` search through databases and emit packages which provides the ``package_name``

View File

@ -1,5 +1,5 @@
# #
# Copyright (c) 2021-2025 ahriman team. # Copyright (c) 2021-2026 ahriman team.
# #
# This file is part of ahriman # This file is part of ahriman
# (see https://github.com/arcan1s/ahriman). # (see https://github.com/arcan1s/ahriman).

View File

@ -1,5 +1,5 @@
# #
# Copyright (c) 2021-2025 ahriman team. # Copyright (c) 2021-2026 ahriman team.
# #
# This file is part of ahriman # This file is part of ahriman
# (see https://github.com/arcan1s/ahriman). # (see https://github.com/arcan1s/ahriman).
@ -21,7 +21,7 @@ import itertools
import re import re
import shlex import shlex
from collections.abc import Generator from collections.abc import Iterator
from enum import StrEnum from enum import StrEnum
from typing import IO from typing import IO
@ -209,7 +209,7 @@ class PkgbuildParser(shlex.shlex):
Raises: Raises:
PkgbuildParserError: if array is not closed PkgbuildParserError: if array is not closed
""" """
def extract() -> Generator[str, None, None]: def extract() -> Iterator[str]:
while token := self.get_token(): while token := self.get_token():
match token: match token:
case _ if self._is_escaped(): case _ if self._is_escaped():
@ -276,7 +276,7 @@ class PkgbuildParser(shlex.shlex):
return content return content
def _parse_token(self, token: str) -> Generator[PkgbuildPatch, None, None]: def _parse_token(self, token: str) -> Iterator[PkgbuildPatch]:
""" """
parse single token to the PKGBUILD field parse single token to the PKGBUILD field
@ -360,7 +360,7 @@ class PkgbuildParser(shlex.shlex):
raise PkgbuildParserError("reached starting position, no valid symbols found") raise PkgbuildParserError("reached starting position, no valid symbols found")
def parse(self) -> Generator[PkgbuildPatch, None, None]: def parse(self) -> Iterator[PkgbuildPatch]:
""" """
parse source stream and yield parsed entries parse source stream and yield parsed entries

View File

@ -1,5 +1,5 @@
# #
# Copyright (c) 2021-2025 ahriman team. # Copyright (c) 2021-2026 ahriman team.
# #
# This file is part of ahriman # This file is part of ahriman
# (see https://github.com/arcan1s/ahriman). # (see https://github.com/arcan1s/ahriman).

View File

@ -1,5 +1,5 @@
# #
# Copyright (c) 2021-2025 ahriman team. # Copyright (c) 2021-2026 ahriman team.
# #
# This file is part of ahriman # This file is part of ahriman
# (see https://github.com/arcan1s/ahriman). # (see https://github.com/arcan1s/ahriman).
@ -44,7 +44,7 @@ class AUR(Remote):
""" """
generate remote git url from the package base generate remote git url from the package base
Args Args:
package_base(str): package base package_base(str): package base
repository(str): repository name repository(str): repository name
@ -58,7 +58,7 @@ class AUR(Remote):
""" """
generate remote web url from the package base generate remote web url from the package base
Args Args:
package_base(str): package base package_base(str): package base
Returns: Returns:

View File

@ -1,5 +1,5 @@
# #
# Copyright (c) 2021-2025 ahriman team. # Copyright (c) 2021-2026 ahriman team.
# #
# This file is part of ahriman # This file is part of ahriman
# (see https://github.com/arcan1s/ahriman). # (see https://github.com/arcan1s/ahriman).
@ -46,7 +46,7 @@ class Official(Remote):
""" """
generate remote git url from the package base generate remote git url from the package base
Args Args:
package_base(str): package base package_base(str): package base
repository(str): repository name repository(str): repository name
@ -60,7 +60,7 @@ class Official(Remote):
""" """
generate remote web url from the package base generate remote web url from the package base
Args Args:
package_base(str): package base package_base(str): package base
Returns: Returns:

View File

@ -1,5 +1,5 @@
# #
# Copyright (c) 2021-2025 ahriman team. # Copyright (c) 2021-2026 ahriman team.
# #
# This file is part of ahriman # This file is part of ahriman
# (see https://github.com/arcan1s/ahriman). # (see https://github.com/arcan1s/ahriman).

View File

@ -1,5 +1,5 @@
# #
# Copyright (c) 2021-2025 ahriman team. # Copyright (c) 2021-2026 ahriman team.
# #
# This file is part of ahriman # This file is part of ahriman
# (see https://github.com/arcan1s/ahriman). # (see https://github.com/arcan1s/ahriman).
@ -110,7 +110,7 @@ class Remote(SyncHttpClient):
""" """
generate remote git url from the package base generate remote git url from the package base
Args Args:
package_base(str): package base package_base(str): package base
repository(str): repository name repository(str): repository name
@ -127,7 +127,7 @@ class Remote(SyncHttpClient):
""" """
generate remote web url from the package base generate remote web url from the package base
Args Args:
package_base(str): package base package_base(str): package base
Returns: Returns:

View File

@ -1,5 +1,5 @@
# #
# Copyright (c) 2021-2025 ahriman team. # Copyright (c) 2021-2026 ahriman team.
# #
# This file is part of ahriman # This file is part of ahriman
# (see https://github.com/arcan1s/ahriman). # (see https://github.com/arcan1s/ahriman).
@ -31,20 +31,21 @@ class Repo(LazyLogging):
Attributes: Attributes:
name(str): repository name name(str): repository name
paths(RepositoryPaths): repository paths instance root(Path): repository root
sign_args(list[str]): additional args which have to be used to sign repository archive sign_args(list[str]): additional args which have to be used to sign repository archive
uid(int): uid of the repository owner user uid(int): uid of the repository owner user
""" """
def __init__(self, name: str, paths: RepositoryPaths, sign_args: list[str]) -> None: def __init__(self, name: str, paths: RepositoryPaths, sign_args: list[str], root: Path | None = None) -> None:
""" """
Args: Args:
name(str): repository name name(str): repository name
paths(RepositoryPaths): repository paths instance paths(RepositoryPaths): repository paths instance
sign_args(list[str]): additional args which have to be used to sign repository archive sign_args(list[str]): additional args which have to be used to sign repository archive
root(Path | None, optional): repository root. If none set, the default will be used (Default value = None)
""" """
self.name = name self.name = name
self.paths = paths self.root = root or paths.repository
self.uid, _ = paths.root_owner self.uid, _ = paths.root_owner
self.sign_args = sign_args self.sign_args = sign_args
@ -56,45 +57,56 @@ class Repo(LazyLogging):
Returns: Returns:
Path: path to repository database Path: path to repository database
""" """
return self.paths.repository / f"{self.name}.db.tar.gz" return self.root / f"{self.name}.db.tar.gz"
def add(self, path: Path) -> None: def add(self, path: Path, *, remove: bool = True) -> None:
""" """
add new package to repository add new package to repository
Args: Args:
path(Path): path to archive to add path(Path): path to archive to add
remove(bool, optional): whether to remove old packages or not (Default value = True)
""" """
command = ["repo-add", *self.sign_args]
if remove:
command.extend(["--remove"])
command.extend([str(self.repo_path), str(path)])
# add to repository
check_output( check_output(
"repo-add", *self.sign_args, "-R", str(self.repo_path), str(path), *command,
exception=BuildError.from_process(path.name), exception=BuildError.from_process(path.name),
cwd=self.paths.repository, cwd=self.root,
logger=self.logger, logger=self.logger,
user=self.uid) user=self.uid,
)
def init(self) -> None: def init(self) -> None:
""" """
create empty repository database. It just calls add with empty arguments create empty repository database. It just calls add with empty arguments
""" """
check_output("repo-add", *self.sign_args, str(self.repo_path), check_output("repo-add", *self.sign_args, str(self.repo_path),
cwd=self.paths.repository, logger=self.logger, user=self.uid) 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 remove package from repository
Args: 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 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 # remove package and signature (if any) from filesystem
for full_path in self.paths.repository.glob(f"{filename}*"): for full_path in self.root.glob(f"**/{filename.name}*"):
full_path.unlink() full_path.unlink()
# remove package from registry # remove package from registry
check_output( check_output(
"repo-remove", *self.sign_args, str(self.repo_path), package, "repo-remove", *self.sign_args, str(self.repo_path), package_name,
exception=BuildError.from_process(package), exception=BuildError.from_process(package_name),
cwd=self.paths.repository, cwd=self.root,
logger=self.logger, logger=self.logger,
user=self.uid) user=self.uid,
)

View File

@ -0,0 +1,20 @@
#
# 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/>.
#
from ahriman.core.archive.archive_trigger import ArchiveTrigger

View File

@ -0,0 +1,131 @@
#
# 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, walk
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
(Default value = None)
Returns:
Path: path to the repository root
"""
date = date or utcnow().date()
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}*"):
symlink = root / file.name
try:
symlink.symlink_to(file.relative_to(symlink.parent, walk_up=True))
has_file = True
except FileExistsError:
continue # symlink is already created, skip processing
if has_file:
repo.add(root / single.filename)
def symlinks_fix(self) -> None:
"""
remove broken symlinks across repositories for all dates
"""
for path in walk(self.paths.archive / "repos"):
root = path.parent
*_, name, architecture = root.parts
if self.repository_id.name != name or self.repository_id.architecture != architecture:
continue # we only process same name repositories
if not path.is_symlink():
continue # find symlinks only
if path.exists():
continue # filter out not broken symlinks
Repo(self.repository_id.name, self.paths, self.sign_args, root).remove(None, path)
def tree_create(self) -> None:
"""
create repository tree for current repository
"""
root = self.repository_for()
if root.exists():
return
with self.paths.preserve_owner(self.paths.archive):
root.mkdir(0o755, parents=True)
# init empty repository here
Repo(self.repository_id.name, self.paths, self.sign_args, root).init()

View File

@ -0,0 +1,69 @@
#
# 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/>.
#
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 ArchiveTrigger(Trigger):
"""
archive repository extension
Attributes:
paths(RepositoryPaths): repository paths instance
tree(ArchiveTree): archive tree wrapper
"""
def __init__(self, repository_id: RepositoryId, configuration: Configuration) -> None:
"""
Args:
repository_id(RepositoryId): repository unique identifier
configuration(Configuration): configuration instance
"""
Trigger.__init__(self, repository_id, configuration)
self.paths = configuration.repository_paths
self.tree = ArchiveTree(self.paths, GPG(configuration).repository_sign_args)
def on_result(self, result: Result, packages: list[Package]) -> None:
"""
run trigger
Args:
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
"""
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()

View File

@ -1,5 +1,5 @@
# #
# Copyright (c) 2021-2025 ahriman team. # Copyright (c) 2021-2026 ahriman team.
# #
# This file is part of ahriman # This file is part of ahriman
# (see https://github.com/arcan1s/ahriman). # (see https://github.com/arcan1s/ahriman).

View File

@ -1,5 +1,5 @@
# #
# Copyright (c) 2021-2025 ahriman team. # Copyright (c) 2021-2026 ahriman team.
# #
# This file is part of ahriman # This file is part of ahriman
# (see https://github.com/arcan1s/ahriman). # (see https://github.com/arcan1s/ahriman).

View File

@ -1,5 +1,5 @@
# #
# Copyright (c) 2021-2025 ahriman team. # Copyright (c) 2021-2026 ahriman team.
# #
# This file is part of ahriman # This file is part of ahriman
# (see https://github.com/arcan1s/ahriman). # (see https://github.com/arcan1s/ahriman).

View File

@ -1,5 +1,5 @@
# #
# Copyright (c) 2021-2025 ahriman team. # Copyright (c) 2021-2026 ahriman team.
# #
# This file is part of ahriman # This file is part of ahriman
# (see https://github.com/arcan1s/ahriman). # (see https://github.com/arcan1s/ahriman).

View File

@ -1,5 +1,5 @@
# #
# Copyright (c) 2021-2025 ahriman team. # Copyright (c) 2021-2026 ahriman team.
# #
# This file is part of ahriman # This file is part of ahriman
# (see https://github.com/arcan1s/ahriman). # (see https://github.com/arcan1s/ahriman).

View File

@ -1,5 +1,5 @@
# #
# Copyright (c) 2021-2025 ahriman team. # Copyright (c) 2021-2026 ahriman team.
# #
# This file is part of ahriman # This file is part of ahriman
# (see https://github.com/arcan1s/ahriman). # (see https://github.com/arcan1s/ahriman).

View File

@ -1,5 +1,5 @@
# #
# Copyright (c) 2021-2025 ahriman team. # Copyright (c) 2021-2026 ahriman team.
# #
# This file is part of ahriman # This file is part of ahriman
# (see https://github.com/arcan1s/ahriman). # (see https://github.com/arcan1s/ahriman).

View File

@ -1,5 +1,5 @@
# #
# Copyright (c) 2021-2025 ahriman team. # Copyright (c) 2021-2026 ahriman team.
# #
# This file is part of ahriman # This file is part of ahriman
# (see https://github.com/arcan1s/ahriman). # (see https://github.com/arcan1s/ahriman).

View File

@ -1,5 +1,5 @@
# #
# Copyright (c) 2021-2025 ahriman team. # Copyright (c) 2021-2026 ahriman team.
# #
# This file is part of ahriman # This file is part of ahriman
# (see https://github.com/arcan1s/ahriman). # (see https://github.com/arcan1s/ahriman).
@ -19,7 +19,7 @@
# #
import shutil import shutil
from collections.abc import Generator from collections.abc import Iterator
from pathlib import Path from pathlib import Path
from typing import ClassVar from typing import ClassVar
@ -347,7 +347,7 @@ class Sources(LazyLogging):
""" """
gitconfig = gitconfig or {} gitconfig = gitconfig or {}
def configuration_flags() -> Generator[str, None, None]: def configuration_flags() -> Iterator[str]:
for option, value in (self.GITCONFIG | gitconfig).items(): for option, value in (self.GITCONFIG | gitconfig).items():
yield "-c" yield "-c"
yield f"{option}=\"{value}\"" yield f"{option}=\"{value}\""

View File

@ -1,5 +1,5 @@
# #
# Copyright (c) 2021-2025 ahriman team. # Copyright (c) 2021-2026 ahriman team.
# #
# This file is part of ahriman # This file is part of ahriman
# (see https://github.com/arcan1s/ahriman). # (see https://github.com/arcan1s/ahriman).
@ -17,7 +17,7 @@
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
# #
from collections.abc import Generator from collections.abc import Iterator
from pathlib import Path from pathlib import Path
from ahriman.core.build_tools.sources import Sources from ahriman.core.build_tools.sources import Sources
@ -77,7 +77,7 @@ class Task(LazyLogging):
Returns: Returns:
list[Path]: list of file paths which looks like freshly generated archives list[Path]: list of file paths which looks like freshly generated archives
""" """
def files() -> Generator[Path, None, None]: def files() -> Iterator[Path]:
for filepath in sources_dir.iterdir(): for filepath in sources_dir.iterdir():
if filepath in source_files: if filepath in source_files:
continue # skip files which were already there continue # skip files which were already there

View File

@ -1,5 +1,5 @@
# #
# Copyright (c) 2021-2025 ahriman team. # Copyright (c) 2021-2026 ahriman team.
# #
# This file is part of ahriman # This file is part of ahriman
# (see https://github.com/arcan1s/ahriman). # (see https://github.com/arcan1s/ahriman).

View File

@ -1,5 +1,5 @@
# #
# Copyright (c) 2021-2025 ahriman team. # Copyright (c) 2021-2026 ahriman team.
# #
# This file is part of ahriman # This file is part of ahriman
# (see https://github.com/arcan1s/ahriman). # (see https://github.com/arcan1s/ahriman).
@ -43,7 +43,6 @@ class Configuration(configparser.RawConfigParser):
SYSTEM_CONFIGURATION_PATH(Path): (class attribute) default system configuration path distributed by package SYSTEM_CONFIGURATION_PATH(Path): (class attribute) default system configuration path distributed by package
includes(list[Path]): list of includes which were read includes(list[Path]): list of includes which were read
path(Path | None): path to root configuration file path(Path | None): path to root configuration file
repository_id(RepositoryId | None): repository unique identifier
Examples: Examples:
Configuration class provides additional method in order to handle application configuration. Since this class is Configuration class provides additional method in order to handle application configuration. Since this class is
@ -94,7 +93,7 @@ class Configuration(configparser.RawConfigParser):
}, },
) )
self.repository_id: RepositoryId | None = None self._repository_id: RepositoryId | None = None
self.path: Path | None = None self.path: Path | None = None
self.includes: list[Path] = [] self.includes: list[Path] = []
@ -129,6 +128,32 @@ class Configuration(configparser.RawConfigParser):
""" """
return self.getpath("settings", "logging") return self.getpath("settings", "logging")
@property
def repository_id(self) -> RepositoryId | None:
"""
repository identifier
Returns:
RepositoryId: repository unique identifier
"""
return self._repository_id
@repository_id.setter
def repository_id(self, repository_id: RepositoryId | None) -> None:
"""
setter for repository identifier
Args:
repository_id(RepositoryId | None): repository unique identifier
"""
self._repository_id = repository_id
if repository_id is None or repository_id.is_empty:
self.remove_option("repository", "name")
self.remove_option("repository", "architecture")
else:
self.set_option("repository", "name", repository_id.name)
self.set_option("repository", "architecture", repository_id.architecture)
@property @property
def repository_name(self) -> str: def repository_name(self) -> str:
""" """

View File

@ -1,5 +1,5 @@
# #
# Copyright (c) 2021-2025 ahriman team. # Copyright (c) 2021-2026 ahriman team.
# #
# This file is part of ahriman # This file is part of ahriman
# (see https://github.com/arcan1s/ahriman). # (see https://github.com/arcan1s/ahriman).
@ -57,7 +57,7 @@ class ConfigurationMultiDict(dict[str, Any]):
OptionError: if the key already exists in the dictionary, but not a single value list or a string OptionError: if the key already exists in the dictionary, but not a single value list or a string
""" """
match self.get(key): match self.get(key):
case [current_value] | str(current_value): case [current_value] | (str() as current_value):
value = f"{current_value} {value}" value = f"{current_value} {value}"
case None: case None:
pass pass

View File

@ -1,5 +1,5 @@
# #
# Copyright (c) 2021-2025 ahriman team. # Copyright (c) 2021-2026 ahriman team.
# #
# This file is part of ahriman # This file is part of ahriman
# (see https://github.com/arcan1s/ahriman). # (see https://github.com/arcan1s/ahriman).
@ -249,6 +249,10 @@ CONFIGURATION_SCHEMA: ConfigurationSchema = {
"repository": { "repository": {
"type": "dict", "type": "dict",
"schema": { "schema": {
"architecture": {
"type": "string",
"empty": False,
},
"name": { "name": {
"type": "string", "type": "string",
"empty": False, "empty": False,

View File

@ -1,5 +1,5 @@
# #
# Copyright (c) 2021-2025 ahriman team. # Copyright (c) 2021-2026 ahriman team.
# #
# This file is part of ahriman # This file is part of ahriman
# (see https://github.com/arcan1s/ahriman). # (see https://github.com/arcan1s/ahriman).
@ -21,7 +21,7 @@ import configparser
import os import os
import sys import sys
from collections.abc import Generator, Mapping, MutableMapping from collections.abc import Iterator, Mapping, MutableMapping
from string import Template from string import Template
from typing import Any, ClassVar from typing import Any, ClassVar
@ -37,7 +37,7 @@ class ShellInterpolator(configparser.Interpolation):
@staticmethod @staticmethod
def _extract_variables(parser: MutableMapping[str, Mapping[str, str]], value: str, def _extract_variables(parser: MutableMapping[str, Mapping[str, str]], value: str,
defaults: Mapping[str, str]) -> Generator[tuple[str, str], None, None]: defaults: Mapping[str, str]) -> Iterator[tuple[str, str]]:
""" """
extract keys and values (if available) from the configuration. In case if a key is not available, it will be extract keys and values (if available) from the configuration. In case if a key is not available, it will be
silently skipped from the result silently skipped from the result
@ -50,7 +50,7 @@ class ShellInterpolator(configparser.Interpolation):
Yields: Yields:
tuple[str, str]: variable name used for substitution and its value tuple[str, str]: variable name used for substitution and its value
""" """
def identifiers() -> Generator[tuple[str | None, str], None, None]: def identifiers() -> Iterator[tuple[str | None, str]]:
# extract all found identifiers and parse them # extract all found identifiers and parse them
for identifier in ShellTemplate(value).get_identifiers(): for identifier in ShellTemplate(value).get_identifiers():
match identifier.rsplit(":", maxsplit=1): match identifier.rsplit(":", maxsplit=1):

View File

@ -1,5 +1,5 @@
# #
# Copyright (c) 2021-2025 ahriman team. # Copyright (c) 2021-2026 ahriman team.
# #
# This file is part of ahriman # This file is part of ahriman
# (see https://github.com/arcan1s/ahriman). # (see https://github.com/arcan1s/ahriman).
@ -20,7 +20,7 @@
import fnmatch import fnmatch
import re import re
from collections.abc import Generator, Mapping from collections.abc import Iterator, Mapping
from string import Template from string import Template
@ -132,7 +132,7 @@ class ShellTemplate(Template):
(self._REPLACE, self._replace, "/"), (self._REPLACE, self._replace, "/"),
) )
def generator(variables: dict[str, str]) -> Generator[tuple[str, str], None, None]: def generator(variables: dict[str, str]) -> Iterator[tuple[str, str]]:
for identifier in self.get_identifiers(): for identifier in self.get_identifiers():
for regex, function, greediness in substitutions: for regex, function, greediness in substitutions:
if m := regex.match(identifier): if m := regex.match(identifier):

View File

@ -1,5 +1,5 @@
# #
# Copyright (c) 2021-2025 ahriman team. # Copyright (c) 2021-2026 ahriman team.
# #
# This file is part of ahriman # This file is part of ahriman
# (see https://github.com/arcan1s/ahriman). # (see https://github.com/arcan1s/ahriman).
@ -109,7 +109,7 @@ class Validator(RootValidator):
Args: Args:
constraint(list[str]): optional list of allowed special words (e.g. ``localhost``) constraint(list[str]): optional list of allowed special words (e.g. ``localhost``)
field(str): field name to be checked field(str): field name to be checked
value(Path): value to be checked value(str): value to be checked
Examples: Examples:
The rule's arguments are validated against this schema: The rule's arguments are validated against this schema:

View File

@ -1,5 +1,5 @@
# #
# Copyright (c) 2021-2025 ahriman team. # Copyright (c) 2021-2026 ahriman team.
# #
# This file is part of ahriman # This file is part of ahriman
# (see https://github.com/arcan1s/ahriman). # (see https://github.com/arcan1s/ahriman).

View File

@ -1,5 +1,5 @@
# #
# Copyright (c) 2021-2025 ahriman team. # Copyright (c) 2021-2026 ahriman team.
# #
# This file is part of ahriman # This file is part of ahriman
# (see https://github.com/arcan1s/ahriman). # (see https://github.com/arcan1s/ahriman).

View File

@ -1,5 +1,5 @@
# #
# Copyright (c) 2021-2025 ahriman team. # Copyright (c) 2021-2026 ahriman team.
# #
# This file is part of ahriman # This file is part of ahriman
# (see https://github.com/arcan1s/ahriman). # (see https://github.com/arcan1s/ahriman).

View File

@ -1,5 +1,5 @@
# #
# Copyright (c) 2021-2025 ahriman team. # Copyright (c) 2021-2026 ahriman team.
# #
# This file is part of ahriman # This file is part of ahriman
# (see https://github.com/arcan1s/ahriman). # (see https://github.com/arcan1s/ahriman).

View File

@ -1,5 +1,5 @@
# #
# Copyright (c) 2021-2025 ahriman team. # Copyright (c) 2021-2026 ahriman team.
# #
# This file is part of ahriman # This file is part of ahriman
# (see https://github.com/arcan1s/ahriman). # (see https://github.com/arcan1s/ahriman).

View File

@ -1,5 +1,5 @@
# #
# Copyright (c) 2021-2025 ahriman team. # Copyright (c) 2021-2026 ahriman team.
# #
# This file is part of ahriman # This file is part of ahriman
# (see https://github.com/arcan1s/ahriman). # (see https://github.com/arcan1s/ahriman).

Some files were not shown because too many files have changed in this diff Show More