mirror of
https://github.com/arcan1s/ahriman.git
synced 2025-11-14 20:43:42 +00:00
Compare commits
5 Commits
fc508e19b8
...
2.14.1
| Author | SHA1 | Date | |
|---|---|---|---|
| 9c1e9ecbdc | |||
| 4b2f6bbee9 | |||
| fd8c8a00d0 | |||
| eaf1984eb3 | |||
| 794dddccd9 |
@ -92,7 +92,7 @@ Again, the most checks can be performed by `tox` command, though some additional
|
||||
```
|
||||
|
||||
* Type annotations are the must, even for local functions. For the function argument `self` (for instance methods) and `cls` (for class methods) should not be annotated.
|
||||
* For collection types built-in classes must be used if possible (e.g. `dict` instead of `typing.Dict`, `tuple` instead of `typing.Tuple`). In case if built-in type is not available, but `collections.abc` provides interface, it must be used (e.g. `collections.abc.Awaitable` instead of `typing.Awaitable`, `collections.abc.Iterable` instead of `typing.Iterable`). For union classes, the bar operator (`|`) must be used (e.g. `float | int` instead of `typing.Union[float, int]`), which also includes `typing.Optional` (e.g. `str | None` instead of `Optional[str]`).
|
||||
* For collection types built-in classes must be used if possible (e.g. `dict` instead of `typing.Dict`, `tuple` instead of `typing.Tuple`). In case if built-in type is not available, but `collections.abc` provides interface, it must be used (e.g. `collections.abc.Awaitable` instead of `typing.Awaitable`, `collections.abc.Iterable` instead of `typing.Iterable`). For union classes, the bar operator (`|`) must be used (e.g. `float | int` instead of `typing.Union[float, int]`), which also includes `typinng.Optional` (e.g. `str | None` instead of `Optional[str]`).
|
||||
* `classmethod` should (almost) always return `Self`. In case of mypy warning (e.g. if there is a branch in which function doesn't return the instance of `cls`) consider using `staticmethod` instead.
|
||||
* Recommended order of function definitions in class:
|
||||
|
||||
@ -132,7 +132,7 @@ Again, the most checks can be performed by `tox` command, though some additional
|
||||
* For any path interactions `pathlib.Path` must be used.
|
||||
* Configuration interactions must go through `ahriman.core.configuration.Configuration` class instance.
|
||||
* In case if class load requires some actions, it is recommended to create class method which can be used for class instantiating.
|
||||
* The code must follow the exception safety, unless it is explicitly asked by end user. It means that most exceptions must be handled and printed to log, no other actions must be done (e.g. raising another exception).
|
||||
* The most (expected) exceptions must be handled and printed to log, allowing service to continue work. However, fatal and (in some cases) unexpected exceptions may lead to the application termination.
|
||||
* Exceptions without parameters should be raised without parentheses, e.g.:
|
||||
|
||||
```python
|
||||
|
||||
@ -2,7 +2,7 @@
|
||||
|
||||
[](https://github.com/arcan1s/ahriman/actions/workflows/tests.yml)
|
||||
[](https://github.com/arcan1s/ahriman/actions/workflows/setup.yml)
|
||||
[](https://hub.docker.com/r/arcan1s/ahriman)
|
||||
[](https://hub.docker.com/r/arcan1s/ahriman)
|
||||
[](https://www.codefactor.io/repository/github/arcan1s/ahriman)
|
||||
[](https://ahriman.readthedocs.io)
|
||||
|
||||
@ -40,3 +40,5 @@ The application provides reasonable defaults which allow to use it out-of-box; h
|
||||
* [Build status page](https://ahriman-demo.arcanis.me). You can log in as `demo` user by using `demo` password. However, you will not be able to run tasks. [HTTP API documentation](https://ahriman-demo.arcanis.me/api-docs) is also available.
|
||||
* [Repository index](https://repo.arcanis.me/arcanisrepo/x86_64/).
|
||||
* [Telegram feed](https://t.me/arcanisrepo).
|
||||
|
||||
Do you have any success story? You can [share it](https://github.com/arcan1s/ahriman/issues/new?template=04-discussion.md)!
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
|
Before Width: | Height: | Size: 1.2 MiB After Width: | Height: | Size: 1.2 MiB |
@ -4,14 +4,6 @@ ahriman.core.build\_tools package
|
||||
Submodules
|
||||
----------
|
||||
|
||||
ahriman.core.build\_tools.package\_archive module
|
||||
-------------------------------------------------
|
||||
|
||||
.. automodule:: ahriman.core.build_tools.package_archive
|
||||
:members:
|
||||
:no-undoc-members:
|
||||
:show-inheritance:
|
||||
|
||||
ahriman.core.build\_tools.sources module
|
||||
----------------------------------------
|
||||
|
||||
|
||||
@ -116,14 +116,6 @@ ahriman.core.database.migrations.m013\_dependencies module
|
||||
:no-undoc-members:
|
||||
:show-inheritance:
|
||||
|
||||
ahriman.core.database.migrations.m014\_auditlog module
|
||||
------------------------------------------------------
|
||||
|
||||
.. automodule:: ahriman.core.database.migrations.m014_auditlog
|
||||
:members:
|
||||
:no-undoc-members:
|
||||
:show-inheritance:
|
||||
|
||||
Module contents
|
||||
---------------
|
||||
|
||||
|
||||
@ -36,14 +36,6 @@ ahriman.core.database.operations.dependencies\_operations module
|
||||
:no-undoc-members:
|
||||
:show-inheritance:
|
||||
|
||||
ahriman.core.database.operations.event\_operations module
|
||||
---------------------------------------------------------
|
||||
|
||||
.. automodule:: ahriman.core.database.operations.event_operations
|
||||
:members:
|
||||
:no-undoc-members:
|
||||
:show-inheritance:
|
||||
|
||||
ahriman.core.database.operations.logs\_operations module
|
||||
--------------------------------------------------------
|
||||
|
||||
|
||||
@ -12,14 +12,6 @@ ahriman.core.repository.cleaner module
|
||||
:no-undoc-members:
|
||||
:show-inheritance:
|
||||
|
||||
ahriman.core.repository.event\_logger module
|
||||
--------------------------------------------
|
||||
|
||||
.. automodule:: ahriman.core.repository.event_logger
|
||||
:members:
|
||||
:no-undoc-members:
|
||||
:show-inheritance:
|
||||
|
||||
ahriman.core.repository.executor module
|
||||
---------------------------------------
|
||||
|
||||
|
||||
@ -68,14 +68,6 @@ ahriman.models.dependencies module
|
||||
:no-undoc-members:
|
||||
:show-inheritance:
|
||||
|
||||
ahriman.models.event module
|
||||
---------------------------
|
||||
|
||||
.. automodule:: ahriman.models.event
|
||||
:members:
|
||||
:no-undoc-members:
|
||||
:show-inheritance:
|
||||
|
||||
ahriman.models.filesystem\_package module
|
||||
-----------------------------------------
|
||||
|
||||
@ -108,14 +100,6 @@ ahriman.models.log\_record\_id module
|
||||
:no-undoc-members:
|
||||
:show-inheritance:
|
||||
|
||||
ahriman.models.metrics\_timer module
|
||||
------------------------------------
|
||||
|
||||
.. automodule:: ahriman.models.metrics_timer
|
||||
:members:
|
||||
:no-undoc-members:
|
||||
:show-inheritance:
|
||||
|
||||
ahriman.models.migration module
|
||||
-------------------------------
|
||||
|
||||
@ -140,6 +124,14 @@ ahriman.models.package module
|
||||
:no-undoc-members:
|
||||
:show-inheritance:
|
||||
|
||||
ahriman.models.package\_archive module
|
||||
--------------------------------------
|
||||
|
||||
.. automodule:: ahriman.models.package_archive
|
||||
:members:
|
||||
:no-undoc-members:
|
||||
:show-inheritance:
|
||||
|
||||
ahriman.models.package\_description module
|
||||
------------------------------------------
|
||||
|
||||
|
||||
@ -60,22 +60,6 @@ ahriman.web.schemas.error\_schema module
|
||||
:no-undoc-members:
|
||||
:show-inheritance:
|
||||
|
||||
ahriman.web.schemas.event\_schema module
|
||||
----------------------------------------
|
||||
|
||||
.. automodule:: ahriman.web.schemas.event_schema
|
||||
:members:
|
||||
:no-undoc-members:
|
||||
:show-inheritance:
|
||||
|
||||
ahriman.web.schemas.event\_search\_schema module
|
||||
------------------------------------------------
|
||||
|
||||
.. automodule:: ahriman.web.schemas.event_search_schema
|
||||
:members:
|
||||
:no-undoc-members:
|
||||
:show-inheritance:
|
||||
|
||||
ahriman.web.schemas.file\_schema module
|
||||
---------------------------------------
|
||||
|
||||
|
||||
@ -1,21 +0,0 @@
|
||||
ahriman.web.views.v1.auditlog package
|
||||
=====================================
|
||||
|
||||
Submodules
|
||||
----------
|
||||
|
||||
ahriman.web.views.v1.auditlog.events module
|
||||
-------------------------------------------
|
||||
|
||||
.. automodule:: ahriman.web.views.v1.auditlog.events
|
||||
:members:
|
||||
:no-undoc-members:
|
||||
:show-inheritance:
|
||||
|
||||
Module contents
|
||||
---------------
|
||||
|
||||
.. automodule:: ahriman.web.views.v1.auditlog
|
||||
:members:
|
||||
:no-undoc-members:
|
||||
:show-inheritance:
|
||||
@ -7,7 +7,6 @@ Subpackages
|
||||
.. toctree::
|
||||
:maxdepth: 4
|
||||
|
||||
ahriman.web.views.v1.auditlog
|
||||
ahriman.web.views.v1.distributed
|
||||
ahriman.web.views.v1.packages
|
||||
ahriman.web.views.v1.service
|
||||
|
||||
@ -81,13 +81,14 @@ Authorized users are stored inside internal database, if any of external provide
|
||||
|
||||
Build related configuration. Group name can refer to architecture, e.g. ``build:x86_64`` can be used for x86_64 architecture specific settings.
|
||||
|
||||
* ``allowed_scan_paths`` - paths to be used for implicit dependencies scan, scape separated list of paths, optional.
|
||||
* ``archbuild_flags`` - additional flags passed to ``archbuild`` command, space separated list of strings, optional.
|
||||
* ``blacklisted_scan_paths`` - paths to be excluded for implicit dependencies scan, scape separated list of paths, optional. Normally all elements of this option must be child paths of any of ``allowed_scan_paths`` element.
|
||||
* ``build_command`` - default build command, string, required.
|
||||
* ``ignore_packages`` - list packages to ignore during a regular update (manual update will still work), space separated list of strings, optional.
|
||||
* ``include_debug_packages`` - distribute debug packages, boolean, optional, default ``yes``.
|
||||
* ``makepkg_flags`` - additional flags passed to ``makepkg`` command, space separated list of strings, optional.
|
||||
* ``makechrootpkg_flags`` - additional flags passed to ``makechrootpkg`` command, space separated list of strings, optional.
|
||||
* ``scan_paths`` - paths to be used for implicit dependencies scan, space separated list of strings, optional. If any of those paths is matched against the path, it will be added to the allowed list.
|
||||
* ``triggers`` - list of ``ahriman.core.triggers.Trigger`` class implementation (e.g. ``ahriman.core.report.ReportTrigger ahriman.core.upload.UploadTrigger``) which will be loaded and run at the end of processing, space separated list of strings, optional. You can also specify triggers by their paths, e.g. ``/usr/lib/python3.10/site-packages/ahriman/core/report/report.py.ReportTrigger``. Triggers are run in the order of definition.
|
||||
* ``triggers_known`` - optional list of ``ahriman.core.triggers.Trigger`` class implementations which are not run automatically and used only for trigger discovery and configuration validation.
|
||||
* ``vcs_allowed_age`` - maximal age in seconds of the VCS packages before their version will be updated with its remote source, integer, optional, default is 7 days.
|
||||
|
||||
@ -379,7 +379,7 @@ After the success build the application extracts all linked libraries and used d
|
||||
|
||||
In order to disable this check completely, the ``--no-check-files`` flag can be used.
|
||||
|
||||
In addition, there is possibility to control paths which will be used for checking, by using option ``build.scan_paths``, which supports regular expressions. Leaving ``build.scan_paths`` blank will effectively disable any check too.
|
||||
In addition, there is possibility to control paths which will be used for checking, by using options ``build.allowed_scan_paths`` and ``build.blacklisted_scan_paths``. Leaving ``build.allowed_scan_paths`` blank will effectively disable any check too.
|
||||
|
||||
How to install built packages
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
# Maintainer: Evgeniy Alekseev
|
||||
|
||||
pkgname='ahriman'
|
||||
pkgver=2.14.0
|
||||
pkgver=2.14.1
|
||||
pkgrel=1
|
||||
pkgdesc="ArcH linux ReposItory MANager"
|
||||
arch=('any')
|
||||
|
||||
@ -50,8 +50,12 @@ allow_read_only = yes
|
||||
;salt =
|
||||
|
||||
[build]
|
||||
; List of paths to be used for implicit dependency scan
|
||||
allowed_scan_paths = /usr/lib
|
||||
; List of additional flags passed to archbuild command.
|
||||
;archbuild_flags =
|
||||
; List of paths to be excluded for implicit dependency scan. Usually they should be subpaths of allowed_scan_paths
|
||||
blacklisted_scan_paths = /usr/lib/cmake
|
||||
; Path to build command
|
||||
;build_command =
|
||||
; List of packages to be ignored during automatic updates.
|
||||
@ -62,8 +66,6 @@ allow_read_only = yes
|
||||
;makechrootpkg_flags =
|
||||
; List of additional flags passed to makepkg command.
|
||||
makepkg_flags = --nocolor --ignorearch
|
||||
; List of paths to be used for implicit dependency scan. Regular expressions are supported
|
||||
scan_paths = ^usr/lib(?!/cmake).*$
|
||||
; List of enabled triggers in the order of calls.
|
||||
triggers = ahriman.core.gitremote.RemotePullTrigger ahriman.core.report.ReportTrigger ahriman.core.upload.UploadTrigger ahriman.core.gitremote.RemotePushTrigger
|
||||
; List of well-known triggers. Used only for configuration purposes.
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
[loggers]
|
||||
keys = root,http,stderr,boto3,botocore,nose,s3transfer,sql
|
||||
keys = root,http,stderr,boto3,botocore,nose,s3transfer
|
||||
|
||||
[handlers]
|
||||
keys = console_handler,journald_handler,syslog_handler
|
||||
@ -64,8 +64,3 @@ propagate = 0
|
||||
level = INFO
|
||||
qualname = s3transfer
|
||||
propagate = 0
|
||||
|
||||
[logger_sql]
|
||||
level = INFO
|
||||
qualname = sql
|
||||
propagate = 0
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
.TH AHRIMAN "1" "2024\-08\-23" "ahriman" "Generated Python Manual"
|
||||
.TH AHRIMAN "1" "2024\-09\-04" "ahriman" "Generated Python Manual"
|
||||
.SH NAME
|
||||
ahriman
|
||||
.SH SYNOPSIS
|
||||
|
||||
@ -17,4 +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/>.
|
||||
#
|
||||
__version__ = "2.14.0"
|
||||
__version__ = "2.14.1"
|
||||
|
||||
@ -59,7 +59,7 @@ class Handler:
|
||||
repository_id(RepositoryId): repository unique identifier
|
||||
|
||||
Returns:
|
||||
bool: ``True`` on success, ``False`` otherwise
|
||||
bool: True on success, False otherwise
|
||||
"""
|
||||
try:
|
||||
configuration = Configuration.from_path(args.configuration, repository_id)
|
||||
@ -129,7 +129,7 @@ class Handler:
|
||||
check condition and flag and raise ExitCode exception in case if it is enabled and condition match
|
||||
|
||||
Args:
|
||||
enabled(bool): if ``False`` no check will be performed
|
||||
enabled(bool): if False no check will be performed
|
||||
predicate(bool): indicates condition on which exception should be thrown
|
||||
|
||||
Raises:
|
||||
|
||||
@ -59,7 +59,7 @@ class Lock(LazyLogging):
|
||||
>>> configuration = Configuration()
|
||||
>>> try:
|
||||
>>> with Lock(args, RepositoryId("x86_64", "aur-clone"), configuration):
|
||||
>>> do_something()
|
||||
>>> perform_actions()
|
||||
>>> except Exception as exception:
|
||||
>>> handle_exceptions(exception)
|
||||
"""
|
||||
@ -75,7 +75,9 @@ class Lock(LazyLogging):
|
||||
"""
|
||||
self.path: Path | None = None
|
||||
if args.lock is not None:
|
||||
self.path = args.lock.with_stem(f"{args.lock.stem}_{repository_id.id}")
|
||||
self.path = args.lock
|
||||
if not repository_id.is_empty:
|
||||
self.path = self.path.with_stem(f"{args.lock.stem}_{repository_id.id}")
|
||||
if not self.path.is_absolute():
|
||||
# prepend full path to the lock file
|
||||
self.path = Path("/") / "run" / "ahriman" / self.path
|
||||
@ -97,7 +99,7 @@ class Lock(LazyLogging):
|
||||
fd(int): file descriptor:
|
||||
|
||||
Returns:
|
||||
bool: ``True`` in case if file is locked and ``False`` otherwise
|
||||
bool: True in case if file is locked and False otherwise
|
||||
"""
|
||||
try:
|
||||
fcntl.flock(fd, fcntl.LOCK_EX | fcntl.LOCK_NB)
|
||||
@ -119,7 +121,7 @@ class Lock(LazyLogging):
|
||||
watch until lock disappear
|
||||
|
||||
Returns:
|
||||
bool: ``True`` in case if file is locked and ``False`` otherwise
|
||||
bool: True in case if file is locked and False otherwise
|
||||
"""
|
||||
# there are reasons why we are not using inotify here. First of all, if we would use it, it would bring to
|
||||
# race conditions because multiple processes will be notified at the same time. Secondly, it is good library,
|
||||
@ -223,7 +225,7 @@ class Lock(LazyLogging):
|
||||
exc_tb(TracebackType): exception traceback if any
|
||||
|
||||
Returns:
|
||||
Literal[False]: always ``False`` (do not suppress any exception)
|
||||
Literal[False]: always False (do not suppress any exception)
|
||||
"""
|
||||
self.clear()
|
||||
status = BuildStatusEnum.Success if exc_val is None else BuildStatusEnum.Failed
|
||||
|
||||
@ -102,7 +102,7 @@ class PacmanDatabase(SyncHttpClient):
|
||||
local_path(Path): path to locally stored file
|
||||
|
||||
Returns:
|
||||
bool: ``True`` in case if remote file is newer than local file
|
||||
bool: True in case if remote file is newer than local file
|
||||
|
||||
Raises:
|
||||
PacmanError: in case if no last-modified header was found
|
||||
|
||||
@ -96,7 +96,7 @@ class Auth(LazyLogging):
|
||||
password(str | None): entered password
|
||||
|
||||
Returns:
|
||||
bool: ``True`` in case if password matches, ``False`` otherwise
|
||||
bool: True in case if password matches, False otherwise
|
||||
"""
|
||||
del username, password
|
||||
return True
|
||||
@ -109,7 +109,7 @@ class Auth(LazyLogging):
|
||||
username(str): username
|
||||
|
||||
Returns:
|
||||
bool: ``True`` in case if user is known and can be authorized and ``False`` otherwise
|
||||
bool: True in case if user is known and can be authorized and False otherwise
|
||||
"""
|
||||
del username
|
||||
return True
|
||||
@ -124,7 +124,7 @@ class Auth(LazyLogging):
|
||||
context(str | None): URI request path
|
||||
|
||||
Returns:
|
||||
bool: ``True`` in case if user is allowed to do this request and ``False`` otherwise
|
||||
bool: True in case if user is allowed to do this request and False otherwise
|
||||
"""
|
||||
del username, required, context
|
||||
return True
|
||||
|
||||
@ -38,7 +38,7 @@ async def authorized_userid(*args: Any, **kwargs: Any) -> Any:
|
||||
**kwargs(Any): named argument list as provided by authorized_userid function
|
||||
|
||||
Returns:
|
||||
Any: ``None`` in case if no aiohttp_security module found and function call otherwise
|
||||
Any: None in case if no aiohttp_security module found and function call otherwise
|
||||
"""
|
||||
if _has_aiohttp_security:
|
||||
return await aiohttp_security.authorized_userid(*args, **kwargs) # pylint: disable=no-value-for-parameter
|
||||
@ -54,7 +54,7 @@ async def check_authorized(*args: Any, **kwargs: Any) -> Any:
|
||||
**kwargs(Any): named argument list as provided by authorized_userid function
|
||||
|
||||
Returns:
|
||||
Any: ``None`` in case if no aiohttp_security module found and function call otherwise
|
||||
Any: None in case if no aiohttp_security module found and function call otherwise
|
||||
"""
|
||||
if _has_aiohttp_security:
|
||||
return await aiohttp_security.check_authorized(*args, **kwargs) # pylint: disable=no-value-for-parameter
|
||||
@ -70,7 +70,7 @@ async def forget(*args: Any, **kwargs: Any) -> Any:
|
||||
**kwargs(Any): named argument list as provided by authorized_userid function
|
||||
|
||||
Returns:
|
||||
Any: ``None`` in case if no aiohttp_security module found and function call otherwise
|
||||
Any: None in case if no aiohttp_security module found and function call otherwise
|
||||
"""
|
||||
if _has_aiohttp_security:
|
||||
return await aiohttp_security.forget(*args, **kwargs) # pylint: disable=no-value-for-parameter
|
||||
@ -86,7 +86,7 @@ async def remember(*args: Any, **kwargs: Any) -> Any:
|
||||
**kwargs(Any): named argument list as provided by authorized_userid function
|
||||
|
||||
Returns:
|
||||
Any: ``None`` in case if no aiohttp_security module found and function call otherwise
|
||||
Any: None in case if no aiohttp_security module found and function call otherwise
|
||||
"""
|
||||
if _has_aiohttp_security:
|
||||
return await aiohttp_security.remember(*args, **kwargs) # pylint: disable=no-value-for-parameter
|
||||
|
||||
@ -57,7 +57,7 @@ class Mapping(Auth):
|
||||
password(str | None): entered password
|
||||
|
||||
Returns:
|
||||
bool: ``True`` in case if password matches, ``False`` otherwise
|
||||
bool: True in case if password matches, False otherwise
|
||||
"""
|
||||
if password is None:
|
||||
return False # invalid data supplied
|
||||
@ -72,7 +72,7 @@ class Mapping(Auth):
|
||||
username(str): username
|
||||
|
||||
Returns:
|
||||
User | None: user descriptor if username is known and ``None`` otherwise
|
||||
User | None: user descriptor if username is known and None otherwise
|
||||
"""
|
||||
return self.database.user_get(username)
|
||||
|
||||
@ -84,7 +84,7 @@ class Mapping(Auth):
|
||||
username(str): username
|
||||
|
||||
Returns:
|
||||
bool: ``True`` in case if user is known and can be authorized and ``False`` otherwise
|
||||
bool: True in case if user is known and can be authorized and False otherwise
|
||||
"""
|
||||
return username is not None and self.get_user(username) is not None
|
||||
|
||||
@ -98,7 +98,7 @@ class Mapping(Auth):
|
||||
context(str | None): URI request path
|
||||
|
||||
Returns:
|
||||
bool: ``True`` in case if user is allowed to do this request and ``False`` otherwise
|
||||
bool: True in case if user is allowed to do this request and False otherwise
|
||||
"""
|
||||
user = self.get_user(username)
|
||||
return user is not None and user.verify_access(required)
|
||||
|
||||
@ -79,7 +79,7 @@ class PAM(Mapping):
|
||||
password(str | None): entered password
|
||||
|
||||
Returns:
|
||||
bool: ``True`` in case if password matches, ``False`` otherwise
|
||||
bool: True in case if password matches, False otherwise
|
||||
"""
|
||||
if password is None:
|
||||
return False # invalid data supplied
|
||||
@ -101,7 +101,7 @@ class PAM(Mapping):
|
||||
username(str): username
|
||||
|
||||
Returns:
|
||||
bool: ``True`` in case if user is known and can be authorized and ``False`` otherwise
|
||||
bool: True in case if user is known and can be authorized and False otherwise
|
||||
"""
|
||||
try:
|
||||
_ = getpwnam(username)
|
||||
@ -119,7 +119,7 @@ class PAM(Mapping):
|
||||
context(str | None): URI request path
|
||||
|
||||
Returns:
|
||||
bool: ``True`` in case if user is allowed to do this request and ``False`` otherwise
|
||||
bool: True in case if user is allowed to do this request and False otherwise
|
||||
"""
|
||||
# this method is basically inverted, first we check overrides in database and then fallback to the PAM logic
|
||||
if (user := self.get_user(username)) is not None:
|
||||
|
||||
@ -138,7 +138,7 @@ class Sources(LazyLogging):
|
||||
sources_dir(Path): local path to git repository
|
||||
|
||||
Returns:
|
||||
bool: ``True`` in case if there is any remote and false otherwise
|
||||
bool: True in case if there is any remote and false otherwise
|
||||
"""
|
||||
instance = Sources()
|
||||
remotes = check_output(*instance.git(), "remote", cwd=sources_dir, logger=instance.logger)
|
||||
@ -261,7 +261,7 @@ class Sources(LazyLogging):
|
||||
commit_author(tuple[str, str] | None, optional): optional commit author if any (Default value = None)
|
||||
|
||||
Returns:
|
||||
bool: ``True`` in case if changes have been committed and ``False`` otherwise
|
||||
bool: True in case if changes have been committed and False otherwise
|
||||
"""
|
||||
if not self.has_changes(sources_dir):
|
||||
return False # nothing to commit
|
||||
@ -351,7 +351,7 @@ class Sources(LazyLogging):
|
||||
sources_dir(Path): local path to git repository
|
||||
|
||||
Returns:
|
||||
bool: ``True`` if there are uncommitted changes and ``False`` otherwise
|
||||
bool: True if there are uncommitted changes and False otherwise
|
||||
"""
|
||||
# there is --exit-code argument to diff, however, there might be other process errors
|
||||
changes = check_output(*self.git(), "diff", "--cached", "--name-only", cwd=sources_dir, logger=self.logger)
|
||||
|
||||
@ -46,8 +46,7 @@ class Configuration(configparser.RawConfigParser):
|
||||
Examples:
|
||||
Configuration class provides additional method in order to handle application configuration. Since this class is
|
||||
derived from built-in :class:`configparser.RawConfigParser` class, the same flow is applicable here.
|
||||
Nevertheless, it is recommended to use :func:`from_path()` class method which also calls initialization
|
||||
methods::
|
||||
Nevertheless, it is recommended to use :func:`from_path` class method which also calls initialization methods::
|
||||
|
||||
>>> from pathlib import Path
|
||||
>>>
|
||||
@ -58,7 +57,7 @@ class Configuration(configparser.RawConfigParser):
|
||||
The configuration instance loaded in this way will contain only sections which are defined for the specified
|
||||
architecture according to the merge rules. Moreover, the architecture names will be removed from section names.
|
||||
|
||||
In order to get current settings, the :func:`check_loaded()` method can be used. This method will raise an
|
||||
In order to get current settings, the :func:`check_loaded` method can be used. This method will raise an
|
||||
:exc:`ahriman.core.exceptions.InitializeError` in case if configuration was not yet loaded::
|
||||
|
||||
>>> path, repository_id = configuration.check_loaded()
|
||||
@ -345,8 +344,7 @@ class Configuration(configparser.RawConfigParser):
|
||||
|
||||
def set_option(self, section: str, option: str, value: str) -> None:
|
||||
"""
|
||||
set option. Unlike default :func:`configparser.RawConfigParser.set()` it also creates section if
|
||||
it does not exist
|
||||
set option. Unlike default :func:`configparser.RawConfigParser.set` it also creates section if it does not exist
|
||||
|
||||
Args:
|
||||
section(str): section name
|
||||
|
||||
@ -169,6 +169,14 @@ CONFIGURATION_SCHEMA: ConfigurationSchema = {
|
||||
"build": {
|
||||
"type": "dict",
|
||||
"schema": {
|
||||
"allowed_scan_paths": {
|
||||
"type": "list",
|
||||
"coerce": "list",
|
||||
"schema": {
|
||||
"type": "path",
|
||||
"coerce": "absolute_path",
|
||||
},
|
||||
},
|
||||
"archbuild_flags": {
|
||||
"type": "list",
|
||||
"coerce": "list",
|
||||
@ -177,6 +185,14 @@ CONFIGURATION_SCHEMA: ConfigurationSchema = {
|
||||
"empty": False,
|
||||
},
|
||||
},
|
||||
"blacklisted_scan_paths": {
|
||||
"type": "list",
|
||||
"coerce": "list",
|
||||
"schema": {
|
||||
"type": "path",
|
||||
"coerce": "absolute_path",
|
||||
},
|
||||
},
|
||||
"build_command": {
|
||||
"type": "string",
|
||||
"required": True,
|
||||
@ -210,14 +226,6 @@ CONFIGURATION_SCHEMA: ConfigurationSchema = {
|
||||
"empty": False,
|
||||
},
|
||||
},
|
||||
"scan_paths": {
|
||||
"type": "list",
|
||||
"coerce": "list",
|
||||
"schema": {
|
||||
"type": "string",
|
||||
"empty": False,
|
||||
},
|
||||
},
|
||||
"triggers": {
|
||||
"type": "list",
|
||||
"coerce": "list",
|
||||
|
||||
@ -149,7 +149,7 @@ class Validator(RootValidator):
|
||||
check if paths exists
|
||||
|
||||
Args:
|
||||
constraint(bool): ``True`` in case if path must exist and ``False`` otherwise
|
||||
constraint(bool): True in case if path must exist and False otherwise
|
||||
field(str): field name to be checked
|
||||
value(Path): value to be checked
|
||||
|
||||
|
||||
@ -1,38 +0,0 @@
|
||||
#
|
||||
# Copyright (c) 2021-2024 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 = [
|
||||
"""
|
||||
create table auditlog (
|
||||
created integer not null,
|
||||
repository text not null,
|
||||
event text not null,
|
||||
object_id text not null,
|
||||
message text,
|
||||
data json
|
||||
)
|
||||
""",
|
||||
"""
|
||||
create index auditlog_created_repository_event_object_id
|
||||
on auditlog (created, repository, event, object_id)
|
||||
""",
|
||||
]
|
||||
@ -21,7 +21,6 @@ from ahriman.core.database.operations.auth_operations import AuthOperations
|
||||
from ahriman.core.database.operations.build_operations import BuildOperations
|
||||
from ahriman.core.database.operations.changes_operations import ChangesOperations
|
||||
from ahriman.core.database.operations.dependencies_operations import DependenciesOperations
|
||||
from ahriman.core.database.operations.event_operations import EventOperations
|
||||
from ahriman.core.database.operations.logs_operations import LogsOperations
|
||||
from ahriman.core.database.operations.package_operations import PackageOperations
|
||||
from ahriman.core.database.operations.patch_operations import PatchOperations
|
||||
|
||||
@ -39,7 +39,7 @@ class DependenciesOperations(Operations):
|
||||
repository_id(RepositoryId, optional): repository unique identifier override (Default value = None)
|
||||
|
||||
Returns:
|
||||
Dependencies: dependencies for the package base if available
|
||||
Dependencies: changes for the package base if available
|
||||
"""
|
||||
repository_id = repository_id or self._repository_id
|
||||
|
||||
|
||||
@ -1,101 +0,0 @@
|
||||
#
|
||||
# Copyright (c) 2021-2024 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 sqlite3 import Connection
|
||||
|
||||
from ahriman.core.database.operations.operations import Operations
|
||||
from ahriman.models.event import Event, EventType
|
||||
from ahriman.models.repository_id import RepositoryId
|
||||
|
||||
|
||||
class EventOperations(Operations):
|
||||
"""
|
||||
operations for audit log table
|
||||
"""
|
||||
|
||||
def event_get(self, event: str | EventType | None = None, object_id: str | None = None,
|
||||
limit: int = -1, offset: int = 0, repository_id: RepositoryId | None = None) -> list[Event]:
|
||||
"""
|
||||
get list of events with filters applied
|
||||
|
||||
Args:
|
||||
event(str | EventType | None, optional): filter by event type (Default value = None)
|
||||
object_id(str | None, optional): filter by event object (Default value = None)
|
||||
limit(int, optional): limit records to the specified count, -1 means unlimited (Default value = -1)
|
||||
offset(int, optional): records offset (Default value = 0)
|
||||
repository_id(RepositoryId, optional): repository unique identifier override (Default value = None)
|
||||
|
||||
Returns:
|
||||
list[Event]: list of audit log events
|
||||
"""
|
||||
repository_id = repository_id or self._repository_id
|
||||
|
||||
def run(connection: Connection) -> list[Event]:
|
||||
return [
|
||||
Event.from_json(row)
|
||||
for row in connection.execute(
|
||||
"""
|
||||
select created, event, object_id, message, data from (
|
||||
select * from auditlog
|
||||
where (:event is null or event = :event)
|
||||
and (:object_id is null or object_id = :object_id)
|
||||
and repository = :repository
|
||||
order by created desc limit :limit offset :offset
|
||||
) order by created asc
|
||||
""",
|
||||
{
|
||||
"event": event,
|
||||
"object_id": object_id,
|
||||
"repository": repository_id.id,
|
||||
"limit": limit,
|
||||
"offset": offset,
|
||||
}
|
||||
)
|
||||
]
|
||||
|
||||
return self.with_connection(run)
|
||||
|
||||
def event_insert(self, event: Event, repository_id: RepositoryId | None = None) -> None:
|
||||
"""
|
||||
insert audit log event
|
||||
|
||||
Args:
|
||||
event(Event): event to insert
|
||||
repository_id(RepositoryId, optional): repository unique identifier override (Default value = None)
|
||||
"""
|
||||
repository_id = repository_id or self._repository_id
|
||||
|
||||
def run(connection: Connection) -> None:
|
||||
connection.execute(
|
||||
"""
|
||||
insert into auditlog
|
||||
(created, repository, event, object_id, message, data)
|
||||
values
|
||||
(:created, :repository, :event, :object_id, :message, :data)
|
||||
""",
|
||||
{
|
||||
"created": event.created,
|
||||
"repository": repository_id.id,
|
||||
"event": event.event,
|
||||
"object_id": event.object_id,
|
||||
"message": event.message,
|
||||
"data": event.data,
|
||||
})
|
||||
|
||||
return self.with_connection(run, commit=True)
|
||||
@ -50,11 +50,9 @@ class LogsOperations(Operations):
|
||||
(row["created"], row["record"])
|
||||
for row in connection.execute(
|
||||
"""
|
||||
select created, record from (
|
||||
select * from logs
|
||||
where package_base = :package_base and repository = :repository
|
||||
order by created desc limit :limit offset :offset
|
||||
) order by created asc
|
||||
select created, record from logs
|
||||
where package_base = :package_base and repository = :repository
|
||||
order by created limit :limit offset :offset
|
||||
""",
|
||||
{
|
||||
"package_base": package_base,
|
||||
|
||||
@ -46,22 +46,11 @@ class Operations(LazyLogging):
|
||||
Args:
|
||||
path(Path): path to the database file
|
||||
repository_id(RepositoryId): repository unique identifier
|
||||
repository_paths(RepositoryPaths): repository paths
|
||||
"""
|
||||
self.path = path
|
||||
self._repository_id = repository_id
|
||||
self._repository_paths = repository_paths
|
||||
|
||||
@property
|
||||
def logger_name(self) -> str:
|
||||
"""
|
||||
extract logger name for the class
|
||||
|
||||
Returns:
|
||||
str: logger name override
|
||||
"""
|
||||
return "sql"
|
||||
|
||||
@staticmethod
|
||||
def factory(cursor: sqlite3.Cursor, row: tuple[Any, ...]) -> dict[str, Any]:
|
||||
"""
|
||||
@ -85,13 +74,12 @@ class Operations(LazyLogging):
|
||||
|
||||
Args:
|
||||
query(Callable[[Connection], T]): function to be called with connection
|
||||
commit(bool, optional): if ``True`` commit() will be called on success (Default value = False)
|
||||
commit(bool, optional): if True commit() will be called on success (Default value = False)
|
||||
|
||||
Returns:
|
||||
T: result of the ``query`` call
|
||||
"""
|
||||
with sqlite3.connect(self.path, detect_types=sqlite3.PARSE_DECLTYPES) as connection:
|
||||
connection.set_trace_callback(self.logger.debug)
|
||||
connection.row_factory = self.factory
|
||||
result = query(connection)
|
||||
if commit:
|
||||
|
||||
@ -26,7 +26,8 @@ from typing import Self
|
||||
from ahriman.core.configuration import Configuration
|
||||
from ahriman.core.database.migrations import Migrations
|
||||
from ahriman.core.database.operations import AuthOperations, BuildOperations, ChangesOperations, \
|
||||
DependenciesOperations, EventOperations, LogsOperations, PackageOperations, PatchOperations
|
||||
DependenciesOperations, LogsOperations, PackageOperations, PatchOperations
|
||||
from ahriman.models.repository_id import RepositoryId
|
||||
|
||||
|
||||
# pylint: disable=too-many-ancestors
|
||||
@ -35,7 +36,6 @@ class SQLite(
|
||||
BuildOperations,
|
||||
ChangesOperations,
|
||||
DependenciesOperations,
|
||||
EventOperations,
|
||||
LogsOperations,
|
||||
PackageOperations,
|
||||
PatchOperations):
|
||||
@ -103,23 +103,26 @@ class SQLite(
|
||||
self.with_connection(lambda connection: Migrations.migrate(connection, configuration))
|
||||
paths.chown(self.path)
|
||||
|
||||
def package_clear(self, package_base: str) -> None:
|
||||
def package_clear(self, package_base: str, repository_id: RepositoryId | None = None) -> None:
|
||||
"""
|
||||
completely remove package from all tables
|
||||
|
||||
Args:
|
||||
package_base(str): package base to remove
|
||||
repository_id(RepositoryId, optional): repository unique identifier override (Default value = None)
|
||||
|
||||
Examples:
|
||||
This method completely removes the package from all tables and must be used, e.g. on package removal::
|
||||
|
||||
>>> database.package_clear("ahriman")
|
||||
"""
|
||||
self.build_queue_clear(package_base)
|
||||
self.patches_remove(package_base, [])
|
||||
self.logs_remove(package_base, None)
|
||||
self.changes_remove(package_base)
|
||||
self.dependencies_remove(package_base)
|
||||
self.build_queue_clear(package_base, repository_id)
|
||||
self.patches_remove(package_base, None)
|
||||
self.logs_remove(package_base, None, repository_id)
|
||||
self.changes_remove(package_base, repository_id)
|
||||
self.dependencies_remove(package_base, repository_id)
|
||||
|
||||
self.package_remove(package_base, repository_id)
|
||||
|
||||
# remove local cache too
|
||||
self._repository_paths.tree_clear(package_base)
|
||||
|
||||
@ -32,7 +32,7 @@ class BuildPrinter(StringPrinter):
|
||||
|
||||
Args:
|
||||
package(Package): built package
|
||||
is_success(bool): ``True`` in case if build has success status and ``False`` otherwise
|
||||
is_success(bool): True in case if build has success status and False otherwise
|
||||
use_utf(bool): use utf instead of normal symbols
|
||||
"""
|
||||
StringPrinter.__init__(self, f"{self.sign(is_success, use_utf)} {package.base}")
|
||||
@ -43,7 +43,7 @@ class BuildPrinter(StringPrinter):
|
||||
generate sign according to settings
|
||||
|
||||
Args:
|
||||
is_success(bool): ``True`` in case if build has success status and ``False`` otherwise
|
||||
is_success(bool): True in case if build has success status and False otherwise
|
||||
use_utf(bool): use utf instead of normal symbols
|
||||
|
||||
Returns:
|
||||
|
||||
@ -57,7 +57,7 @@ class ChangesPrinter(Printer):
|
||||
generate entry title from content
|
||||
|
||||
Returns:
|
||||
str | None: content title if it can be generated and ``None`` otherwise
|
||||
str | None: content title if it can be generated and None otherwise
|
||||
"""
|
||||
if self.changes.is_empty:
|
||||
return None
|
||||
|
||||
@ -63,7 +63,7 @@ class Printer:
|
||||
generate entry title from content
|
||||
|
||||
Returns:
|
||||
str | None: content title if it can be generated and ``None`` otherwise
|
||||
str | None: content title if it can be generated and None otherwise
|
||||
"""
|
||||
return None
|
||||
|
||||
|
||||
@ -42,6 +42,6 @@ class StringPrinter(Printer):
|
||||
generate entry title from content
|
||||
|
||||
Returns:
|
||||
str | None: content title if it can be generated and ``None`` otherwise
|
||||
str | None: content title if it can be generated and None otherwise
|
||||
"""
|
||||
return self.content
|
||||
|
||||
@ -85,7 +85,7 @@ class SyncHttpClient(LazyLogging):
|
||||
exception(requests.RequestException): exception raised
|
||||
|
||||
Returns:
|
||||
str: text of the response if it is not ``None`` and empty string otherwise
|
||||
str: text of the response if it is not None and empty string otherwise
|
||||
"""
|
||||
result: str = exception.response.text if exception.response is not None else ""
|
||||
return result
|
||||
|
||||
@ -29,8 +29,7 @@ from ahriman.models.repository_id import RepositoryId
|
||||
class HttpLogHandler(logging.Handler):
|
||||
"""
|
||||
handler for the http logging. Because default :class:`logging.handlers.HTTPHandler` does not support cookies
|
||||
authorization, we have to implement own handler which overrides the :func:`logging.handlers.HTTPHandler.emit()`
|
||||
method
|
||||
authorization, we have to implement own handler which overrides the :func:`logging.handlers.HTTPHandler.emit` method
|
||||
|
||||
Attributes:
|
||||
reporter(Client): build status reporter instance
|
||||
|
||||
@ -38,8 +38,8 @@ class JinjaTemplate:
|
||||
|
||||
* homepage - link to homepage, string, optional
|
||||
* link_path - prefix fo packages to download, string, required
|
||||
* has_package_signed - ``True`` in case if package sign enabled, ``False`` otherwise, required
|
||||
* has_repo_signed - ``True`` in case if repository database sign enabled, ``False`` otherwise, required
|
||||
* has_package_signed - True in case if package sign enabled, False otherwise, required
|
||||
* has_repo_signed - True in case if repository database sign enabled, False otherwise, required
|
||||
* packages - sorted list of packages properties, required
|
||||
* architecture, string
|
||||
* archive_size, pretty printed size, string
|
||||
|
||||
@ -78,7 +78,7 @@ class RemoteCall(Report):
|
||||
process_id(str): remote process id
|
||||
|
||||
Returns:
|
||||
bool: ``True`` in case if remote process is alive and ``False`` otherwise
|
||||
bool: True in case if remote process is alive and False otherwise
|
||||
"""
|
||||
try:
|
||||
response = self.client.make_request("GET", f"{self.client.address}/api/v1/service/process/{process_id}")
|
||||
|
||||
@ -1,84 +0,0 @@
|
||||
#
|
||||
# Copyright (c) 2021-2024 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 contextlib
|
||||
|
||||
from typing import Generator
|
||||
|
||||
from ahriman.core.status import Client
|
||||
from ahriman.models.event import Event, EventType
|
||||
from ahriman.models.metrics_timer import MetricsTimer
|
||||
|
||||
|
||||
class EventLogger:
|
||||
"""
|
||||
wrapper for logging events
|
||||
|
||||
Attributes:
|
||||
reporter(Client): build status reporter instance
|
||||
"""
|
||||
|
||||
reporter: Client
|
||||
|
||||
def event(self, package_base: str, event: EventType, message: str | None = None) -> None:
|
||||
"""
|
||||
log single event. For timed events use context manager :func:`in_event()` instead
|
||||
|
||||
Args:
|
||||
package_base(str): package base name
|
||||
event(EventType): event type to be logged on success action
|
||||
message(str | None, optional): optional message describing the action (Default value = None)
|
||||
|
||||
Examples:
|
||||
This method must be used as simple wrapper for :class:`ahriman.core.status.Client` methods, e.g.::
|
||||
|
||||
>>> do_something()
|
||||
>>> self.event(package_base, EventType.PackageUpdated)
|
||||
"""
|
||||
self.reporter.event_add(Event(event, package_base, message))
|
||||
|
||||
@contextlib.contextmanager
|
||||
def in_event(self, package_base: str, event: EventType, message: str | None = None,
|
||||
failure: EventType | None = None) -> Generator[None, None, None]:
|
||||
"""
|
||||
perform action in package context and log event with time elapsed
|
||||
|
||||
Args:
|
||||
package_base(str): package base name
|
||||
event(EventType): event type to be logged on success action
|
||||
message(str | None, optional): optional message describing the action (Default value = None)
|
||||
failure(EventType | None, optional): event type to be logged on exception (Default value = None)
|
||||
|
||||
Examples:
|
||||
This method must be used to perform action in context with time measurement::
|
||||
|
||||
>>> with self.in_event(package_base, EventType.PackageUpdated):
|
||||
>>> do_something()
|
||||
|
||||
Additional parameter ``failure`` can be set in order to emit an event on exception occured. If none set
|
||||
(default), then no event will be recorded on exception
|
||||
"""
|
||||
with MetricsTimer() as timer:
|
||||
try:
|
||||
yield
|
||||
self.reporter.event_add(Event(event, package_base, message, took=timer.elapsed))
|
||||
except Exception:
|
||||
if failure is not None:
|
||||
self.reporter.event_add(Event(failure, package_base, took=timer.elapsed))
|
||||
raise
|
||||
@ -23,14 +23,13 @@ from collections.abc import Iterable
|
||||
from pathlib import Path
|
||||
from tempfile import TemporaryDirectory
|
||||
|
||||
from ahriman.core.build_tools.package_archive import PackageArchive
|
||||
from ahriman.core.build_tools.task import Task
|
||||
from ahriman.core.repository.cleaner import Cleaner
|
||||
from ahriman.core.repository.package_info import PackageInfo
|
||||
from ahriman.core.utils import safe_filename
|
||||
from ahriman.models.changes import Changes
|
||||
from ahriman.models.event import EventType
|
||||
from ahriman.models.package import Package
|
||||
from ahriman.models.package_archive import PackageArchive
|
||||
from ahriman.models.package_description import PackageDescription
|
||||
from ahriman.models.packagers import Packagers
|
||||
from ahriman.models.result import Result
|
||||
@ -76,17 +75,16 @@ class Executor(PackageInfo, Cleaner):
|
||||
with self.in_package_context(single.base, local_versions.get(single.base)), \
|
||||
TemporaryDirectory(ignore_cleanup_errors=True) as dir_name:
|
||||
try:
|
||||
with self.in_event(single.base, EventType.PackageUpdated, failure=EventType.PackageUpdateFailed):
|
||||
packager = self.packager(packagers, single.base)
|
||||
last_commit_sha = build_single(single, Path(dir_name), packager.packager_id)
|
||||
# clear changes and update commit hash
|
||||
self.reporter.package_changes_update(single.base, Changes(last_commit_sha))
|
||||
# update dependencies list
|
||||
package_archive = PackageArchive(self.paths.build_root, single, self.pacman, self.scan_paths)
|
||||
dependencies = package_archive.depends_on()
|
||||
self.reporter.package_dependencies_update(single.base, dependencies)
|
||||
# update result set
|
||||
result.add_updated(single)
|
||||
packager = self.packager(packagers, single.base)
|
||||
last_commit_sha = build_single(single, Path(dir_name), packager.packager_id)
|
||||
# clear changes and update commit hash
|
||||
self.reporter.package_changes_update(single.base, Changes(last_commit_sha))
|
||||
# update dependencies list
|
||||
package_archive = PackageArchive(self.paths.build_directory, single, self.pacman, self.scan_paths)
|
||||
dependencies = package_archive.depends_on()
|
||||
self.reporter.package_dependencies_update(single.base, dependencies)
|
||||
# update result set
|
||||
result.add_updated(single)
|
||||
except Exception:
|
||||
self.reporter.set_failed(single.base)
|
||||
result.add_failed(single)
|
||||
@ -106,8 +104,7 @@ class Executor(PackageInfo, Cleaner):
|
||||
"""
|
||||
def remove_base(package_base: str) -> None:
|
||||
try:
|
||||
with self.in_event(package_base, EventType.PackageRemoved):
|
||||
self.reporter.package_remove(package_base)
|
||||
self.reporter.package_remove(package_base)
|
||||
except Exception:
|
||||
self.logger.exception("could not remove base %s", package_base)
|
||||
|
||||
|
||||
@ -22,7 +22,6 @@ from ahriman.core.alpm.repo import Repo
|
||||
from ahriman.core.configuration import Configuration
|
||||
from ahriman.core.database import SQLite
|
||||
from ahriman.core.log import LazyLogging
|
||||
from ahriman.core.repository.event_logger import EventLogger
|
||||
from ahriman.core.sign.gpg import GPG
|
||||
from ahriman.core.status import Client
|
||||
from ahriman.core.triggers import TriggerLoader
|
||||
@ -35,7 +34,7 @@ from ahriman.models.user import User
|
||||
from ahriman.models.user_access import UserAccess
|
||||
|
||||
|
||||
class RepositoryProperties(EventLogger, LazyLogging):
|
||||
class RepositoryProperties(LazyLogging):
|
||||
"""
|
||||
repository internal objects holder
|
||||
|
||||
@ -81,7 +80,10 @@ class RepositoryProperties(EventLogger, LazyLogging):
|
||||
self.reporter = Client.load(repository_id, configuration, database, report=report)
|
||||
self.triggers = TriggerLoader.load(repository_id, configuration)
|
||||
|
||||
self.scan_paths = ScanPaths(configuration.getlist("build", "scan_paths", fallback=[]))
|
||||
self.scan_paths = ScanPaths(
|
||||
allowed_paths=configuration.getpathlist("build", "allowed_scan_paths", fallback=[]),
|
||||
blacklisted_paths=configuration.getpathlist("build", "blacklisted_scan_paths", fallback=[]),
|
||||
)
|
||||
|
||||
@property
|
||||
def architecture(self) -> str:
|
||||
|
||||
@ -23,7 +23,6 @@ from ahriman.core.build_tools.sources import Sources
|
||||
from ahriman.core.exceptions import UnknownPackageError
|
||||
from ahriman.core.repository.cleaner import Cleaner
|
||||
from ahriman.core.repository.package_info import PackageInfo
|
||||
from ahriman.models.event import EventType
|
||||
from ahriman.models.package import Package
|
||||
from ahriman.models.package_source import PackageSource
|
||||
from ahriman.models.remote_source import RemoteSource
|
||||
@ -72,7 +71,6 @@ class UpdateHandler(PackageInfo, Cleaner):
|
||||
vcs_allowed_age=self.vcs_allowed_age,
|
||||
calculate_version=vcs):
|
||||
self.reporter.set_pending(local.base)
|
||||
self.event(local.base, EventType.PackageOutdated, "Remote version is newer than local")
|
||||
result.append(remote)
|
||||
except Exception:
|
||||
self.reporter.set_failed(local.base)
|
||||
@ -100,8 +98,8 @@ class UpdateHandler(PackageInfo, Cleaner):
|
||||
return files
|
||||
|
||||
result: list[Package] = []
|
||||
for local in self.packages(filter_packages):
|
||||
dependencies = self.reporter.package_dependencies_get(local.base)
|
||||
for package in self.packages(filter_packages):
|
||||
dependencies = self.reporter.package_dependencies_get(package.base)
|
||||
if not dependencies.paths:
|
||||
continue # skip check if no package dependencies found
|
||||
|
||||
@ -114,10 +112,7 @@ class UpdateHandler(PackageInfo, Cleaner):
|
||||
continue
|
||||
|
||||
# there are no packages found in filesystem with the same paths
|
||||
self.reporter.set_pending(local.base)
|
||||
self.event(local.base, EventType.PackageOutdated, "Implicit dependencies are broken")
|
||||
result.append(local)
|
||||
|
||||
result.append(package)
|
||||
break
|
||||
|
||||
return result
|
||||
@ -158,7 +153,6 @@ class UpdateHandler(PackageInfo, Cleaner):
|
||||
vcs_allowed_age=self.vcs_allowed_age,
|
||||
calculate_version=vcs):
|
||||
self.reporter.set_pending(local.base)
|
||||
self.event(local.base, EventType.PackageOutdated, "Locally pulled sources are outdated")
|
||||
result.append(remote)
|
||||
except Exception:
|
||||
self.logger.exception("could not process package at %s", cache_dir)
|
||||
@ -182,7 +176,6 @@ class UpdateHandler(PackageInfo, Cleaner):
|
||||
self.reporter.set_unknown(local)
|
||||
else:
|
||||
self.reporter.set_pending(local.base)
|
||||
self.event(local.base, EventType.PackageOutdated, "Manual update is requested")
|
||||
except Exception:
|
||||
self.logger.exception("could not load packages from database")
|
||||
self.clear_queue()
|
||||
|
||||
@ -20,6 +20,7 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import time
|
||||
import uuid
|
||||
|
||||
from collections.abc import Callable, Iterable
|
||||
@ -27,7 +28,6 @@ from multiprocessing import Process, Queue
|
||||
from threading import Lock, Thread
|
||||
|
||||
from ahriman.core.log import LazyLogging
|
||||
from ahriman.models.metrics_timer import MetricsTimer
|
||||
from ahriman.models.pkgbuild_patch import PkgbuildPatch
|
||||
from ahriman.models.process_status import ProcessStatus
|
||||
from ahriman.models.repository_id import RepositoryId
|
||||
@ -72,7 +72,7 @@ class Spawn(Thread, LazyLogging):
|
||||
value(bool): command line argument value
|
||||
|
||||
Returns:
|
||||
str: if ``value`` is ``True``, then returns positive flag and negative otherwise
|
||||
str: if ``value`` is True, then returns positive flag and negative otherwise
|
||||
"""
|
||||
return name if value else f"no-{name}"
|
||||
|
||||
@ -90,9 +90,11 @@ class Spawn(Thread, LazyLogging):
|
||||
process_id(str): process unique identifier
|
||||
queue(Queue[ProcessStatus | None]): output queue
|
||||
"""
|
||||
with MetricsTimer() as timer:
|
||||
result = callback(args, repository_id)
|
||||
consumed_time = timer.elapsed
|
||||
start_time = time.monotonic()
|
||||
result = callback(args, repository_id)
|
||||
stop_time = time.monotonic()
|
||||
|
||||
consumed_time = int(1000 * (stop_time - start_time))
|
||||
|
||||
queue.put(ProcessStatus(process_id, result, consumed_time))
|
||||
|
||||
@ -151,7 +153,7 @@ class Spawn(Thread, LazyLogging):
|
||||
process_id(str): process id to be checked as returned by :func:`_spawn_process()`
|
||||
|
||||
Returns:
|
||||
bool: ``True`` in case if process still counts as active and ``False`` otherwise
|
||||
bool: True in case if process still counts as active and False otherwise
|
||||
"""
|
||||
with self._lock:
|
||||
return process_id in self.active
|
||||
@ -269,7 +271,7 @@ class Spawn(Thread, LazyLogging):
|
||||
"""
|
||||
for terminated in iter(self.queue.get, None):
|
||||
self.logger.info("process %s has been terminated with status %s, consumed time %ss",
|
||||
terminated.process_id, terminated.status, terminated.consumed_time)
|
||||
terminated.process_id, terminated.status, terminated.consumed_time / 1000)
|
||||
|
||||
with self._lock:
|
||||
process = self.active.pop(terminated.process_id, None)
|
||||
|
||||
@ -25,7 +25,6 @@ from ahriman.core.database import SQLite
|
||||
from ahriman.models.build_status import BuildStatus, BuildStatusEnum
|
||||
from ahriman.models.changes import Changes
|
||||
from ahriman.models.dependencies import Dependencies
|
||||
from ahriman.models.event import Event, EventType
|
||||
from ahriman.models.internal_status import InternalStatus
|
||||
from ahriman.models.log_record_id import LogRecordId
|
||||
from ahriman.models.package import Package
|
||||
@ -80,37 +79,6 @@ class Client:
|
||||
|
||||
return make_local_client()
|
||||
|
||||
def event_add(self, event: Event) -> None:
|
||||
"""
|
||||
create new event
|
||||
|
||||
Args:
|
||||
event(Event): audit log event
|
||||
|
||||
Raises:
|
||||
NotImplementedError: not implemented method
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
def event_get(self, event: str | EventType | None, object_id: str | None,
|
||||
limit: int = -1, offset: int = 0) -> list[Event]:
|
||||
"""
|
||||
retrieve list of events
|
||||
|
||||
Args:
|
||||
event(str | EventType | None): filter by event type
|
||||
object_id(str | None): filter by event object
|
||||
limit(int, optional): limit records to the specified count, -1 means unlimited (Default value = -1)
|
||||
offset(int, optional): records offset (Default value = 0)
|
||||
|
||||
Returns:
|
||||
list[Event]: list of audit log events
|
||||
|
||||
Raises:
|
||||
NotImplementedError: not implemented method
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
def package_changes_get(self, package_base: str) -> Changes:
|
||||
"""
|
||||
get package changes
|
||||
@ -216,7 +184,7 @@ class Client:
|
||||
|
||||
Args:
|
||||
package_base(str): package base
|
||||
version(str | None): package version to remove logs. If ``None`` is set, all logs will be removed
|
||||
version(str | None): package version to remove logs. If None set, all logs will be removed
|
||||
|
||||
Raises:
|
||||
NotImplementedError: not implemented method
|
||||
@ -245,7 +213,7 @@ class Client:
|
||||
|
||||
Args:
|
||||
package_base(str): package base to update
|
||||
variable(str | None): patch name. If ``None`` is set, all patches will be removed
|
||||
variable(str | None): patch name. If None set, all patches will be removed
|
||||
|
||||
Raises:
|
||||
NotImplementedError: not implemented method
|
||||
|
||||
@ -22,7 +22,6 @@ from ahriman.core.status import Client
|
||||
from ahriman.models.build_status import BuildStatus, BuildStatusEnum
|
||||
from ahriman.models.changes import Changes
|
||||
from ahriman.models.dependencies import Dependencies
|
||||
from ahriman.models.event import Event, EventType
|
||||
from ahriman.models.log_record_id import LogRecordId
|
||||
from ahriman.models.package import Package
|
||||
from ahriman.models.pkgbuild_patch import PkgbuildPatch
|
||||
@ -49,31 +48,6 @@ class LocalClient(Client):
|
||||
self.database = database
|
||||
self.repository_id = repository_id
|
||||
|
||||
def event_add(self, event: Event) -> None:
|
||||
"""
|
||||
create new event
|
||||
|
||||
Args:
|
||||
event(Event): audit log event
|
||||
"""
|
||||
self.database.event_insert(event, self.repository_id)
|
||||
|
||||
def event_get(self, event: str | EventType | None, object_id: str | None,
|
||||
limit: int = -1, offset: int = 0) -> list[Event]:
|
||||
"""
|
||||
retrieve list of events
|
||||
|
||||
Args:
|
||||
event(str | EventType | None): filter by event type
|
||||
object_id(str | None): filter by event object
|
||||
limit(int, optional): limit records to the specified count, -1 means unlimited (Default value = -1)
|
||||
offset(int, optional): records offset (Default value = 0)
|
||||
|
||||
Returns:
|
||||
list[Event]: list of audit log events
|
||||
"""
|
||||
return self.database.event_get(event, object_id, limit, offset, self.repository_id)
|
||||
|
||||
def package_changes_get(self, package_base: str) -> Changes:
|
||||
"""
|
||||
get package changes
|
||||
@ -164,7 +138,7 @@ class LocalClient(Client):
|
||||
|
||||
Args:
|
||||
package_base(str): package base
|
||||
version(str | None): package version to remove logs. If ``None`` is set, all logs will be removed
|
||||
version(str | None): package version to remove logs. If None set, all logs will be removed
|
||||
"""
|
||||
self.database.logs_remove(package_base, version, self.repository_id)
|
||||
|
||||
@ -188,7 +162,7 @@ class LocalClient(Client):
|
||||
|
||||
Args:
|
||||
package_base(str): package base to update
|
||||
variable(str | None): patch name. If ``None`` is set, all patches will be removed
|
||||
variable(str | None): patch name. If None set, all patches will be removed
|
||||
"""
|
||||
variables = [variable] if variable is not None else None
|
||||
self.database.patches_remove(package_base, variables)
|
||||
@ -210,7 +184,7 @@ class LocalClient(Client):
|
||||
Args:
|
||||
package_base(str): package base to remove
|
||||
"""
|
||||
self.database.package_clear(package_base)
|
||||
self.database.package_clear(package_base, self.repository_id)
|
||||
|
||||
def package_status_update(self, package_base: str, status: BuildStatusEnum) -> None:
|
||||
"""
|
||||
|
||||
@ -27,7 +27,6 @@ from ahriman.core.status import Client
|
||||
from ahriman.models.build_status import BuildStatus, BuildStatusEnum
|
||||
from ahriman.models.changes import Changes
|
||||
from ahriman.models.dependencies import Dependencies
|
||||
from ahriman.models.event import Event, EventType
|
||||
from ahriman.models.log_record_id import LogRecordId
|
||||
from ahriman.models.package import Package
|
||||
from ahriman.models.pkgbuild_patch import PkgbuildPatch
|
||||
@ -69,10 +68,6 @@ class Watcher(LazyLogging):
|
||||
with self._lock:
|
||||
return list(self._known.values())
|
||||
|
||||
event_add: Callable[[Event], None]
|
||||
|
||||
event_get: Callable[[str | EventType | None, str | None, int, int], list[Event]]
|
||||
|
||||
def load(self) -> None:
|
||||
"""
|
||||
load packages from local database
|
||||
@ -145,7 +140,6 @@ class Watcher(LazyLogging):
|
||||
with self._lock:
|
||||
self._known.pop(package_base, None)
|
||||
self.client.package_remove(package_base)
|
||||
self.package_logs_remove(package_base, None)
|
||||
|
||||
def package_status_update(self, package_base: str, status: BuildStatusEnum) -> None:
|
||||
"""
|
||||
|
||||
@ -27,7 +27,6 @@ from ahriman.core.status import Client
|
||||
from ahriman.models.build_status import BuildStatus, BuildStatusEnum
|
||||
from ahriman.models.changes import Changes
|
||||
from ahriman.models.dependencies import Dependencies
|
||||
from ahriman.models.event import Event, EventType
|
||||
from ahriman.models.internal_status import InternalStatus
|
||||
from ahriman.models.log_record_id import LogRecordId
|
||||
from ahriman.models.package import Package
|
||||
@ -110,15 +109,6 @@ class WebClient(Client, SyncAhrimanClient):
|
||||
"""
|
||||
return f"{self.address}/api/v1/packages/{urlencode(package_base)}/dependencies"
|
||||
|
||||
def _events_url(self) -> str:
|
||||
"""
|
||||
get url for the events api
|
||||
|
||||
Returns:
|
||||
str: full url for web service for events
|
||||
"""
|
||||
return f"{self.address}/api/v1/events"
|
||||
|
||||
def _logs_url(self, package_base: str) -> str:
|
||||
"""
|
||||
get url for the logs api
|
||||
@ -167,44 +157,6 @@ class WebClient(Client, SyncAhrimanClient):
|
||||
"""
|
||||
return f"{self.address}/api/v1/status"
|
||||
|
||||
def event_add(self, event: Event) -> None:
|
||||
"""
|
||||
create new event
|
||||
|
||||
Args:
|
||||
event(Event): audit log event
|
||||
"""
|
||||
with contextlib.suppress(Exception):
|
||||
self.make_request("POST", self._events_url(), params=self.repository_id.query(), json=event.view())
|
||||
|
||||
def event_get(self, event: str | EventType | None, object_id: str | None,
|
||||
limit: int = -1, offset: int = 0) -> list[Event]:
|
||||
"""
|
||||
retrieve list of events
|
||||
|
||||
Args:
|
||||
event(str | EventType | None): filter by event type
|
||||
object_id(str | None): filter by event object
|
||||
limit(int, optional): limit records to the specified count, -1 means unlimited (Default value = -1)
|
||||
offset(int, optional): records offset (Default value = 0)
|
||||
|
||||
Returns:
|
||||
list[Event]: list of audit log events
|
||||
"""
|
||||
query = self.repository_id.query() + [("limit", str(limit)), ("offset", str(offset))]
|
||||
if event is not None:
|
||||
query.append(("event", str(event)))
|
||||
if object_id is not None:
|
||||
query.append(("object_id", object_id))
|
||||
|
||||
with contextlib.suppress(Exception):
|
||||
response = self.make_request("GET", self._events_url(), params=query)
|
||||
response_json = response.json()
|
||||
|
||||
return [Event.from_json(event) for event in response_json]
|
||||
|
||||
return []
|
||||
|
||||
def package_changes_get(self, package_base: str) -> Changes:
|
||||
"""
|
||||
get package changes
|
||||
@ -322,9 +274,8 @@ class WebClient(Client, SyncAhrimanClient):
|
||||
Returns:
|
||||
list[tuple[float, str]]: package logs
|
||||
"""
|
||||
query = self.repository_id.query() + [("limit", str(limit)), ("offset", str(offset))]
|
||||
|
||||
with contextlib.suppress(Exception):
|
||||
query = self.repository_id.query() + [("limit", str(limit)), ("offset", str(offset))]
|
||||
response = self.make_request("GET", self._logs_url(package_base), params=query)
|
||||
response_json = response.json()
|
||||
|
||||
@ -338,13 +289,12 @@ class WebClient(Client, SyncAhrimanClient):
|
||||
|
||||
Args:
|
||||
package_base(str): package base
|
||||
version(str | None): package version to remove logs. If ``None`` is set, all logs will be removed
|
||||
version(str | None): package version to remove logs. If None set, all logs will be removed
|
||||
"""
|
||||
query = self.repository_id.query()
|
||||
if version is not None:
|
||||
query += [("version", version)]
|
||||
|
||||
with contextlib.suppress(Exception):
|
||||
query = self.repository_id.query()
|
||||
if version is not None:
|
||||
query += [("version", version)]
|
||||
self.make_request("DELETE", self._logs_url(package_base), params=query)
|
||||
|
||||
def package_patches_get(self, package_base: str, variable: str | None) -> list[PkgbuildPatch]:
|
||||
@ -373,7 +323,7 @@ class WebClient(Client, SyncAhrimanClient):
|
||||
|
||||
Args:
|
||||
package_base(str): package base to update
|
||||
variable(str | None): patch name. If ``None`` is set, all patches will be removed
|
||||
variable(str | None): patch name. If None set, all patches will be removed
|
||||
"""
|
||||
with contextlib.suppress(Exception):
|
||||
self.make_request("DELETE", self._patches_url(package_base, variable or ""))
|
||||
@ -411,7 +361,6 @@ class WebClient(Client, SyncAhrimanClient):
|
||||
NotImplementedError: not implemented method
|
||||
"""
|
||||
payload = {"status": status.value}
|
||||
|
||||
with contextlib.suppress(Exception):
|
||||
self.make_request("POST", self._package_url(package_base),
|
||||
params=self.repository_id.query(), json=payload)
|
||||
@ -431,7 +380,6 @@ class WebClient(Client, SyncAhrimanClient):
|
||||
"status": status.value,
|
||||
"package": package.view(),
|
||||
}
|
||||
|
||||
with contextlib.suppress(Exception):
|
||||
self.make_request("POST", self._package_url(package.base),
|
||||
params=self.repository_id.query(), json=payload)
|
||||
@ -459,6 +407,5 @@ class WebClient(Client, SyncAhrimanClient):
|
||||
status(BuildStatusEnum): current ahriman status
|
||||
"""
|
||||
payload = {"status": status.value}
|
||||
|
||||
with contextlib.suppress(Exception):
|
||||
self.make_request("POST", self._status_url(), params=self.repository_id.query(), json=payload)
|
||||
|
||||
@ -64,7 +64,7 @@ class Leaf:
|
||||
packages(Iterable[Leaf]): list of known leaves
|
||||
|
||||
Returns:
|
||||
bool: ``True`` in case if package is dependency of others and ``False`` otherwise
|
||||
bool: True in case if package is dependency of others and False otherwise
|
||||
"""
|
||||
for leaf in packages:
|
||||
if leaf.dependencies.intersection(self.items):
|
||||
@ -79,7 +79,7 @@ class Leaf:
|
||||
packages(Iterable[Leaf]): list of known leaves
|
||||
|
||||
Returns:
|
||||
bool: ``True`` if any of packages is dependency of the leaf, ``False`` otherwise
|
||||
bool: True if any of packages is dependency of the leaf, False otherwise
|
||||
"""
|
||||
for leaf in packages:
|
||||
if self.dependencies.intersection(leaf.items):
|
||||
|
||||
@ -160,7 +160,7 @@ class GitHub(Upload, HttpUpload):
|
||||
get release object if any
|
||||
|
||||
Returns:
|
||||
dict[str, Any] | None: GitHub API release object if release found and ``None`` otherwise
|
||||
dict[str, Any] | None: GitHub API release object if release found and None otherwise
|
||||
"""
|
||||
url = f"https://api.github.com/repos/{self.github_owner}/{
|
||||
self.github_repository}/releases/tags/{self.github_release_tag}"
|
||||
|
||||
@ -39,15 +39,14 @@ class Upload(LazyLogging):
|
||||
|
||||
Examples:
|
||||
These classes provide the way to upload packages to remote sources as it is described in their implementations.
|
||||
Basic flow includes class instantiating by using the :func:`load()` method and then calling the :func:`run()`
|
||||
method which wraps any internal exceptions into the :exc:`ahriman.core.exceptions.SynchronizationError`
|
||||
exception::
|
||||
Basic flow includes class instantiating by using the :func:`load` method and then calling the :func:`run` method
|
||||
which wraps any internal exceptions into the :exc:`ahriman.core.exceptions.SynchronizationError` exception::
|
||||
|
||||
>>> configuration = Configuration()
|
||||
>>> upload = Upload.load(RepositoryId("x86_64", "aur-clone"), configuration, "s3")
|
||||
>>> upload.run(configuration.repository_paths.repository, [])
|
||||
|
||||
Or in case if direct access to exception is required, the :func:`sync()` method can be used::
|
||||
Or in case if direct access to exception is required, the :func:`sync` method can be used::
|
||||
|
||||
>>> try:
|
||||
>>> upload.sync(configuration.repository_paths.repository, [])
|
||||
|
||||
@ -225,8 +225,8 @@ def extract_user() -> str | None:
|
||||
extract user from system environment
|
||||
|
||||
Returns:
|
||||
str | None: SUDO_USER in case if set and USER otherwise. It can return ``None`` in case if environment has been
|
||||
cleared before application start
|
||||
str | None: SUDO_USER in case if set and USER otherwise. It can return None in case if environment has been
|
||||
cleared before application start
|
||||
"""
|
||||
return os.getenv("SUDO_USER") or os.getenv("DOAS_USER") or os.getenv("USER")
|
||||
|
||||
@ -295,7 +295,7 @@ def package_like(filename: Path) -> bool:
|
||||
filename(Path): name of file to check
|
||||
|
||||
Returns:
|
||||
bool: ``True`` in case if name contains ``.pkg.`` and not signature, ``False`` otherwise
|
||||
bool: True in case if name contains ``.pkg.`` and not signature, False otherwise
|
||||
"""
|
||||
name = filename.name
|
||||
return not name.startswith(".") and ".pkg." in name and not name.endswith(".sig")
|
||||
|
||||
@ -44,7 +44,7 @@ class AuthSettings(StrEnum):
|
||||
get enabled flag
|
||||
|
||||
Returns:
|
||||
bool: ``False`` in case if authorization is disabled and ``True`` otherwise
|
||||
bool: False in case if authorization is disabled and True otherwise
|
||||
"""
|
||||
return self != AuthSettings.Disabled
|
||||
|
||||
|
||||
@ -1,140 +0,0 @@
|
||||
#
|
||||
# Copyright (c) 2021-2024 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 enum import StrEnum
|
||||
from typing import Any, Self
|
||||
|
||||
from ahriman.core.utils import utcnow
|
||||
|
||||
|
||||
class EventType(StrEnum):
|
||||
"""
|
||||
predefined event types
|
||||
|
||||
Attributes:
|
||||
PackageOutdated(EventType): (class attribute) package has been marked as out-of-date
|
||||
PackageRemoved(EventType): (class attribute) package has been removed
|
||||
PackageUpdateFailed(EventType): (class attribute) package update has been failed
|
||||
PackageUpdated(EventType): (class attribute) package has been updated
|
||||
"""
|
||||
|
||||
PackageOutdated = "package-outdated"
|
||||
PackageRemoved = "package-removed"
|
||||
PackageUpdateFailed = "package-update-failed"
|
||||
PackageUpdated = "package-updated"
|
||||
|
||||
|
||||
class Event:
|
||||
"""
|
||||
audit log event
|
||||
|
||||
Attributes:
|
||||
created(int): event timestamp
|
||||
data(dict[str, Any]): event metadata
|
||||
event(str | EventType): event type
|
||||
message(str | None): event message if available
|
||||
object_id(str): object identifier
|
||||
"""
|
||||
|
||||
def __init__(self, event: str | EventType, object_id: str, message: str | None = None, created: int | None = None,
|
||||
**kwargs: Any):
|
||||
"""
|
||||
default constructor
|
||||
|
||||
Args:
|
||||
event(str | EventType): event type
|
||||
object_id(str): object identifier
|
||||
message(str | None): event message if available
|
||||
created(int | None, optional): event timestamp (Default value = None)
|
||||
**kwargs(Any): event metadata
|
||||
"""
|
||||
self.event = EventType(event) if event in EventType else event
|
||||
self.object_id = object_id
|
||||
self.created = created or int(utcnow().timestamp())
|
||||
|
||||
self.message = message
|
||||
self.data = kwargs
|
||||
|
||||
@classmethod
|
||||
def from_json(cls, dump: dict[str, Any]) -> Self:
|
||||
"""
|
||||
construct event from the json dump
|
||||
|
||||
Args:
|
||||
dump(dict[str, Any]): json dump body
|
||||
|
||||
Returns:
|
||||
Self: dependencies object
|
||||
"""
|
||||
return cls(
|
||||
event=dump["event"],
|
||||
object_id=dump["object_id"],
|
||||
message=dump.get("message"),
|
||||
created=dump.get("created"),
|
||||
**dump.get("data", {}),
|
||||
)
|
||||
|
||||
def get(self, key: str) -> Any:
|
||||
"""
|
||||
get a property
|
||||
|
||||
Args:
|
||||
key(str): key to lookup in data
|
||||
|
||||
Returns:
|
||||
Any: metadata property if available or ``None`` otherwise
|
||||
"""
|
||||
return self.data.get(key)
|
||||
|
||||
def view(self) -> dict[str, Any]:
|
||||
"""
|
||||
generate json event view
|
||||
|
||||
Returns:
|
||||
dict[str, Any]: json-friendly dictionary
|
||||
"""
|
||||
dump = {
|
||||
"event": self.event,
|
||||
"object_id": self.object_id,
|
||||
"created": self.created,
|
||||
}
|
||||
if self.message is not None:
|
||||
dump["message"] = self.message
|
||||
if self.data:
|
||||
dump["data"] = self.data
|
||||
|
||||
return dump
|
||||
|
||||
def __eq__(self, other: Any) -> bool:
|
||||
"""
|
||||
check if other is the same object
|
||||
|
||||
Args:
|
||||
other(Any): other object instance
|
||||
|
||||
Returns:
|
||||
bool: ``True`` if the other object is the same and ``False`` otherwise
|
||||
"""
|
||||
if not isinstance(other, Event):
|
||||
return False
|
||||
return self.event == other.event \
|
||||
and self.object_id == other.object_id \
|
||||
and self.message == other.message \
|
||||
and self.created == other.created \
|
||||
and self.data == other.data
|
||||
@ -1,93 +0,0 @@
|
||||
#
|
||||
# Copyright (c) 2021-2024 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 time
|
||||
|
||||
from types import TracebackType
|
||||
from typing import Literal, Self
|
||||
|
||||
from ahriman.core.exceptions import InitializeError
|
||||
|
||||
|
||||
class MetricsTimer:
|
||||
"""
|
||||
metrics implementation
|
||||
|
||||
Attributes:
|
||||
start_time(float | None): timer start time in monotonic time
|
||||
|
||||
Examples:
|
||||
This class implements simple timer which allows to measure the time elapsed of the function. Usually it should
|
||||
be used like::
|
||||
|
||||
>>> with MetricsTimer() as timer:
|
||||
>>> do_something()
|
||||
>>> print("Time elapsed for first function: %f", timer.elapsed)
|
||||
>>> do_something_different()
|
||||
>>> print("Time elapsed for all functions: %f", timer.elapsed)
|
||||
"""
|
||||
|
||||
def __init__(self) -> None:
|
||||
"""
|
||||
default constructor
|
||||
"""
|
||||
self.start_time: float | None = None
|
||||
|
||||
@property
|
||||
def elapsed(self) -> float:
|
||||
"""
|
||||
get elapsed time since the start of the timer
|
||||
|
||||
Returns:
|
||||
float: time elapsed in seconds
|
||||
|
||||
Raises:
|
||||
InitializeError: in case if timer was not initialized correctly
|
||||
"""
|
||||
if self.start_time is None:
|
||||
raise InitializeError("Timer must be started in the context manager")
|
||||
|
||||
stop_time = time.monotonic()
|
||||
consumed_time_ms = int(1000 * (stop_time - self.start_time))
|
||||
return consumed_time_ms / 1000
|
||||
|
||||
def __enter__(self) -> Self:
|
||||
"""
|
||||
start timer context
|
||||
|
||||
Returns:
|
||||
Self: always instance of self
|
||||
"""
|
||||
self.start_time = time.monotonic()
|
||||
return self
|
||||
|
||||
def __exit__(self, exc_type: type[Exception] | None, exc_val: Exception | None,
|
||||
exc_tb: TracebackType) -> Literal[False]:
|
||||
"""
|
||||
finish timer context
|
||||
|
||||
Args:
|
||||
exc_type(type[Exception] | None): exception type name if any
|
||||
exc_val(Exception | None): exception raised if any
|
||||
exc_tb(TracebackType): exception traceback if any
|
||||
|
||||
Returns:
|
||||
Literal[False]: always ``False`` (do not suppress any exception)
|
||||
"""
|
||||
return False
|
||||
@ -41,7 +41,7 @@ class MigrationResult:
|
||||
check migration and check if there are pending migrations
|
||||
|
||||
Returns:
|
||||
bool: ``True`` in case if it requires migrations and ``False`` otherwise
|
||||
bool: True in case if it requires migrations and False otherwise
|
||||
"""
|
||||
self.validate()
|
||||
return self.new_version > self.old_version
|
||||
|
||||
@ -158,7 +158,7 @@ class Package(LazyLogging):
|
||||
get VCS flag based on the package base
|
||||
|
||||
Returns:
|
||||
bool: ``True`` in case if package base looks like VCS package and ``False`` otherwise
|
||||
bool: True in case if package base looks like VCS package and False otherwise
|
||||
"""
|
||||
return self.base.endswith("-bzr") \
|
||||
or self.base.endswith("-csv")\
|
||||
@ -504,7 +504,7 @@ class Package(LazyLogging):
|
||||
timestamp(float | int): timestamp to check build date against
|
||||
|
||||
Returns:
|
||||
bool: ``True`` in case if package was built after the specified date and ``False`` otherwise. In case if build date
|
||||
bool: True in case if package was built after the specified date and False otherwise. In case if build date
|
||||
is not set by any of packages, it returns False
|
||||
"""
|
||||
return any(
|
||||
@ -528,7 +528,7 @@ class Package(LazyLogging):
|
||||
(Default value = True)
|
||||
|
||||
Returns:
|
||||
bool: ``True`` if the package is out-of-dated and ``False`` otherwise
|
||||
bool: True if the package is out-of-dated and False otherwise
|
||||
"""
|
||||
min_vcs_build_date = utcnow().timestamp() - vcs_allowed_age
|
||||
if calculate_version and not self.is_newer_than(min_vcs_build_date):
|
||||
|
||||
@ -17,6 +17,7 @@
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
from dataclasses import dataclass
|
||||
from elftools.elf.dynamic import DynamicSection
|
||||
from elftools.elf.elffile import ELFFile
|
||||
from pathlib import Path
|
||||
@ -32,6 +33,7 @@ from ahriman.models.package import Package
|
||||
from ahriman.models.scan_paths import ScanPaths
|
||||
|
||||
|
||||
@dataclass
|
||||
class PackageArchive:
|
||||
"""
|
||||
helper for package archives
|
||||
@ -43,20 +45,10 @@ class PackageArchive:
|
||||
scan_paths(ScanPaths): scan paths holder
|
||||
"""
|
||||
|
||||
def __init__(self, root: Path, package: Package, pacman: Pacman, scan_paths: ScanPaths) -> None:
|
||||
"""
|
||||
default constructor
|
||||
|
||||
Args:
|
||||
root(Path): path to root filesystem
|
||||
package(Package): package descriptor
|
||||
pacman(Pacman): alpm wrapper instance
|
||||
scan_paths(ScanPaths): scan paths holder
|
||||
"""
|
||||
self.root = root
|
||||
self.package = package
|
||||
self.pacman = pacman
|
||||
self.scan_paths = scan_paths
|
||||
root: Path
|
||||
package: Package
|
||||
pacman: Pacman
|
||||
scan_paths: ScanPaths
|
||||
|
||||
@staticmethod
|
||||
def dynamic_needed(binary_path: Path) -> list[str]:
|
||||
@ -55,7 +55,7 @@ class PkgbuildPatch:
|
||||
parse key and define whether it function or not
|
||||
|
||||
Returns:
|
||||
bool: ``True`` in case if key ends with parentheses and ``False`` otherwise
|
||||
bool: True in case if key ends with parentheses and False otherwise
|
||||
"""
|
||||
return self.key is not None and self.key.endswith("()")
|
||||
|
||||
@ -65,7 +65,7 @@ class PkgbuildPatch:
|
||||
check if patch is full diff one or just single-variable patch
|
||||
|
||||
Returns:
|
||||
bool: ``True`` in case key set and ``False`` otherwise
|
||||
bool: True in case key set and False otherwise
|
||||
"""
|
||||
return self.key is None
|
||||
|
||||
|
||||
@ -28,9 +28,9 @@ class ProcessStatus:
|
||||
Attributes:
|
||||
process_id(str): unique process identifier
|
||||
status(bool): process exit code status
|
||||
consumed_time(float): consumed time in seconds
|
||||
consumed_time(int): consumed time in ms
|
||||
"""
|
||||
|
||||
process_id: str
|
||||
status: bool
|
||||
consumed_time: float
|
||||
consumed_time: int
|
||||
|
||||
@ -29,7 +29,7 @@ class Property:
|
||||
Attributes:
|
||||
name(str): name of the property
|
||||
value(Any): property value
|
||||
is_required(bool): if set to ``True`` then this property is required
|
||||
is_required(bool): if set to True then this property is required
|
||||
indent(int): property indentation level
|
||||
"""
|
||||
|
||||
|
||||
@ -57,7 +57,7 @@ class RemoteSource:
|
||||
check if source is remote
|
||||
|
||||
Returns:
|
||||
bool: ``True`` in case if package is well-known remote source (e.g. AUR) and ``False`` otherwise
|
||||
bool: True in case if package is well-known remote source (e.g. AUR) and False otherwise
|
||||
"""
|
||||
return self.source in (PackageSource.AUR, PackageSource.Repository)
|
||||
|
||||
|
||||
@ -41,9 +41,12 @@ class RepositoryId:
|
||||
|
||||
Returns:
|
||||
str: unique id for this repository
|
||||
|
||||
Raises:
|
||||
ValueError: if repository identifier is empty
|
||||
"""
|
||||
if self.is_empty:
|
||||
return ""
|
||||
raise ValueError("Repository ID is called on empty repository identifier")
|
||||
return f"{self.architecture}-{self.name}" # basically the same as used for command line
|
||||
|
||||
@property
|
||||
@ -52,7 +55,7 @@ class RepositoryId:
|
||||
check if all data is supplied for the loading
|
||||
|
||||
Returns:
|
||||
bool: ``True`` in case if architecture or name are not set and ``False`` otherwise
|
||||
bool: True in case if architecture or name are not set and False otherwise
|
||||
"""
|
||||
return not self.architecture or not self.name
|
||||
|
||||
@ -85,7 +88,7 @@ class RepositoryId:
|
||||
other(Any): other object to compare
|
||||
|
||||
Returns:
|
||||
bool: ``True`` in case if this is less than other and ``False`` otherwise
|
||||
bool: True in case if this is less than other and False otherwise
|
||||
|
||||
Raises:
|
||||
TypeError: if other is different from RepositoryId type
|
||||
|
||||
@ -85,7 +85,7 @@ class RepositoryPaths(LazyLogging):
|
||||
return Path(self.repository_id.name) / self.repository_id.architecture
|
||||
|
||||
@property
|
||||
def build_root(self) -> Path:
|
||||
def build_directory(self) -> Path:
|
||||
"""
|
||||
same as :attr:`chroot`, but exactly build chroot
|
||||
|
||||
|
||||
@ -82,7 +82,7 @@ class Result:
|
||||
get if build result is empty or not
|
||||
|
||||
Returns:
|
||||
bool: ``True`` in case if success list is empty and ``False`` otherwise
|
||||
bool: True in case if success list is empty and False otherwise
|
||||
"""
|
||||
return not self._added and not self._updated
|
||||
|
||||
@ -191,7 +191,7 @@ class Result:
|
||||
other(Any): other object instance
|
||||
|
||||
Returns:
|
||||
bool: ``True`` if the other object is the same and ``False`` otherwise
|
||||
bool: True if the other object is the same and False otherwise
|
||||
"""
|
||||
if not isinstance(other, Result):
|
||||
return False
|
||||
|
||||
@ -17,33 +17,29 @@
|
||||
# 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 re
|
||||
|
||||
from dataclasses import dataclass
|
||||
from functools import cached_property
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
@dataclass(frozen=True, kw_only=True)
|
||||
class ScanPaths:
|
||||
"""
|
||||
paths used for scan filesystem
|
||||
|
||||
Attributes:
|
||||
paths(list[str]): list of regular expressions to be used to match paths
|
||||
allowed_paths(list[Path]): list of whitelisted paths
|
||||
blacklisted_paths(list[Path]): list of paths to be skipped from scan
|
||||
"""
|
||||
|
||||
paths: list[str]
|
||||
allowed_paths: list[Path]
|
||||
blacklisted_paths: list[Path]
|
||||
|
||||
@cached_property
|
||||
def patterns(self) -> list[re.Pattern[str]]:
|
||||
def __post_init__(self) -> None:
|
||||
"""
|
||||
compiled regular expressions
|
||||
|
||||
Returns:
|
||||
list[re.Pattern]: a list of compiled regular expressions
|
||||
compute relative to / paths
|
||||
"""
|
||||
return [re.compile(path) for path in self.paths]
|
||||
object.__setattr__(self, "allowed_paths", [path.relative_to("/") for path in self.allowed_paths])
|
||||
object.__setattr__(self, "blacklisted_paths", [path.relative_to("/") for path in self.blacklisted_paths])
|
||||
|
||||
def is_allowed(self, path: Path) -> bool:
|
||||
"""
|
||||
@ -53,7 +49,10 @@ class ScanPaths:
|
||||
path(Path): path to be checked
|
||||
|
||||
Returns:
|
||||
bool: ``True`` in case if :attr:`paths` contains at least one element to which the path is matched
|
||||
and ``False`` otherwise
|
||||
bool: ``True`` in case if :attr:`allowed_paths` contains element which is parent for the path and
|
||||
:attr:`blacklisted_paths` doesn't and ``False`` otherwise
|
||||
"""
|
||||
return any(pattern.match(str(path)) for pattern in self.patterns)
|
||||
if any(path.is_relative_to(blacklisted) for blacklisted in self.blacklisted_paths):
|
||||
return False # path is blacklisted
|
||||
# check if we actually have to check this path
|
||||
return any(path.is_relative_to(allowed) for allowed in self.allowed_paths)
|
||||
|
||||
@ -98,7 +98,7 @@ class User:
|
||||
salt(str): salt for hashed password
|
||||
|
||||
Returns:
|
||||
bool: ``True`` in case if password matches, ``False`` otherwise
|
||||
bool: True in case if password matches, False otherwise
|
||||
"""
|
||||
try:
|
||||
verified: bool = self._HASHER.verify(password + salt, self.password)
|
||||
@ -131,7 +131,7 @@ class User:
|
||||
required(UserAccess): required access level
|
||||
|
||||
Returns:
|
||||
bool: ``True`` in case if user is allowed to do this request and ``False`` otherwise
|
||||
bool: True in case if user is allowed to do this request and False otherwise
|
||||
"""
|
||||
return self.access.permits(required)
|
||||
|
||||
|
||||
@ -47,7 +47,7 @@ class UserAccess(StrEnum):
|
||||
other(UserAccess): other permission to compare
|
||||
|
||||
Returns:
|
||||
bool: ``True`` in case if current permission allows the operation and ``False`` otherwise
|
||||
bool: True in case if current permission allows the operation and False otherwise
|
||||
"""
|
||||
for member in UserAccess:
|
||||
if member == other:
|
||||
|
||||
@ -23,8 +23,6 @@ from collections.abc import Callable
|
||||
from dataclasses import dataclass, field
|
||||
from typing import Literal, ParamSpec
|
||||
|
||||
from ahriman.models.metrics_timer import MetricsTimer
|
||||
|
||||
|
||||
Params = ParamSpec("Params")
|
||||
|
||||
@ -96,25 +94,25 @@ class Waiter:
|
||||
|
||||
Attributes:
|
||||
interval(float): interval in seconds between checks
|
||||
start_time(float): monotonic time of the waiter start. More likely must not be assigned explicitly
|
||||
wait_timeout(float): timeout in seconds to wait for. Negative value will result in immediate exit. Zero value
|
||||
means infinite timeout
|
||||
"""
|
||||
|
||||
wait_timeout: float
|
||||
start_time: float = field(default_factory=time.monotonic, kw_only=True)
|
||||
interval: float = field(default=10, kw_only=True)
|
||||
|
||||
def is_timed_out(self, elapsed: float) -> bool:
|
||||
def is_timed_out(self) -> bool:
|
||||
"""
|
||||
check if timer is out
|
||||
|
||||
Attributes:
|
||||
elapsed(float): elapsed time in seconds
|
||||
|
||||
Returns:
|
||||
bool: ``True`` in case current monotonic time is more than :attr:`start_time` and :attr:`wait_timeout`
|
||||
doesn't equal to 0
|
||||
"""
|
||||
return self.wait_timeout != 0 and elapsed > self.wait_timeout
|
||||
since_start = time.monotonic() - self.start_time
|
||||
return self.wait_timeout != 0 and since_start > self.wait_timeout
|
||||
|
||||
def wait(self, in_progress: Callable[Params, bool], *args: Params.args, **kwargs: Params.kwargs) -> WaiterResult:
|
||||
"""
|
||||
@ -128,10 +126,9 @@ class Waiter:
|
||||
Returns:
|
||||
WaiterResult: waiter result object
|
||||
"""
|
||||
with MetricsTimer() as timer:
|
||||
while not (timed_out := self.is_timed_out(timer.elapsed)) and in_progress(*args, **kwargs):
|
||||
time.sleep(self.interval)
|
||||
took = timer.elapsed
|
||||
while not (timed_out := self.is_timed_out()) and in_progress(*args, **kwargs):
|
||||
time.sleep(self.interval)
|
||||
took = time.monotonic() - self.start_time
|
||||
|
||||
if timed_out:
|
||||
return WaiterTimedOut(took)
|
||||
|
||||
@ -63,7 +63,7 @@ class _AuthorizationPolicy(aiohttp_security.AbstractAuthorizationPolicy):
|
||||
identity(str): username
|
||||
|
||||
Returns:
|
||||
str | None: user identity (username) in case if user exists and ``None`` otherwise
|
||||
str | None: user identity (username) in case if user exists and None otherwise
|
||||
"""
|
||||
return identity if await self.validator.known_username(identity) else None
|
||||
|
||||
@ -77,7 +77,7 @@ class _AuthorizationPolicy(aiohttp_security.AbstractAuthorizationPolicy):
|
||||
context(str | None, optional): URI request path (Default value = None)
|
||||
|
||||
Returns:
|
||||
bool: ``True`` in case if user is allowed to perform this request and ``False`` otherwise
|
||||
bool: True in case if user is allowed to perform this request and False otherwise
|
||||
"""
|
||||
# some methods for type checking and parent class compatibility
|
||||
if identity is None or not isinstance(permission, UserAccess):
|
||||
|
||||
@ -38,7 +38,7 @@ def _is_templated_unauthorized(request: Request) -> bool:
|
||||
request(Request): source request to check
|
||||
|
||||
Returns:
|
||||
bool: ``True`` in case if response should be rendered as html and ``False`` otherwise
|
||||
bool: True in case if response should be rendered as html and False otherwise
|
||||
"""
|
||||
return request.path in ("/api/v1/login", "/api/v1/logout") \
|
||||
and "application/json" not in request.headers.getall("accept", [])
|
||||
|
||||
@ -24,8 +24,6 @@ from ahriman.web.schemas.changes_schema import ChangesSchema
|
||||
from ahriman.web.schemas.counters_schema import CountersSchema
|
||||
from ahriman.web.schemas.dependencies_schema import DependenciesSchema
|
||||
from ahriman.web.schemas.error_schema import ErrorSchema
|
||||
from ahriman.web.schemas.event_schema import EventSchema
|
||||
from ahriman.web.schemas.event_search_schema import EventSearchSchema
|
||||
from ahriman.web.schemas.file_schema import FileSchema
|
||||
from ahriman.web.schemas.info_schema import InfoSchema
|
||||
from ahriman.web.schemas.internal_status_schema import InternalStatusSchema
|
||||
|
||||
@ -1,47 +0,0 @@
|
||||
#
|
||||
# Copyright (c) 2021-2024 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 marshmallow import Schema, fields
|
||||
|
||||
from ahriman.models.event import EventType
|
||||
|
||||
|
||||
class EventSchema(Schema):
|
||||
"""
|
||||
request/response event schema
|
||||
"""
|
||||
|
||||
created = fields.Integer(required=True, metadata={
|
||||
"description": "Event creation timestamp",
|
||||
"example": 1680537091,
|
||||
})
|
||||
event = fields.String(required=True, metadata={
|
||||
"description": "Event type",
|
||||
"example": EventType.PackageUpdated,
|
||||
})
|
||||
object_id = fields.String(required=True, metadata={
|
||||
"description": "Event object identifier",
|
||||
"example": "ahriman",
|
||||
})
|
||||
message = fields.String(metadata={
|
||||
"description": "Event message if available",
|
||||
})
|
||||
data = fields.Dict(keys=fields.String(), metadata={
|
||||
"description": "Event metadata if available",
|
||||
})
|
||||
@ -1,38 +0,0 @@
|
||||
#
|
||||
# Copyright (c) 2021-2024 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 marshmallow import fields
|
||||
|
||||
from ahriman.models.event import EventType
|
||||
from ahriman.web.schemas.pagination_schema import PaginationSchema
|
||||
|
||||
|
||||
class EventSearchSchema(PaginationSchema):
|
||||
"""
|
||||
request event search schema
|
||||
"""
|
||||
|
||||
event = fields.String(metadata={
|
||||
"description": "Event type",
|
||||
"example": EventType.PackageUpdated,
|
||||
})
|
||||
object_id = fields.String(metadata={
|
||||
"description": "Event object identifier",
|
||||
"example": "ahriman",
|
||||
})
|
||||
@ -247,7 +247,7 @@ class BaseView(View, CorsViewMixin):
|
||||
extract username from request if any
|
||||
|
||||
Returns:
|
||||
str | None: authorized username if any and ``None`` otherwise (e.g. if authorization is disabled)
|
||||
str | None: authorized username if any and None otherwise (e.g. if authorization is disabled)
|
||||
"""
|
||||
try: # try to read from payload
|
||||
data: dict[str, str] = await self.request.json() # technically it is not, but we only need str here
|
||||
|
||||
@ -1,19 +0,0 @@
|
||||
#
|
||||
# Copyright (c) 2021-2024 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/>.
|
||||
#
|
||||
@ -1,104 +0,0 @@
|
||||
#
|
||||
# Copyright (c) 2021-2024 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 aiohttp_apispec # type: ignore[import-untyped]
|
||||
|
||||
from aiohttp.web import HTTPBadRequest, HTTPNoContent, Response, json_response
|
||||
|
||||
from ahriman.models.event import Event
|
||||
from ahriman.models.user_access import UserAccess
|
||||
from ahriman.web.schemas import AuthSchema, ErrorSchema, EventSchema, EventSearchSchema
|
||||
from ahriman.web.views.base import BaseView
|
||||
|
||||
|
||||
class EventsView(BaseView):
|
||||
"""
|
||||
audit log view
|
||||
|
||||
Attributes:
|
||||
GET_PERMISSION(UserAccess): (class attribute) get permissions of self
|
||||
POST_PERMISSION(UserAccess): (class attribute) post permissions of self
|
||||
"""
|
||||
|
||||
GET_PERMISSION = POST_PERMISSION = UserAccess.Full
|
||||
ROUTES = ["/api/v1/events"]
|
||||
|
||||
@aiohttp_apispec.docs(
|
||||
tags=["Audit log"],
|
||||
summary="Get events",
|
||||
description="Retrieve events from audit log",
|
||||
responses={
|
||||
200: {"description": "Success response", "schema": EventSchema(many=True)},
|
||||
400: {"description": "Bad data is supplied", "schema": ErrorSchema},
|
||||
401: {"description": "Authorization required", "schema": ErrorSchema},
|
||||
403: {"description": "Access is forbidden", "schema": ErrorSchema},
|
||||
500: {"description": "Internal server error", "schema": ErrorSchema},
|
||||
},
|
||||
security=[{"token": [GET_PERMISSION]}],
|
||||
)
|
||||
@aiohttp_apispec.cookies_schema(AuthSchema)
|
||||
@aiohttp_apispec.querystring_schema(EventSearchSchema)
|
||||
async def get(self) -> Response:
|
||||
"""
|
||||
get events list
|
||||
|
||||
Returns:
|
||||
Response: 200 with workers list on success
|
||||
"""
|
||||
limit, offset = self.page()
|
||||
event = self.request.query.get("event") or None
|
||||
object_id = self.request.query.get("object_id") or None
|
||||
|
||||
events = self.service().event_get(event, object_id, limit, offset)
|
||||
response = [event.view() for event in events]
|
||||
|
||||
return json_response(response)
|
||||
|
||||
@aiohttp_apispec.docs(
|
||||
tags=["Audit log"],
|
||||
summary="Create event",
|
||||
description="Add new event to the audit log",
|
||||
responses={
|
||||
204: {"description": "Success response"},
|
||||
400: {"description": "Bad data is supplied", "schema": ErrorSchema},
|
||||
401: {"description": "Authorization required", "schema": ErrorSchema},
|
||||
403: {"description": "Access is forbidden", "schema": ErrorSchema},
|
||||
500: {"description": "Internal server error", "schema": ErrorSchema},
|
||||
},
|
||||
security=[{"token": [POST_PERMISSION]}],
|
||||
)
|
||||
@aiohttp_apispec.cookies_schema(AuthSchema)
|
||||
@aiohttp_apispec.json_schema(EventSchema)
|
||||
async def post(self) -> None:
|
||||
"""
|
||||
add new audit log event
|
||||
|
||||
Raises:
|
||||
HTTPBadRequest: if bad data is supplied
|
||||
HTTPNoContent: in case of success response
|
||||
"""
|
||||
try:
|
||||
data = await self.request.json()
|
||||
event = Event.from_json(data)
|
||||
except Exception as ex:
|
||||
raise HTTPBadRequest(reason=str(ex))
|
||||
|
||||
self.service().event_add(event)
|
||||
|
||||
raise HTTPNoContent
|
||||
@ -14,6 +14,7 @@ from ahriman.core.configuration import Configuration
|
||||
from ahriman.core.exceptions import DuplicateRunError, UnsafeRunError
|
||||
from ahriman.models.build_status import BuildStatus, BuildStatusEnum
|
||||
from ahriman.models.internal_status import InternalStatus
|
||||
from ahriman.models.repository_id import RepositoryId
|
||||
|
||||
|
||||
def test_path(args: argparse.Namespace, configuration: Configuration) -> None:
|
||||
@ -30,6 +31,8 @@ def test_path(args: argparse.Namespace, configuration: Configuration) -> None:
|
||||
args.lock = Path("ahriman.pid")
|
||||
assert Lock(args, repository_id, configuration).path == Path("/run/ahriman/ahriman_x86_64-aur-clone.pid")
|
||||
|
||||
assert Lock(args, RepositoryId("", ""), configuration).path == Path("/run/ahriman/ahriman.pid")
|
||||
|
||||
with pytest.raises(ValueError):
|
||||
args.lock = Path("/")
|
||||
assert Lock(args, repository_id, configuration).path # special case
|
||||
|
||||
@ -25,7 +25,6 @@ from ahriman.models.remote_source import RemoteSource
|
||||
from ahriman.models.repository_id import RepositoryId
|
||||
from ahriman.models.repository_paths import RepositoryPaths
|
||||
from ahriman.models.result import Result
|
||||
from ahriman.models.scan_paths import ScanPaths
|
||||
from ahriman.models.user import User
|
||||
from ahriman.models.user_access import UserAccess
|
||||
|
||||
@ -588,20 +587,6 @@ def result(package_ahriman: Package) -> Result:
|
||||
return result
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def scan_paths(configuration: Configuration) -> ScanPaths:
|
||||
"""
|
||||
scan paths fixture
|
||||
|
||||
Args:
|
||||
configuration(Configuration): configuration test instance
|
||||
|
||||
Returns:
|
||||
ScanPaths: scan paths test instance
|
||||
"""
|
||||
return ScanPaths(configuration.getlist("build", "scan_paths", fallback=[]))
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def spawner(configuration: Configuration) -> Spawn:
|
||||
"""
|
||||
|
||||
@ -1,35 +1,6 @@
|
||||
import pytest
|
||||
|
||||
from pytest_mock import MockerFixture
|
||||
from typing import Any
|
||||
|
||||
from ahriman.core.alpm.pacman import Pacman
|
||||
from ahriman.core.build_tools.package_archive import PackageArchive
|
||||
from ahriman.core.build_tools.sources import Sources
|
||||
from ahriman.models.package import Package
|
||||
from ahriman.models.repository_paths import RepositoryPaths
|
||||
from ahriman.models.scan_paths import ScanPaths
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def package_archive_ahriman(package_ahriman: Package, repository_paths: RepositoryPaths, pacman: Pacman,
|
||||
scan_paths: ScanPaths, passwd: Any, mocker: MockerFixture) -> PackageArchive:
|
||||
"""
|
||||
package archive fixture
|
||||
|
||||
Args:
|
||||
package_ahriman(Package): package test instance
|
||||
repository_paths(RepositoryPaths): repository paths test instance
|
||||
pacman(Pacman): pacman test instance
|
||||
scan_paths(ScanPaths): scan paths test instance
|
||||
passwd(Any): passwd structure test instance
|
||||
mocker(MockerFixture): mocker object
|
||||
|
||||
Returns:
|
||||
PackageArchive: package archive test instance
|
||||
"""
|
||||
mocker.patch("ahriman.models.repository_paths.getpwuid", return_value=passwd)
|
||||
return PackageArchive(repository_paths.build_root, package_ahriman, pacman, scan_paths)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
|
||||
@ -1,8 +0,0 @@
|
||||
from ahriman.core.database.migrations.m014_auditlog import steps
|
||||
|
||||
|
||||
def test_migration_auditlog() -> None:
|
||||
"""
|
||||
migration must not be empty
|
||||
"""
|
||||
assert steps
|
||||
@ -1,40 +0,0 @@
|
||||
from ahriman.core.database import SQLite
|
||||
from ahriman.models.event import Event, EventType
|
||||
from ahriman.models.package import Package
|
||||
from ahriman.models.repository_id import RepositoryId
|
||||
|
||||
|
||||
def test_event_insert_get(database: SQLite, package_ahriman: Package) -> None:
|
||||
"""
|
||||
must insert and get event
|
||||
"""
|
||||
event = Event(EventType.PackageUpdated, package_ahriman.base, "Updated", key="value")
|
||||
database.event_insert(event)
|
||||
assert database.event_get() == [event]
|
||||
|
||||
event2 = Event("event", "object")
|
||||
database.event_insert(event2, RepositoryId("i686", database._repository_id.name))
|
||||
assert database.event_get() == [event]
|
||||
assert database.event_get(repository_id=RepositoryId("i686", database._repository_id.name)) == [event2]
|
||||
|
||||
|
||||
def test_event_insert_get_filter(database: SQLite) -> None:
|
||||
"""
|
||||
must insert and get events with filter
|
||||
"""
|
||||
database.event_insert(Event("event 1", "object 1", created=1))
|
||||
database.event_insert(Event("event 2", "object 2"))
|
||||
database.event_insert(Event(EventType.PackageUpdated, "package"))
|
||||
|
||||
assert database.event_get(event="event 1") == [Event("event 1", "object 1", created=1)]
|
||||
assert database.event_get(object_id="object 1") == [Event("event 1", "object 1", created=1)]
|
||||
assert all(event.event == EventType.PackageUpdated for event in database.event_get(event=EventType.PackageUpdated))
|
||||
|
||||
|
||||
def test_event_insert_get_pagination(database: SQLite) -> None:
|
||||
"""
|
||||
must insert and get events with pagination
|
||||
"""
|
||||
database.event_insert(Event("1", "1"))
|
||||
database.event_insert(Event("2", "2"))
|
||||
assert all(event.event == "1" for event in database.event_get(limit=1, offset=1))
|
||||
@ -59,7 +59,7 @@ def test_logs_insert_get_pagination(database: SQLite, package_ahriman: Package)
|
||||
"""
|
||||
database.logs_insert(LogRecordId(package_ahriman.base, "1"), 42.0, "message 1")
|
||||
database.logs_insert(LogRecordId(package_ahriman.base, "1"), 43.0, "message 2")
|
||||
assert database.logs_get(package_ahriman.base, 1, 1) == [(42.0, "message 1")]
|
||||
assert database.logs_get(package_ahriman.base, 1, 1) == [(43.0, "message 2")]
|
||||
|
||||
|
||||
def test_logs_insert_get_multi(database: SQLite, package_ahriman: Package) -> None:
|
||||
|
||||
@ -6,13 +6,6 @@ from unittest.mock import MagicMock
|
||||
from ahriman.core.database import SQLite
|
||||
|
||||
|
||||
def test_logger_name(database: SQLite) -> None:
|
||||
"""
|
||||
must return correct logger name
|
||||
"""
|
||||
assert database.logger_name == "sql"
|
||||
|
||||
|
||||
def test_factory(database: SQLite) -> None:
|
||||
"""
|
||||
must convert response to dictionary
|
||||
@ -31,7 +24,6 @@ def test_with_connection(database: SQLite, mocker: MockerFixture) -> None:
|
||||
|
||||
database.with_connection(lambda conn: conn.execute("select 1"))
|
||||
connect_mock.assert_called_once_with(database.path, detect_types=sqlite3.PARSE_DECLTYPES)
|
||||
connection_mock.__enter__().set_trace_callback.assert_called_once_with(database.logger.debug)
|
||||
connection_mock.__enter__().commit.assert_not_called()
|
||||
|
||||
|
||||
|
||||
@ -4,6 +4,7 @@ from pytest_mock import MockerFixture
|
||||
|
||||
from ahriman.core.configuration import Configuration
|
||||
from ahriman.core.database import SQLite
|
||||
from ahriman.models.repository_id import RepositoryId
|
||||
|
||||
|
||||
def test_load(configuration: Configuration, mocker: MockerFixture) -> None:
|
||||
@ -35,7 +36,7 @@ def test_init_skip_migration(database: SQLite, configuration: Configuration, moc
|
||||
migrate_schema_mock.assert_not_called()
|
||||
|
||||
|
||||
def test_package_clear(database: SQLite, mocker: MockerFixture) -> None:
|
||||
def test_package_clear(database: SQLite, repository_id: RepositoryId, mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must clear package data
|
||||
"""
|
||||
@ -44,12 +45,14 @@ def test_package_clear(database: SQLite, mocker: MockerFixture) -> None:
|
||||
logs_mock = mocker.patch("ahriman.core.database.SQLite.logs_remove")
|
||||
changes_mock = mocker.patch("ahriman.core.database.SQLite.changes_remove")
|
||||
dependencies_mock = mocker.patch("ahriman.core.database.SQLite.dependencies_remove")
|
||||
package_mock = mocker.patch("ahriman.core.database.SQLite.package_remove")
|
||||
tree_clear_mock = mocker.patch("ahriman.models.repository_paths.RepositoryPaths.tree_clear")
|
||||
|
||||
database.package_clear("package")
|
||||
build_queue_mock.assert_called_once_with("package")
|
||||
patches_mock.assert_called_once_with("package", [])
|
||||
logs_mock.assert_called_once_with("package", None)
|
||||
changes_mock.assert_called_once_with("package")
|
||||
dependencies_mock.assert_called_once_with("package")
|
||||
database.package_clear("package", repository_id)
|
||||
build_queue_mock.assert_called_once_with("package", repository_id)
|
||||
patches_mock.assert_called_once_with("package", None)
|
||||
logs_mock.assert_called_once_with("package", None, repository_id)
|
||||
changes_mock.assert_called_once_with("package", repository_id)
|
||||
dependencies_mock.assert_called_once_with("package", repository_id)
|
||||
package_mock.assert_called_once_with("package", repository_id)
|
||||
tree_clear_mock.assert_called_once_with("package")
|
||||
|
||||
@ -4,19 +4,17 @@ import pytest
|
||||
from pytest_mock import MockerFixture
|
||||
|
||||
from ahriman.core.alpm.repo import Repo
|
||||
from ahriman.core.build_tools.task import Task
|
||||
from ahriman.core.database import SQLite
|
||||
from ahriman.models.log_record_id import LogRecordId
|
||||
from ahriman.models.package import Package
|
||||
|
||||
|
||||
def test_logger_name(database: SQLite, repo: Repo, task_ahriman: Task) -> None:
|
||||
def test_logger_name(database: SQLite, repo: Repo) -> None:
|
||||
"""
|
||||
must correctly generate logger name
|
||||
"""
|
||||
assert database.logger_name == "sql"
|
||||
assert database.logger_name == "ahriman.core.database.sqlite.SQLite"
|
||||
assert repo.logger_name == "ahriman.core.alpm.repo.Repo"
|
||||
assert task_ahriman.logger_name == "ahriman.core.build_tools.task.Task"
|
||||
|
||||
|
||||
def test_package_logger_set_reset(database: SQLite) -> None:
|
||||
@ -77,12 +75,9 @@ def test_in_package_context_failed(database: SQLite, package_ahriman: Package, m
|
||||
reset_mock.assert_called_once_with()
|
||||
|
||||
|
||||
def test_logger(database: SQLite, repo: Repo) -> None:
|
||||
def test_logger(database: SQLite) -> None:
|
||||
"""
|
||||
must set logger attribute
|
||||
"""
|
||||
assert database.logger
|
||||
assert database.logger.name == "sql"
|
||||
|
||||
assert repo.logger
|
||||
assert repo.logger.name == "ahriman.core.alpm.repo.Repo"
|
||||
assert database.logger.name == "ahriman.core.database.sqlite.SQLite"
|
||||
|
||||
@ -1,58 +0,0 @@
|
||||
import pytest
|
||||
|
||||
from pytest_mock import MockerFixture
|
||||
|
||||
from ahriman.core.repository.event_logger import EventLogger
|
||||
from ahriman.models.event import Event, EventType
|
||||
|
||||
|
||||
def test_event(repository: EventLogger, mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must log event
|
||||
"""
|
||||
event = Event(EventType.PackageUpdated, "base", "message", created=pytest.helpers.anyvar(int, True))
|
||||
event_mock = mocker.patch("ahriman.core.status.local_client.LocalClient.event_add")
|
||||
|
||||
repository.event(event.object_id, event.event, event.message)
|
||||
event_mock.assert_called_once_with(event)
|
||||
|
||||
|
||||
def test_in_event(repository: EventLogger, mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must log success action
|
||||
"""
|
||||
event = Event(EventType.PackageUpdated, "base", "message",
|
||||
created=pytest.helpers.anyvar(int, True), took=pytest.helpers.anyvar(float, True))
|
||||
event_mock = mocker.patch("ahriman.core.status.local_client.LocalClient.event_add")
|
||||
|
||||
with repository.in_event(event.object_id, event.event, event.message):
|
||||
pass
|
||||
event_mock.assert_called_once_with(event)
|
||||
|
||||
|
||||
def test_in_event_exception(repository: EventLogger, mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must reraise exception in context
|
||||
"""
|
||||
event = Event(EventType.PackageUpdated, "base", "message",
|
||||
created=pytest.helpers.anyvar(int, True), took=pytest.helpers.anyvar(float, True))
|
||||
event_mock = mocker.patch("ahriman.core.status.local_client.LocalClient.event_add")
|
||||
|
||||
with pytest.raises(Exception):
|
||||
with repository.in_event(event.object_id, event.event, event.message):
|
||||
raise Exception
|
||||
event_mock.assert_not_called()
|
||||
|
||||
|
||||
def test_in_event_exception_event(repository: EventLogger, mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must reraise exception in context and emit new event
|
||||
"""
|
||||
event = Event(EventType.PackageUpdateFailed, "base", created=pytest.helpers.anyvar(int, True),
|
||||
took=pytest.helpers.anyvar(float, True))
|
||||
event_mock = mocker.patch("ahriman.core.status.local_client.LocalClient.event_add")
|
||||
|
||||
with pytest.raises(Exception):
|
||||
with repository.in_event(event.object_id, EventType.PackageUpdated, failure=event.event):
|
||||
raise Exception
|
||||
event_mock.assert_called_once_with(event)
|
||||
@ -24,7 +24,7 @@ def test_process_build(executor: Executor, package_ahriman: Package, passwd: Any
|
||||
move_mock = mocker.patch("shutil.move")
|
||||
status_client_mock = mocker.patch("ahriman.core.status.Client.set_building")
|
||||
commit_sha_mock = mocker.patch("ahriman.core.status.local_client.LocalClient.package_changes_update")
|
||||
depends_on_mock = mocker.patch("ahriman.core.build_tools.package_archive.PackageArchive.depends_on",
|
||||
depends_on_mock = mocker.patch("ahriman.models.package_archive.PackageArchive.depends_on",
|
||||
return_value=Dependencies())
|
||||
dependencies_mock = mocker.patch("ahriman.core.status.local_client.LocalClient.package_dependencies_update")
|
||||
|
||||
|
||||
@ -7,7 +7,6 @@ from typing import Any
|
||||
from ahriman.core.exceptions import UnknownPackageError
|
||||
from ahriman.core.repository.update_handler import UpdateHandler
|
||||
from ahriman.models.dependencies import Dependencies
|
||||
from ahriman.models.event import EventType
|
||||
from ahriman.models.package import Package
|
||||
from ahriman.models.package_source import PackageSource
|
||||
from ahriman.models.remote_source import RemoteSource
|
||||
@ -22,14 +21,11 @@ def test_updates_aur(update_handler: UpdateHandler, package_ahriman: Package,
|
||||
return_value=[package_ahriman])
|
||||
mocker.patch("ahriman.models.package.Package.from_aur", return_value=package_ahriman)
|
||||
status_client_mock = mocker.patch("ahriman.core.status.Client.set_pending")
|
||||
event_mock = mocker.patch("ahriman.core.repository.update_handler.UpdateHandler.event")
|
||||
package_is_outdated_mock = mocker.patch("ahriman.models.package.Package.is_outdated", return_value=True)
|
||||
|
||||
assert update_handler.updates_aur([], vcs=True) == [package_ahriman]
|
||||
packages_mock.assert_called_once_with([])
|
||||
status_client_mock.assert_called_once_with(package_ahriman.base)
|
||||
event_mock.assert_called_once_with(package_ahriman.base, EventType.PackageOutdated,
|
||||
pytest.helpers.anyvar(str, True))
|
||||
package_is_outdated_mock.assert_called_once_with(
|
||||
package_ahriman, update_handler.paths,
|
||||
vcs_allowed_age=update_handler.vcs_allowed_age,
|
||||
@ -46,12 +42,9 @@ def test_updates_aur_official(update_handler: UpdateHandler, package_ahriman: Pa
|
||||
mocker.patch("ahriman.models.package.Package.is_outdated", return_value=True)
|
||||
mocker.patch("ahriman.models.package.Package.from_official", return_value=package_ahriman)
|
||||
status_client_mock = mocker.patch("ahriman.core.status.Client.set_pending")
|
||||
event_mock = mocker.patch("ahriman.core.repository.update_handler.UpdateHandler.event")
|
||||
|
||||
assert update_handler.updates_aur([], vcs=True) == [package_ahriman]
|
||||
status_client_mock.assert_called_once_with(package_ahriman.base)
|
||||
event_mock.assert_called_once_with(package_ahriman.base, EventType.PackageOutdated,
|
||||
pytest.helpers.anyvar(str, True))
|
||||
|
||||
|
||||
def test_updates_aur_failed(update_handler: UpdateHandler, package_ahriman: Package,
|
||||
@ -160,8 +153,6 @@ def test_updates_dependencies(update_handler: UpdateHandler, package_ahriman: Pa
|
||||
"""
|
||||
packages_mock = mocker.patch("ahriman.core.repository.update_handler.UpdateHandler.packages",
|
||||
return_value=[package_ahriman, package_python_schedule])
|
||||
status_client_mock = mocker.patch("ahriman.core.status.Client.set_pending")
|
||||
event_mock = mocker.patch("ahriman.core.repository.update_handler.UpdateHandler.event")
|
||||
dependencies = {
|
||||
package_ahriman.base: Dependencies({"usr/lib/python3.11/site-packages": ["python"]}),
|
||||
package_python_schedule.base: Dependencies({"usr/lib/python3.12/site-packages": ["python"]}),
|
||||
@ -173,9 +164,6 @@ def test_updates_dependencies(update_handler: UpdateHandler, package_ahriman: Pa
|
||||
|
||||
assert update_handler.updates_dependencies(["filter"]) == [package_ahriman]
|
||||
packages_mock.assert_called_once_with(["filter"])
|
||||
status_client_mock.assert_called_once_with(package_ahriman.base)
|
||||
event_mock.assert_called_once_with(package_ahriman.base, EventType.PackageOutdated,
|
||||
pytest.helpers.anyvar(str, True))
|
||||
|
||||
|
||||
def test_updates_dependencies_skip_unknown(update_handler: UpdateHandler, package_ahriman: Package,
|
||||
@ -217,15 +205,12 @@ def test_updates_local(update_handler: UpdateHandler, package_ahriman: Package,
|
||||
fetch_mock = mocker.patch("ahriman.core.build_tools.sources.Sources.fetch")
|
||||
package_load_mock = mocker.patch("ahriman.models.package.Package.from_build", return_value=package_ahriman)
|
||||
status_client_mock = mocker.patch("ahriman.core.status.Client.set_pending")
|
||||
event_mock = mocker.patch("ahriman.core.repository.update_handler.UpdateHandler.event")
|
||||
package_is_outdated_mock = mocker.patch("ahriman.models.package.Package.is_outdated", return_value=True)
|
||||
|
||||
assert update_handler.updates_local(vcs=True) == [package_ahriman]
|
||||
fetch_mock.assert_called_once_with(Path(package_ahriman.base), pytest.helpers.anyvar(int))
|
||||
package_load_mock.assert_called_once_with(Path(package_ahriman.base), "x86_64", None)
|
||||
status_client_mock.assert_called_once_with(package_ahriman.base)
|
||||
event_mock.assert_called_once_with(package_ahriman.base, EventType.PackageOutdated,
|
||||
pytest.helpers.anyvar(str, True))
|
||||
package_is_outdated_mock.assert_called_once_with(
|
||||
package_ahriman, update_handler.paths,
|
||||
vcs_allowed_age=update_handler.vcs_allowed_age,
|
||||
@ -296,12 +281,9 @@ def test_updates_manual_status_known(update_handler: UpdateHandler, package_ahri
|
||||
mocker.patch("ahriman.core.database.SQLite.build_queue_get", return_value=[package_ahriman])
|
||||
mocker.patch("ahriman.core.repository.update_handler.UpdateHandler.packages", return_value=[package_ahriman])
|
||||
status_client_mock = mocker.patch("ahriman.core.status.Client.set_pending")
|
||||
event_mock = mocker.patch("ahriman.core.repository.update_handler.UpdateHandler.event")
|
||||
|
||||
update_handler.updates_manual()
|
||||
status_client_mock.assert_called_once_with(package_ahriman.base)
|
||||
event_mock.assert_called_once_with(package_ahriman.base, EventType.PackageOutdated,
|
||||
pytest.helpers.anyvar(str, True))
|
||||
|
||||
|
||||
def test_updates_manual_status_unknown(update_handler: UpdateHandler, package_ahriman: Package,
|
||||
@ -312,12 +294,9 @@ def test_updates_manual_status_unknown(update_handler: UpdateHandler, package_ah
|
||||
mocker.patch("ahriman.core.database.SQLite.build_queue_get", return_value=[package_ahriman])
|
||||
mocker.patch("ahriman.core.repository.update_handler.UpdateHandler.packages", return_value=[])
|
||||
status_client_mock = mocker.patch("ahriman.core.status.Client.set_unknown")
|
||||
event_mock = mocker.patch("ahriman.core.repository.update_handler.UpdateHandler.event")
|
||||
|
||||
update_handler.updates_manual()
|
||||
status_client_mock.assert_called_once_with(package_ahriman)
|
||||
event_mock.assert_called_once_with(package_ahriman.base, EventType.PackageOutdated,
|
||||
pytest.helpers.anyvar(str, True))
|
||||
|
||||
|
||||
def test_updates_manual_with_failures(update_handler: UpdateHandler, package_ahriman: Package,
|
||||
|
||||
@ -11,7 +11,6 @@ from ahriman.core.status.web_client import WebClient
|
||||
from ahriman.models.build_status import BuildStatus, BuildStatusEnum
|
||||
from ahriman.models.changes import Changes
|
||||
from ahriman.models.dependencies import Dependencies
|
||||
from ahriman.models.event import Event
|
||||
from ahriman.models.internal_status import InternalStatus
|
||||
from ahriman.models.log_record_id import LogRecordId
|
||||
from ahriman.models.package import Package
|
||||
@ -95,22 +94,6 @@ def test_load_web_client_from_legacy_unix_socket(configuration: Configuration, d
|
||||
assert isinstance(Client.load(repository_id, configuration, database, report=True), WebClient)
|
||||
|
||||
|
||||
def test_event_add(client: Client) -> None:
|
||||
"""
|
||||
must raise not implemented on event insertion
|
||||
"""
|
||||
with pytest.raises(NotImplementedError):
|
||||
client.event_add(Event("", ""))
|
||||
|
||||
|
||||
def test_event_get(client: Client) -> None:
|
||||
"""
|
||||
must raise not implemented on events request
|
||||
"""
|
||||
with pytest.raises(NotImplementedError):
|
||||
client.event_get(None, None)
|
||||
|
||||
|
||||
def test_package_changes_get(client: Client, package_ahriman: Package) -> None:
|
||||
"""
|
||||
must raise not implemented on package changes request
|
||||
|
||||
@ -7,32 +7,11 @@ from ahriman.core.status.local_client import LocalClient
|
||||
from ahriman.models.build_status import BuildStatus, BuildStatusEnum
|
||||
from ahriman.models.changes import Changes
|
||||
from ahriman.models.dependencies import Dependencies
|
||||
from ahriman.models.event import Event, EventType
|
||||
from ahriman.models.log_record_id import LogRecordId
|
||||
from ahriman.models.package import Package
|
||||
from ahriman.models.pkgbuild_patch import PkgbuildPatch
|
||||
|
||||
|
||||
def test_event_add(local_client: LocalClient, package_ahriman: Package, mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must add new event
|
||||
"""
|
||||
event_mock = mocker.patch("ahriman.core.database.SQLite.event_insert")
|
||||
event = Event(EventType.PackageUpdated, package_ahriman.base)
|
||||
|
||||
local_client.event_add(event)
|
||||
event_mock.assert_called_once_with(event, local_client.repository_id)
|
||||
|
||||
|
||||
def test_event_get(local_client: LocalClient, package_ahriman: Package, mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must retrieve events
|
||||
"""
|
||||
event_mock = mocker.patch("ahriman.core.database.SQLite.event_get")
|
||||
local_client.event_get(EventType.PackageUpdated, package_ahriman.base, 1, 2)
|
||||
event_mock.assert_called_once_with(EventType.PackageUpdated, package_ahriman.base, 1, 2, local_client.repository_id)
|
||||
|
||||
|
||||
def test_package_changes_get(local_client: LocalClient, package_ahriman: Package, mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must retrieve package changes
|
||||
@ -179,7 +158,7 @@ def test_package_remove(local_client: LocalClient, package_ahriman: Package, moc
|
||||
"""
|
||||
package_mock = mocker.patch("ahriman.core.database.SQLite.package_clear")
|
||||
local_client.package_remove(package_ahriman.base)
|
||||
package_mock.assert_called_once_with(package_ahriman.base)
|
||||
package_mock.assert_called_once_with(package_ahriman.base, local_client.repository_id)
|
||||
|
||||
|
||||
def test_package_status_update(local_client: LocalClient, package_ahriman: Package, mocker: MockerFixture) -> None:
|
||||
|
||||
@ -101,13 +101,11 @@ def test_package_remove(watcher: Watcher, package_ahriman: Package, mocker: Mock
|
||||
must remove package base
|
||||
"""
|
||||
cache_mock = mocker.patch("ahriman.core.status.local_client.LocalClient.package_remove")
|
||||
logs_mock = mocker.patch("ahriman.core.status.watcher.Watcher.package_logs_remove", create=True)
|
||||
watcher._known = {package_ahriman.base: (package_ahriman, BuildStatus())}
|
||||
|
||||
watcher.package_remove(package_ahriman.base)
|
||||
assert not watcher._known
|
||||
cache_mock.assert_called_once_with(package_ahriman.base)
|
||||
logs_mock.assert_called_once_with(package_ahriman.base, None)
|
||||
|
||||
|
||||
def test_package_remove_unknown(watcher: Watcher, package_ahriman: Package, mocker: MockerFixture) -> None:
|
||||
|
||||
@ -10,7 +10,6 @@ from ahriman.core.status.web_client import WebClient
|
||||
from ahriman.models.build_status import BuildStatus, BuildStatusEnum
|
||||
from ahriman.models.changes import Changes
|
||||
from ahriman.models.dependencies import Dependencies
|
||||
from ahriman.models.event import Event, EventType
|
||||
from ahriman.models.internal_status import InternalStatus
|
||||
from ahriman.models.log_record_id import LogRecordId
|
||||
from ahriman.models.package import Package
|
||||
@ -55,12 +54,18 @@ def test_dependencies_url(web_client: WebClient, package_ahriman: Package) -> No
|
||||
"/api/v1/packages/some%2Fpackage%25name/dependencies")
|
||||
|
||||
|
||||
def test_event_url(web_client: WebClient) -> None:
|
||||
def test__patches_url(web_client: WebClient, package_ahriman: Package) -> None:
|
||||
"""
|
||||
must generate audit log url correctly
|
||||
must generate changes url correctly
|
||||
"""
|
||||
assert web_client._events_url().startswith(web_client.address)
|
||||
assert web_client._events_url().endswith("/api/v1/events")
|
||||
assert web_client._patches_url(package_ahriman.base).startswith(web_client.address)
|
||||
assert web_client._patches_url(package_ahriman.base).endswith(f"/api/v1/packages/{package_ahriman.base}/patches")
|
||||
assert web_client._patches_url("some/package%name").endswith("/api/v1/packages/some%2Fpackage%25name/patches")
|
||||
|
||||
assert web_client._patches_url(package_ahriman.base, "var").endswith(
|
||||
f"/api/v1/packages/{package_ahriman.base}/patches/var")
|
||||
assert web_client._patches_url(package_ahriman.base, "some/variable%name").endswith(
|
||||
f"/api/v1/packages/{package_ahriman.base}/patches/some%2Fvariable%25name")
|
||||
|
||||
|
||||
def test_logs_url(web_client: WebClient, package_ahriman: Package) -> None:
|
||||
@ -84,20 +89,6 @@ def test_package_url(web_client: WebClient, package_ahriman: Package) -> None:
|
||||
assert web_client._package_url("some/package%name").endswith("/api/v1/packages/some%2Fpackage%25name")
|
||||
|
||||
|
||||
def test_patches_url(web_client: WebClient, package_ahriman: Package) -> None:
|
||||
"""
|
||||
must generate changes url correctly
|
||||
"""
|
||||
assert web_client._patches_url(package_ahriman.base).startswith(web_client.address)
|
||||
assert web_client._patches_url(package_ahriman.base).endswith(f"/api/v1/packages/{package_ahriman.base}/patches")
|
||||
assert web_client._patches_url("some/package%name").endswith("/api/v1/packages/some%2Fpackage%25name/patches")
|
||||
|
||||
assert web_client._patches_url(package_ahriman.base, "var").endswith(
|
||||
f"/api/v1/packages/{package_ahriman.base}/patches/var")
|
||||
assert web_client._patches_url(package_ahriman.base, "some/variable%name").endswith(
|
||||
f"/api/v1/packages/{package_ahriman.base}/patches/some%2Fvariable%25name")
|
||||
|
||||
|
||||
def test_status_url(web_client: WebClient) -> None:
|
||||
"""
|
||||
must generate package status url correctly
|
||||
@ -106,135 +97,6 @@ def test_status_url(web_client: WebClient) -> None:
|
||||
assert web_client._status_url().endswith("/api/v1/status")
|
||||
|
||||
|
||||
def test_event_add(web_client: WebClient, package_ahriman: Package, mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must create event
|
||||
"""
|
||||
event = Event(EventType.PackageUpdated, package_ahriman.base)
|
||||
requests_mock = mocker.patch("ahriman.core.status.web_client.WebClient.make_request")
|
||||
|
||||
web_client.event_add(event)
|
||||
requests_mock.assert_called_once_with("POST", pytest.helpers.anyvar(str, True),
|
||||
params=web_client.repository_id.query(), json=event.view())
|
||||
|
||||
|
||||
def test_event_add_failed(web_client: WebClient, mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must suppress any exception happened during events creation
|
||||
"""
|
||||
mocker.patch("requests.Session.request", side_effect=Exception())
|
||||
web_client.event_add(Event("", ""))
|
||||
|
||||
|
||||
def test_event_add_failed_http_error(web_client: WebClient, mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must suppress HTTP exception happened during events creation
|
||||
"""
|
||||
mocker.patch("requests.Session.request", side_effect=requests.HTTPError())
|
||||
web_client.event_add(Event("", ""))
|
||||
|
||||
|
||||
def test_event_add_failed_suppress(web_client: WebClient, mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must suppress any exception happened during events creaton and don't log
|
||||
"""
|
||||
web_client.suppress_errors = True
|
||||
mocker.patch("requests.Session.request", side_effect=Exception())
|
||||
logging_mock = mocker.patch("logging.exception")
|
||||
|
||||
web_client.event_add(Event("", ""))
|
||||
logging_mock.assert_not_called()
|
||||
|
||||
|
||||
def test_event_add_failed_http_error_suppress(web_client: WebClient, mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must suppress HTTP exception happened during events creation and don't log
|
||||
"""
|
||||
web_client.suppress_errors = True
|
||||
mocker.patch("requests.Session.request", side_effect=requests.HTTPError())
|
||||
logging_mock = mocker.patch("logging.exception")
|
||||
|
||||
web_client.event_add(Event("", ""))
|
||||
logging_mock.assert_not_called()
|
||||
|
||||
|
||||
def test_event_get(web_client: WebClient, package_ahriman: Package, mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must get events
|
||||
"""
|
||||
event = Event(EventType.PackageUpdated, package_ahriman.base)
|
||||
response_obj = requests.Response()
|
||||
response_obj._content = json.dumps([event.view()]).encode("utf8")
|
||||
response_obj.status_code = 200
|
||||
|
||||
requests_mock = mocker.patch("ahriman.core.status.web_client.WebClient.make_request", return_value=response_obj)
|
||||
|
||||
result = web_client.event_get(None, None)
|
||||
requests_mock.assert_called_once_with("GET", pytest.helpers.anyvar(str, True),
|
||||
params=web_client.repository_id.query() + [("limit", "-1"), ("offset", "0")])
|
||||
assert result == [event]
|
||||
|
||||
|
||||
def test_event_get_filter(web_client: WebClient, mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must get events with filter
|
||||
"""
|
||||
response_obj = requests.Response()
|
||||
response_obj._content = json.dumps(Event("", "").view()).encode("utf8")
|
||||
response_obj.status_code = 200
|
||||
|
||||
requests_mock = mocker.patch("ahriman.core.status.web_client.WebClient.make_request", return_value=response_obj)
|
||||
|
||||
web_client.event_get("event", "object", 1, 2)
|
||||
requests_mock.assert_called_once_with("GET", pytest.helpers.anyvar(str, True),
|
||||
params=web_client.repository_id.query() + [
|
||||
("limit", "1"),
|
||||
("offset", "2"),
|
||||
("event", "event"),
|
||||
("object_id", "object"),
|
||||
])
|
||||
|
||||
|
||||
def test_event_get_failed(web_client: WebClient, mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must suppress any exception happened during events fetch
|
||||
"""
|
||||
mocker.patch("requests.Session.request", side_effect=Exception())
|
||||
web_client.event_get(None, None)
|
||||
|
||||
|
||||
def test_event_get_failed_http_error(web_client: WebClient, mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must suppress HTTP exception happened during events fetch
|
||||
"""
|
||||
mocker.patch("requests.Session.request", side_effect=requests.HTTPError())
|
||||
web_client.event_get(None, None)
|
||||
|
||||
|
||||
def test_event_get_failed_suppress(web_client: WebClient, mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must suppress any exception happened during events fetch and don't log
|
||||
"""
|
||||
web_client.suppress_errors = True
|
||||
mocker.patch("requests.Session.request", side_effect=Exception())
|
||||
logging_mock = mocker.patch("logging.exception")
|
||||
|
||||
web_client.event_get(None, None)
|
||||
logging_mock.assert_not_called()
|
||||
|
||||
|
||||
def test_event_get_failed_http_error_suppress(web_client: WebClient, mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must suppress HTTP exception happened during events fetch and don't log
|
||||
"""
|
||||
web_client.suppress_errors = True
|
||||
mocker.patch("requests.Session.request", side_effect=requests.HTTPError())
|
||||
logging_mock = mocker.patch("logging.exception")
|
||||
|
||||
web_client.event_get(None, None)
|
||||
logging_mock.assert_not_called()
|
||||
|
||||
|
||||
def test_package_changes_get(web_client: WebClient, package_ahriman: Package, mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must get changes
|
||||
|
||||
@ -1,17 +1,24 @@
|
||||
import pytest
|
||||
|
||||
from typing import Any
|
||||
from unittest.mock import MagicMock, PropertyMock
|
||||
from pytest_mock import MockerFixture
|
||||
|
||||
from ahriman import __version__
|
||||
from ahriman.core.alpm.pacman import Pacman
|
||||
from ahriman.core.alpm.remote import AUR
|
||||
from ahriman.core.configuration import Configuration
|
||||
from ahriman.models.build_status import BuildStatus, BuildStatusEnum
|
||||
from ahriman.models.counters import Counters
|
||||
from ahriman.models.filesystem_package import FilesystemPackage
|
||||
from ahriman.models.internal_status import InternalStatus
|
||||
from ahriman.models.package import Package
|
||||
from ahriman.models.package_archive import PackageArchive
|
||||
from ahriman.models.package_description import PackageDescription
|
||||
from ahriman.models.package_source import PackageSource
|
||||
from ahriman.models.remote_source import RemoteSource
|
||||
from ahriman.models.repository_paths import RepositoryPaths
|
||||
from ahriman.models.scan_paths import ScanPaths
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
@ -70,6 +77,27 @@ def internal_status(counters: Counters) -> InternalStatus:
|
||||
repository="aur-clone")
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def package_archive_ahriman(package_ahriman: Package, repository_paths: RepositoryPaths, pacman: Pacman,
|
||||
scan_paths: ScanPaths, passwd: Any, mocker: MockerFixture) -> PackageArchive:
|
||||
"""
|
||||
package archive fixture
|
||||
|
||||
Args:
|
||||
package_ahriman(Package): package test instance
|
||||
repository_paths(RepositoryPaths): repository paths test instance
|
||||
pacman(Pacman): pacman test instance
|
||||
scan_paths(ScanPaths): scan paths test instance
|
||||
passwd(Any): passwd structure test instance
|
||||
mocker(MockerFixture): mocker object
|
||||
|
||||
Returns:
|
||||
PackageArchive: package archive test instance
|
||||
"""
|
||||
mocker.patch("ahriman.models.repository_paths.getpwuid", return_value=passwd)
|
||||
return PackageArchive(repository_paths.build_directory, package_ahriman, pacman, scan_paths)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def package_tpacpi_bat_git() -> Package:
|
||||
"""
|
||||
@ -133,3 +161,20 @@ def pyalpm_package_description_ahriman(package_description_ahriman: PackageDescr
|
||||
type(mock).provides = PropertyMock(return_value=package_description_ahriman.provides)
|
||||
type(mock).url = PropertyMock(return_value=package_description_ahriman.url)
|
||||
return mock
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def scan_paths(configuration: Configuration) -> ScanPaths:
|
||||
"""
|
||||
scan paths fixture
|
||||
|
||||
Args:
|
||||
configuration(Configuration): configuration test instance
|
||||
|
||||
Returns:
|
||||
ScanPaths: scan paths test instance
|
||||
"""
|
||||
return ScanPaths(
|
||||
allowed_paths=configuration.getpathlist("build", "allowed_scan_paths"),
|
||||
blacklisted_paths=configuration.getpathlist("build", "blacklisted_scan_paths"),
|
||||
)
|
||||
|
||||
@ -1,57 +0,0 @@
|
||||
from ahriman.models.event import Event, EventType
|
||||
|
||||
|
||||
def test_init() -> None:
|
||||
"""
|
||||
must replace event type for known types
|
||||
"""
|
||||
assert Event("random", "")
|
||||
assert isinstance(Event(str(EventType.PackageUpdated), "").event, EventType)
|
||||
|
||||
assert Event("", "", key="value").data == {"key": "value"}
|
||||
|
||||
assert Event("", "").created > 0
|
||||
|
||||
|
||||
def test_from_json_view() -> None:
|
||||
"""
|
||||
must construct and serialize event to json
|
||||
"""
|
||||
event = Event("event", "object", "message", key="value")
|
||||
assert Event.from_json(event.view()) == event
|
||||
|
||||
|
||||
def test_get() -> None:
|
||||
"""
|
||||
must return property correctly
|
||||
"""
|
||||
assert Event("event", "object", "message", key="value").get("key") == "value"
|
||||
assert Event("event", "object").get("key") is None
|
||||
|
||||
|
||||
def test_view_empty() -> None:
|
||||
"""
|
||||
must skip empty fields during (de-)serialization
|
||||
"""
|
||||
event = Event("event", "object")
|
||||
assert Event.from_json(event.view()) == event
|
||||
assert "message" not in event.view()
|
||||
assert "data" not in event.view()
|
||||
|
||||
|
||||
def test_eq() -> None:
|
||||
"""
|
||||
must compare two events
|
||||
"""
|
||||
event1 = Event("1", "1", "1", 1, key="value")
|
||||
assert event1 == event1
|
||||
|
||||
event2 = Event("2", "2", "2", 2, key="value")
|
||||
assert event1 != event2
|
||||
|
||||
|
||||
def test_eq_other() -> None:
|
||||
"""
|
||||
must return False in case if object is not an instance of event
|
||||
"""
|
||||
assert Event("1", "1") != 42
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user