mirror of
https://github.com/arcan1s/ahriman.git
synced 2025-07-15 23:09:56 +00:00
Compare commits
10 Commits
2.2.2
...
d98211e5e5
Author | SHA1 | Date | |
---|---|---|---|
d98211e5e5 | |||
b97c8928e1 | |||
649df81aa5 | |||
ad7cdb7d95 | |||
4bb598d2eb | |||
f47be6cab0 | |||
342b3cb652 | |||
fc0d8387df | |||
e0b0c3caeb | |||
61969dd682 |
16
.github/workflows/run-setup.yml
vendored
16
.github/workflows/run-setup.yml
vendored
@ -7,6 +7,22 @@ on:
|
||||
branches: [ master ]
|
||||
|
||||
jobs:
|
||||
run-setup-minimal:
|
||||
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
container:
|
||||
image: archlinux:latest
|
||||
volumes:
|
||||
- ${{ github.workspace }}:/build
|
||||
options: --privileged -w /build
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
|
||||
- name: setup the minimal service in arch linux container
|
||||
run: .github/workflows/setup.sh minimal
|
||||
|
||||
run-setup:
|
||||
|
||||
runs-on: ubuntu-latest
|
||||
|
31
.github/workflows/setup.sh
vendored
31
.github/workflows/setup.sh
vendored
@ -3,6 +3,8 @@
|
||||
|
||||
set -ex
|
||||
|
||||
[[ $1 = "minimal" ]] && MINIMAL_INSTALL=1
|
||||
|
||||
# install dependencies
|
||||
echo -e '[arcanisrepo]\nServer = http://repo.arcanis.me/$arch\nSigLevel = Never' | tee -a /etc/pacman.conf
|
||||
# refresh the image
|
||||
@ -12,12 +14,14 @@ pacman --noconfirm -Sy base-devel devtools git pyalpm python-aur python-passlib
|
||||
# make dependencies
|
||||
pacman --noconfirm -Sy python-build python-installer python-wheel
|
||||
# optional dependencies
|
||||
# VCS support
|
||||
pacman --noconfirm -Sy breezy darcs mercurial subversion
|
||||
# web server
|
||||
pacman --noconfirm -Sy python-aioauth-client python-aiohttp python-aiohttp-debugtoolbar python-aiohttp-jinja2 python-aiohttp-security python-aiohttp-session python-cryptography python-jinja
|
||||
# additional features
|
||||
pacman --noconfirm -Sy gnupg python-boto3 rsync
|
||||
if [[ -z $MINIMAL_INSTALL ]]; then
|
||||
# VCS support
|
||||
pacman --noconfirm -Sy breezy darcs mercurial subversion
|
||||
# web server
|
||||
pacman --noconfirm -Sy python-aioauth-client python-aiohttp python-aiohttp-debugtoolbar python-aiohttp-jinja2 python-aiohttp-security python-aiohttp-session python-cryptography python-jinja
|
||||
# additional features
|
||||
pacman --noconfirm -Sy gnupg python-boto3 rsync
|
||||
fi
|
||||
|
||||
# create fresh tarball
|
||||
make VERSION=1.0.0 archlinux # well, it does not really matter which version we will put here
|
||||
@ -33,14 +37,17 @@ systemd-machine-id-setup
|
||||
# special thing for the container, because /dev/log interface is not available there
|
||||
sed -i "s/handlers = syslog_handler/handlers = console_handler/g" /etc/ahriman.ini.d/logging.ini
|
||||
# initial setup command as root
|
||||
ahriman -a x86_64 repo-setup --packager "ahriman bot <ahriman@example.com>" --repository "github" --web-port 8080
|
||||
[[ -z $MINIMAL_INSTALL ]] && WEB_ARGS=("--web-port" "8080")
|
||||
ahriman -a x86_64 repo-setup --packager "ahriman bot <ahriman@example.com>" --repository "github" "${WEB_ARGS[@]}"
|
||||
# enable services
|
||||
systemctl enable ahriman-web@x86_64
|
||||
systemctl enable ahriman@x86_64.timer
|
||||
# run web service (detached)
|
||||
sudo -u ahriman -- ahriman -a x86_64 web &
|
||||
WEBPID=$!
|
||||
sleep 15s # wait for the web service activation
|
||||
if [[ -z $MINIMAL_INSTALL ]]; then
|
||||
# run web service (detached)
|
||||
sudo -u ahriman -- ahriman -a x86_64 web &
|
||||
WEB_PID=$!
|
||||
sleep 15s # wait for the web service activation
|
||||
fi
|
||||
# add the first package
|
||||
# the build itself does not really work in the container
|
||||
sudo -u ahriman -- ahriman package-add --now yay
|
||||
@ -49,4 +56,4 @@ sudo -u ahriman -- ahriman package-add --now yay
|
||||
# run package check
|
||||
sudo -u ahriman -- ahriman repo-update
|
||||
# stop web service lol
|
||||
kill $WEBPID
|
||||
[[ -z $WEB_PID ]] || kill $WEB_PID
|
||||
|
@ -82,7 +82,8 @@ disable=raw-checker-failed,
|
||||
fixme,
|
||||
too-many-arguments,
|
||||
duplicate-code,
|
||||
cyclic-import
|
||||
cyclic-import,
|
||||
confusing-with-statement,
|
||||
|
||||
|
||||
# Enable the message, report, category or checker with the given id(s). You can
|
||||
|
@ -5,7 +5,7 @@ formats: all
|
||||
build:
|
||||
os: ubuntu-20.04
|
||||
tools:
|
||||
python: "3.9"
|
||||
python: "3.10"
|
||||
|
||||
sphinx:
|
||||
builder: html
|
||||
|
2
Makefile
2
Makefile
@ -56,4 +56,4 @@ version:
|
||||
ifndef VERSION
|
||||
$(error VERSION is required, but not set)
|
||||
endif
|
||||
sed -i '/__version__ = .*/s/[^"][^)]*/__version__ = "$(VERSION)"/' src/ahriman/version.py
|
||||
sed -i 's/^__version__ = .*/__version__ = "$(VERSION)"/' src/ahriman/version.py
|
||||
|
@ -3,7 +3,7 @@
|
||||
ahriman
|
||||
.SH SYNOPSIS
|
||||
.B ahriman
|
||||
[-h] [-a ARCHITECTURE] [-c CONFIGURATION] [--force] [-l LOCK] [--no-report] [-q] [--unsafe] [-V] {aur-search,search,help,help-commands-unsafe,key-import,package-add,add,package-update,package-remove,remove,package-status,status,package-status-remove,package-status-update,status-update,patch-add,patch-list,patch-remove,repo-backup,repo-check,check,repo-clean,clean,repo-config,config,repo-rebuild,rebuild,repo-remove-unknown,remove-unknown,repo-report,report,repo-restore,repo-setup,init,repo-init,setup,repo-sign,sign,repo-status-update,repo-sync,sync,repo-triggers,repo-update,update,shell,user-add,user-list,user-remove,version,web} ...
|
||||
[-h] [-a ARCHITECTURE] [-c CONFIGURATION] [--force] [-l LOCK] [--no-report] [-q] [--unsafe] [-V] {aur-search,search,help,help-commands-unsafe,key-import,package-add,add,package-update,package-remove,remove,package-status,status,package-status-remove,package-status-update,status-update,patch-add,patch-list,patch-remove,patch-set-add,repo-backup,repo-check,check,repo-clean,clean,repo-config,config,repo-rebuild,rebuild,repo-remove-unknown,remove-unknown,repo-report,report,repo-restore,repo-setup,init,repo-init,setup,repo-sign,sign,repo-status-update,repo-sync,sync,repo-triggers,repo-update,update,shell,user-add,user-list,user-remove,version,web} ...
|
||||
.SH DESCRIPTION
|
||||
ArcH linux ReposItory MANager
|
||||
|
||||
@ -71,7 +71,7 @@ remove package status
|
||||
update package status
|
||||
.TP
|
||||
\fBahriman\fR \fI\,patch-add\/\fR
|
||||
add patch set
|
||||
add patch for PKGBUILD function
|
||||
.TP
|
||||
\fBahriman\fR \fI\,patch-list\/\fR
|
||||
list patch sets
|
||||
@ -79,6 +79,9 @@ list patch sets
|
||||
\fBahriman\fR \fI\,patch-remove\/\fR
|
||||
remove patch set
|
||||
.TP
|
||||
\fBahriman\fR \fI\,patch-set-add\/\fR
|
||||
add patch set
|
||||
.TP
|
||||
\fBahriman\fR \fI\,repo-backup\/\fR
|
||||
backup repository data
|
||||
.TP
|
||||
@ -284,21 +287,25 @@ set status for specified packages. If no packages supplied, service status will
|
||||
new status
|
||||
|
||||
.SH COMMAND \fI\,'ahriman patch-add'\/\fR
|
||||
usage: ahriman patch-add [-h] [-t TRACK] package
|
||||
usage: ahriman patch-add [-h] [-p PATCH] package variable
|
||||
|
||||
create or update source patches
|
||||
create or update patched PKGBUILD function or variable
|
||||
|
||||
.TP
|
||||
\fBpackage\fR
|
||||
path to directory with changed files for patch addition/update
|
||||
package base
|
||||
|
||||
.TP
|
||||
\fBvariable\fR
|
||||
PKGBUILD variable or function name. If variable is a function, it must end with ()
|
||||
|
||||
.SH OPTIONS \fI\,'ahriman patch-add'\/\fR
|
||||
.TP
|
||||
\fB\-t\fR \fI\,TRACK\/\fR, \fB\-\-track\fR \fI\,TRACK\/\fR
|
||||
files which has to be tracked
|
||||
\fB\-p\fR \fI\,PATCH\/\fR, \fB\-\-patch\fR \fI\,PATCH\/\fR
|
||||
path to file which contains function or variable value. If not set, the value will be read from stdin
|
||||
|
||||
.SH COMMAND \fI\,'ahriman patch-list'\/\fR
|
||||
usage: ahriman patch-list [-h] [-e] [package]
|
||||
usage: ahriman patch-list [-h] [-e] [-v VARIABLE] [package]
|
||||
|
||||
list available patches for the package
|
||||
|
||||
@ -311,8 +318,12 @@ package base
|
||||
\fB\-e\fR, \fB\-\-exit\-code\fR
|
||||
return non\-zero exit status if result is empty
|
||||
|
||||
.TP
|
||||
\fB\-v\fR \fI\,VARIABLE\/\fR, \fB\-\-variable\fR \fI\,VARIABLE\/\fR
|
||||
if set, show only patches for specified PKGBUILD variables
|
||||
|
||||
.SH COMMAND \fI\,'ahriman patch-remove'\/\fR
|
||||
usage: ahriman patch-remove [-h] package
|
||||
usage: ahriman patch-remove [-h] [-v VARIABLE] package
|
||||
|
||||
remove patches for the package
|
||||
|
||||
@ -320,6 +331,26 @@ remove patches for the package
|
||||
\fBpackage\fR
|
||||
package base
|
||||
|
||||
.SH OPTIONS \fI\,'ahriman patch-remove'\/\fR
|
||||
.TP
|
||||
\fB\-v\fR \fI\,VARIABLE\/\fR, \fB\-\-variable\fR \fI\,VARIABLE\/\fR
|
||||
should be used for single\-function patches in case if you wold like to remove only specified PKGBUILD variables. In case
|
||||
if not set, it will remove all patches related to the package
|
||||
|
||||
.SH COMMAND \fI\,'ahriman patch-set-add'\/\fR
|
||||
usage: ahriman patch-set-add [-h] [-t TRACK] package
|
||||
|
||||
create or update source patches
|
||||
|
||||
.TP
|
||||
\fBpackage\fR
|
||||
path to directory with changed files for patch addition/update
|
||||
|
||||
.SH OPTIONS \fI\,'ahriman patch-set-add'\/\fR
|
||||
.TP
|
||||
\fB\-t\fR \fI\,TRACK\/\fR, \fB\-\-track\fR \fI\,TRACK\/\fR
|
||||
files which has to be tracked
|
||||
|
||||
.SH COMMAND \fI\,'ahriman repo-backup'\/\fR
|
||||
usage: ahriman repo-backup [-h] path
|
||||
|
||||
@ -391,7 +422,7 @@ just perform check for packages without rebuild process itself
|
||||
.TP
|
||||
\fB\-\-from\-database\fR
|
||||
read packages from database instead of filesystem. This feature in particular is required in case if you would like to
|
||||
restore repository from another repository instance. Note however that in order to restore packages you need to have
|
||||
restore repository from another repository instance. Note, however, that in order to restore packages you need to have
|
||||
original ahriman instance run with web service and have run repo\-update at least once.
|
||||
|
||||
.TP
|
||||
|
@ -28,6 +28,14 @@ ahriman.core.database.migrations.m002\_user\_access module
|
||||
:no-undoc-members:
|
||||
:show-inheritance:
|
||||
|
||||
ahriman.core.database.migrations.m003\_patch\_variables module
|
||||
--------------------------------------------------------------
|
||||
|
||||
.. automodule:: ahriman.core.database.migrations.m003_patch_variables
|
||||
:members:
|
||||
:no-undoc-members:
|
||||
:show-inheritance:
|
||||
|
||||
Module contents
|
||||
---------------
|
||||
|
||||
|
@ -36,6 +36,14 @@ ahriman.core.formatters.package\_printer module
|
||||
:no-undoc-members:
|
||||
:show-inheritance:
|
||||
|
||||
ahriman.core.formatters.patch\_printer module
|
||||
---------------------------------------------
|
||||
|
||||
.. automodule:: ahriman.core.formatters.patch_printer
|
||||
:members:
|
||||
:no-undoc-members:
|
||||
:show-inheritance:
|
||||
|
||||
ahriman.core.formatters.printer module
|
||||
--------------------------------------
|
||||
|
||||
|
29
docs/ahriman.core.gitremote.rst
Normal file
29
docs/ahriman.core.gitremote.rst
Normal file
@ -0,0 +1,29 @@
|
||||
ahriman.core.gitremote package
|
||||
==============================
|
||||
|
||||
Submodules
|
||||
----------
|
||||
|
||||
ahriman.core.gitremote.remote\_pull\_trigger module
|
||||
---------------------------------------------------
|
||||
|
||||
.. automodule:: ahriman.core.gitremote.remote_pull_trigger
|
||||
:members:
|
||||
:no-undoc-members:
|
||||
:show-inheritance:
|
||||
|
||||
ahriman.core.gitremote.remote\_push\_trigger module
|
||||
---------------------------------------------------
|
||||
|
||||
.. automodule:: ahriman.core.gitremote.remote_push_trigger
|
||||
:members:
|
||||
:no-undoc-members:
|
||||
:show-inheritance:
|
||||
|
||||
Module contents
|
||||
---------------
|
||||
|
||||
.. automodule:: ahriman.core.gitremote
|
||||
:members:
|
||||
:no-undoc-members:
|
||||
:show-inheritance:
|
@ -12,6 +12,7 @@ Subpackages
|
||||
ahriman.core.build_tools
|
||||
ahriman.core.database
|
||||
ahriman.core.formatters
|
||||
ahriman.core.gitremote
|
||||
ahriman.core.report
|
||||
ahriman.core.repository
|
||||
ahriman.core.sign
|
||||
|
@ -35,12 +35,13 @@ This package contains everything which is required for any time of application r
|
||||
* ``ahriman.core.build_tools`` is a package which provides wrapper for ``devtools`` commands.
|
||||
* ``ahriman.core.database`` is everything including data and schema migrations for database.
|
||||
* ``ahriman.core.formatters`` package provides ``Printer`` sub-classes for printing data (e.g. package properties) to stdout which are used by some handlers.
|
||||
* ``ahriman.core.report`` is a package with reporting classes. Usually it must be called by ``ahriman.core.report.Report.load`` method.
|
||||
* ``ahriman.core.gitremote`` is a package with remote PKGBUILD triggers. Should not be called directly.
|
||||
* ``ahriman.core.report`` is a package with reporting triggers. Should not be called directly.
|
||||
* ``ahriman.core.repository`` contains several traits and base repository (``ahriman.core.repository.Repository`` class) implementation.
|
||||
* ``ahriman.core.sign`` package provides sign feature (only gpg calls are available).
|
||||
* ``ahriman.core.status`` contains helpers and watcher class which are required for web application. Reporter must be initialized by using ``ahriman.core.status.client.Client.load`` method.
|
||||
* ``ahriman.core.triggers`` package contains base trigger classes. Classes from this package must be imported in order to implement user extensions. In fact, ``ahriman.core.report`` and ``ahriman.core.upload`` use this package.
|
||||
* ``ahriman.core.upload`` package provides sync feature, must be called by ``ahriman.core.upload.Upload.load`` method.
|
||||
* ``ahriman.core.upload`` package provides sync feature, should not be called directly.
|
||||
|
||||
This package also provides some generic functions and classes which may be used by other packages:
|
||||
|
||||
@ -202,12 +203,14 @@ In order to configure users there are special commands.
|
||||
Triggers
|
||||
^^^^^^^^
|
||||
|
||||
Triggers are extensions which can be used in order to perform any actions after the update process. The package provides two default extensions - one is report generation and another one is remote upload feature.
|
||||
Triggers are extensions which can be used in order to perform any actions on application start, after the update process and, finally, before the application exit. The package provides two default extensions - one is report generation and another one is remote upload feature.
|
||||
|
||||
The main idea is to load classes by their full path (e.g. ``ahriman.core.upload.UploadTrigger``) by using ``importlib``: get the last part of the import and treat it as class name, join remain part by ``.`` and interpret as module path, import module and extract attribute from it.
|
||||
|
||||
The loaded triggers will be called with ``ahriman.models.result.Result`` and ``List[Packages]`` arguments, which describes the process result and current repository packages respectively. Any exception raised will be suppressed and will generate an exception message in logs.
|
||||
|
||||
In addition triggers can implement ``on_start`` and ``on_stop`` actions which will be called on the application start and right before the application exit. The ``on_start`` action is usually being called from handlers directly in order to make sure that no trigger will be run when it is not required (e.g. on user management). As soon as ``on_start`` action is called, the additional flag will be set; ``ahriman.core.triggers.TriggerLoader`` class implements ``__del__`` method in which, if the flag is set, the ``on_stop`` actions will be called.
|
||||
|
||||
For more details how to deal with the triggers, refer to :doc:`documentation <triggers>` and modules descriptions.
|
||||
|
||||
Remote synchronization
|
||||
|
@ -75,6 +75,18 @@ Settings for signing packages or repository. Group name can refer to architectur
|
||||
* ``key`` - default PGP key, string, required. This key will also be used for database signing if enabled.
|
||||
* ``key_*`` settings - PGP key which will be used for specific packages, string, optional. For example, if there is ``key_yay`` option the specified key will be used for yay package and default key for others.
|
||||
|
||||
``gitremote`` group
|
||||
-------------------
|
||||
|
||||
Remote git source synchronization settings. Unlike ``Upload`` triggers those triggers are used for PKGBUILD synchronization - e.g. fetch from remote repository PKGBUILDs before updating process or pulling updated PKGBUILDs to the remote repository.
|
||||
|
||||
Both urls support authorization; to do so you'd need to prefix the url with authorization part, e.g. ``https://key:token@github.com/arcan1s/ahriman.git``. It is highly recommended to use application tokens instead of your user authorization details.
|
||||
|
||||
* ``pull_url`` - url of the remote repository from which PKGBUILDs can be pulled before build process, string, required.
|
||||
* ``pull_branch`` - branch of the remote repository from which PKGBUILDs can be pulled before build process, string, optional, default is ``master``.
|
||||
* ``push_url`` - url of the remote repository to which PKGBUILDs should be pushed after build process, string, required.
|
||||
* ``push_branch`` - branch of the remote repository to which PKGBUILDs should be pushed after build process, string, optional, default is ``master``.
|
||||
|
||||
``report`` group
|
||||
----------------
|
||||
|
||||
|
53
docs/faq.rst
53
docs/faq.rst
@ -112,14 +112,63 @@ TL;DR
|
||||
|
||||
Before using this command you will need to create local directory, put ``PKGBUILD`` there and generate ``.SRCINFO`` by using ``makepkg --printsrcinfo > .SRCINFO`` command. These packages will be stored locally and *will be ignored* during automatic update; in order to update the package you will need to run ``package-add`` command again.
|
||||
|
||||
|
||||
Err, I have remote repository with PKGBUILDs and would like to get versions from there automatically
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
For that purpose you could use ``RemotePullTrigger`` trigger. To do so you will need:
|
||||
|
||||
#.
|
||||
Append ``triggers`` option in ``build`` section with the following line:
|
||||
|
||||
.. code-block:: ini
|
||||
|
||||
[build]
|
||||
triggers = ahriman.core.gitremote.RemotePullTrigger
|
||||
|
||||
#.
|
||||
Configure trigger as following:
|
||||
|
||||
.. code-block:: ini
|
||||
|
||||
[gitremote]
|
||||
pull_url = https://github.com/username/repository
|
||||
|
||||
During the next application run it will fetch repository from the specified url and will try to find packages there which can be used as local sources.
|
||||
|
||||
I would like to push PKGBUILDs to the remote repository
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
For that purpose you'd need to use another trigger called ``RemotePushTrigger``. Configure it as following:
|
||||
|
||||
#.
|
||||
Append ``triggers`` option in ``build`` section with the trigger name:
|
||||
|
||||
.. code-block:: ini
|
||||
|
||||
[build]
|
||||
triggers = ahriman.core.gitremote.RemotePushTrigger
|
||||
|
||||
#.
|
||||
Configure trigger as following:
|
||||
|
||||
.. code-block:: ini
|
||||
|
||||
[gitremote]
|
||||
push_url = https://github.com/username/repository
|
||||
|
||||
Unlike ``RemotePullTrigger`` trigger, the ``RemotePushTrigger`` more likely will require authorization. It is highly recommended to use application tokens for that instead of using your password (e.g. for Github you can generate tokens `here <https://github.com/settings/tokens>`_ with scope ``public_repo``). Authorization can be supplied by using authorization part of the url, e.g. ``https://key:token@github.com/username/repository``.
|
||||
|
||||
But I just wanted to change PKGBUILD from AUR a bit!
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
Well it is supported also.
|
||||
Well it is supported also. The recommended way is to patch specific function, e.g. by running ``sudo -u ahriman ahriman patch-add ahriman version``. This command will prompt for new value of the PKGBUILD variable ``version``. You can also write it to file and read from it ``sudo -u ahriman ahriman patch-add ahriman version version.patch``.
|
||||
|
||||
Alternatively you can create full-diff patches, which are calculated by using ``git diff`` from current PKGBUILD master branch:
|
||||
|
||||
#. Clone sources from AUR.
|
||||
#. Make changes you would like to (e.g. edit ``PKGBUILD``, add external patches).
|
||||
#. Run ``sudo -u ahriman ahriman patch-add /path/to/local/directory/with/PKGBUILD``.
|
||||
#. Run ``sudo -u ahriman ahriman patch-set-add /path/to/local/directory/with/PKGBUILD``.
|
||||
|
||||
The last command will calculate diff from current tree to the ``HEAD`` and will store it locally. Patches will be applied on any package actions (e.g. it can be used for dependency management).
|
||||
|
||||
|
@ -1,12 +1,45 @@
|
||||
Triggers
|
||||
========
|
||||
|
||||
The package provides ability to write custom extensions which will be run on (the most) actions, e.g. after updates. By default ahriman provides two types of extensions - reporting and files uploading. Each extension must derive from the ``ahriman.core.triggers.Trigger`` class and implement ``run`` method
|
||||
The package provides ability to write custom extensions which will be run on (the most) actions, e.g. after updates. By default ahriman provides three types of extensions - reporting, files uploading and PKGBUILD syncronization. Each extension must derive from the ``ahriman.core.triggers.Trigger`` class and should implement at least one of the abstract methods:
|
||||
|
||||
* ``on_result`` - trigger action which will be called after build process, the build result and the list of repository packages will be supplied as arguments.
|
||||
* ``on_start`` - trigger action which will be called right before the start of the application process.
|
||||
* ``on_stop`` - action which will be called right before the exit.
|
||||
|
||||
Note, it isn't required to implement all of those methods (or even one of them), however, it is highly recommended to avoid trigger actions in ``__init__`` method as it will be run on any application start (e.g. even if you are just searching in AUR).
|
||||
|
||||
Built-in triggers
|
||||
-----------------
|
||||
|
||||
For the configuration details and settings explanation kindly refer to the :doc:`documentation <configuration>`.
|
||||
|
||||
``ahriman.core.gitremote.RemotePullTrigger``
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
This trigger will be called before any action (``on_start``) and pulls remote PKGBUILD repository locally; after that it copies found PKGBUILDs from the cloned repository to the local cache. It is useful in case if you have patched PGKBUILDs (or even missing in AUR) which you would like to use for package building and, technically, just simplifies the local package building.
|
||||
|
||||
In order to update those packages you would need to clone your repository separately, make changes in PKGBUILD (e.g. bump version and update checksums), commit them and push back. On the next ahriman's repository update, it will pull changes you commited and will perform package update.
|
||||
|
||||
``ahriman.core.gitremote.RemotePushTrigger``
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
This trigger will be called right after build process (``on_result``). It will pick PKGBUILDs for the updated packages, pull them (together with any other files) and commit and push changes to remote repository. No real use cases, but the most of user repositories do it.
|
||||
|
||||
``ahriman.core.report.ReportTrigger``
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
Trigger which can be used for reporting. It implements ``on_result`` method and thus being called on each build update and generates report (e.g. html, telegram etc) according to the current settings.
|
||||
|
||||
``ahriman.core.upload.UploadTrigger``
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
This trigger takes build result (``on_result``) and performs syncing of the local packages to the remote mirror (e.g. S3 or just by rsync).
|
||||
|
||||
Trigger example
|
||||
---------------
|
||||
|
||||
Lets consider example of reporting trigger (e.g. `slack <https://slack.com/>`_, which provides easy HTTP API for integration triggers).
|
||||
Lets consider example of reporting trigger (e.g. `slack <https://slack.com/>`_, which provides easy HTTP API for integration triggers).gre
|
||||
|
||||
In order to post message to slack we will need a specific trigger url (something like ``https://hooks.slack.com/services/company_id/trigger_id``), channel (e.g. ``#archrepo``) and username (``repo-bot``).
|
||||
|
||||
@ -49,12 +82,12 @@ Obviously you can implement the specified method in class, but for guide purpose
|
||||
self.channel = configuration.get("slack", "channel")
|
||||
self.username = configuration.get("slack", "username")
|
||||
|
||||
def run(self, result, packages):
|
||||
def on_result(self, result, packages):
|
||||
notify(result, self.slack_url, self.channel, self.username)
|
||||
|
||||
Setup the trigger
|
||||
-----------------
|
||||
^^^^^^^^^^^^^^^^^
|
||||
|
||||
First, put the trigger in any path it can be exported, e.g. by packing the resource into python package (which will lead to import path as ``package.slack_reporter.SlackReporter``) or just put file somewhere it can be accessed by application (e.g. ``/usr/local/lib/slack_reporter.py.SlackReporter``.
|
||||
First, put the trigger in any path it can be exported, e.g. by packing the resource into python package (which will lead to import path as ``package.slack_reporter.SlackReporter``) or just put file somewhere it can be accessed by application (e.g. ``/usr/local/lib/slack_reporter.py.SlackReporter``).
|
||||
|
||||
After that run application as usual and receive notification in your slack channel.
|
||||
|
@ -27,7 +27,7 @@
|
||||
<button id="add-btn" class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#add-form" hidden>
|
||||
<i class="bi bi-plus"></i> add
|
||||
</button>
|
||||
<button id="update-btn" class="btn btn-secondary" onclick="updatePackages()" disabled hidden>
|
||||
<button id="update-btn" class="btn btn-secondary" onclick="updatePackages()" hidden>
|
||||
<i class="bi bi-play"></i> update
|
||||
</button>
|
||||
<button id="remove-btn" class="btn btn-danger" onclick="removePackages()" disabled hidden>
|
||||
|
@ -7,7 +7,6 @@
|
||||
table.on("check.bs.table uncheck.bs.table check-all.bs.table uncheck-all.bs.table",
|
||||
() => {
|
||||
removeButton.prop("disabled", !table.bootstrapTable("getSelections").length);
|
||||
updateButton.prop("disabled", !table.bootstrapTable("getSelections").length);
|
||||
});
|
||||
|
||||
const architectureBadge = $("#badge-architecture");
|
||||
@ -16,8 +15,6 @@
|
||||
const versionBadge = $("#badge-version");
|
||||
|
||||
function doPackageAction(uri, packages) {
|
||||
if (packages.length === 0)
|
||||
return;
|
||||
$.ajax({
|
||||
url: uri,
|
||||
data: JSON.stringify({packages: packages}),
|
||||
|
@ -82,6 +82,7 @@ def _parser() -> argparse.ArgumentParser:
|
||||
subparsers = parser.add_subparsers(title="command", help="command to run", dest="command", required=True)
|
||||
|
||||
_set_aur_search_parser(subparsers)
|
||||
_set_daemon_parser(subparsers)
|
||||
_set_help_parser(subparsers)
|
||||
_set_help_commands_unsafe_parser(subparsers)
|
||||
_set_key_import_parser(subparsers)
|
||||
@ -93,6 +94,7 @@ def _parser() -> argparse.ArgumentParser:
|
||||
_set_patch_add_parser(subparsers)
|
||||
_set_patch_list_parser(subparsers)
|
||||
_set_patch_remove_parser(subparsers)
|
||||
_set_patch_set_add_parser(subparsers)
|
||||
_set_repo_backup_parser(subparsers)
|
||||
_set_repo_check_parser(subparsers)
|
||||
_set_repo_clean_parser(subparsers)
|
||||
@ -140,6 +142,28 @@ def _set_aur_search_parser(root: SubParserAction) -> argparse.ArgumentParser:
|
||||
return parser
|
||||
|
||||
|
||||
def _set_daemon_parser(root: SubParserAction) -> argparse.ArgumentParser:
|
||||
"""
|
||||
add parser for daemon subcommand
|
||||
|
||||
Args:
|
||||
root(SubParserAction): subparsers for the commands
|
||||
|
||||
Returns:
|
||||
argparse.ArgumentParser: created argument parser
|
||||
"""
|
||||
parser = root.add_parser("daemon", help="run application as daemon",
|
||||
description="start process which periodically will run update process",
|
||||
formatter_class=_formatter)
|
||||
parser.add_argument("-i", "--interval", help="interval between runs in seconds", type=int, default=60 * 60 * 12)
|
||||
parser.add_argument("--no-aur", help="do not check for AUR updates. Implies --no-vcs", action="store_true")
|
||||
parser.add_argument("--no-local", help="do not check local packages for updates", action="store_true")
|
||||
parser.add_argument("--no-manual", help="do not include manual updates", action="store_true")
|
||||
parser.add_argument("--no-vcs", help="do not check VCS packages", action="store_true")
|
||||
parser.set_defaults(handler=handlers.Daemon, dry_run=False, exit_code=False, package=[])
|
||||
return parser
|
||||
|
||||
|
||||
def _set_help_parser(root: SubParserAction) -> argparse.ArgumentParser:
|
||||
"""
|
||||
add parser for listing help subcommand
|
||||
@ -320,7 +344,7 @@ def _set_package_status_update_parser(root: SubParserAction) -> argparse.Argumen
|
||||
|
||||
def _set_patch_add_parser(root: SubParserAction) -> argparse.ArgumentParser:
|
||||
"""
|
||||
add parser for new patch subcommand
|
||||
add parser for new single-function patch subcommand
|
||||
|
||||
Args:
|
||||
root(SubParserAction): subparsers for the commands
|
||||
@ -328,16 +352,18 @@ def _set_patch_add_parser(root: SubParserAction) -> argparse.ArgumentParser:
|
||||
Returns:
|
||||
argparse.ArgumentParser: created argument parser
|
||||
"""
|
||||
parser = root.add_parser("patch-add", help="add patch set", description="create or update source patches",
|
||||
epilog="In order to add a patch set for the package you will need to clone "
|
||||
"the AUR package manually, add required changes (e.g. external patches, "
|
||||
"edit PKGBUILD) and run command, e.g. ``ahriman patch path/to/directory``. "
|
||||
"By default it tracks *.patch and *.diff files, but this behavior can be changed "
|
||||
"by using --track option",
|
||||
parser = root.add_parser("patch-add", help="add patch for PKGBUILD function",
|
||||
description="create or update patched PKGBUILD function or variable",
|
||||
epilog="Unlike ``patch-set-add``, this function allows to patch only one PKGBUILD f"
|
||||
"unction, e.g. typing ``ahriman patch-add ahriman version`` it will change the "
|
||||
"``version`` inside PKGBUILD, typing ``ahriman patch-add ahriman build()`` "
|
||||
"it will change ``build()`` function inside PKGBUILD",
|
||||
formatter_class=_formatter)
|
||||
parser.add_argument("package", help="path to directory with changed files for patch addition/update")
|
||||
parser.add_argument("-t", "--track", help="files which has to be tracked", action="append",
|
||||
default=["*.diff", "*.patch"])
|
||||
parser.add_argument("package", help="package base")
|
||||
parser.add_argument("variable", help="PKGBUILD variable or function name. If variable is a function, "
|
||||
"it must end with ()")
|
||||
parser.add_argument("patch", help="path to file which contains function or variable value. If not set, "
|
||||
"the value will be read from stdin", type=Path, nargs="?")
|
||||
parser.set_defaults(handler=handlers.Patch, action=Action.Update, architecture=[""], lock=None, no_report=True)
|
||||
return parser
|
||||
|
||||
@ -356,6 +382,8 @@ def _set_patch_list_parser(root: SubParserAction) -> argparse.ArgumentParser:
|
||||
description="list available patches for the package", formatter_class=_formatter)
|
||||
parser.add_argument("package", help="package base", nargs="?")
|
||||
parser.add_argument("-e", "--exit-code", help="return non-zero exit status if result is empty", action="store_true")
|
||||
parser.add_argument("-v", "--variable", help="if set, show only patches for specified PKGBUILD variables",
|
||||
action="append")
|
||||
parser.set_defaults(handler=handlers.Patch, action=Action.List, architecture=[""], lock=None, no_report=True)
|
||||
return parser
|
||||
|
||||
@ -373,10 +401,39 @@ def _set_patch_remove_parser(root: SubParserAction) -> argparse.ArgumentParser:
|
||||
parser = root.add_parser("patch-remove", help="remove patch set", description="remove patches for the package",
|
||||
formatter_class=_formatter)
|
||||
parser.add_argument("package", help="package base")
|
||||
parser.add_argument("-v", "--variable", help="should be used for single-function patches in case if you wold like "
|
||||
"to remove only specified PKGBUILD variables. In case if not set, "
|
||||
"it will remove all patches related to the package",
|
||||
action="append")
|
||||
parser.set_defaults(handler=handlers.Patch, action=Action.Remove, architecture=[""], lock=None, no_report=True)
|
||||
return parser
|
||||
|
||||
|
||||
def _set_patch_set_add_parser(root: SubParserAction) -> argparse.ArgumentParser:
|
||||
"""
|
||||
add parser for new full-diff patch subcommand
|
||||
|
||||
Args:
|
||||
root(SubParserAction): subparsers for the commands
|
||||
|
||||
Returns:
|
||||
argparse.ArgumentParser: created argument parser
|
||||
"""
|
||||
parser = root.add_parser("patch-set-add", help="add patch set", description="create or update source patches",
|
||||
epilog="In order to add a patch set for the package you will need to clone "
|
||||
"the AUR package manually, add required changes (e.g. external patches, "
|
||||
"edit PKGBUILD) and run command, e.g. ``ahriman patch-set-add path/to/directory``. "
|
||||
"By default it tracks *.patch and *.diff files, but this behavior can be changed "
|
||||
"by using --track option",
|
||||
formatter_class=_formatter)
|
||||
parser.add_argument("package", help="path to directory with changed files for patch addition/update", type=Path)
|
||||
parser.add_argument("-t", "--track", help="files which has to be tracked", action="append",
|
||||
default=["*.diff", "*.patch"])
|
||||
parser.set_defaults(handler=handlers.Patch, action=Action.Update, architecture=[""], lock=None, no_report=True,
|
||||
variable=None)
|
||||
return parser
|
||||
|
||||
|
||||
def _set_repo_backup_parser(root: SubParserAction) -> argparse.ArgumentParser:
|
||||
"""
|
||||
add parser for repository backup subcommand
|
||||
@ -473,7 +530,7 @@ def _set_repo_rebuild_parser(root: SubParserAction) -> argparse.ArgumentParser:
|
||||
parser.add_argument("--from-database",
|
||||
help="read packages from database instead of filesystem. This feature in particular is "
|
||||
"required in case if you would like to restore repository from another repository "
|
||||
"instance. Note however that in order to restore packages you need to have original "
|
||||
"instance. Note, however, that in order to restore packages you need to have original "
|
||||
"ahriman instance run with web service and have run repo-update at least once.",
|
||||
action="store_true")
|
||||
parser.add_argument("-e", "--exit-code", help="return non-zero exit status if result is empty", action="store_true")
|
||||
|
@ -49,15 +49,6 @@ class Application(ApplicationPackages, ApplicationRepository):
|
||||
be used instead.
|
||||
"""
|
||||
|
||||
def _finalize(self, result: Result) -> None:
|
||||
"""
|
||||
generate report and sync to remote server
|
||||
|
||||
Args:
|
||||
result(Result): build result
|
||||
"""
|
||||
self.repository.process_triggers(result)
|
||||
|
||||
def _known_packages(self) -> Set[str]:
|
||||
"""
|
||||
load packages from repository and pacman repositories
|
||||
@ -73,3 +64,26 @@ class Application(ApplicationPackages, ApplicationRepository):
|
||||
known_packages.update(properties.provides)
|
||||
known_packages.update(self.repository.pacman.all_packages())
|
||||
return known_packages
|
||||
|
||||
def on_result(self, result: Result) -> None:
|
||||
"""
|
||||
generate report and sync to remote server
|
||||
|
||||
Args:
|
||||
result(Result): build result
|
||||
"""
|
||||
packages = self.repository.packages()
|
||||
self.repository.triggers.on_result(result, packages)
|
||||
|
||||
def on_start(self) -> None:
|
||||
"""
|
||||
run triggers on start of the application
|
||||
"""
|
||||
self.repository.triggers.on_start()
|
||||
|
||||
def on_stop(self) -> None:
|
||||
"""
|
||||
run triggers on stop of the application. Note, however, that in most cases this method should not be called
|
||||
directly as it will be called after on_start action
|
||||
"""
|
||||
self.repository.triggers.on_stop()
|
||||
|
@ -37,18 +37,6 @@ class ApplicationPackages(ApplicationProperties):
|
||||
package control class
|
||||
"""
|
||||
|
||||
def _finalize(self, result: Result) -> None:
|
||||
"""
|
||||
generate report and sync to remote server
|
||||
|
||||
Args:
|
||||
result(Result): build result
|
||||
|
||||
Raises:
|
||||
NotImplementedError: not implemented method
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
def _known_packages(self) -> Set[str]:
|
||||
"""
|
||||
load packages from repository and pacman repositories
|
||||
@ -61,6 +49,18 @@ class ApplicationPackages(ApplicationProperties):
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
def on_result(self, result: Result) -> None:
|
||||
"""
|
||||
generate report and sync to remote server
|
||||
|
||||
Args:
|
||||
result(Result): build result
|
||||
|
||||
Raises:
|
||||
NotImplementedError: not implemented method
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
def _add_archive(self, source: str, *_: Any) -> None:
|
||||
"""
|
||||
add package from archive
|
||||
@ -86,8 +86,7 @@ class ApplicationPackages(ApplicationProperties):
|
||||
self.database.build_queue_insert(package)
|
||||
self.database.remote_update(package)
|
||||
|
||||
with TemporaryDirectory(ignore_cleanup_errors=True) as dir_name, \
|
||||
(local_dir := Path(dir_name)): # pylint: disable=confusing-with-statement
|
||||
with TemporaryDirectory(ignore_cleanup_errors=True) as dir_name, (local_dir := Path(dir_name)):
|
||||
Sources.load(local_dir, package, self.database.patches_get(package.base), self.repository.paths)
|
||||
self._process_dependencies(local_dir, known_packages, without_dependencies)
|
||||
|
||||
@ -187,4 +186,4 @@ class ApplicationPackages(ApplicationProperties):
|
||||
names(Iterable[str]): list of packages (either base or name) to remove
|
||||
"""
|
||||
self.repository.process_remove(names)
|
||||
self._finalize(Result())
|
||||
self.on_result(Result())
|
||||
|
@ -35,7 +35,7 @@ class ApplicationRepository(ApplicationProperties):
|
||||
repository control class
|
||||
"""
|
||||
|
||||
def _finalize(self, result: Result) -> None:
|
||||
def on_result(self, result: Result) -> None:
|
||||
"""
|
||||
generate report and sync to remote server
|
||||
|
||||
@ -89,7 +89,7 @@ class ApplicationRepository(ApplicationProperties):
|
||||
self.update([])
|
||||
# sign repository database if set
|
||||
self.repository.sign.process_sign_repository(self.repository.repo.repo_path)
|
||||
self._finalize(Result())
|
||||
self.on_result(Result())
|
||||
|
||||
def unknown(self) -> List[str]:
|
||||
"""
|
||||
@ -139,7 +139,7 @@ class ApplicationRepository(ApplicationProperties):
|
||||
if not paths:
|
||||
return # don't need to process if no update supplied
|
||||
update_result = self.repository.process_update(paths)
|
||||
self._finalize(result.merge(update_result))
|
||||
self.on_result(result.merge(update_result))
|
||||
|
||||
# process built packages
|
||||
build_result = Result()
|
||||
|
@ -22,6 +22,7 @@ from ahriman.application.handlers.handler import Handler
|
||||
from ahriman.application.handlers.add import Add
|
||||
from ahriman.application.handlers.backup import Backup
|
||||
from ahriman.application.handlers.clean import Clean
|
||||
from ahriman.application.handlers.daemon import Daemon
|
||||
from ahriman.application.handlers.dump import Dump
|
||||
from ahriman.application.handlers.help import Help
|
||||
from ahriman.application.handlers.key_import import KeyImport
|
||||
|
@ -45,6 +45,7 @@ class Add(Handler):
|
||||
unsafe(bool): if set no user check will be performed before path creation
|
||||
"""
|
||||
application = Application(architecture, configuration, no_report, unsafe)
|
||||
application.on_start()
|
||||
application.add(args.package, args.source, args.without_dependencies)
|
||||
if not args.now:
|
||||
return
|
||||
|
@ -44,5 +44,6 @@ class Clean(Handler):
|
||||
no_report(bool): force disable reporting
|
||||
unsafe(bool): if set no user check will be performed before path creation
|
||||
"""
|
||||
Application(architecture, configuration, no_report, unsafe).clean(
|
||||
args.cache, args.chroot, args.manual, args.packages)
|
||||
application = Application(architecture, configuration, no_report, unsafe)
|
||||
application.on_start()
|
||||
application.clean(args.cache, args.chroot, args.manual, args.packages)
|
||||
|
51
src/ahriman/application/handlers/daemon.py
Normal file
51
src/ahriman/application/handlers/daemon.py
Normal file
@ -0,0 +1,51 @@
|
||||
#
|
||||
# Copyright (c) 2021-2022 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
|
||||
import threading
|
||||
|
||||
from typing import Type
|
||||
|
||||
from ahriman.application.handlers import Handler
|
||||
from ahriman.core.configuration import Configuration
|
||||
|
||||
|
||||
class Daemon(Handler):
|
||||
"""
|
||||
daemon packages handler
|
||||
"""
|
||||
|
||||
@classmethod
|
||||
def run(cls: Type[Handler], args: argparse.Namespace, architecture: str,
|
||||
configuration: Configuration, no_report: bool, unsafe: bool) -> None:
|
||||
"""
|
||||
callback for command line
|
||||
|
||||
Args:
|
||||
args(argparse.Namespace): command line args
|
||||
architecture(str): repository architecture
|
||||
configuration(Configuration): configuration instance
|
||||
no_report(bool): force disable reporting
|
||||
unsafe(bool): if set no user check will be performed before path creation
|
||||
"""
|
||||
from ahriman.application.handlers import Update
|
||||
Update.run(args, architecture, configuration, no_report, unsafe)
|
||||
timer = threading.Timer(args.interval, Daemon.run, (args, architecture, configuration, no_report, unsafe))
|
||||
timer.start()
|
||||
timer.join()
|
@ -46,5 +46,5 @@ class KeyImport(Handler):
|
||||
no_report(bool): force disable reporting
|
||||
unsafe(bool): if set no user check will be performed before path creation
|
||||
"""
|
||||
Application(architecture, configuration, no_report, unsafe).repository.sign.key_import(
|
||||
args.key_server, args.key)
|
||||
application = Application(architecture, configuration, no_report, unsafe)
|
||||
application.repository.sign.key_import(args.key_server, args.key)
|
||||
|
@ -18,17 +18,19 @@
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
import argparse
|
||||
import sys
|
||||
|
||||
from pathlib import Path
|
||||
from typing import List, Optional, Type
|
||||
from typing import List, Optional, Tuple, Type
|
||||
|
||||
from ahriman.application.application import Application
|
||||
from ahriman.application.handlers import Handler
|
||||
from ahriman.core.build_tools.sources import Sources
|
||||
from ahriman.core.configuration import Configuration
|
||||
from ahriman.core.formatters import StringPrinter
|
||||
from ahriman.core.formatters import PatchPrinter
|
||||
from ahriman.models.action import Action
|
||||
from ahriman.models.package import Package
|
||||
from ahriman.models.pkgbuild_patch import PkgbuildPatch
|
||||
|
||||
|
||||
class Patch(Handler):
|
||||
@ -50,52 +52,95 @@ class Patch(Handler):
|
||||
unsafe(bool): if set no user check will be performed before path creation
|
||||
"""
|
||||
application = Application(architecture, configuration, no_report, unsafe)
|
||||
application.on_start()
|
||||
|
||||
if args.action == Action.List:
|
||||
Patch.patch_set_list(application, args.package, args.exit_code)
|
||||
if args.action == Action.Update and args.variable is not None:
|
||||
patch = Patch.patch_create_from_function(args.variable, args.patch)
|
||||
Patch.patch_set_create(application, args.package, patch)
|
||||
elif args.action == Action.Update and args.variable is None:
|
||||
package_base, patch = Patch.patch_create_from_diff(args.package, args.track)
|
||||
Patch.patch_set_create(application, package_base, patch)
|
||||
elif args.action == Action.List:
|
||||
Patch.patch_set_list(application, args.package, args.variable, args.exit_code)
|
||||
elif args.action == Action.Remove:
|
||||
Patch.patch_set_remove(application, args.package)
|
||||
elif args.action == Action.Update:
|
||||
Patch.patch_set_create(application, Path(args.package), args.track)
|
||||
Patch.patch_set_remove(application, args.package, args.variable)
|
||||
|
||||
@staticmethod
|
||||
def patch_set_create(application: Application, sources_dir: Path, track: List[str]) -> None:
|
||||
def patch_create_from_diff(sources_dir: Path, track: List[str]) -> Tuple[str, PkgbuildPatch]:
|
||||
"""
|
||||
create PKGBUILD plain diff patches from sources directory
|
||||
|
||||
Args:
|
||||
sources_dir(Path): path to directory with the package sources
|
||||
track(List[str]): track files which match the glob before creating the patch
|
||||
|
||||
Returns:
|
||||
Tuple[str, PkgbuildPatch]: package base and created PKGBUILD patch based on the diff from master HEAD
|
||||
to current files
|
||||
"""
|
||||
package = Package.from_build(sources_dir)
|
||||
patch = Sources.patch_create(sources_dir, *track)
|
||||
return package.base, PkgbuildPatch(None, patch)
|
||||
|
||||
@staticmethod
|
||||
def patch_create_from_function(variable: str, patch_path: Optional[Path]) -> PkgbuildPatch:
|
||||
"""
|
||||
create single-function patch set for the package base
|
||||
|
||||
Args:
|
||||
variable(str): function or variable name inside PKGBUILD
|
||||
patch_path(Path): optional path to patch content. If not set, it will be read from stdin
|
||||
|
||||
Returns:
|
||||
PkgbuildPatch: created patch for the PKGBUILD function
|
||||
"""
|
||||
if patch_path is None:
|
||||
print("Post new function or variable value below. Press Ctrl-D to finish:", file=sys.stderr)
|
||||
patch = "".join(list(sys.stdin))
|
||||
else:
|
||||
patch = patch_path.read_text(encoding="utf8")
|
||||
patch = patch.strip() # remove spaces around the patch
|
||||
return PkgbuildPatch(variable, patch)
|
||||
|
||||
@staticmethod
|
||||
def patch_set_create(application: Application, package_base: str, patch: PkgbuildPatch) -> None:
|
||||
"""
|
||||
create patch set for the package base
|
||||
|
||||
Args:
|
||||
application(Application): application instance
|
||||
sources_dir(Path): path to directory with the package sources
|
||||
track(List[str]): track files which match the glob before creating the patch
|
||||
package_base(str): package base
|
||||
patch(PkgbuildPatch): patch descriptor
|
||||
"""
|
||||
package = Package.from_build(sources_dir)
|
||||
patch = Sources.patch_create(sources_dir, *track)
|
||||
application.database.patches_insert(package.base, patch)
|
||||
application.database.patches_insert(package_base, patch)
|
||||
|
||||
@staticmethod
|
||||
def patch_set_list(application: Application, package_base: Optional[str], exit_code: bool) -> None:
|
||||
def patch_set_list(application: Application, package_base: Optional[str], variables: List[str],
|
||||
exit_code: bool) -> None:
|
||||
"""
|
||||
list patches available for the package base
|
||||
|
||||
Args:
|
||||
application(Application): application instance
|
||||
package_base(Optional[str]): package base
|
||||
variables(List[str]): extract patches only for specified PKGBUILD variables
|
||||
exit_code(bool): exit with error on empty search result
|
||||
:
|
||||
"""
|
||||
patches = application.database.patches_list(package_base)
|
||||
patches = application.database.patches_list(package_base, variables)
|
||||
Patch.check_if_empty(exit_code, not patches)
|
||||
|
||||
for base, patch in patches.items():
|
||||
content = base if package_base is None else patch
|
||||
StringPrinter(content).print(verbose=True)
|
||||
PatchPrinter(base, patch).print(verbose=True, separator=" = ")
|
||||
|
||||
@staticmethod
|
||||
def patch_set_remove(application: Application, package_base: str) -> None:
|
||||
def patch_set_remove(application: Application, package_base: str, variables: List[str]) -> None:
|
||||
"""
|
||||
remove patch set for the package base
|
||||
|
||||
Args:
|
||||
application(Application): application instance
|
||||
package_base(str): package base
|
||||
variables(List[str]): remove patches only for specified PKGBUILD variables
|
||||
"""
|
||||
application.database.patches_remove(package_base)
|
||||
application.database.patches_remove(package_base, variables)
|
||||
|
@ -49,6 +49,8 @@ class Rebuild(Handler):
|
||||
depends_on = set(args.depends_on) if args.depends_on else None
|
||||
|
||||
application = Application(architecture, configuration, no_report, unsafe)
|
||||
application.on_start()
|
||||
|
||||
if args.from_database:
|
||||
updates = Rebuild.extract_packages(application)
|
||||
else:
|
||||
|
@ -44,4 +44,6 @@ class Remove(Handler):
|
||||
no_report(bool): force disable reporting
|
||||
unsafe(bool): if set no user check will be performed before path creation
|
||||
"""
|
||||
Application(architecture, configuration, no_report, unsafe).remove(args.package)
|
||||
application = Application(architecture, configuration, no_report, unsafe)
|
||||
application.on_start()
|
||||
application.remove(args.package)
|
||||
|
@ -46,6 +46,7 @@ class RemoveUnknown(Handler):
|
||||
unsafe(bool): if set no user check will be performed before path creation
|
||||
"""
|
||||
application = Application(architecture, configuration, no_report, unsafe)
|
||||
application.on_start()
|
||||
unknown_packages = application.unknown()
|
||||
|
||||
if args.dry_run:
|
||||
|
@ -133,7 +133,9 @@ class Setup(Handler):
|
||||
repository(str): repository name
|
||||
paths(RepositoryPaths): repository paths instance
|
||||
"""
|
||||
configuration = Configuration()
|
||||
# allow_no_value=True is required because pacman uses boolean configuration in which just keys present
|
||||
# (e.g. NoProgressBar) which will lead to exception
|
||||
configuration = Configuration(allow_no_value=True)
|
||||
# preserve case
|
||||
# stupid mypy thinks that it is impossible
|
||||
configuration.optionxform = lambda key: key # type: ignore
|
||||
|
@ -49,4 +49,5 @@ class Triggers(Handler):
|
||||
if args.trigger:
|
||||
loader = application.repository.triggers
|
||||
loader.triggers = [loader.load_trigger(trigger) for trigger in args.trigger]
|
||||
application.repository.process_triggers(Result())
|
||||
application.on_start()
|
||||
application.on_result(Result())
|
||||
|
@ -45,6 +45,7 @@ class Update(Handler):
|
||||
unsafe(bool): if set no user check will be performed before path creation
|
||||
"""
|
||||
application = Application(architecture, configuration, no_report, unsafe)
|
||||
application.on_start()
|
||||
packages = application.updates(args.package, args.no_aur, args.no_local, args.no_manual, args.no_vcs,
|
||||
Update.log_fn(application, args.dry_run))
|
||||
Update.check_if_empty(args.exit_code, not packages)
|
||||
|
@ -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/>.
|
||||
#
|
||||
import datetime
|
||||
import shutil
|
||||
|
||||
from pathlib import Path
|
||||
@ -44,24 +45,22 @@ class Sources(LazyLogging):
|
||||
_check_output = check_output
|
||||
|
||||
@staticmethod
|
||||
def extend_architectures(sources_dir: Path, architecture: str) -> None:
|
||||
def extend_architectures(sources_dir: Path, architecture: str) -> List[PkgbuildPatch]:
|
||||
"""
|
||||
extend existing PKGBUILD with repository architecture
|
||||
|
||||
Args:
|
||||
sources_dir(Path): local path to directory with source files
|
||||
architecture(str): repository architecture
|
||||
"""
|
||||
pkgbuild_path = sources_dir / "PKGBUILD"
|
||||
if not pkgbuild_path.is_file():
|
||||
return
|
||||
|
||||
Returns:
|
||||
List[PkgbuildPatch]: generated patch for PKGBUILD architectures if required
|
||||
"""
|
||||
architectures = Package.supported_architectures(sources_dir)
|
||||
if "any" in architectures: # makepkg does not like when there is any other arch except for any
|
||||
return
|
||||
return []
|
||||
architectures.add(architecture)
|
||||
patch = PkgbuildPatch("arch", list(architectures))
|
||||
patch.write(pkgbuild_path)
|
||||
return [PkgbuildPatch("arch", list(architectures))]
|
||||
|
||||
@staticmethod
|
||||
def fetch(sources_dir: Path, remote: Optional[RemoteSource]) -> None:
|
||||
@ -133,14 +132,14 @@ class Sources(LazyLogging):
|
||||
exception=None, cwd=sources_dir, logger=instance.logger)
|
||||
|
||||
@staticmethod
|
||||
def load(sources_dir: Path, package: Package, patch: Optional[str], paths: RepositoryPaths) -> None:
|
||||
def load(sources_dir: Path, package: Package, patches: List[PkgbuildPatch], paths: RepositoryPaths) -> None:
|
||||
"""
|
||||
fetch sources from remote and apply patches
|
||||
|
||||
Args:
|
||||
sources_dir(Path): local path to fetch
|
||||
package(Package): package definitions
|
||||
patch(Optional[str]): optional patch to be applied
|
||||
patches(List[PkgbuildPatch]): optional patch to be applied
|
||||
paths(RepositoryPaths): repository paths instance
|
||||
"""
|
||||
instance = Sources()
|
||||
@ -149,9 +148,9 @@ class Sources(LazyLogging):
|
||||
shutil.copytree(cache_dir, sources_dir, dirs_exist_ok=True)
|
||||
instance.fetch(sources_dir, package.remote)
|
||||
|
||||
if patch is not None:
|
||||
patches.extend(instance.extend_architectures(sources_dir, paths.architecture))
|
||||
for patch in patches:
|
||||
instance.patch_apply(sources_dir, patch)
|
||||
instance.extend_architectures(sources_dir, paths.architecture)
|
||||
|
||||
@staticmethod
|
||||
def patch_create(sources_dir: Path, *pattern: str) -> str:
|
||||
@ -170,6 +169,22 @@ class Sources(LazyLogging):
|
||||
diff = instance.diff(sources_dir)
|
||||
return f"{diff}\n" # otherwise, patch will be broken
|
||||
|
||||
@staticmethod
|
||||
def push(sources_dir: Path, remote: RemoteSource, *pattern: str) -> None:
|
||||
"""
|
||||
commit selected changes and push files to the remote repository
|
||||
|
||||
Args:
|
||||
sources_dir(Path): local path to git repository
|
||||
remote(RemoteSource): remote target, branch and url
|
||||
*pattern(str): glob patterns
|
||||
"""
|
||||
instance = Sources()
|
||||
instance.add(sources_dir, *pattern)
|
||||
instance.commit(sources_dir)
|
||||
Sources._check_output("git", "push", remote.git_url, remote.branch,
|
||||
exception=None, cwd=sources_dir, logger=instance.logger)
|
||||
|
||||
def add(self, sources_dir: Path, *pattern: str) -> None:
|
||||
"""
|
||||
track found files via git
|
||||
@ -190,6 +205,20 @@ class Sources(LazyLogging):
|
||||
*[str(fn.relative_to(sources_dir)) for fn in found_files],
|
||||
exception=None, cwd=sources_dir, logger=self.logger)
|
||||
|
||||
def commit(self, sources_dir: Path, commit_message: Optional[str] = None) -> None:
|
||||
"""
|
||||
commit changes
|
||||
|
||||
Args:
|
||||
sources_dir(Path): local path to git repository
|
||||
commit_message(Optional[str]): optional commit message if any. If none set, message will be generated
|
||||
according to the current timestamp
|
||||
"""
|
||||
if commit_message is None:
|
||||
commit_message = f"Autogenerated commit at {datetime.datetime.utcnow()}"
|
||||
Sources._check_output("git", "commit", "--all", "--message", commit_message,
|
||||
exception=None, cwd=sources_dir, logger=self.logger)
|
||||
|
||||
def diff(self, sources_dir: Path) -> str:
|
||||
"""
|
||||
generate diff from the current version and write it to the output file
|
||||
@ -217,15 +246,18 @@ class Sources(LazyLogging):
|
||||
dst = sources_dir / src.relative_to(pkgbuild_dir)
|
||||
shutil.move(src, dst)
|
||||
|
||||
def patch_apply(self, sources_dir: Path, patch: str) -> None:
|
||||
def patch_apply(self, sources_dir: Path, patch: PkgbuildPatch) -> None:
|
||||
"""
|
||||
apply patches if any
|
||||
|
||||
Args:
|
||||
sources_dir(Path): local path to directory with git sources
|
||||
patch(str): patch to be applied
|
||||
patch(PkgbuildPatch): patch to be applied
|
||||
"""
|
||||
# create patch
|
||||
self.logger.info("apply patch from database")
|
||||
Sources._check_output("git", "apply", "--ignore-space-change", "--ignore-whitespace",
|
||||
exception=None, cwd=sources_dir, input_data=patch, logger=self.logger)
|
||||
self.logger.info("apply patch %s from database at %s", patch.key, sources_dir)
|
||||
if patch.is_plain_diff:
|
||||
Sources._check_output("git", "apply", "--ignore-space-change", "--ignore-whitespace",
|
||||
exception=None, cwd=sources_dir, input_data=patch.serialize(), logger=self.logger)
|
||||
else:
|
||||
patch.write(sources_dir / "PKGBUILD")
|
||||
|
@ -70,11 +70,15 @@ class Configuration(configparser.RawConfigParser):
|
||||
ARCHITECTURE_SPECIFIC_SECTIONS = ["build", "sign", "web"]
|
||||
SYSTEM_CONFIGURATION_PATH = Path(sys.prefix) / "share" / "ahriman" / "settings" / "ahriman.ini"
|
||||
|
||||
def __init__(self) -> None:
|
||||
def __init__(self, allow_no_value: bool = False) -> None:
|
||||
"""
|
||||
default constructor. In the most cases must not be called directly
|
||||
|
||||
Args:
|
||||
allow_no_value(bool): copies ``configparser.RawConfigParser`` behaviour. In case if it is set to ``True``,
|
||||
the keys without values will be allowed
|
||||
"""
|
||||
configparser.RawConfigParser.__init__(self, allow_no_value=True, converters={
|
||||
configparser.RawConfigParser.__init__(self, allow_no_value=allow_no_value, converters={
|
||||
"list": self.__convert_list,
|
||||
"path": self.__convert_path,
|
||||
})
|
||||
|
43
src/ahriman/core/database/migrations/m003_patch_variables.py
Normal file
43
src/ahriman/core/database/migrations/m003_patch_variables.py
Normal file
@ -0,0 +1,43 @@
|
||||
#
|
||||
# Copyright (c) 2021-2022 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/>.
|
||||
#
|
||||
__all__ = ["steps"]
|
||||
|
||||
|
||||
steps = [
|
||||
"""
|
||||
alter table patches rename to patches_
|
||||
""",
|
||||
"""
|
||||
create table patches (
|
||||
package_base text not null,
|
||||
variable text,
|
||||
patch blob not null
|
||||
)
|
||||
""",
|
||||
"""
|
||||
create unique index patches_package_base_variable on patches (package_base, coalesce(variable, ''))
|
||||
""",
|
||||
"""
|
||||
insert into patches (package_base, patch) select package_base, patch from patches_
|
||||
""",
|
||||
"""
|
||||
drop table patches_
|
||||
""",
|
||||
]
|
@ -20,7 +20,6 @@
|
||||
import sqlite3
|
||||
|
||||
from pathlib import Path
|
||||
from sqlite3 import Connection, Cursor
|
||||
from typing import Any, Dict, Tuple, TypeVar, Callable
|
||||
|
||||
from ahriman.core.lazy_logging import LazyLogging
|
||||
@ -46,7 +45,7 @@ class Operations(LazyLogging):
|
||||
self.path = path
|
||||
|
||||
@staticmethod
|
||||
def factory(cursor: Cursor, row: Tuple[Any, ...]) -> Dict[str, Any]:
|
||||
def factory(cursor: sqlite3.Cursor, row: Tuple[Any, ...]) -> Dict[str, Any]:
|
||||
"""
|
||||
dictionary factory based on official documentation
|
||||
|
||||
@ -62,7 +61,7 @@ class Operations(LazyLogging):
|
||||
result[column[0]] = row[index]
|
||||
return result
|
||||
|
||||
def with_connection(self, query: Callable[[Connection], T], commit: bool = False) -> T:
|
||||
def with_connection(self, query: Callable[[sqlite3.Connection], T], commit: bool = False) -> T:
|
||||
"""
|
||||
perform operation in connection
|
||||
|
||||
|
@ -17,10 +17,13 @@
|
||||
# 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 collections import defaultdict
|
||||
|
||||
from sqlite3 import Connection
|
||||
from typing import Dict, Optional
|
||||
from typing import Dict, List, Optional, Tuple
|
||||
|
||||
from ahriman.core.database.operations import Operations
|
||||
from ahriman.models.pkgbuild_patch import PkgbuildPatch
|
||||
|
||||
|
||||
class PatchOperations(Operations):
|
||||
@ -28,7 +31,7 @@ class PatchOperations(Operations):
|
||||
operations for patches
|
||||
"""
|
||||
|
||||
def patches_get(self, package_base: str) -> Optional[str]:
|
||||
def patches_get(self, package_base: str) -> List[PkgbuildPatch]:
|
||||
"""
|
||||
retrieve patches for the package
|
||||
|
||||
@ -36,62 +39,77 @@ class PatchOperations(Operations):
|
||||
package_base(str): package base to search for patches
|
||||
|
||||
Returns:
|
||||
Optional[str]: plain text patch for the package
|
||||
List[PkgbuildPatch]: plain text patch for the package
|
||||
"""
|
||||
return self.patches_list(package_base).get(package_base)
|
||||
return self.patches_list(package_base, []).get(package_base, [])
|
||||
|
||||
def patches_insert(self, package_base: str, patch: str) -> None:
|
||||
def patches_insert(self, package_base: str, patch: PkgbuildPatch) -> None:
|
||||
"""
|
||||
insert or update patch in database
|
||||
|
||||
Args:
|
||||
package_base(str): package base to insert
|
||||
patch(str): patch content
|
||||
patch(PkgbuildPatch): patch content
|
||||
"""
|
||||
def run(connection: Connection) -> None:
|
||||
connection.execute(
|
||||
"""
|
||||
insert into patches
|
||||
(package_base, patch)
|
||||
(package_base, variable, patch)
|
||||
values
|
||||
(:package_base, :patch)
|
||||
on conflict (package_base) do update set
|
||||
(:package_base, :variable, :patch)
|
||||
on conflict (package_base, coalesce(variable, '')) do update set
|
||||
patch = :patch
|
||||
""",
|
||||
{"package_base": package_base, "patch": patch})
|
||||
{"package_base": package_base, "variable": patch.key, "patch": patch.value})
|
||||
|
||||
return self.with_connection(run, commit=True)
|
||||
|
||||
def patches_list(self, package_base: Optional[str]) -> Dict[str, str]:
|
||||
def patches_list(self, package_base: Optional[str], variables: List[str]) -> Dict[str, List[PkgbuildPatch]]:
|
||||
"""
|
||||
extract all patches
|
||||
|
||||
Args:
|
||||
package_base(Optional[str]): optional filter by package base
|
||||
variables(List[str]): extract patches only for specified PKGBUILD variables
|
||||
|
||||
Returns:
|
||||
Dict[str, str]: map of package base to patch content
|
||||
Dict[str, List[PkgbuildPatch]]: map of package base to patch content
|
||||
"""
|
||||
def run(connection: Connection) -> Dict[str, str]:
|
||||
return {
|
||||
cursor["package_base"]: cursor["patch"]
|
||||
def run(connection: Connection) -> List[Tuple[str, PkgbuildPatch]]:
|
||||
return [
|
||||
(cursor["package_base"], PkgbuildPatch(cursor["variable"], cursor["patch"]))
|
||||
for cursor in connection.execute(
|
||||
"""select * from patches where :package_base is null or package_base = :package_base""",
|
||||
{"package_base": package_base})
|
||||
}
|
||||
]
|
||||
|
||||
return self.with_connection(run)
|
||||
# we could use itertools & operator but why?
|
||||
patches: Dict[str, List[PkgbuildPatch]] = defaultdict(list)
|
||||
for package, patch in self.with_connection(run):
|
||||
if variables and patch.key not in variables:
|
||||
continue
|
||||
patches[package].append(patch)
|
||||
return dict(patches)
|
||||
|
||||
def patches_remove(self, package_base: str) -> None:
|
||||
def patches_remove(self, package_base: str, variables: List[str]) -> None:
|
||||
"""
|
||||
remove patch set
|
||||
|
||||
Args:
|
||||
package_base(str): package base to clear patches
|
||||
variables(List[str]): remove patches only for specified PKGBUILD variables
|
||||
"""
|
||||
def run_many(connection: Connection) -> None:
|
||||
connection.executemany(
|
||||
"""delete from patches where package_base = :package_base and variable = :variable""",
|
||||
[{"package_base": package_base, "variable": variable} for variable in variables])
|
||||
|
||||
def run(connection: Connection) -> None:
|
||||
connection.execute(
|
||||
"""delete from patches where package_base = :package_base""",
|
||||
{"package_base": package_base})
|
||||
|
||||
if variables:
|
||||
return self.with_connection(run_many, commit=True)
|
||||
return self.with_connection(run, commit=True)
|
||||
|
@ -24,6 +24,7 @@ from ahriman.core.formatters.aur_printer import AurPrinter
|
||||
from ahriman.core.formatters.build_printer import BuildPrinter
|
||||
from ahriman.core.formatters.configuration_printer import ConfigurationPrinter
|
||||
from ahriman.core.formatters.package_printer import PackagePrinter
|
||||
from ahriman.core.formatters.patch_printer import PatchPrinter
|
||||
from ahriman.core.formatters.status_printer import StatusPrinter
|
||||
from ahriman.core.formatters.update_printer import UpdatePrinter
|
||||
from ahriman.core.formatters.user_printer import UserPrinter
|
||||
|
56
src/ahriman/core/formatters/patch_printer.py
Normal file
56
src/ahriman/core/formatters/patch_printer.py
Normal file
@ -0,0 +1,56 @@
|
||||
#
|
||||
# Copyright (c) 2021-2022 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 typing import List
|
||||
|
||||
from ahriman.core.formatters import StringPrinter
|
||||
from ahriman.models.pkgbuild_patch import PkgbuildPatch
|
||||
from ahriman.models.property import Property
|
||||
|
||||
|
||||
class PatchPrinter(StringPrinter):
|
||||
"""
|
||||
print content of the PKGBUILD patch
|
||||
|
||||
Attributes:
|
||||
patches(List[PkgbuildPatch]): PKGBUILD patch object
|
||||
"""
|
||||
|
||||
def __init__(self, package_base: str, patches: List[PkgbuildPatch]) -> None:
|
||||
"""
|
||||
default constructor
|
||||
|
||||
Args:
|
||||
package_base(str): package base
|
||||
patches(List[PkgbuildPatch]): PKGBUILD patch object
|
||||
"""
|
||||
StringPrinter.__init__(self, package_base)
|
||||
self.patches = patches
|
||||
|
||||
def properties(self) -> List[Property]:
|
||||
"""
|
||||
convert content into printable data
|
||||
|
||||
Returns:
|
||||
List[Property]: list of content properties
|
||||
"""
|
||||
return [
|
||||
Property(patch.key or "Full source diff", patch.value, is_required=True)
|
||||
for patch in self.patches
|
||||
]
|
21
src/ahriman/core/gitremote/__init__.py
Normal file
21
src/ahriman/core/gitremote/__init__.py
Normal file
@ -0,0 +1,21 @@
|
||||
#
|
||||
# copyright (c) 2021-2022 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.gitremote.remote_pull_trigger import RemotePullTrigger
|
||||
from ahriman.core.gitremote.remote_push_trigger import RemotePushTrigger
|
86
src/ahriman/core/gitremote/remote_pull_trigger.py
Normal file
86
src/ahriman/core/gitremote/remote_pull_trigger.py
Normal file
@ -0,0 +1,86 @@
|
||||
#
|
||||
# copyright (c) 2021-2022 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 shutil
|
||||
|
||||
from pathlib import Path
|
||||
from tempfile import TemporaryDirectory
|
||||
|
||||
from ahriman.core.build_tools.sources import Sources
|
||||
from ahriman.core.configuration import Configuration
|
||||
from ahriman.core.triggers import Trigger
|
||||
from ahriman.core.util import walk
|
||||
from ahriman.models.package_source import PackageSource
|
||||
from ahriman.models.remote_source import RemoteSource
|
||||
|
||||
|
||||
class RemotePullTrigger(Trigger):
|
||||
"""
|
||||
trigger for fetching PKGBUILDs from remote repository
|
||||
|
||||
Attributes:
|
||||
remote_source(RemoteSource): repository remote source (remote pull url and branch)
|
||||
repository_paths(RepositoryPaths): repository paths instance
|
||||
"""
|
||||
|
||||
def __init__(self, architecture: str, configuration: Configuration) -> None:
|
||||
"""
|
||||
default constructor
|
||||
|
||||
Args:
|
||||
architecture(str): repository architecture
|
||||
configuration(Configuration): configuration instance
|
||||
"""
|
||||
Trigger.__init__(self, architecture, configuration)
|
||||
self.remote_source = RemoteSource(
|
||||
git_url=configuration.get("gitremote", "pull_url"),
|
||||
web_url="",
|
||||
path=".",
|
||||
branch=configuration.get("gitremote", "pull_branch", fallback="master"),
|
||||
source=PackageSource.Local,
|
||||
)
|
||||
self.repository_paths = configuration.repository_paths
|
||||
|
||||
def on_start(self) -> None:
|
||||
"""
|
||||
trigger action which will be called at the start of the application
|
||||
"""
|
||||
self.repo_clone()
|
||||
|
||||
def repo_clone(self) -> None:
|
||||
"""
|
||||
clone repository from remote source
|
||||
"""
|
||||
with TemporaryDirectory(ignore_cleanup_errors=True) as dir_name, (clone_dir := Path(dir_name)):
|
||||
Sources.fetch(clone_dir, self.remote_source)
|
||||
self.repo_copy(clone_dir)
|
||||
|
||||
def repo_copy(self, clone_dir: Path) -> None:
|
||||
"""
|
||||
copy directories from cloned remote source to local cache
|
||||
|
||||
Args:
|
||||
clone_dir(Path): path to temporary cloned directory
|
||||
"""
|
||||
for pkgbuild_path in filter(lambda path: path.name == "PKGBUILD", walk(clone_dir)):
|
||||
cloned_pkgbuild_dir = pkgbuild_path.parent
|
||||
package_base = cloned_pkgbuild_dir.name
|
||||
local_pkgbuild_dir = self.repository_paths.cache_for(package_base)
|
||||
shutil.copytree(cloned_pkgbuild_dir, local_pkgbuild_dir, dirs_exist_ok=True)
|
||||
Sources.init(local_pkgbuild_dir) # initialized git repository is required for local sources
|
110
src/ahriman/core/gitremote/remote_push_trigger.py
Normal file
110
src/ahriman/core/gitremote/remote_push_trigger.py
Normal file
@ -0,0 +1,110 @@
|
||||
#
|
||||
# copyright (c) 2021-2022 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 shutil
|
||||
|
||||
from pathlib import Path
|
||||
from tempfile import TemporaryDirectory
|
||||
from typing import Generator, Iterable
|
||||
|
||||
from ahriman.core.build_tools.sources import Sources
|
||||
from ahriman.core.configuration import Configuration
|
||||
from ahriman.core.triggers import Trigger
|
||||
from ahriman.models.package import Package
|
||||
from ahriman.models.package_source import PackageSource
|
||||
from ahriman.models.remote_source import RemoteSource
|
||||
from ahriman.models.result import Result
|
||||
|
||||
|
||||
class RemotePushTrigger(Trigger):
|
||||
"""
|
||||
trigger for syncing PKGBUILDs to remote repository
|
||||
|
||||
Attributes:
|
||||
remote_source(RemoteSource): repository remote source (remote pull url and branch)
|
||||
"""
|
||||
|
||||
def __init__(self, architecture: str, configuration: Configuration) -> None:
|
||||
"""
|
||||
default constructor
|
||||
|
||||
Args:
|
||||
architecture(str): repository architecture
|
||||
configuration(Configuration): configuration instance
|
||||
"""
|
||||
Trigger.__init__(self, architecture, configuration)
|
||||
self.remote_source = RemoteSource(
|
||||
git_url=configuration.get("gitremote", "push_url"),
|
||||
web_url="",
|
||||
path=".",
|
||||
branch=configuration.get("gitremote", "push_branch", fallback="master"),
|
||||
source=PackageSource.Local,
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def package_update(package: Package, target_dir: Path) -> str:
|
||||
"""
|
||||
clone specified package and update its content in cloned PKGBUILD repository
|
||||
|
||||
Args:
|
||||
package(Package): built package to update pkgbuild repository
|
||||
target_dir(Path): path to the cloned PKGBUILD repository
|
||||
|
||||
Returns:
|
||||
str: relative path to be added as new file
|
||||
"""
|
||||
# instead of iterating by directory we can simplify the process
|
||||
# firstly, we need to remove old data to make sure that removed files are not tracked anymore...
|
||||
package_target_dir = target_dir / package.base
|
||||
shutil.rmtree(package_target_dir, ignore_errors=True)
|
||||
# ...secondly, we copy whole tree...
|
||||
with TemporaryDirectory(ignore_cleanup_errors=True) as dir_name, (clone_dir := Path(dir_name)):
|
||||
Sources.fetch(clone_dir, package.remote)
|
||||
shutil.copytree(clone_dir, package_target_dir)
|
||||
# ...and last, but not least, we remove the dot-git directory...
|
||||
shutil.rmtree(package_target_dir / ".git", ignore_errors=True)
|
||||
# ...and finally return path to the copied directory
|
||||
return package.base
|
||||
|
||||
@staticmethod
|
||||
def packages_update(result: Result, target_dir: Path) -> Generator[str, None, None]:
|
||||
"""
|
||||
update all packages from the build result
|
||||
|
||||
Args:
|
||||
result(Result): build result
|
||||
target_dir(Path): path to the cloned PKGBUILD repository
|
||||
|
||||
Yields:
|
||||
str: path to updated files
|
||||
"""
|
||||
for package in result.success:
|
||||
yield RemotePushTrigger.package_update(package, target_dir)
|
||||
|
||||
def on_result(self, result: Result, packages: Iterable[Package]) -> None:
|
||||
"""
|
||||
trigger action which will be called after build process with process result
|
||||
|
||||
Args:
|
||||
result(Result): build result
|
||||
packages(Iterable[Package]): list of all available packages
|
||||
"""
|
||||
with TemporaryDirectory(ignore_cleanup_errors=True) as dir_name, (clone_dir := Path(dir_name)):
|
||||
Sources.fetch(clone_dir, self.remote_source)
|
||||
Sources.push(clone_dir, self.remote_source, *RemotePushTrigger.packages_update(result, clone_dir))
|
@ -17,5 +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.report.report import Report
|
||||
from ahriman.core.report.report_trigger import ReportTrigger
|
||||
|
@ -21,7 +21,7 @@ from typing import Iterable
|
||||
|
||||
from ahriman.core.configuration import Configuration
|
||||
from ahriman.core.formatters import BuildPrinter
|
||||
from ahriman.core.report import Report
|
||||
from ahriman.core.report.report import Report
|
||||
from ahriman.models.package import Package
|
||||
from ahriman.models.result import Result
|
||||
|
||||
|
@ -25,8 +25,8 @@ from email.mime.text import MIMEText
|
||||
from typing import Dict, Iterable
|
||||
|
||||
from ahriman.core.configuration import Configuration
|
||||
from ahriman.core.report import Report
|
||||
from ahriman.core.report.jinja_template import JinjaTemplate
|
||||
from ahriman.core.report.report import Report
|
||||
from ahriman.core.util import pretty_datetime
|
||||
from ahriman.models.package import Package
|
||||
from ahriman.models.result import Result
|
||||
|
@ -20,8 +20,8 @@
|
||||
from typing import Iterable
|
||||
|
||||
from ahriman.core.configuration import Configuration
|
||||
from ahriman.core.report import Report
|
||||
from ahriman.core.report.jinja_template import JinjaTemplate
|
||||
from ahriman.core.report.report import Report
|
||||
from ahriman.models.package import Package
|
||||
from ahriman.models.result import Result
|
||||
|
||||
|
@ -21,7 +21,7 @@ from typing import Iterable
|
||||
|
||||
from ahriman.core.configuration import Configuration
|
||||
from ahriman.core.triggers import Trigger
|
||||
from ahriman.core.report import Report
|
||||
from ahriman.core.report.report import Report
|
||||
from ahriman.models.package import Package
|
||||
from ahriman.models.result import Result
|
||||
|
||||
@ -43,9 +43,9 @@ class ReportTrigger(Trigger):
|
||||
configuration(Configuration): configuration instance
|
||||
"""
|
||||
Trigger.__init__(self, architecture, configuration)
|
||||
self.targets = configuration.getlist("report", "target")
|
||||
self.targets = configuration.getlist("report", "target", fallback=[])
|
||||
|
||||
def run(self, result: Result, packages: Iterable[Package]) -> None:
|
||||
def on_result(self, result: Result, packages: Iterable[Package]) -> None:
|
||||
"""
|
||||
run trigger
|
||||
|
||||
|
@ -23,8 +23,8 @@ import requests
|
||||
from typing import Iterable
|
||||
|
||||
from ahriman.core.configuration import Configuration
|
||||
from ahriman.core.report import Report
|
||||
from ahriman.core.report.jinja_template import JinjaTemplate
|
||||
from ahriman.core.report.report import Report
|
||||
from ahriman.core.util import exception_response_text
|
||||
from ahriman.models.package import Package
|
||||
from ahriman.models.result import Result
|
||||
|
@ -84,8 +84,7 @@ class Executor(Cleaner):
|
||||
|
||||
result = Result()
|
||||
for single in updates:
|
||||
with TemporaryDirectory(ignore_cleanup_errors=True) as dir_name, \
|
||||
(build_dir := Path(dir_name)): # pylint: disable=confusing-with-statement
|
||||
with TemporaryDirectory(ignore_cleanup_errors=True) as dir_name, (build_dir := Path(dir_name)):
|
||||
try:
|
||||
build_single(single, build_dir)
|
||||
result.add_success(single)
|
||||
@ -110,7 +109,7 @@ class Executor(Cleaner):
|
||||
try:
|
||||
self.paths.tree_clear(package_base) # remove all internal files
|
||||
self.database.build_queue_clear(package_base)
|
||||
self.database.patches_remove(package_base)
|
||||
self.database.patches_remove(package_base, [])
|
||||
self.reporter.remove(package_base) # we only update status page in case of base removal
|
||||
except Exception:
|
||||
self.logger.exception("could not remove base %s", package_base)
|
||||
@ -144,15 +143,6 @@ class Executor(Cleaner):
|
||||
|
||||
return self.repo.repo_path
|
||||
|
||||
def process_triggers(self, result: Result) -> None:
|
||||
"""
|
||||
process triggers setup by settings
|
||||
|
||||
Args:
|
||||
result(Result): build result
|
||||
"""
|
||||
self.triggers.process(result, self.packages())
|
||||
|
||||
def process_update(self, packages: Iterable[Path]) -> Result:
|
||||
"""
|
||||
sign packages, add them to repository and update repository database
|
||||
|
@ -97,7 +97,7 @@ class GPG(LazyLogging):
|
||||
Tuple[Set[SignSettings], Optional[str]]: tuple of sign targets and default PGP key
|
||||
"""
|
||||
targets: Set[SignSettings] = set()
|
||||
for option in configuration.getlist("sign", "target"):
|
||||
for option in configuration.getlist("sign", "target", fallback=[]):
|
||||
target = SignSettings.from_option(option)
|
||||
if target == SignSettings.Disabled:
|
||||
continue
|
||||
@ -181,7 +181,7 @@ class GPG(LazyLogging):
|
||||
sign repository if required by configuration
|
||||
|
||||
Note:
|
||||
more likely you just want to pass ``repository_sign_args`` to repo wrapper
|
||||
More likely you just want to pass ``repository_sign_args`` to repo wrapper
|
||||
|
||||
Args:
|
||||
path(Path): path to repository database
|
||||
|
@ -86,10 +86,12 @@ class Spawn(Thread, LazyLogging):
|
||||
packages(Iterable[str]): packages list to add
|
||||
now(bool): build packages now
|
||||
"""
|
||||
if not packages:
|
||||
return self.spawn_process("repo-update")
|
||||
kwargs = {"source": PackageSource.AUR.value} # avoid abusing by building non-aur packages
|
||||
if now:
|
||||
kwargs["now"] = ""
|
||||
self.spawn_process("add", *packages, **kwargs)
|
||||
return self.spawn_process("package-add", *packages, **kwargs)
|
||||
|
||||
def packages_remove(self, packages: Iterable[str]) -> None:
|
||||
"""
|
||||
@ -98,7 +100,7 @@ class Spawn(Thread, LazyLogging):
|
||||
Args:
|
||||
packages(Iterable[str]): packages list to remove
|
||||
"""
|
||||
self.spawn_process("remove", *packages)
|
||||
self.spawn_process("package-remove", *packages)
|
||||
|
||||
def spawn_process(self, command: str, *args: str, **kwargs: str) -> None:
|
||||
"""
|
||||
|
@ -72,8 +72,7 @@ class Leaf:
|
||||
Returns:
|
||||
Leaf: loaded class
|
||||
"""
|
||||
with TemporaryDirectory(ignore_cleanup_errors=True) as dir_name, \
|
||||
(clone_dir := Path(dir_name)): # pylint: disable=confusing-with-statement
|
||||
with TemporaryDirectory(ignore_cleanup_errors=True) as dir_name, (clone_dir := Path(dir_name)):
|
||||
Sources.load(clone_dir, package, database.patches_get(package.base), paths)
|
||||
dependencies = Package.dependencies(clone_dir)
|
||||
return cls(package, dependencies)
|
||||
|
@ -37,7 +37,7 @@ class Trigger(LazyLogging):
|
||||
This class must be used in order to create own extension. Basically idea is the following::
|
||||
|
||||
>>> class CustomTrigger(Trigger):
|
||||
>>> def run(self, result: Result, packages: Iterable[Package]) -> None:
|
||||
>>> def on_result(self, result: Result, packages: Iterable[Package]) -> None:
|
||||
>>> perform_some_action()
|
||||
|
||||
Having this class you can pass it to ``configuration`` and it will be run on action::
|
||||
@ -48,7 +48,7 @@ class Trigger(LazyLogging):
|
||||
>>> configuration.set_option("build", "triggers", "my.awesome.package.CustomTrigger")
|
||||
>>>
|
||||
>>> loader = TriggerLoader("x86_64", configuration)
|
||||
>>> loader.process(Result(), [])
|
||||
>>> loader.on_result(Result(), [])
|
||||
"""
|
||||
|
||||
def __init__(self, architecture: str, configuration: Configuration) -> None:
|
||||
@ -62,15 +62,23 @@ class Trigger(LazyLogging):
|
||||
self.architecture = architecture
|
||||
self.configuration = configuration
|
||||
|
||||
def run(self, result: Result, packages: Iterable[Package]) -> None:
|
||||
def on_result(self, result: Result, packages: Iterable[Package]) -> None:
|
||||
"""
|
||||
run trigger
|
||||
trigger action which will be called after build process with process result
|
||||
|
||||
Args:
|
||||
result(Result): build result
|
||||
packages(Iterable[Package]): list of all available packages
|
||||
|
||||
Raises:
|
||||
NotImplementedError: not implemented method
|
||||
"""
|
||||
raise NotImplementedError
|
||||
if (run := getattr(self, "run", None)) is not None:
|
||||
run(result, packages) # compatibility with old triggers
|
||||
|
||||
def on_start(self) -> None:
|
||||
"""
|
||||
trigger action which will be called at the start of the application
|
||||
"""
|
||||
|
||||
def on_stop(self) -> None:
|
||||
"""
|
||||
trigger action which will be called before the stop of the application
|
||||
"""
|
||||
|
@ -17,12 +17,13 @@
|
||||
# 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 contextlib
|
||||
import importlib
|
||||
import os
|
||||
|
||||
from pathlib import Path
|
||||
from types import ModuleType
|
||||
from typing import Iterable
|
||||
from typing import Generator, Iterable
|
||||
|
||||
from ahriman.core.configuration import Configuration
|
||||
from ahriman.core.exceptions import InvalidExtension
|
||||
@ -54,7 +55,7 @@ class TriggerLoader(LazyLogging):
|
||||
|
||||
After that you are free to run triggers::
|
||||
|
||||
>>> loader.process(Result(), [])
|
||||
>>> loader.on_result(Result(), [])
|
||||
"""
|
||||
|
||||
def __init__(self, architecture: str, configuration: Configuration) -> None:
|
||||
@ -72,6 +73,31 @@ class TriggerLoader(LazyLogging):
|
||||
self.load_trigger(trigger)
|
||||
for trigger in configuration.getlist("build", "triggers")
|
||||
]
|
||||
self._on_stop_requested = False
|
||||
|
||||
def __del__(self) -> None:
|
||||
"""
|
||||
custom destructor object which calls on_stop in case if it was requested
|
||||
"""
|
||||
if not self._on_stop_requested:
|
||||
return
|
||||
self.on_stop()
|
||||
|
||||
@contextlib.contextmanager
|
||||
def __execute_trigger(self, trigger: Trigger) -> Generator[None, None, None]:
|
||||
"""
|
||||
decorator for calling triggers
|
||||
|
||||
Args:
|
||||
trigger(Trigger): trigger instance to be called
|
||||
"""
|
||||
trigger_name = type(trigger).__name__
|
||||
|
||||
try:
|
||||
self.logger.info("executing extension %s", trigger_name)
|
||||
yield
|
||||
except Exception:
|
||||
self.logger.exception("got exception while run trigger %s", trigger_name)
|
||||
|
||||
def _load_module_from_file(self, module_path: str, implementation: str) -> ModuleType:
|
||||
"""
|
||||
@ -149,18 +175,34 @@ class TriggerLoader(LazyLogging):
|
||||
|
||||
return trigger
|
||||
|
||||
def process(self, result: Result, packages: Iterable[Package]) -> None:
|
||||
def on_result(self, result: Result, packages: Iterable[Package]) -> None:
|
||||
"""
|
||||
run remote sync
|
||||
run trigger with result of application run
|
||||
|
||||
Args:
|
||||
result(Result): build result
|
||||
packages(Iterable[Package]): list of all available packages
|
||||
"""
|
||||
self.logger.debug("executing triggers on result")
|
||||
for trigger in self.triggers:
|
||||
trigger_name = type(trigger).__name__
|
||||
try:
|
||||
self.logger.info("executing extension %s", trigger_name)
|
||||
trigger.run(result, packages)
|
||||
except Exception:
|
||||
self.logger.exception("got exception while run trigger %s", trigger_name)
|
||||
with self.__execute_trigger(trigger):
|
||||
trigger.on_result(result, packages)
|
||||
|
||||
def on_start(self) -> None:
|
||||
"""
|
||||
run triggers on load
|
||||
"""
|
||||
self.logger.debug("executing triggers on start")
|
||||
self._on_stop_requested = True
|
||||
for trigger in self.triggers:
|
||||
with self.__execute_trigger(trigger):
|
||||
trigger.on_start()
|
||||
|
||||
def on_stop(self) -> None:
|
||||
"""
|
||||
run triggers before the application exit
|
||||
"""
|
||||
self.logger.debug("executing triggers on stop")
|
||||
for trigger in self.triggers:
|
||||
with self.__execute_trigger(trigger):
|
||||
trigger.on_stop()
|
||||
|
@ -17,5 +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.upload.upload import Upload
|
||||
from ahriman.core.upload.upload_trigger import UploadTrigger
|
||||
|
@ -24,7 +24,7 @@ from pathlib import Path
|
||||
from typing import Any, Dict
|
||||
|
||||
from ahriman.core.configuration import Configuration
|
||||
from ahriman.core.upload import Upload
|
||||
from ahriman.core.upload.upload import Upload
|
||||
from ahriman.core.util import exception_response_text
|
||||
|
||||
|
||||
@ -33,7 +33,7 @@ class HttpUpload(Upload):
|
||||
helper for the http based uploads
|
||||
|
||||
Attributes:
|
||||
auth(Tuple[str, str]): HTTP auth object
|
||||
auth(Optional[Tuple[str, str]]): HTTP auth object if set
|
||||
timeout(int): HTTP request timeout in seconds
|
||||
"""
|
||||
|
||||
@ -47,9 +47,9 @@ class HttpUpload(Upload):
|
||||
section(str): configuration section name
|
||||
"""
|
||||
Upload.__init__(self, architecture, configuration)
|
||||
password = configuration.get(section, "password")
|
||||
username = configuration.get(section, "username")
|
||||
self.auth = (password, username)
|
||||
password = configuration.get(section, "password", fallback=None)
|
||||
username = configuration.get(section, "username", fallback=None)
|
||||
self.auth = (password, username) if password and username else None
|
||||
self.timeout = configuration.getint(section, "timeout", fallback=30)
|
||||
|
||||
@staticmethod
|
||||
|
@ -21,7 +21,7 @@ from pathlib import Path
|
||||
from typing import Iterable
|
||||
|
||||
from ahriman.core.configuration import Configuration
|
||||
from ahriman.core.upload import Upload
|
||||
from ahriman.core.upload.upload import Upload
|
||||
from ahriman.core.util import check_output
|
||||
from ahriman.models.package import Package
|
||||
|
||||
|
@ -25,7 +25,7 @@ from pathlib import Path
|
||||
from typing import Any, Dict, Iterable
|
||||
|
||||
from ahriman.core.configuration import Configuration
|
||||
from ahriman.core.upload import Upload
|
||||
from ahriman.core.upload.upload import Upload
|
||||
from ahriman.core.util import walk
|
||||
from ahriman.models.package import Package
|
||||
|
||||
|
@ -21,7 +21,7 @@ from typing import Iterable
|
||||
|
||||
from ahriman.core.configuration import Configuration
|
||||
from ahriman.core.triggers import Trigger
|
||||
from ahriman.core.upload import Upload
|
||||
from ahriman.core.upload.upload import Upload
|
||||
from ahriman.models.package import Package
|
||||
from ahriman.models.result import Result
|
||||
|
||||
@ -43,9 +43,9 @@ class UploadTrigger(Trigger):
|
||||
configuration(Configuration): configuration instance
|
||||
"""
|
||||
Trigger.__init__(self, architecture, configuration)
|
||||
self.targets = configuration.getlist("upload", "target")
|
||||
self.targets = configuration.getlist("upload", "target", fallback=[])
|
||||
|
||||
def run(self, result: Result, packages: Iterable[Package]) -> None:
|
||||
def on_result(self, result: Result, packages: Iterable[Package]) -> None:
|
||||
"""
|
||||
run trigger
|
||||
|
||||
|
@ -305,7 +305,7 @@ class Package(LazyLogging):
|
||||
|
||||
from ahriman.core.build_tools.sources import Sources
|
||||
|
||||
Sources.load(paths.cache_for(self.base), self, None, paths)
|
||||
Sources.load(paths.cache_for(self.base), self, [], paths)
|
||||
|
||||
try:
|
||||
# update pkgver first
|
||||
|
@ -21,7 +21,7 @@ import shlex
|
||||
|
||||
from dataclasses import dataclass, field
|
||||
from pathlib import Path
|
||||
from typing import List, Union
|
||||
from typing import List, Optional, Union
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
@ -30,16 +30,23 @@ class PkgbuildPatch:
|
||||
wrapper for patching PKBGUILDs
|
||||
|
||||
Attributes:
|
||||
key(str): name of the property in PKGBUILD, e.g. version, url etc
|
||||
key(Optional[str]): name of the property in PKGBUILD, e.g. version, url etc. If not set, patch will be
|
||||
considered as full PKGBUILD diffs
|
||||
value(Union[str, List[str]]): value of the stored PKGBUILD property. It must be either string or list of string
|
||||
values
|
||||
unsafe(bool): if set, value will be not quoted, might break PKGBUILD
|
||||
"""
|
||||
|
||||
key: str
|
||||
key: Optional[str]
|
||||
value: Union[str, List[str]]
|
||||
unsafe: bool = field(default=False, kw_only=True)
|
||||
|
||||
def __post_init__(self) -> None:
|
||||
"""
|
||||
remove empty key
|
||||
"""
|
||||
object.__setattr__(self, "key", self.key or None)
|
||||
|
||||
@property
|
||||
def is_function(self) -> bool:
|
||||
"""
|
||||
@ -48,7 +55,17 @@ class PkgbuildPatch:
|
||||
Returns:
|
||||
bool: True in case if key ends with parentheses and False otherwise
|
||||
"""
|
||||
return self.key.endswith("()")
|
||||
return self.key is not None and self.key.endswith("()")
|
||||
|
||||
@property
|
||||
def is_plain_diff(self) -> bool:
|
||||
"""
|
||||
check if patch is full diff one or just single-variable patch
|
||||
|
||||
Returns:
|
||||
bool: True in case key set and False otherwise
|
||||
"""
|
||||
return self.key is None
|
||||
|
||||
def quote(self, value: str) -> str:
|
||||
"""
|
||||
@ -74,6 +91,8 @@ class PkgbuildPatch:
|
||||
if isinstance(self.value, list): # list like
|
||||
value = " ".join(map(self.quote, self.value))
|
||||
return f"""{self.key}=({value})"""
|
||||
if self.is_plain_diff: # no additional logic for plain diffs
|
||||
return self.value
|
||||
# we suppose that function values are only supported in string-like values
|
||||
if self.is_function:
|
||||
return f"{self.key} {self.value}" # no quoting enabled here
|
||||
|
@ -17,7 +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 aiohttp.web import HTTPBadRequest, HTTPFound
|
||||
from aiohttp.web import HTTPFound
|
||||
|
||||
from ahriman.models.user_access import UserAccess
|
||||
from ahriman.web.views.base import BaseView
|
||||
@ -47,11 +47,8 @@ class AddView(BaseView):
|
||||
HTTPBadRequest: if bad data is supplied
|
||||
HTTPFound: in case of success response
|
||||
"""
|
||||
try:
|
||||
data = await self.extract_data(["packages"])
|
||||
packages = data["packages"]
|
||||
except Exception as e:
|
||||
raise HTTPBadRequest(reason=str(e))
|
||||
data = await self.extract_data(["packages"])
|
||||
packages = data.get("packages", [])
|
||||
|
||||
self.spawner.packages_add(packages, now=True)
|
||||
|
||||
|
@ -5,16 +5,6 @@ from ahriman.models.package import Package
|
||||
from ahriman.models.result import Result
|
||||
|
||||
|
||||
def test_finalize(application: Application, mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must report and sync at the last
|
||||
"""
|
||||
triggers_mock = mocker.patch("ahriman.core.repository.Repository.process_triggers")
|
||||
|
||||
application._finalize(Result())
|
||||
triggers_mock.assert_called_once_with(Result())
|
||||
|
||||
|
||||
def test_known_packages(application: Application, package_ahriman: Package, mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must return not empty list of known packages
|
||||
@ -23,3 +13,34 @@ def test_known_packages(application: Application, package_ahriman: Package, mock
|
||||
packages = application._known_packages()
|
||||
assert len(packages) > 1
|
||||
assert package_ahriman.base in packages
|
||||
|
||||
|
||||
def test_on_result(application: Application, package_ahriman: Package, mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must call on_result trigger function
|
||||
"""
|
||||
mocker.patch("ahriman.core.repository.Repository.packages", return_value=[package_ahriman])
|
||||
triggers_mock = mocker.patch("ahriman.core.triggers.TriggerLoader.on_result")
|
||||
|
||||
application.on_result(Result())
|
||||
triggers_mock.assert_called_once_with(Result(), [package_ahriman])
|
||||
|
||||
|
||||
def test_on_start(application: Application, mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must call on_start trigger function
|
||||
"""
|
||||
triggers_mock = mocker.patch("ahriman.core.triggers.TriggerLoader.on_start")
|
||||
|
||||
application.on_start()
|
||||
triggers_mock.assert_called_once_with()
|
||||
|
||||
|
||||
def test_on_stop(application: Application, mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must call on_stop trigger function
|
||||
"""
|
||||
triggers_mock = mocker.patch("ahriman.core.triggers.TriggerLoader.on_stop")
|
||||
|
||||
application.on_stop()
|
||||
triggers_mock.assert_called_once_with()
|
||||
|
@ -11,12 +11,12 @@ from ahriman.models.package_source import PackageSource
|
||||
from ahriman.models.result import Result
|
||||
|
||||
|
||||
def test_finalize(application_packages: ApplicationPackages) -> None:
|
||||
def test_on_result(application_packages: ApplicationPackages) -> None:
|
||||
"""
|
||||
must raise NotImplemented for missing finalize method
|
||||
"""
|
||||
with pytest.raises(NotImplementedError):
|
||||
application_packages._finalize([])
|
||||
application_packages.on_result(Result())
|
||||
|
||||
|
||||
def test_known_packages(application_packages: ApplicationPackages) -> None:
|
||||
@ -242,8 +242,8 @@ def test_remove(application_packages: ApplicationPackages, mocker: MockerFixture
|
||||
must remove package
|
||||
"""
|
||||
executor_mock = mocker.patch("ahriman.core.repository.executor.Executor.process_remove")
|
||||
finalize_mock = mocker.patch("ahriman.application.application.application_packages.ApplicationPackages._finalize")
|
||||
on_result_mock = mocker.patch("ahriman.application.application.application_packages.ApplicationPackages.on_result")
|
||||
|
||||
application_packages.remove([])
|
||||
executor_mock.assert_called_once_with([])
|
||||
finalize_mock.assert_called_once_with(Result())
|
||||
on_result_mock.assert_called_once_with(Result())
|
||||
|
@ -9,12 +9,12 @@ from ahriman.models.package import Package
|
||||
from ahriman.models.result import Result
|
||||
|
||||
|
||||
def test_finalize(application_repository: ApplicationRepository) -> None:
|
||||
def test_on_result(application_repository: ApplicationRepository) -> None:
|
||||
"""
|
||||
must raise NotImplemented for missing finalize method
|
||||
"""
|
||||
with pytest.raises(NotImplementedError):
|
||||
application_repository._finalize(Result())
|
||||
application_repository.on_result(Result())
|
||||
|
||||
|
||||
def test_clean_cache(application_repository: ApplicationRepository, mocker: MockerFixture) -> None:
|
||||
@ -63,8 +63,8 @@ def test_sign(application_repository: ApplicationRepository, package_ahriman: Pa
|
||||
copy_mock = mocker.patch("shutil.copy")
|
||||
update_mock = mocker.patch("ahriman.application.application.application_repository.ApplicationRepository.update")
|
||||
sign_repository_mock = mocker.patch("ahriman.core.sign.gpg.GPG.process_sign_repository")
|
||||
finalize_mock = mocker.patch(
|
||||
"ahriman.application.application.application_repository.ApplicationRepository._finalize")
|
||||
on_result_mock = mocker.patch(
|
||||
"ahriman.application.application.application_repository.ApplicationRepository.on_result")
|
||||
|
||||
application_repository.sign([])
|
||||
copy_mock.assert_has_calls([
|
||||
@ -73,7 +73,7 @@ def test_sign(application_repository: ApplicationRepository, package_ahriman: Pa
|
||||
])
|
||||
update_mock.assert_called_once_with([])
|
||||
sign_repository_mock.assert_called_once_with(application_repository.repository.repo.repo_path)
|
||||
finalize_mock.assert_called_once_with(Result())
|
||||
on_result_mock.assert_called_once_with(Result())
|
||||
|
||||
|
||||
def test_sign_skip(application_repository: ApplicationRepository, package_ahriman: Package,
|
||||
@ -84,7 +84,7 @@ def test_sign_skip(application_repository: ApplicationRepository, package_ahrima
|
||||
package_ahriman.packages[package_ahriman.base].filename = None
|
||||
mocker.patch("ahriman.core.repository.repository.Repository.packages", return_value=[package_ahriman])
|
||||
mocker.patch("ahriman.application.application.application_repository.ApplicationRepository.update")
|
||||
mocker.patch("ahriman.application.application.application_repository.ApplicationRepository._finalize")
|
||||
mocker.patch("ahriman.application.application.application_repository.ApplicationRepository.on_result")
|
||||
|
||||
application_repository.sign([])
|
||||
|
||||
@ -99,8 +99,8 @@ def test_sign_specific(application_repository: ApplicationRepository, package_ah
|
||||
copy_mock = mocker.patch("shutil.copy")
|
||||
update_mock = mocker.patch("ahriman.application.application.application_repository.ApplicationRepository.update")
|
||||
sign_repository_mock = mocker.patch("ahriman.core.sign.gpg.GPG.process_sign_repository")
|
||||
finalize_mock = mocker.patch(
|
||||
"ahriman.application.application.application_repository.ApplicationRepository._finalize")
|
||||
on_result_mock = mocker.patch(
|
||||
"ahriman.application.application.application_repository.ApplicationRepository.on_result")
|
||||
|
||||
filename = package_ahriman.packages[package_ahriman.base].filepath
|
||||
application_repository.sign([package_ahriman.base])
|
||||
@ -109,7 +109,7 @@ def test_sign_specific(application_repository: ApplicationRepository, package_ah
|
||||
application_repository.repository.paths.packages / filename.name)
|
||||
update_mock.assert_called_once_with([])
|
||||
sign_repository_mock.assert_called_once_with(application_repository.repository.repo.repo_path)
|
||||
finalize_mock.assert_called_once_with(Result())
|
||||
on_result_mock.assert_called_once_with(Result())
|
||||
|
||||
|
||||
def test_unknown_no_aur(application_repository: ApplicationRepository, package_ahriman: Package,
|
||||
@ -163,13 +163,13 @@ def test_update(application_repository: ApplicationRepository, package_ahriman:
|
||||
mocker.patch("ahriman.core.repository.repository.Repository.packages_built", return_value=paths)
|
||||
build_mock = mocker.patch("ahriman.core.repository.executor.Executor.process_build", return_value=result)
|
||||
update_mock = mocker.patch("ahriman.core.repository.executor.Executor.process_update", return_value=result)
|
||||
finalize_mock = mocker.patch(
|
||||
"ahriman.application.application.application_repository.ApplicationRepository._finalize")
|
||||
on_result_mock = mocker.patch(
|
||||
"ahriman.application.application.application_repository.ApplicationRepository.on_result")
|
||||
|
||||
application_repository.update([package_ahriman])
|
||||
build_mock.assert_called_once_with([package_ahriman])
|
||||
update_mock.assert_has_calls([mock.call(paths), mock.call(paths)])
|
||||
finalize_mock.assert_has_calls([mock.call(result), mock.call(result)])
|
||||
on_result_mock.assert_has_calls([mock.call(result), mock.call(result)])
|
||||
|
||||
|
||||
def test_update_empty(application_repository: ApplicationRepository, package_ahriman: Package,
|
||||
|
@ -35,9 +35,11 @@ def test_run(args: argparse.Namespace, configuration: Configuration, mocker: Moc
|
||||
args = _default_args(args)
|
||||
mocker.patch("ahriman.models.repository_paths.RepositoryPaths.tree_create")
|
||||
application_mock = mocker.patch("ahriman.application.application.Application.add")
|
||||
on_start_mock = mocker.patch("ahriman.application.application.Application.on_start")
|
||||
|
||||
Add.run(args, "x86_64", configuration, True, False)
|
||||
application_mock.assert_called_once_with(args.package, args.source, args.without_dependencies)
|
||||
on_start_mock.assert_called_once_with()
|
||||
|
||||
|
||||
def test_run_with_updates(args: argparse.Namespace, configuration: Configuration,
|
||||
|
@ -30,6 +30,8 @@ def test_run(args: argparse.Namespace, configuration: Configuration, mocker: Moc
|
||||
args = _default_args(args)
|
||||
mocker.patch("ahriman.models.repository_paths.RepositoryPaths.tree_create")
|
||||
application_mock = mocker.patch("ahriman.application.application.Application.clean")
|
||||
on_start_mock = mocker.patch("ahriman.application.application.Application.on_start")
|
||||
|
||||
Clean.run(args, "x86_64", configuration, True, False)
|
||||
application_mock.assert_called_once_with(False, False, False, False)
|
||||
on_start_mock.assert_called_once_with()
|
||||
|
40
tests/ahriman/application/handlers/test_handler_daemon.py
Normal file
40
tests/ahriman/application/handlers/test_handler_daemon.py
Normal file
@ -0,0 +1,40 @@
|
||||
import argparse
|
||||
|
||||
from pytest_mock import MockerFixture
|
||||
|
||||
from ahriman.application.handlers import Daemon
|
||||
from ahriman.core.configuration import Configuration
|
||||
|
||||
|
||||
def _default_args(args: argparse.Namespace) -> argparse.Namespace:
|
||||
"""
|
||||
default arguments for these test cases
|
||||
|
||||
Args:
|
||||
args(argparse.Namespace): command line arguments fixture
|
||||
|
||||
Returns:
|
||||
argparse.Namespace: generated arguments for these test cases
|
||||
"""
|
||||
args.interval = 60 * 60 * 12
|
||||
args.no_aur = False
|
||||
args.no_local = False
|
||||
args.no_manual = False
|
||||
args.no_vcs = False
|
||||
return args
|
||||
|
||||
|
||||
def test_run(args: argparse.Namespace, configuration: Configuration, mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must run command
|
||||
"""
|
||||
args = _default_args(args)
|
||||
run_mock = mocker.patch("ahriman.application.handlers.Update.run")
|
||||
start_mock = mocker.patch("threading.Timer.start")
|
||||
join_mock = mocker.patch("threading.Timer.join")
|
||||
|
||||
Daemon.run(args, "x86_64", configuration, True, False)
|
||||
Daemon._SHOULD_RUN = False
|
||||
run_mock.assert_called_once_with(args, "x86_64", configuration, True, False)
|
||||
start_mock.assert_called_once_with()
|
||||
join_mock.assert_called_once_with()
|
@ -1,5 +1,6 @@
|
||||
import argparse
|
||||
import pytest
|
||||
import sys
|
||||
|
||||
from pathlib import Path
|
||||
from pytest_mock import MockerFixture
|
||||
@ -9,6 +10,7 @@ from ahriman.application.handlers import Patch
|
||||
from ahriman.core.configuration import Configuration
|
||||
from ahriman.models.action import Action
|
||||
from ahriman.models.package import Package
|
||||
from ahriman.models.pkgbuild_patch import PkgbuildPatch
|
||||
|
||||
|
||||
def _default_args(args: argparse.Namespace) -> argparse.Namespace:
|
||||
@ -25,6 +27,7 @@ def _default_args(args: argparse.Namespace) -> argparse.Namespace:
|
||||
args.exit_code = False
|
||||
args.remove = False
|
||||
args.track = ["*.diff", "*.patch"]
|
||||
args.variable = None
|
||||
return args
|
||||
|
||||
|
||||
@ -35,10 +38,31 @@ def test_run(args: argparse.Namespace, configuration: Configuration, mocker: Moc
|
||||
args = _default_args(args)
|
||||
args.action = Action.Update
|
||||
mocker.patch("ahriman.models.repository_paths.RepositoryPaths.tree_create")
|
||||
patch_mock = mocker.patch("ahriman.application.handlers.Patch.patch_create_from_diff",
|
||||
return_value=(args.package, PkgbuildPatch(None, "patch")))
|
||||
application_mock = mocker.patch("ahriman.application.handlers.Patch.patch_set_create")
|
||||
|
||||
Patch.run(args, "x86_64", configuration, True, False)
|
||||
application_mock.assert_called_once_with(pytest.helpers.anyvar(int), Path(args.package), args.track)
|
||||
patch_mock.assert_called_once_with(args.package, args.track)
|
||||
application_mock.assert_called_once_with(pytest.helpers.anyvar(int), args.package, PkgbuildPatch(None, "patch"))
|
||||
|
||||
|
||||
def test_run_function(args: argparse.Namespace, configuration: Configuration, mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must run command with patch function flag
|
||||
"""
|
||||
args = _default_args(args)
|
||||
args.action = Action.Update
|
||||
args.patch = "patch"
|
||||
args.variable = "version"
|
||||
patch = PkgbuildPatch(args.variable, args.patch)
|
||||
mocker.patch("ahriman.models.repository_paths.RepositoryPaths.tree_create")
|
||||
patch_mock = mocker.patch("ahriman.application.handlers.Patch.patch_create_from_function", return_value=patch)
|
||||
application_mock = mocker.patch("ahriman.application.handlers.Patch.patch_set_create")
|
||||
|
||||
Patch.run(args, "x86_64", configuration, True, False)
|
||||
patch_mock.assert_called_once_with(args.variable, args.patch)
|
||||
application_mock.assert_called_once_with(pytest.helpers.anyvar(int), args.package, patch)
|
||||
|
||||
|
||||
def test_run_list(args: argparse.Namespace, configuration: Configuration, mocker: MockerFixture) -> None:
|
||||
@ -47,11 +71,12 @@ def test_run_list(args: argparse.Namespace, configuration: Configuration, mocker
|
||||
"""
|
||||
args = _default_args(args)
|
||||
args.action = Action.List
|
||||
args.variable = ["version"]
|
||||
mocker.patch("ahriman.models.repository_paths.RepositoryPaths.tree_create")
|
||||
application_mock = mocker.patch("ahriman.application.handlers.Patch.patch_set_list")
|
||||
|
||||
Patch.run(args, "x86_64", configuration, True, False)
|
||||
application_mock.assert_called_once_with(pytest.helpers.anyvar(int), args.package, False)
|
||||
application_mock.assert_called_once_with(pytest.helpers.anyvar(int), args.package, ["version"], False)
|
||||
|
||||
|
||||
def test_run_remove(args: argparse.Namespace, configuration: Configuration, mocker: MockerFixture) -> None:
|
||||
@ -60,24 +85,71 @@ def test_run_remove(args: argparse.Namespace, configuration: Configuration, mock
|
||||
"""
|
||||
args = _default_args(args)
|
||||
args.action = Action.Remove
|
||||
args.variable = ["version"]
|
||||
mocker.patch("ahriman.models.repository_paths.RepositoryPaths.tree_create")
|
||||
application_mock = mocker.patch("ahriman.application.handlers.Patch.patch_set_remove")
|
||||
|
||||
Patch.run(args, "x86_64", configuration, True, False)
|
||||
application_mock.assert_called_once_with(pytest.helpers.anyvar(int), args.package)
|
||||
application_mock.assert_called_once_with(pytest.helpers.anyvar(int), args.package, ["version"])
|
||||
|
||||
|
||||
def test_patch_create_from_diff(package_ahriman: Package, mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must create patch from directory tree diff
|
||||
"""
|
||||
patch = PkgbuildPatch(None, "patch")
|
||||
path = Path("local")
|
||||
mocker.patch("pathlib.Path.mkdir")
|
||||
package_mock = mocker.patch("ahriman.models.package.Package.from_build", return_value=package_ahriman)
|
||||
sources_mock = mocker.patch("ahriman.core.build_tools.sources.Sources.patch_create", return_value=patch.value)
|
||||
|
||||
assert Patch.patch_create_from_diff(path, ["*.diff"]) == (package_ahriman.base, patch)
|
||||
package_mock.assert_called_once_with(path)
|
||||
sources_mock.assert_called_once_with(path, "*.diff")
|
||||
|
||||
|
||||
def test_patch_create_from_function(mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must create function patch from file
|
||||
"""
|
||||
path = Path("local")
|
||||
patch = PkgbuildPatch("version", "patch")
|
||||
read_mock = mocker.patch("pathlib.Path.read_text", return_value=patch.value)
|
||||
|
||||
assert Patch.patch_create_from_function(patch.key, path) == patch
|
||||
read_mock.assert_called_once_with(encoding="utf8")
|
||||
|
||||
|
||||
def test_patch_create_from_function_stdin(mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must create function patch from stdin
|
||||
"""
|
||||
patch = PkgbuildPatch("version", "This is a patch")
|
||||
mocker.patch.object(sys, "stdin", patch.value.splitlines())
|
||||
assert Patch.patch_create_from_function(patch.key, None) == patch
|
||||
|
||||
|
||||
def test_patch_create_from_function_strip(mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must remove spaces at the beginning and at the end of the line
|
||||
"""
|
||||
patch = PkgbuildPatch("version", "This is a patch")
|
||||
mocker.patch.object(sys, "stdin", ["\n"] + patch.value.splitlines() + ["\n"])
|
||||
assert Patch.patch_create_from_function(patch.key, None) == patch
|
||||
|
||||
|
||||
def test_patch_set_list(application: Application, mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must list available patches for the command
|
||||
"""
|
||||
get_mock = mocker.patch("ahriman.core.database.SQLite.patches_list", return_value={"ahriman": "patch"})
|
||||
get_mock = mocker.patch("ahriman.core.database.SQLite.patches_list",
|
||||
return_value={"ahriman": PkgbuildPatch(None, "patch")})
|
||||
print_mock = mocker.patch("ahriman.core.formatters.Printer.print")
|
||||
check_mock = mocker.patch("ahriman.application.handlers.Handler.check_if_empty")
|
||||
|
||||
Patch.patch_set_list(application, "ahriman", False)
|
||||
get_mock.assert_called_once_with("ahriman")
|
||||
print_mock.assert_called_once_with(verbose=True)
|
||||
Patch.patch_set_list(application, "ahriman", ["version"], False)
|
||||
get_mock.assert_called_once_with("ahriman", ["version"])
|
||||
print_mock.assert_called_once_with(verbose=True, separator=" = ")
|
||||
check_mock.assert_called_once_with(False, False)
|
||||
|
||||
|
||||
@ -88,7 +160,7 @@ def test_patch_set_list_empty_exception(application: Application, mocker: Mocker
|
||||
mocker.patch("ahriman.core.database.SQLite.patches_list", return_value={})
|
||||
check_mock = mocker.patch("ahriman.application.handlers.Handler.check_if_empty")
|
||||
|
||||
Patch.patch_set_list(application, "ahriman", True)
|
||||
Patch.patch_set_list(application, "ahriman", [], True)
|
||||
check_mock.assert_called_once_with(True, True)
|
||||
|
||||
|
||||
@ -96,13 +168,9 @@ def test_patch_set_create(application: Application, package_ahriman: Package, mo
|
||||
"""
|
||||
must create patch set for the package
|
||||
"""
|
||||
mocker.patch("pathlib.Path.mkdir")
|
||||
mocker.patch("ahriman.models.package.Package.from_build", return_value=package_ahriman)
|
||||
mocker.patch("ahriman.core.build_tools.sources.Sources.patch_create", return_value="patch")
|
||||
create_mock = mocker.patch("ahriman.core.database.SQLite.patches_insert")
|
||||
|
||||
Patch.patch_set_create(application, Path("path"), ["*.patch"])
|
||||
create_mock.assert_called_once_with(package_ahriman.base, "patch")
|
||||
Patch.patch_set_create(application, package_ahriman.base, PkgbuildPatch("version", package_ahriman.version))
|
||||
create_mock.assert_called_once_with(package_ahriman.base, PkgbuildPatch("version", package_ahriman.version))
|
||||
|
||||
|
||||
def test_patch_set_remove(application: Application, package_ahriman: Package, mocker: MockerFixture) -> None:
|
||||
@ -110,5 +178,5 @@ def test_patch_set_remove(application: Application, package_ahriman: Package, mo
|
||||
must remove patch set for the package
|
||||
"""
|
||||
remove_mock = mocker.patch("ahriman.core.database.SQLite.patches_remove")
|
||||
Patch.patch_set_remove(application, package_ahriman.base)
|
||||
remove_mock.assert_called_once_with(package_ahriman.base)
|
||||
Patch.patch_set_remove(application, package_ahriman.base, ["version"])
|
||||
remove_mock.assert_called_once_with(package_ahriman.base, ["version"])
|
||||
|
@ -41,11 +41,13 @@ def test_run(args: argparse.Namespace, package_ahriman: Package,
|
||||
return_value=[package_ahriman])
|
||||
application_mock = mocker.patch("ahriman.application.application.Application.update", return_value=result)
|
||||
check_mock = mocker.patch("ahriman.application.handlers.Handler.check_if_empty")
|
||||
on_start_mock = mocker.patch("ahriman.application.application.Application.on_start")
|
||||
|
||||
Rebuild.run(args, "x86_64", configuration, True, False)
|
||||
application_packages_mock.assert_called_once_with(None)
|
||||
application_mock.assert_called_once_with([package_ahriman])
|
||||
check_mock.assert_has_calls([mock.call(False, False), mock.call(False, False)])
|
||||
on_start_mock.assert_called_once_with()
|
||||
|
||||
|
||||
def test_run_extract_packages(args: argparse.Namespace, configuration: Configuration, mocker: MockerFixture) -> None:
|
||||
|
@ -27,6 +27,8 @@ def test_run(args: argparse.Namespace, configuration: Configuration, mocker: Moc
|
||||
args = _default_args(args)
|
||||
mocker.patch("ahriman.models.repository_paths.RepositoryPaths.tree_create")
|
||||
application_mock = mocker.patch("ahriman.application.application.Application.remove")
|
||||
on_start_mock = mocker.patch("ahriman.application.application.Application.on_start")
|
||||
|
||||
Remove.run(args, "x86_64", configuration, True, False)
|
||||
application_mock.assert_called_once_with([])
|
||||
on_start_mock.assert_called_once_with()
|
||||
|
@ -32,10 +32,12 @@ def test_run(args: argparse.Namespace, package_ahriman: Package,
|
||||
application_mock = mocker.patch("ahriman.application.application.Application.unknown",
|
||||
return_value=[package_ahriman])
|
||||
remove_mock = mocker.patch("ahriman.application.application.Application.remove")
|
||||
on_start_mock = mocker.patch("ahriman.application.application.Application.on_start")
|
||||
|
||||
RemoveUnknown.run(args, "x86_64", configuration, True, False)
|
||||
application_mock.assert_called_once_with()
|
||||
remove_mock.assert_called_once_with([package_ahriman])
|
||||
on_start_mock.assert_called_once_with()
|
||||
|
||||
|
||||
def test_run_dry_run(args: argparse.Namespace, configuration: Configuration, package_ahriman: Package,
|
||||
|
@ -28,10 +28,12 @@ def test_run(args: argparse.Namespace, configuration: Configuration, mocker: Moc
|
||||
"""
|
||||
args = _default_args(args)
|
||||
mocker.patch("ahriman.models.repository_paths.RepositoryPaths.tree_create")
|
||||
application_mock = mocker.patch("ahriman.core.repository.Repository.process_triggers")
|
||||
application_mock = mocker.patch("ahriman.application.application.Application.on_result")
|
||||
on_start_mock = mocker.patch("ahriman.application.application.Application.on_start")
|
||||
|
||||
Triggers.run(args, "x86_64", configuration, True, False)
|
||||
application_mock.assert_called_once_with(Result())
|
||||
on_start_mock.assert_called_once_with()
|
||||
|
||||
|
||||
def test_run_trigger(args: argparse.Namespace, configuration: Configuration, package_ahriman: Package,
|
||||
@ -43,8 +45,8 @@ def test_run_trigger(args: argparse.Namespace, configuration: Configuration, pac
|
||||
args.trigger = ["ahriman.core.report.ReportTrigger"]
|
||||
mocker.patch("ahriman.core.repository.Repository.packages", return_value=[package_ahriman])
|
||||
mocker.patch("ahriman.models.repository_paths.RepositoryPaths.tree_create")
|
||||
report_mock = mocker.patch("ahriman.core.report.ReportTrigger.run")
|
||||
upload_mock = mocker.patch("ahriman.core.upload.UploadTrigger.run")
|
||||
report_mock = mocker.patch("ahriman.core.report.ReportTrigger.on_result")
|
||||
upload_mock = mocker.patch("ahriman.core.upload.UploadTrigger.on_result")
|
||||
|
||||
Triggers.run(args, "x86_64", configuration, True, False)
|
||||
report_mock.assert_called_once_with(Result(), [package_ahriman])
|
||||
|
@ -43,12 +43,14 @@ def test_run(args: argparse.Namespace, package_ahriman: Package,
|
||||
application_mock = mocker.patch("ahriman.application.application.Application.update", return_value=result)
|
||||
check_mock = mocker.patch("ahriman.application.handlers.Handler.check_if_empty")
|
||||
updates_mock = mocker.patch("ahriman.application.application.Application.updates", return_value=[package_ahriman])
|
||||
on_start_mock = mocker.patch("ahriman.application.application.Application.on_start")
|
||||
|
||||
Update.run(args, "x86_64", configuration, True, False)
|
||||
application_mock.assert_called_once_with([package_ahriman])
|
||||
updates_mock.assert_called_once_with(args.package, args.no_aur, args.no_local, args.no_manual, args.no_vcs,
|
||||
pytest.helpers.anyvar(int))
|
||||
check_mock.assert_has_calls([mock.call(False, False), mock.call(False, False)])
|
||||
on_start_mock.assert_called_once_with()
|
||||
|
||||
|
||||
def test_run_empty_exception(args: argparse.Namespace, configuration: Configuration, mocker: MockerFixture) -> None:
|
||||
|
@ -65,6 +65,26 @@ def test_subparsers_aur_search_architecture(parser: argparse.ArgumentParser) ->
|
||||
assert args.architecture == [""]
|
||||
|
||||
|
||||
def test_subparsers_daemon(parser: argparse.ArgumentParser) -> None:
|
||||
"""
|
||||
daemon command must imply dry run, exit code and package
|
||||
"""
|
||||
args = parser.parse_args(["daemon"])
|
||||
assert not args.dry_run
|
||||
assert not args.exit_code
|
||||
assert args.package == []
|
||||
|
||||
|
||||
def test_subparsers_daemon_option_interval(parser: argparse.ArgumentParser) -> None:
|
||||
"""
|
||||
daemon command must convert interval option to int instance
|
||||
"""
|
||||
args = parser.parse_args(["daemon"])
|
||||
assert isinstance(args.interval, int)
|
||||
args = parser.parse_args(["daemon", "--interval", "10"])
|
||||
assert isinstance(args.interval, int)
|
||||
|
||||
|
||||
def test_subparsers_help(parser: argparse.ArgumentParser) -> None:
|
||||
"""
|
||||
help command must imply architecture list, lock, no-report, quiet, unsafe and parser
|
||||
@ -197,7 +217,7 @@ def test_subparsers_patch_add(parser: argparse.ArgumentParser) -> None:
|
||||
"""
|
||||
patch-add command must imply action, architecture list, lock and no-report
|
||||
"""
|
||||
args = parser.parse_args(["patch-add", "ahriman"])
|
||||
args = parser.parse_args(["patch-add", "ahriman", "version"])
|
||||
assert args.action == Action.Update
|
||||
assert args.architecture == [""]
|
||||
assert args.lock is None
|
||||
@ -208,18 +228,10 @@ def test_subparsers_patch_add_architecture(parser: argparse.ArgumentParser) -> N
|
||||
"""
|
||||
patch-add command must correctly parse architecture list
|
||||
"""
|
||||
args = parser.parse_args(["-a", "x86_64", "patch-add", "ahriman"])
|
||||
args = parser.parse_args(["-a", "x86_64", "patch-add", "ahriman", "version"])
|
||||
assert args.architecture == [""]
|
||||
|
||||
|
||||
def test_subparsers_patch_add_track(parser: argparse.ArgumentParser) -> None:
|
||||
"""
|
||||
patch-add command must correctly parse track files patterns
|
||||
"""
|
||||
args = parser.parse_args(["patch-add", "-t", "*.py", "ahriman"])
|
||||
assert args.track == ["*.diff", "*.patch", "*.py"]
|
||||
|
||||
|
||||
def test_subparsers_patch_list(parser: argparse.ArgumentParser) -> None:
|
||||
"""
|
||||
patch-list command must imply action, architecture list, lock and no-report
|
||||
@ -258,6 +270,42 @@ def test_subparsers_patch_remove_architecture(parser: argparse.ArgumentParser) -
|
||||
assert args.architecture == [""]
|
||||
|
||||
|
||||
def test_subparsers_patch_set_add(parser: argparse.ArgumentParser) -> None:
|
||||
"""
|
||||
patch-set-add command must imply action, architecture list, lock, no-report and variable
|
||||
"""
|
||||
args = parser.parse_args(["patch-set-add", "ahriman"])
|
||||
assert args.action == Action.Update
|
||||
assert args.architecture == [""]
|
||||
assert args.lock is None
|
||||
assert args.no_report
|
||||
assert args.variable is None
|
||||
|
||||
|
||||
def test_subparsers_patch_set_add_architecture(parser: argparse.ArgumentParser) -> None:
|
||||
"""
|
||||
patch-set-add command must correctly parse architecture list
|
||||
"""
|
||||
args = parser.parse_args(["-a", "x86_64", "patch-set-add", "ahriman"])
|
||||
assert args.architecture == [""]
|
||||
|
||||
|
||||
def test_subparsers_patch_set_add_option_package(parser: argparse.ArgumentParser) -> None:
|
||||
"""
|
||||
patch-set-add command must convert package option to path instance
|
||||
"""
|
||||
args = parser.parse_args(["patch-set-add", "ahriman"])
|
||||
assert isinstance(args.package, Path)
|
||||
|
||||
|
||||
def test_subparsers_patch_set_add_option_track(parser: argparse.ArgumentParser) -> None:
|
||||
"""
|
||||
patch-set-add command must correctly parse track files patterns
|
||||
"""
|
||||
args = parser.parse_args(["patch-set-add", "-t", "*.py", "ahriman"])
|
||||
assert args.track == ["*.diff", "*.patch", "*.py"]
|
||||
|
||||
|
||||
def test_subparsers_repo_backup(parser: argparse.ArgumentParser) -> None:
|
||||
"""
|
||||
repo-backup command must imply architecture list, lock, no-report and unsafe
|
||||
|
@ -6,6 +6,7 @@ from unittest import mock
|
||||
|
||||
from ahriman.core.build_tools.sources import Sources
|
||||
from ahriman.models.package import Package
|
||||
from ahriman.models.pkgbuild_patch import PkgbuildPatch
|
||||
from ahriman.models.remote_source import RemoteSource
|
||||
from ahriman.models.repository_paths import RepositoryPaths
|
||||
|
||||
@ -16,11 +17,9 @@ def test_extend_architectures(mocker: MockerFixture) -> None:
|
||||
"""
|
||||
mocker.patch("pathlib.Path.is_file", return_value=True)
|
||||
archs_mock = mocker.patch("ahriman.models.package.Package.supported_architectures", return_value={"x86_64"})
|
||||
write_mock = mocker.patch("ahriman.models.pkgbuild_patch.PkgbuildPatch.write")
|
||||
|
||||
Sources.extend_architectures(Path("local"), "i686")
|
||||
assert Sources.extend_architectures(Path("local"), "i686") == [PkgbuildPatch("arch", list({"x86_64", "i686"}))]
|
||||
archs_mock.assert_called_once_with(Path("local"))
|
||||
write_mock.assert_called_once_with(Path("local") / "PKGBUILD")
|
||||
|
||||
|
||||
def test_extend_architectures_any(mocker: MockerFixture) -> None:
|
||||
@ -29,21 +28,7 @@ def test_extend_architectures_any(mocker: MockerFixture) -> None:
|
||||
"""
|
||||
mocker.patch("pathlib.Path.is_file", return_value=True)
|
||||
mocker.patch("ahriman.models.package.Package.supported_architectures", return_value={"any"})
|
||||
write_mock = mocker.patch("ahriman.models.pkgbuild_patch.PkgbuildPatch.write")
|
||||
|
||||
Sources.extend_architectures(Path("local"), "i686")
|
||||
write_mock.assert_not_called()
|
||||
|
||||
|
||||
def test_extend_architectures_skip(mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must skip extending list of the architectures in case if no PKGBUILD file found
|
||||
"""
|
||||
mocker.patch("pathlib.Path.is_file", return_value=False)
|
||||
write_mock = mocker.patch("ahriman.models.pkgbuild_patch.PkgbuildPatch.write")
|
||||
|
||||
Sources.extend_architectures(Path("local"), "i686")
|
||||
write_mock.assert_not_called()
|
||||
assert Sources.extend_architectures(Path("local"), "i686") == []
|
||||
|
||||
|
||||
def test_fetch_empty(remote_source: RemoteSource, mocker: MockerFixture) -> None:
|
||||
@ -167,15 +152,16 @@ def test_load(package_ahriman: Package, repository_paths: RepositoryPaths, mocke
|
||||
"""
|
||||
must load packages sources correctly
|
||||
"""
|
||||
mocker.patch("pathlib.Path.is_dir", return_value=False)
|
||||
patch = PkgbuildPatch(None, "patch")
|
||||
path = Path("local")
|
||||
fetch_mock = mocker.patch("ahriman.core.build_tools.sources.Sources.fetch")
|
||||
patch_mock = mocker.patch("ahriman.core.build_tools.sources.Sources.patch_apply")
|
||||
architectures_mock = mocker.patch("ahriman.core.build_tools.sources.Sources.extend_architectures")
|
||||
architectures_mock = mocker.patch("ahriman.core.build_tools.sources.Sources.extend_architectures", return_value=[])
|
||||
|
||||
Sources.load(Path("local"), package_ahriman, "patch", repository_paths)
|
||||
fetch_mock.assert_called_once_with(Path("local"), package_ahriman.remote)
|
||||
patch_mock.assert_called_once_with(Path("local"), "patch")
|
||||
architectures_mock.assert_called_once_with(Path("local"), repository_paths.architecture)
|
||||
Sources.load(path, package_ahriman, [patch], repository_paths)
|
||||
fetch_mock.assert_called_once_with(path, package_ahriman.remote)
|
||||
patch_mock.assert_called_once_with(path, patch)
|
||||
architectures_mock.assert_called_once_with(path, repository_paths.architecture)
|
||||
|
||||
|
||||
def test_load_no_patch(package_ahriman: Package, repository_paths: RepositoryPaths, mocker: MockerFixture) -> None:
|
||||
@ -184,9 +170,10 @@ def test_load_no_patch(package_ahriman: Package, repository_paths: RepositoryPat
|
||||
"""
|
||||
mocker.patch("pathlib.Path.is_dir", return_value=False)
|
||||
mocker.patch("ahriman.core.build_tools.sources.Sources.fetch")
|
||||
mocker.patch("ahriman.core.build_tools.sources.Sources.extend_architectures", return_value=[])
|
||||
patch_mock = mocker.patch("ahriman.core.build_tools.sources.Sources.patch_apply")
|
||||
|
||||
Sources.load(Path("local"), package_ahriman, None, repository_paths)
|
||||
Sources.load(Path("local"), package_ahriman, [], repository_paths)
|
||||
patch_mock.assert_not_called()
|
||||
|
||||
|
||||
@ -197,8 +184,9 @@ def test_load_with_cache(package_ahriman: Package, repository_paths: RepositoryP
|
||||
mocker.patch("pathlib.Path.is_dir", return_value=True)
|
||||
copytree_mock = mocker.patch("shutil.copytree")
|
||||
mocker.patch("ahriman.core.build_tools.sources.Sources.fetch")
|
||||
mocker.patch("ahriman.core.build_tools.sources.Sources.extend_architectures", return_value=[])
|
||||
|
||||
Sources.load(Path("local"), package_ahriman, None, repository_paths)
|
||||
Sources.load(Path("local"), package_ahriman, [], repository_paths)
|
||||
copytree_mock.assert_called_once() # we do not check full command here, sorry
|
||||
|
||||
|
||||
@ -223,6 +211,23 @@ def test_patch_create_with_newline(mocker: MockerFixture) -> None:
|
||||
assert Sources.patch_create(Path("local"), "glob").endswith("\n")
|
||||
|
||||
|
||||
def test_push(package_ahriman: Package, mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must correctly push files to remote repository
|
||||
"""
|
||||
add_mock = mocker.patch("ahriman.core.build_tools.sources.Sources.add")
|
||||
commit_mock = mocker.patch("ahriman.core.build_tools.sources.Sources.commit")
|
||||
check_output_mock = mocker.patch("ahriman.core.build_tools.sources.Sources._check_output")
|
||||
|
||||
local = Path("local")
|
||||
Sources.push(Path("local"), package_ahriman.remote, "glob")
|
||||
add_mock.assert_called_once_with(local, "glob")
|
||||
commit_mock.assert_called_once_with(local)
|
||||
check_output_mock.assert_called_once_with(
|
||||
"git", "push", package_ahriman.remote.git_url, package_ahriman.remote.branch,
|
||||
exception=None, cwd=local, logger=pytest.helpers.anyvar(int))
|
||||
|
||||
|
||||
def test_add(sources: Sources, mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must add files to git
|
||||
@ -249,6 +254,35 @@ def test_add_skip(sources: Sources, mocker: MockerFixture) -> None:
|
||||
check_output_mock.assert_not_called()
|
||||
|
||||
|
||||
def test_commit(sources: Sources, mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must commit changes
|
||||
"""
|
||||
check_output_mock = mocker.patch("ahriman.core.build_tools.sources.Sources._check_output")
|
||||
|
||||
local = Path("local")
|
||||
commit_message = "Commit message"
|
||||
sources.commit(local, commit_message=commit_message)
|
||||
check_output_mock.assert_called_once_with(
|
||||
"git", "commit", "--all", "--message", commit_message,
|
||||
exception=None, cwd=local, logger=pytest.helpers.anyvar(int)
|
||||
)
|
||||
|
||||
|
||||
def test_commit_autogenerated(sources: Sources, mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must commit changes with autogenerated commit message
|
||||
"""
|
||||
check_output_mock = mocker.patch("ahriman.core.build_tools.sources.Sources._check_output")
|
||||
|
||||
local = Path("local")
|
||||
sources.commit(Path("local"))
|
||||
check_output_mock.assert_called_once_with(
|
||||
"git", "commit", "--all", "--message", pytest.helpers.anyvar(str, strict=True),
|
||||
exception=None, cwd=local, logger=pytest.helpers.anyvar(int)
|
||||
)
|
||||
|
||||
|
||||
def test_diff(sources: Sources, mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must calculate diff
|
||||
@ -285,11 +319,24 @@ def test_patch_apply(sources: Sources, mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must apply patches if any
|
||||
"""
|
||||
patch = PkgbuildPatch(None, "patch")
|
||||
check_output_mock = mocker.patch("ahriman.core.build_tools.sources.Sources._check_output")
|
||||
|
||||
local = Path("local")
|
||||
sources.patch_apply(local, "patches")
|
||||
sources.patch_apply(local, patch)
|
||||
check_output_mock.assert_called_once_with(
|
||||
"git", "apply", "--ignore-space-change", "--ignore-whitespace",
|
||||
exception=None, cwd=local, input_data="patches", logger=pytest.helpers.anyvar(int)
|
||||
exception=None, cwd=local, input_data=patch.value, logger=pytest.helpers.anyvar(int)
|
||||
)
|
||||
|
||||
|
||||
def test_patch_apply_function(sources: Sources, mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must apply single-function patches
|
||||
"""
|
||||
patch = PkgbuildPatch("version", "42")
|
||||
local = Path("local")
|
||||
write_mock = mocker.patch("ahriman.models.pkgbuild_patch.PkgbuildPatch.write")
|
||||
|
||||
sources.patch_apply(local, patch)
|
||||
write_mock.assert_called_once_with(local / "PKGBUILD")
|
||||
|
@ -20,4 +20,4 @@ def test_init(task_ahriman: Task, database: SQLite, mocker: MockerFixture) -> No
|
||||
"""
|
||||
load_mock = mocker.patch("ahriman.core.build_tools.sources.Sources.load")
|
||||
task_ahriman.init(Path("ahriman"), database)
|
||||
load_mock.assert_called_once_with(Path("ahriman"), task_ahriman.package, None, task_ahriman.paths)
|
||||
load_mock.assert_called_once_with(Path("ahriman"), task_ahriman.package, [], task_ahriman.paths)
|
||||
|
@ -0,0 +1,8 @@
|
||||
from ahriman.core.database.migrations.m003_patch_variables import steps
|
||||
|
||||
|
||||
def test_migration_package_source() -> None:
|
||||
"""
|
||||
migration must not be empty
|
||||
"""
|
||||
assert steps
|
@ -1,55 +1,96 @@
|
||||
from ahriman.core.database import SQLite
|
||||
from ahriman.models.package import Package
|
||||
from ahriman.models.pkgbuild_patch import PkgbuildPatch
|
||||
|
||||
|
||||
def test_patches_get_insert(database: SQLite, package_ahriman: Package, package_python_schedule: Package) -> None:
|
||||
"""
|
||||
must insert patch to database
|
||||
"""
|
||||
database.patches_insert(package_ahriman.base, "patch_1")
|
||||
database.patches_insert(package_python_schedule.base, "patch_2")
|
||||
assert database.patches_get(package_ahriman.base) == "patch_1"
|
||||
assert not database.build_queue_get()
|
||||
database.patches_insert(package_ahriman.base, PkgbuildPatch(None, "patch_1"))
|
||||
database.patches_insert(package_ahriman.base, PkgbuildPatch("key", "patch_3"))
|
||||
database.patches_insert(package_python_schedule.base, PkgbuildPatch(None, "patch_2"))
|
||||
assert database.patches_get(package_ahriman.base) == [
|
||||
PkgbuildPatch(None, "patch_1"), PkgbuildPatch("key", "patch_3")
|
||||
]
|
||||
|
||||
|
||||
def test_patches_list(database: SQLite, package_ahriman: Package, package_python_schedule: Package) -> None:
|
||||
"""
|
||||
must list all patches
|
||||
"""
|
||||
database.patches_insert(package_ahriman.base, "patch1")
|
||||
database.patches_insert(package_python_schedule.base, "patch2")
|
||||
assert database.patches_list(None) == {package_ahriman.base: "patch1", package_python_schedule.base: "patch2"}
|
||||
database.patches_insert(package_ahriman.base, PkgbuildPatch(None, "patch1"))
|
||||
database.patches_insert(package_ahriman.base, PkgbuildPatch("key", "patch3"))
|
||||
database.patches_insert(package_python_schedule.base, PkgbuildPatch(None, "patch2"))
|
||||
assert database.patches_list(None, []) == {
|
||||
package_ahriman.base: [PkgbuildPatch(None, "patch1"), PkgbuildPatch("key", "patch3")],
|
||||
package_python_schedule.base: [PkgbuildPatch(None, "patch2")],
|
||||
}
|
||||
|
||||
|
||||
def test_patches_list_filter(database: SQLite, package_ahriman: Package, package_python_schedule: Package) -> None:
|
||||
"""
|
||||
must list all patches filtered by package name (same as get)
|
||||
"""
|
||||
database.patches_insert(package_ahriman.base, "patch1")
|
||||
database.patches_insert(package_python_schedule.base, "patch2")
|
||||
database.patches_insert(package_ahriman.base, PkgbuildPatch(None, "patch1"))
|
||||
database.patches_insert(package_python_schedule.base, PkgbuildPatch(None, "patch2"))
|
||||
|
||||
assert database.patches_list(package_ahriman.base) == {package_ahriman.base: "patch1"}
|
||||
assert database.patches_list(package_python_schedule.base) == {package_python_schedule.base: "patch2"}
|
||||
assert database.patches_list(package_ahriman.base, []) == {package_ahriman.base: [PkgbuildPatch(None, "patch1")]}
|
||||
assert database.patches_list(package_python_schedule.base, []) == {
|
||||
package_python_schedule.base: [PkgbuildPatch(None, "patch2")],
|
||||
}
|
||||
|
||||
|
||||
def test_patches_list_filter_by_variable(database: SQLite, package_ahriman: Package,
|
||||
package_python_schedule: Package) -> None:
|
||||
"""
|
||||
must list all patches filtered by package name (same as get)
|
||||
"""
|
||||
database.patches_insert(package_ahriman.base, PkgbuildPatch(None, "patch1"))
|
||||
database.patches_insert(package_ahriman.base, PkgbuildPatch("key", "patch2"))
|
||||
database.patches_insert(package_python_schedule.base, PkgbuildPatch(None, "patch3"))
|
||||
|
||||
assert database.patches_list(None, []) == {
|
||||
package_ahriman.base: [PkgbuildPatch(None, "patch1"), PkgbuildPatch("key", "patch2")],
|
||||
package_python_schedule.base: [PkgbuildPatch(None, "patch3")],
|
||||
}
|
||||
assert database.patches_list(None, ["key"]) == {
|
||||
package_ahriman.base: [PkgbuildPatch("key", "patch2")],
|
||||
}
|
||||
|
||||
|
||||
def test_patches_insert_remove(database: SQLite, package_ahriman: Package, package_python_schedule: Package) -> None:
|
||||
"""
|
||||
must remove patch from database
|
||||
"""
|
||||
database.patches_insert(package_ahriman.base, "patch_1")
|
||||
database.patches_insert(package_python_schedule.base, "patch_2")
|
||||
database.patches_remove(package_ahriman.base)
|
||||
database.patches_insert(package_ahriman.base, PkgbuildPatch(None, "patch1"))
|
||||
database.patches_insert(package_python_schedule.base, PkgbuildPatch(None, "patch2"))
|
||||
database.patches_remove(package_ahriman.base, [])
|
||||
|
||||
assert database.patches_get(package_ahriman.base) is None
|
||||
database.patches_insert(package_python_schedule.base, "patch_2")
|
||||
assert database.patches_get(package_ahriman.base) == []
|
||||
assert database.patches_get(package_python_schedule.base) == [PkgbuildPatch(None, "patch2")]
|
||||
|
||||
|
||||
def test_patches_insert_remove_by_variable(database: SQLite, package_ahriman: Package,
|
||||
package_python_schedule: Package) -> None:
|
||||
"""
|
||||
must remove patch from database by variable
|
||||
"""
|
||||
database.patches_insert(package_ahriman.base, PkgbuildPatch(None, "patch1"))
|
||||
database.patches_insert(package_ahriman.base, PkgbuildPatch("key", "patch3"))
|
||||
database.patches_insert(package_python_schedule.base, PkgbuildPatch(None, "patch2"))
|
||||
database.patches_remove(package_ahriman.base, ["key"])
|
||||
|
||||
assert database.patches_get(package_ahriman.base) == [PkgbuildPatch(None, "patch1")]
|
||||
assert database.patches_get(package_python_schedule.base) == [PkgbuildPatch(None, "patch2")]
|
||||
|
||||
|
||||
def test_patches_insert_insert(database: SQLite, package_ahriman: Package) -> None:
|
||||
"""
|
||||
must update patch in database
|
||||
"""
|
||||
database.patches_insert(package_ahriman.base, "patch_1")
|
||||
assert database.patches_get(package_ahriman.base) == "patch_1"
|
||||
database.patches_insert(package_ahriman.base, PkgbuildPatch(None, "patch1"))
|
||||
assert database.patches_get(package_ahriman.base) == [PkgbuildPatch(None, "patch1")]
|
||||
|
||||
database.patches_insert(package_ahriman.base, "patch_2")
|
||||
assert database.patches_get(package_ahriman.base) == "patch_2"
|
||||
database.patches_insert(package_ahriman.base, PkgbuildPatch(None, "patch2"))
|
||||
assert database.patches_get(package_ahriman.base) == [PkgbuildPatch(None, "patch2")]
|
||||
|
@ -1,10 +1,11 @@
|
||||
import pytest
|
||||
|
||||
from ahriman.core.formatters import AurPrinter, ConfigurationPrinter, PackagePrinter, StatusPrinter, StringPrinter, \
|
||||
UpdatePrinter, UserPrinter, VersionPrinter
|
||||
from ahriman.core.formatters import AurPrinter, ConfigurationPrinter, PackagePrinter, PatchPrinter, StatusPrinter, \
|
||||
StringPrinter, UpdatePrinter, UserPrinter, VersionPrinter
|
||||
from ahriman.models.aur_package import AURPackage
|
||||
from ahriman.models.build_status import BuildStatus
|
||||
from ahriman.models.package import Package
|
||||
from ahriman.models.pkgbuild_patch import PkgbuildPatch
|
||||
from ahriman.models.user import User
|
||||
|
||||
|
||||
@ -47,6 +48,20 @@ def package_ahriman_printer(package_ahriman: Package) -> PackagePrinter:
|
||||
return PackagePrinter(package_ahriman, BuildStatus())
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def patch_printer(package_ahriman: Package) -> PatchPrinter:
|
||||
"""
|
||||
fixture for patch printer
|
||||
|
||||
Args:
|
||||
package_ahriman(Package): package fixture
|
||||
|
||||
Returns:
|
||||
PatchPrinter: patch printer test instance
|
||||
"""
|
||||
return PatchPrinter(package_ahriman.base, [PkgbuildPatch("key", "value")])
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def status_printer() -> StatusPrinter:
|
||||
"""
|
||||
|
22
tests/ahriman/core/formatters/test_patch_printer.py
Normal file
22
tests/ahriman/core/formatters/test_patch_printer.py
Normal file
@ -0,0 +1,22 @@
|
||||
from ahriman.core.formatters import PatchPrinter
|
||||
|
||||
|
||||
def test_properties(patch_printer: PatchPrinter) -> None:
|
||||
"""
|
||||
must return non empty properties list
|
||||
"""
|
||||
assert patch_printer.properties()
|
||||
|
||||
|
||||
def test_properties_required(patch_printer: PatchPrinter) -> None:
|
||||
"""
|
||||
must return all properties as required
|
||||
"""
|
||||
assert all(prop.is_required for prop in patch_printer.properties())
|
||||
|
||||
|
||||
def test_title(patch_printer: PatchPrinter) -> None:
|
||||
"""
|
||||
must return non empty title
|
||||
"""
|
||||
assert patch_printer.title() == "ahriman"
|
58
tests/ahriman/core/gitremote/test_remote_pull_trigger.py
Normal file
58
tests/ahriman/core/gitremote/test_remote_pull_trigger.py
Normal file
@ -0,0 +1,58 @@
|
||||
import pytest
|
||||
|
||||
from pathlib import Path
|
||||
from pytest_mock import MockerFixture
|
||||
from unittest import mock
|
||||
|
||||
from ahriman.core.configuration import Configuration
|
||||
from ahriman.core.gitremote import RemotePullTrigger
|
||||
|
||||
|
||||
def test_on_start(configuration: Configuration, mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must clone repo on start
|
||||
"""
|
||||
clone_mock = mocker.patch("ahriman.core.gitremote.RemotePullTrigger.repo_clone")
|
||||
trigger = RemotePullTrigger("x86_64", configuration)
|
||||
|
||||
trigger.on_start()
|
||||
clone_mock.assert_called_once_with()
|
||||
|
||||
|
||||
def test_repo_clone(configuration: Configuration, mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must clone repository locally and copy its content
|
||||
"""
|
||||
fetch_mock = mocker.patch("ahriman.core.build_tools.sources.Sources.fetch")
|
||||
copy_mock = mocker.patch("ahriman.core.gitremote.RemotePullTrigger.repo_copy")
|
||||
trigger = RemotePullTrigger("x86_64", configuration)
|
||||
|
||||
trigger.repo_clone()
|
||||
fetch_mock.assert_called_once_with(pytest.helpers.anyvar(int), trigger.remote_source)
|
||||
copy_mock.assert_called_once_with(pytest.helpers.anyvar(int))
|
||||
|
||||
|
||||
def test_repo_copy(configuration: Configuration, mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must copy repository tree from temporary directory to the local cache
|
||||
"""
|
||||
mocker.patch("ahriman.core.gitremote.remote_pull_trigger.walk", return_value=[
|
||||
Path("local") / "package1" / "PKGBUILD",
|
||||
Path("local") / "package1" / ".SRCINFO",
|
||||
Path("local") / "package2" / ".SRCINFO",
|
||||
Path("local") / "package3" / "PKGBUILD",
|
||||
Path("local") / "package3" / ".SRCINFO",
|
||||
])
|
||||
copytree_mock = mocker.patch("shutil.copytree")
|
||||
init_mock = mocker.patch("ahriman.core.build_tools.sources.Sources.init")
|
||||
trigger = RemotePullTrigger("x86_64", configuration)
|
||||
|
||||
trigger.repo_copy(Path("local"))
|
||||
copytree_mock.assert_has_calls([
|
||||
mock.call(Path("local") / "package1", configuration.repository_paths.cache_for("package1"), dirs_exist_ok=True),
|
||||
mock.call(Path("local") / "package3", configuration.repository_paths.cache_for("package3"), dirs_exist_ok=True),
|
||||
])
|
||||
init_mock.assert_has_calls([
|
||||
mock.call(configuration.repository_paths.cache_for("package1")),
|
||||
mock.call(configuration.repository_paths.cache_for("package3")),
|
||||
])
|
56
tests/ahriman/core/gitremote/test_remote_push_trigger.py
Normal file
56
tests/ahriman/core/gitremote/test_remote_push_trigger.py
Normal file
@ -0,0 +1,56 @@
|
||||
import pytest
|
||||
|
||||
from pathlib import Path
|
||||
from pytest_mock import MockerFixture
|
||||
from unittest import mock
|
||||
|
||||
from ahriman.core.configuration import Configuration
|
||||
from ahriman.core.gitremote import RemotePushTrigger
|
||||
from ahriman.models.package import Package
|
||||
from ahriman.models.result import Result
|
||||
|
||||
|
||||
def test_package_update(package_ahriman: Package, mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must update single package
|
||||
"""
|
||||
rmtree_mock = mocker.patch("shutil.rmtree")
|
||||
fetch_mock = mocker.patch("ahriman.core.build_tools.sources.Sources.fetch")
|
||||
copytree_mock = mocker.patch("shutil.copytree")
|
||||
|
||||
local = Path("local")
|
||||
RemotePushTrigger.package_update(package_ahriman, local)
|
||||
rmtree_mock.assert_has_calls([
|
||||
mock.call(local / package_ahriman.base, ignore_errors=True),
|
||||
mock.call(pytest.helpers.anyvar(int), onerror=pytest.helpers.anyvar(int)), # removal of the TemporaryDirectory
|
||||
mock.call(local / package_ahriman.base / ".git", ignore_errors=True),
|
||||
])
|
||||
fetch_mock.assert_called_once_with(pytest.helpers.anyvar(int), package_ahriman.remote)
|
||||
copytree_mock.assert_called_once_with(pytest.helpers.anyvar(int), local / package_ahriman.base)
|
||||
|
||||
|
||||
def test_packages_update(result: Result, package_ahriman: Package, mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must generate packages update
|
||||
"""
|
||||
update_mock = mocker.patch("ahriman.core.gitremote.RemotePushTrigger.package_update",
|
||||
return_value=[package_ahriman.base])
|
||||
|
||||
local = Path("local")
|
||||
assert list(RemotePushTrigger.packages_update(result, local))
|
||||
update_mock.assert_called_once_with(package_ahriman, local)
|
||||
|
||||
|
||||
def test_on_result(configuration: Configuration, result: Result, package_ahriman: Package,
|
||||
mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must push changes on result
|
||||
"""
|
||||
mocker.patch("ahriman.core.gitremote.RemotePushTrigger.packages_update", return_value=[package_ahriman.base])
|
||||
fetch_mock = mocker.patch("ahriman.core.build_tools.sources.Sources.fetch")
|
||||
push_mock = mocker.patch("ahriman.core.build_tools.sources.Sources.push")
|
||||
trigger = RemotePushTrigger("x86_64", configuration)
|
||||
|
||||
trigger.on_result(result, [package_ahriman])
|
||||
fetch_mock.assert_called_once_with(pytest.helpers.anyvar(int), trigger.remote_source)
|
||||
push_mock.assert_called_once_with(pytest.helpers.anyvar(int), trigger.remote_source, package_ahriman.base)
|
@ -4,7 +4,7 @@ from pytest_mock import MockerFixture
|
||||
|
||||
from ahriman.core.configuration import Configuration
|
||||
from ahriman.core.exceptions import ReportFailed
|
||||
from ahriman.core.report import Report
|
||||
from ahriman.core.report.report import Report
|
||||
from ahriman.models.report_settings import ReportSettings
|
||||
from ahriman.models.result import Result
|
||||
|
||||
@ -23,7 +23,7 @@ def test_report_dummy(configuration: Configuration, result: Result, mocker: Mock
|
||||
must construct dummy report class
|
||||
"""
|
||||
mocker.patch("ahriman.models.report_settings.ReportSettings.from_option", return_value=ReportSettings.Disabled)
|
||||
report_mock = mocker.patch("ahriman.core.report.Report.generate")
|
||||
report_mock = mocker.patch("ahriman.core.report.report.Report.generate")
|
||||
Report.load("x86_64", configuration, "disabled").run(result, [])
|
||||
report_mock.assert_called_once_with([], result)
|
||||
|
||||
|
@ -5,13 +5,13 @@ from ahriman.core.report import ReportTrigger
|
||||
from ahriman.models.result import Result
|
||||
|
||||
|
||||
def test_run(configuration: Configuration, mocker: MockerFixture) -> None:
|
||||
def test_on_result(configuration: Configuration, mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must run report for specified targets
|
||||
"""
|
||||
configuration.set_option("report", "target", "email")
|
||||
run_mock = mocker.patch("ahriman.core.report.Report.run")
|
||||
run_mock = mocker.patch("ahriman.core.report.report.Report.run")
|
||||
|
||||
trigger = ReportTrigger("x86_64", configuration)
|
||||
trigger.run(Result(), [])
|
||||
trigger.on_result(Result(), [])
|
||||
run_mock.assert_called_once_with(Result(), [])
|
||||
|
@ -4,10 +4,8 @@ from pathlib import Path
|
||||
from pytest_mock import MockerFixture
|
||||
from unittest import mock
|
||||
|
||||
from ahriman.core.report import Report
|
||||
from ahriman.core.repository.executor import Executor
|
||||
from ahriman.models.package import Package
|
||||
from ahriman.models.result import Result
|
||||
|
||||
|
||||
def test_load_archives(executor: Executor) -> None:
|
||||
@ -74,7 +72,7 @@ def test_process_remove_base(executor: Executor, package_ahriman: Package, mocke
|
||||
# must update status and remove package files
|
||||
tree_clear_mock.assert_called_once_with(package_ahriman.base)
|
||||
build_queue_mock.assert_called_once_with(package_ahriman.base)
|
||||
patches_mock.assert_called_once_with(package_ahriman.base)
|
||||
patches_mock.assert_called_once_with(package_ahriman.base, [])
|
||||
status_client_mock.assert_called_once_with(package_ahriman.base)
|
||||
|
||||
|
||||
@ -144,17 +142,6 @@ def test_process_remove_nothing(executor: Executor, package_ahriman: Package, pa
|
||||
repo_remove_mock.assert_not_called()
|
||||
|
||||
|
||||
def test_process_triggers(executor: Executor, package_ahriman: Package, result: Result, mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must process report
|
||||
"""
|
||||
mocker.patch("ahriman.core.repository.executor.Executor.packages", return_value=[package_ahriman])
|
||||
triggers_mock = mocker.patch("ahriman.core.triggers.TriggerLoader.process")
|
||||
|
||||
executor.process_triggers(result)
|
||||
triggers_mock.assert_called_once_with(result, [package_ahriman])
|
||||
|
||||
|
||||
def test_process_update(executor: Executor, package_ahriman: Package, mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must run update process
|
||||
|
@ -42,7 +42,7 @@ def test_packages_add(spawner: Spawn, mocker: MockerFixture) -> None:
|
||||
"""
|
||||
spawn_mock = mocker.patch("ahriman.core.spawn.Spawn.spawn_process")
|
||||
spawner.packages_add(["ahriman", "linux"], now=False)
|
||||
spawn_mock.assert_called_once_with("add", "ahriman", "linux", source="aur")
|
||||
spawn_mock.assert_called_once_with("package-add", "ahriman", "linux", source="aur")
|
||||
|
||||
|
||||
def test_packages_add_with_build(spawner: Spawn, mocker: MockerFixture) -> None:
|
||||
@ -51,7 +51,16 @@ def test_packages_add_with_build(spawner: Spawn, mocker: MockerFixture) -> None:
|
||||
"""
|
||||
spawn_mock = mocker.patch("ahriman.core.spawn.Spawn.spawn_process")
|
||||
spawner.packages_add(["ahriman", "linux"], now=True)
|
||||
spawn_mock.assert_called_once_with("add", "ahriman", "linux", source="aur", now="")
|
||||
spawn_mock.assert_called_once_with("package-add", "ahriman", "linux", source="aur", now="")
|
||||
|
||||
|
||||
def test_packages_add_update(spawner: Spawn, mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must call repo update
|
||||
"""
|
||||
spawn_mock = mocker.patch("ahriman.core.spawn.Spawn.spawn_process")
|
||||
spawner.packages_add([], now=False)
|
||||
spawn_mock.assert_called_once_with("repo-update")
|
||||
|
||||
|
||||
def test_packages_remove(spawner: Spawn, mocker: MockerFixture) -> None:
|
||||
@ -60,7 +69,7 @@ def test_packages_remove(spawner: Spawn, mocker: MockerFixture) -> None:
|
||||
"""
|
||||
spawn_mock = mocker.patch("ahriman.core.spawn.Spawn.spawn_process")
|
||||
spawner.packages_remove(["ahriman", "linux"])
|
||||
spawn_mock.assert_called_once_with("remove", "ahriman", "linux")
|
||||
spawn_mock.assert_called_once_with("package-remove", "ahriman", "linux")
|
||||
|
||||
|
||||
def test_spawn_process(spawner: Spawn, mocker: MockerFixture) -> None:
|
||||
|
@ -49,8 +49,7 @@ def test_leaf_load(package_ahriman: Package, repository_paths: RepositoryPaths,
|
||||
leaf = Leaf.load(package_ahriman, repository_paths, database)
|
||||
assert leaf.package == package_ahriman
|
||||
assert leaf.dependencies == {"ahriman-dependency"}
|
||||
load_mock.assert_called_once_with(
|
||||
pytest.helpers.anyvar(int), package_ahriman, None, repository_paths)
|
||||
load_mock.assert_called_once_with(pytest.helpers.anyvar(int), package_ahriman, [], repository_paths)
|
||||
dependencies_mock.assert_called_once_with(pytest.helpers.anyvar(int))
|
||||
|
||||
|
||||
|
@ -1,12 +1,36 @@
|
||||
import pytest
|
||||
from unittest.mock import MagicMock
|
||||
|
||||
from ahriman.core.triggers import Trigger
|
||||
from ahriman.models.result import Result
|
||||
|
||||
|
||||
def test_run(trigger: Trigger) -> None:
|
||||
def test_on_result(trigger: Trigger) -> None:
|
||||
"""
|
||||
must raise NotImplemented for missing rum method
|
||||
must pass execution nto run method
|
||||
"""
|
||||
with pytest.raises(NotImplementedError):
|
||||
trigger.run(Result(), [])
|
||||
trigger.on_result(Result(), [])
|
||||
|
||||
|
||||
def test_on_result_run(trigger: Trigger) -> None:
|
||||
"""
|
||||
must fallback to run method if it exists
|
||||
"""
|
||||
run_mock = MagicMock()
|
||||
setattr(trigger, "run", run_mock)
|
||||
|
||||
trigger.on_result(Result(), [])
|
||||
run_mock.assert_called_once_with(Result(), [])
|
||||
|
||||
|
||||
def test_on_start(trigger: Trigger) -> None:
|
||||
"""
|
||||
must do nothing for not implemented method on_start
|
||||
"""
|
||||
trigger.on_start()
|
||||
|
||||
|
||||
def test_on_stop(trigger: Trigger) -> None:
|
||||
"""
|
||||
must do nothing for not implemented method on_stop
|
||||
"""
|
||||
trigger.on_stop()
|
||||
|
@ -3,6 +3,7 @@ import pytest
|
||||
from pathlib import Path
|
||||
from pytest_mock import MockerFixture
|
||||
|
||||
from ahriman.core.configuration import Configuration
|
||||
from ahriman.core.exceptions import InvalidExtension
|
||||
from ahriman.core.triggers import TriggerLoader
|
||||
from ahriman.models.package import Package
|
||||
@ -75,27 +76,75 @@ def test_load_trigger_path_not_found(trigger_loader: TriggerLoader) -> None:
|
||||
trigger_loader.load_trigger("/some/random/path.py.SomeRandomModule")
|
||||
|
||||
|
||||
def test_process(trigger_loader: TriggerLoader, package_ahriman: Package, mocker: MockerFixture) -> None:
|
||||
def test_on_result(trigger_loader: TriggerLoader, package_ahriman: Package, mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must run triggers
|
||||
"""
|
||||
upload_mock = mocker.patch("ahriman.core.upload.UploadTrigger.run")
|
||||
report_mock = mocker.patch("ahriman.core.report.ReportTrigger.run")
|
||||
upload_mock = mocker.patch("ahriman.core.upload.UploadTrigger.on_result")
|
||||
report_mock = mocker.patch("ahriman.core.report.ReportTrigger.on_result")
|
||||
|
||||
trigger_loader.process(Result(), [package_ahriman])
|
||||
trigger_loader.on_result(Result(), [package_ahriman])
|
||||
report_mock.assert_called_once_with(Result(), [package_ahriman])
|
||||
upload_mock.assert_called_once_with(Result(), [package_ahriman])
|
||||
|
||||
|
||||
def test_process_exception(trigger_loader: TriggerLoader, package_ahriman: Package, mocker: MockerFixture) -> None:
|
||||
def test_on_result_exception(trigger_loader: TriggerLoader, package_ahriman: Package, mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must suppress exception during trigger run
|
||||
"""
|
||||
upload_mock = mocker.patch("ahriman.core.upload.UploadTrigger.run", side_effect=Exception())
|
||||
report_mock = mocker.patch("ahriman.core.report.ReportTrigger.run")
|
||||
upload_mock = mocker.patch("ahriman.core.upload.UploadTrigger.on_result", side_effect=Exception())
|
||||
report_mock = mocker.patch("ahriman.core.report.ReportTrigger.on_result")
|
||||
log_mock = mocker.patch("logging.Logger.exception")
|
||||
|
||||
trigger_loader.process(Result(), [package_ahriman])
|
||||
trigger_loader.on_result(Result(), [package_ahriman])
|
||||
report_mock.assert_called_once_with(Result(), [package_ahriman])
|
||||
upload_mock.assert_called_once_with(Result(), [package_ahriman])
|
||||
log_mock.assert_called_once()
|
||||
|
||||
|
||||
def test_on_start(trigger_loader: TriggerLoader, package_ahriman: Package, mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must run triggers on start
|
||||
"""
|
||||
upload_mock = mocker.patch("ahriman.core.upload.UploadTrigger.on_start")
|
||||
report_mock = mocker.patch("ahriman.core.report.ReportTrigger.on_start")
|
||||
|
||||
trigger_loader.on_start()
|
||||
assert trigger_loader._on_stop_requested
|
||||
report_mock.assert_called_once_with()
|
||||
upload_mock.assert_called_once_with()
|
||||
|
||||
|
||||
def test_on_stop_with_on_start(configuration: Configuration, mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must call on_stop on exit if on_start was called
|
||||
"""
|
||||
on_stop_mock = mocker.patch("ahriman.core.triggers.trigger_loader.TriggerLoader.on_stop")
|
||||
|
||||
trigger_loader = TriggerLoader("x86_64", configuration)
|
||||
trigger_loader.on_start()
|
||||
del trigger_loader
|
||||
on_stop_mock.assert_called_once_with()
|
||||
|
||||
|
||||
def test_on_stop_without_on_start(configuration: Configuration, mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must call not on_stop on exit if on_start wasn't called
|
||||
"""
|
||||
on_stop_mock = mocker.patch("ahriman.core.triggers.trigger_loader.TriggerLoader.on_stop")
|
||||
|
||||
trigger_loader = TriggerLoader("x86_64", configuration)
|
||||
del trigger_loader
|
||||
on_stop_mock.assert_not_called()
|
||||
|
||||
|
||||
def test_on_stop(trigger_loader: TriggerLoader, package_ahriman: Package, mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must run triggers on stop
|
||||
"""
|
||||
upload_mock = mocker.patch("ahriman.core.upload.UploadTrigger.on_stop")
|
||||
report_mock = mocker.patch("ahriman.core.report.ReportTrigger.on_stop")
|
||||
|
||||
trigger_loader.on_stop()
|
||||
report_mock.assert_called_once_with()
|
||||
upload_mock.assert_called_once_with()
|
||||
|
@ -5,7 +5,7 @@ from pytest_mock import MockerFixture
|
||||
|
||||
from ahriman.core.configuration import Configuration
|
||||
from ahriman.core.exceptions import SyncFailed
|
||||
from ahriman.core.upload import Upload
|
||||
from ahriman.core.upload.upload import Upload
|
||||
from ahriman.models.upload_settings import UploadSettings
|
||||
|
||||
|
||||
@ -23,7 +23,7 @@ def test_report_dummy(configuration: Configuration, mocker: MockerFixture) -> No
|
||||
must construct dummy upload class
|
||||
"""
|
||||
mocker.patch("ahriman.models.upload_settings.UploadSettings.from_option", return_value=UploadSettings.Disabled)
|
||||
upload_mock = mocker.patch("ahriman.core.upload.Upload.sync")
|
||||
upload_mock = mocker.patch("ahriman.core.upload.upload.Upload.sync")
|
||||
Upload.load("x86_64", configuration, "disabled").run(Path("path"), [])
|
||||
upload_mock.assert_called_once_with(Path("path"), [])
|
||||
|
||||
|
@ -5,13 +5,13 @@ from ahriman.core.upload import UploadTrigger
|
||||
from ahriman.models.result import Result
|
||||
|
||||
|
||||
def test_run(configuration: Configuration, mocker: MockerFixture) -> None:
|
||||
def test_on_result(configuration: Configuration, mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must run report for specified targets
|
||||
"""
|
||||
configuration.set_option("upload", "target", "rsync")
|
||||
run_mock = mocker.patch("ahriman.core.upload.Upload.run")
|
||||
run_mock = mocker.patch("ahriman.core.upload.upload.Upload.run")
|
||||
|
||||
trigger = UploadTrigger("x86_64", configuration)
|
||||
trigger.run(Result(), [])
|
||||
trigger.on_result(Result(), [])
|
||||
run_mock.assert_called_once_with(configuration.repository_paths.repository, [])
|
||||
|
@ -5,6 +5,15 @@ from unittest.mock import MagicMock, call
|
||||
from ahriman.models.pkgbuild_patch import PkgbuildPatch
|
||||
|
||||
|
||||
def test_post_init() -> None:
|
||||
"""
|
||||
must remove empty keys
|
||||
"""
|
||||
assert PkgbuildPatch("", "value").key is None
|
||||
assert PkgbuildPatch(None, "value").key is None
|
||||
assert PkgbuildPatch("key", "value").key == "key"
|
||||
|
||||
|
||||
def test_is_function() -> None:
|
||||
"""
|
||||
must correctly define key as function
|
||||
@ -13,6 +22,14 @@ def test_is_function() -> None:
|
||||
assert PkgbuildPatch("key()", "value").is_function
|
||||
|
||||
|
||||
def test_is_plain_diff() -> None:
|
||||
"""
|
||||
must correctly define key as function
|
||||
"""
|
||||
assert not PkgbuildPatch("key", "value").is_plain_diff
|
||||
assert PkgbuildPatch(None, "value").is_plain_diff
|
||||
|
||||
|
||||
def test_quote() -> None:
|
||||
"""
|
||||
must quote strings if unsafe flag is not set
|
||||
@ -32,6 +49,13 @@ def test_serialize() -> None:
|
||||
assert PkgbuildPatch("key", "4'2", unsafe=True).serialize() == "key=4'2"
|
||||
|
||||
|
||||
def test_serialize_plain_diff() -> None:
|
||||
"""
|
||||
must correctly serialize function values
|
||||
"""
|
||||
assert PkgbuildPatch(None, "{ value }").serialize() == "{ value }"
|
||||
|
||||
|
||||
def test_serialize_function() -> None:
|
||||
"""
|
||||
must correctly serialize function values
|
||||
|
@ -27,17 +27,6 @@ async def test_post(client: TestClient, mocker: MockerFixture) -> None:
|
||||
add_mock.assert_called_once_with(["ahriman"], now=True)
|
||||
|
||||
|
||||
async def test_post_exception(client: TestClient, mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must raise exception on missing packages payload
|
||||
"""
|
||||
add_mock = mocker.patch("ahriman.core.spawn.Spawn.packages_add")
|
||||
response = await client.post("/api/v1/service/add")
|
||||
|
||||
assert response.status == 400
|
||||
add_mock.assert_not_called()
|
||||
|
||||
|
||||
async def test_post_update(client: TestClient, mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must call post request correctly for alias
|
||||
|
@ -31,6 +31,10 @@ root = ../../../
|
||||
[sign]
|
||||
target =
|
||||
|
||||
[gitremote]
|
||||
pull_url = https://github.com/arcan1s/repository.git
|
||||
push_url = https://github.com/arcan1s/repository.git
|
||||
|
||||
[report]
|
||||
target =
|
||||
|
||||
|
Reference in New Issue
Block a user