mirror of
https://github.com/arcan1s/ahriman.git
synced 2025-04-24 15:27:17 +00:00
feat: add separated switch for status reporting
This commit is contained in:
parent
95056cfbe7
commit
62dd77317d
@ -43,7 +43,6 @@ Base configuration settings.
|
|||||||
* ``database`` - path to SQLite database, string, required.
|
* ``database`` - path to SQLite database, string, required.
|
||||||
* ``include`` - path to directory with configuration files overrides, string, optional.
|
* ``include`` - path to directory with configuration files overrides, string, optional.
|
||||||
* ``logging`` - path to logging configuration, string, required. Check ``logging.ini`` for reference.
|
* ``logging`` - path to logging configuration, string, required. Check ``logging.ini`` for reference.
|
||||||
* ``suppress_http_log_errors`` - suppress http log errors, boolean, optional, default ``no``. If set to ``yes``, any http log errors (e.g. if web server is not available, but http logging is enabled) will be suppressed.
|
|
||||||
|
|
||||||
``alpm:*`` groups
|
``alpm:*`` groups
|
||||||
-----------------
|
-----------------
|
||||||
@ -86,7 +85,7 @@ Build related configuration. Group name can refer to architecture, e.g. ``build:
|
|||||||
* ``makechrootpkg_flags`` - additional flags passed to ``makechrootpkg`` command, space separated list of strings, optional.
|
* ``makechrootpkg_flags`` - additional flags passed to ``makechrootpkg`` command, space separated list of strings, optional.
|
||||||
* ``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 mention.
|
* ``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 mention.
|
||||||
* ``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.
|
* ``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, int, optional, default ``604800``.
|
* ``vcs_allowed_age`` - maximal age in seconds of the VCS packages before their version will be updated with its remote source, integer, optional, default ``604800``.
|
||||||
|
|
||||||
``repository`` group
|
``repository`` group
|
||||||
--------------------
|
--------------------
|
||||||
@ -103,6 +102,17 @@ Settings for signing packages or repository. Group name can refer to architectur
|
|||||||
* ``target`` - configuration flag to enable signing, space separated list of strings, required. Allowed values are ``package`` (sign each package separately), ``repository`` (sign repository database file).
|
* ``target`` - configuration flag to enable signing, space separated list of strings, required. Allowed values are ``package`` (sign each package separately), ``repository`` (sign repository database file).
|
||||||
* ``key`` - default PGP key, string, required. This key will also be used for database signing if enabled.
|
* ``key`` - default PGP key, string, required. This key will also be used for database signing if enabled.
|
||||||
|
|
||||||
|
``status`` group
|
||||||
|
----------------
|
||||||
|
|
||||||
|
Reporting to web service related settings. In most cases there is fallback to web section settings.
|
||||||
|
|
||||||
|
* ``enabled`` - enable reporting to web service, boolean, optional, default ``yes`` for backward compatibility.
|
||||||
|
* ``address`` - remote web service address with protocol, string, optional. In case of websocket, the ``http+unix`` scheme and url encoded address (e.g. ``%2Fvar%2Flib%2Fahriman`` for ``/var/lib/ahriman``) must be used, e.g. ``http+unix://%2Fvar%2Flib%2Fahriman%2Fsocket``. In case if none set, it will be guessed from ``web`` section.
|
||||||
|
* ``password`` - password to authorize in web service in order to update service status, string, required in case if authorization enabled.
|
||||||
|
* ``suppress_http_log_errors`` - suppress http log errors, boolean, optional, default ``no``. If set to ``yes``, any http log errors (e.g. if web server is not available, but http logging is enabled) will be suppressed.
|
||||||
|
* ``username`` - username to authorize in web service in order to update service status, string, required in case if authorization enabled.
|
||||||
|
|
||||||
``web`` group
|
``web`` group
|
||||||
-------------
|
-------------
|
||||||
|
|
||||||
@ -116,15 +126,13 @@ Web server settings. If any of ``host``/``port`` is not set, web integration wil
|
|||||||
* ``host`` - host to bind, string, optional.
|
* ``host`` - host to bind, string, optional.
|
||||||
* ``index_url`` - full url of the repository index page, string, optional.
|
* ``index_url`` - full url of the repository index page, string, optional.
|
||||||
* ``max_body_size`` - max body size in bytes to be validated for archive upload, integer, optional. If not set, validation will be disabled.
|
* ``max_body_size`` - max body size in bytes to be validated for archive upload, integer, optional. If not set, validation will be disabled.
|
||||||
* ``password`` - password to authorize in web service in order to update service status, string, required in case if authorization enabled.
|
* ``port`` - port to bind, integer, optional.
|
||||||
* ``port`` - port to bind, int, optional.
|
|
||||||
* ``static_path`` - path to directory with static files, string, required.
|
* ``static_path`` - path to directory with static files, string, required.
|
||||||
* ``templates`` - path to templates directories, space separated list of strings, required.
|
* ``templates`` - path to templates directories, space separated list of strings, required.
|
||||||
* ``timeout`` - HTTP request timeout in seconds, int, optional, default is ``30``.
|
* ``timeout`` - HTTP request timeout in seconds, integer, optional, default is ``30``.
|
||||||
* ``unix_socket`` - path to the listening unix socket, string, optional. If set, server will create the socket on the specified address which can (and will) be used by application. Note, that unlike usual host/port configuration, unix socket allows to perform requests without authorization.
|
* ``unix_socket`` - path to the listening unix socket, string, optional. If set, server will create the socket on the specified address which can (and will) be used by application. Note, that unlike usual host/port configuration, unix socket allows to perform requests without authorization.
|
||||||
* ``unix_socket_unsafe`` - set unsafe (o+w) permissions to unix socket, boolean, optional, default ``yes``. This option is enabled by default, because it is supposed that unix socket is created in safe environment (only web service is supposed to be used in unsafe), but it can be disabled by configuration.
|
* ``unix_socket_unsafe`` - set unsafe (o+w) permissions to unix socket, boolean, optional, default ``yes``. This option is enabled by default, because it is supposed that unix socket is created in safe environment (only web service is supposed to be used in unsafe), but it can be disabled by configuration.
|
||||||
* ``username`` - username to authorize in web service in order to update service status, string, required in case if authorization enabled.
|
* ``wait_timeout`` - wait timeout in seconds, maximum amount of time to be waited before lock will be free, integer, optional.
|
||||||
* ``wait_timeout`` - wait timeout in seconds, maximum amount of time to be waited before lock will be free, int, optional.
|
|
||||||
|
|
||||||
``keyring`` group
|
``keyring`` group
|
||||||
--------------------
|
--------------------
|
||||||
@ -237,7 +245,7 @@ Section name must be either ``email`` (plus optional architecture name, e.g. ``e
|
|||||||
* ``link_path`` - prefix for HTML links, string, required.
|
* ``link_path`` - prefix for HTML links, string, required.
|
||||||
* ``no_empty_report`` - skip report generation for empty packages list, boolean, optional, default ``yes``.
|
* ``no_empty_report`` - skip report generation for empty packages list, boolean, optional, default ``yes``.
|
||||||
* ``password`` - SMTP password to authenticate, string, optional.
|
* ``password`` - SMTP password to authenticate, string, optional.
|
||||||
* ``port`` - SMTP port for sending emails, int, required.
|
* ``port`` - SMTP port for sending emails, integer, required.
|
||||||
* ``receivers`` - SMTP receiver addresses, space separated list of strings, required.
|
* ``receivers`` - SMTP receiver addresses, space separated list of strings, required.
|
||||||
* ``sender`` - SMTP sender address, string, required.
|
* ``sender`` - SMTP sender address, string, required.
|
||||||
* ``ssl`` - SSL mode for SMTP connection, one of ``ssl``, ``starttls``, ``disabled``, optional, default ``disabled``.
|
* ``ssl`` - SSL mode for SMTP connection, one of ``ssl``, ``starttls``, ``disabled``, optional, default ``disabled``.
|
||||||
@ -267,7 +275,7 @@ Section name must be either ``remote-call`` (plus optional architecture name, e.
|
|||||||
* ``aur`` - check for AUR packages updates, boolean, optional, default ``no``.
|
* ``aur`` - check for AUR packages updates, boolean, optional, default ``no``.
|
||||||
* ``local`` - check for local packages updates, boolean, optional, default ``no``.
|
* ``local`` - check for local packages updates, boolean, optional, default ``no``.
|
||||||
* ``manual`` - update manually built packages, boolean, optional, default ``no``.
|
* ``manual`` - update manually built packages, boolean, optional, default ``no``.
|
||||||
* ``wait_timeout`` - maximum amount of time in seconds to be waited before remote process will be terminated, int, optional, default ``-1``.
|
* ``wait_timeout`` - maximum amount of time in seconds to be waited before remote process will be terminated, integer, optional, default ``-1``.
|
||||||
|
|
||||||
``telegram`` type
|
``telegram`` type
|
||||||
^^^^^^^^^^^^^^^^^
|
^^^^^^^^^^^^^^^^^
|
||||||
@ -282,7 +290,7 @@ Section name must be either ``telegram`` (plus optional architecture name, e.g.
|
|||||||
* ``template`` - Jinja2 template name, string, required.
|
* ``template`` - Jinja2 template name, string, required.
|
||||||
* ``template_type`` - ``parse_mode`` to be passed to telegram API, one of ``MarkdownV2``, ``HTML``, ``Markdown``, string, optional, default ``HTML``.
|
* ``template_type`` - ``parse_mode`` to be passed to telegram API, one of ``MarkdownV2``, ``HTML``, ``Markdown``, string, optional, default ``HTML``.
|
||||||
* ``templates`` - path to templates directories, space separated list of strings, required.
|
* ``templates`` - path to templates directories, space separated list of strings, required.
|
||||||
* ``timeout`` - HTTP request timeout in seconds, int, optional, default is ``30``.
|
* ``timeout`` - HTTP request timeout in seconds, integer, optional, default is ``30``.
|
||||||
|
|
||||||
``upload`` group
|
``upload`` group
|
||||||
----------------
|
----------------
|
||||||
@ -312,7 +320,7 @@ This feature requires GitHub key creation (see below). Section name must be eith
|
|||||||
#. Generate new token. Required scope is ``public_repo`` (or ``repo`` for private repository support).
|
#. Generate new token. Required scope is ``public_repo`` (or ``repo`` for private repository support).
|
||||||
|
|
||||||
* ``repository`` - GitHub repository name, string, required. Repository must be created before any action and must have active branch (e.g. with readme).
|
* ``repository`` - GitHub repository name, string, required. Repository must be created before any action and must have active branch (e.g. with readme).
|
||||||
* ``timeout`` - HTTP request timeout in seconds, int, optional, default is ``30``.
|
* ``timeout`` - HTTP request timeout in seconds, integer, optional, default is ``30``.
|
||||||
* ``use_full_release_name`` - if set to ``yes``, the release will contain both repository name and architecture, and only architecture otherwise, boolean, optional, default ``no`` (legacy behavior).
|
* ``use_full_release_name`` - if set to ``yes``, the release will contain both repository name and architecture, and only architecture otherwise, boolean, optional, default ``no`` (legacy behavior).
|
||||||
* ``username`` - GitHub authorization user, string, required. Basically the same as ``owner``.
|
* ``username`` - GitHub authorization user, string, required. Basically the same as ``owner``.
|
||||||
|
|
||||||
@ -322,7 +330,7 @@ This feature requires GitHub key creation (see below). Section name must be eith
|
|||||||
Section name must be either ``remote-service`` (plus optional architecture name, e.g. ``remote-service:x86_64``) or random name with ``type`` set.
|
Section name must be either ``remote-service`` (plus optional architecture name, e.g. ``remote-service:x86_64``) or random name with ``type`` set.
|
||||||
|
|
||||||
* ``type`` - type of the report, string, optional, must be set to ``remote-service`` if exists.
|
* ``type`` - type of the report, string, optional, must be set to ``remote-service`` if exists.
|
||||||
* ``timeout`` - HTTP request timeout in seconds, int, optional, default is ``30``.
|
* ``timeout`` - HTTP request timeout in seconds, integer, optional, default is ``30``.
|
||||||
|
|
||||||
``rsync`` type
|
``rsync`` type
|
||||||
^^^^^^^^^^^^^^
|
^^^^^^^^^^^^^^
|
||||||
@ -341,7 +349,7 @@ Requires ``boto3`` library to be installed. Section name must be either ``s3`` (
|
|||||||
* ``type`` - type of the upload, string, optional, must be set to ``s3`` if exists.
|
* ``type`` - type of the upload, string, optional, must be set to ``s3`` if exists.
|
||||||
* ``access_key`` - AWS access key ID, string, required.
|
* ``access_key`` - AWS access key ID, string, required.
|
||||||
* ``bucket`` - bucket name (e.g. ``bucket``), string, required.
|
* ``bucket`` - bucket name (e.g. ``bucket``), string, required.
|
||||||
* ``chunk_size`` - chunk size for calculating entity tags, int, optional, default 8 * 1024 * 1024.
|
* ``chunk_size`` - chunk size for calculating entity tags, integer, optional, default 8 * 1024 * 1024.
|
||||||
* ``object_path`` - path prefix for stored objects, string, optional. If none set, the prefix as in repository tree will be used.
|
* ``object_path`` - path prefix for stored objects, string, optional. If none set, the prefix as in repository tree will be used.
|
||||||
* ``region`` - bucket region (e.g. ``eu-central-1``), string, required.
|
* ``region`` - bucket region (e.g. ``eu-central-1``), string, required.
|
||||||
* ``secret_key`` - AWS secret access key, string, required.
|
* ``secret_key`` - AWS secret access key, string, required.
|
10
docs/faq.rst
10
docs/faq.rst
@ -869,12 +869,12 @@ Worker nodes configuration
|
|||||||
|
|
||||||
.. code-block:: ini
|
.. code-block:: ini
|
||||||
|
|
||||||
[web]
|
[status]
|
||||||
address = master.example.com
|
address = https://master.example.com
|
||||||
username = worker-user
|
username = worker-user
|
||||||
password = very-secure-password
|
password = very-secure-password
|
||||||
|
|
||||||
As it has been mentioned above, ``web.address`` must be available for workers. In case if unix socket is used, it can be passed as ``web.unix_socket`` variable as usual. Optional ``web.username``/``web.password`` can be supplied in case if authentication was enabled on master node.
|
As it has been mentioned above, ``status.address`` must be available for workers. In case if unix socket is used, it can be passed in the same option as usual. Optional ``status.username``/``status.password`` can be supplied in case if authentication was enabled on master node.
|
||||||
|
|
||||||
#.
|
#.
|
||||||
Each worker must call master node on success:
|
Each worker must call master node on success:
|
||||||
@ -958,7 +958,7 @@ The user ``worker-user`` has been created additionally. Worker node config (``wo
|
|||||||
|
|
||||||
.. code-block:: ini
|
.. code-block:: ini
|
||||||
|
|
||||||
[web]
|
[status]
|
||||||
address = http://172.17.0.1:8080
|
address = http://172.17.0.1:8080
|
||||||
username = worker-user
|
username = worker-user
|
||||||
password = very-secure-password
|
password = very-secure-password
|
||||||
@ -1142,7 +1142,7 @@ How to enable basic authorization
|
|||||||
|
|
||||||
.. code-block:: ini
|
.. code-block:: ini
|
||||||
|
|
||||||
[web]
|
[status]
|
||||||
username = api
|
username = api
|
||||||
password = pa55w0rd
|
password = pa55w0rd
|
||||||
|
|
||||||
|
@ -3,7 +3,6 @@ include = ahriman.ini.d
|
|||||||
logging = ahriman.ini.d/logging.ini
|
logging = ahriman.ini.d/logging.ini
|
||||||
apply_migrations = yes
|
apply_migrations = yes
|
||||||
database = /var/lib/ahriman/ahriman.db
|
database = /var/lib/ahriman/ahriman.db
|
||||||
suppress_http_log_errors = yes
|
|
||||||
|
|
||||||
[alpm]
|
[alpm]
|
||||||
database = /var/lib/pacman
|
database = /var/lib/pacman
|
||||||
@ -62,6 +61,10 @@ ssl = disabled
|
|||||||
template = repo-index.jinja2
|
template = repo-index.jinja2
|
||||||
templates = /usr/share/ahriman/templates
|
templates = /usr/share/ahriman/templates
|
||||||
|
|
||||||
|
[status]
|
||||||
|
enabled = yes
|
||||||
|
suppress_http_log_errors = yes
|
||||||
|
|
||||||
[telegram]
|
[telegram]
|
||||||
template = telegram-index.jinja2
|
template = telegram-index.jinja2
|
||||||
templates = /usr/share/ahriman/templates
|
templates = /usr/share/ahriman/templates
|
||||||
|
@ -21,6 +21,7 @@ import argparse
|
|||||||
|
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from pwd import getpwuid
|
from pwd import getpwuid
|
||||||
|
from urllib.parse import quote_plus as urlencode
|
||||||
|
|
||||||
from ahriman.application.application import Application
|
from ahriman.application.application import Application
|
||||||
from ahriman.application.handlers import Handler
|
from ahriman.application.handlers import Handler
|
||||||
@ -128,8 +129,12 @@ class Setup(Handler):
|
|||||||
|
|
||||||
if args.web_port is not None:
|
if args.web_port is not None:
|
||||||
configuration.set_option("web", "port", str(args.web_port))
|
configuration.set_option("web", "port", str(args.web_port))
|
||||||
|
if (host := root.get("web", "host", fallback=None)) is not None:
|
||||||
|
configuration.set_option("status", "address", f"http://{host}:{args.web_port}")
|
||||||
if args.web_unix_socket is not None:
|
if args.web_unix_socket is not None:
|
||||||
configuration.set_option("web", "unix_socket", str(args.web_unix_socket))
|
unix_socket = str(args.web_unix_socket)
|
||||||
|
configuration.set_option("web", "unix_socket", unix_socket)
|
||||||
|
configuration.set_option("status", "address", f"http+unix://{urlencode(unix_socket)}")
|
||||||
|
|
||||||
if args.generate_salt:
|
if args.generate_salt:
|
||||||
configuration.set_option("auth", "salt", User.generate_password(20))
|
configuration.set_option("auth", "salt", User.generate_password(20))
|
||||||
|
@ -249,6 +249,32 @@ CONFIGURATION_SCHEMA: ConfigurationSchema = {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
"status": {
|
||||||
|
"type": "dict",
|
||||||
|
"schema": {
|
||||||
|
"enabled": {
|
||||||
|
"type": "boolean",
|
||||||
|
"coerce": "boolean",
|
||||||
|
},
|
||||||
|
"address": {
|
||||||
|
"type": "string",
|
||||||
|
"empty": False,
|
||||||
|
"is_url": [],
|
||||||
|
},
|
||||||
|
"password": {
|
||||||
|
"type": "string",
|
||||||
|
"empty": False,
|
||||||
|
},
|
||||||
|
"suppress_http_log_errors": {
|
||||||
|
"type": "boolean",
|
||||||
|
"coerce": "boolean",
|
||||||
|
},
|
||||||
|
"username": {
|
||||||
|
"type": "string",
|
||||||
|
"empty": False,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
"web": {
|
"web": {
|
||||||
"type": "dict",
|
"type": "dict",
|
||||||
"schema": {
|
"schema": {
|
||||||
|
@ -72,7 +72,9 @@ class HttpLogHandler(logging.Handler):
|
|||||||
if (handler := next((handler for handler in root.handlers if isinstance(handler, cls)), None)) is not None:
|
if (handler := next((handler for handler in root.handlers if isinstance(handler, cls)), None)) is not None:
|
||||||
return handler # there is already registered instance
|
return handler # there is already registered instance
|
||||||
|
|
||||||
suppress_errors = configuration.getboolean("settings", "suppress_http_log_errors", fallback=False)
|
suppress_errors = configuration.getboolean( # read old-style first and then fallback to new style
|
||||||
|
"settings", "suppress_http_log_errors",
|
||||||
|
fallback=configuration.getboolean("status", "suppress_http_log_errors", fallback=False))
|
||||||
handler = cls(repository_id, configuration, report=report, suppress_errors=suppress_errors)
|
handler = cls(repository_id, configuration, report=report, suppress_errors=suppress_errors)
|
||||||
root.addHandler(handler)
|
root.addHandler(handler)
|
||||||
|
|
||||||
|
@ -49,16 +49,19 @@ class Client:
|
|||||||
"""
|
"""
|
||||||
if not report:
|
if not report:
|
||||||
return Client()
|
return Client()
|
||||||
|
if not configuration.getboolean("status", "enabled", fallback=True): # global switch
|
||||||
|
return Client()
|
||||||
|
|
||||||
address = configuration.get("web", "address", fallback=None)
|
# new-style section
|
||||||
|
address = configuration.get("status", "address", fallback=None)
|
||||||
|
# old-style section
|
||||||
|
legacy_address = configuration.get("web", "address", fallback=None)
|
||||||
host = configuration.get("web", "host", fallback=None)
|
host = configuration.get("web", "host", fallback=None)
|
||||||
port = configuration.getint("web", "port", fallback=None)
|
port = configuration.getint("web", "port", fallback=None)
|
||||||
socket = configuration.get("web", "unix_socket", fallback=None)
|
socket = configuration.get("web", "unix_socket", fallback=None)
|
||||||
|
|
||||||
# basically we just check if there is something we can use for interaction with remote server
|
# basically we just check if there is something we can use for interaction with remote server
|
||||||
# at the moment (end of 2022) I think it would be much better idea to introduce flag like `enabled`,
|
if address or legacy_address or (host and port) or socket:
|
||||||
# but it will totally break used experience
|
|
||||||
if address or (host and port) or socket:
|
|
||||||
from ahriman.core.status.web_client import WebClient
|
from ahriman.core.status.web_client import WebClient
|
||||||
return WebClient(repository_id, configuration)
|
return WebClient(repository_id, configuration)
|
||||||
return Client()
|
return Client()
|
||||||
|
@ -22,7 +22,7 @@ import logging
|
|||||||
import requests
|
import requests
|
||||||
|
|
||||||
from functools import cached_property
|
from functools import cached_property
|
||||||
from urllib.parse import quote_plus as urlencode
|
from urllib.parse import quote_plus as urlencode, urlparse
|
||||||
|
|
||||||
from ahriman import __version__
|
from ahriman import __version__
|
||||||
from ahriman.core.configuration import Configuration
|
from ahriman.core.configuration import Configuration
|
||||||
@ -42,7 +42,6 @@ class WebClient(Client, SyncHttpClient):
|
|||||||
Attributes:
|
Attributes:
|
||||||
address(str): address of the web service
|
address(str): address of the web service
|
||||||
repository_id(RepositoryId): repository unique identifier
|
repository_id(RepositoryId): repository unique identifier
|
||||||
use_unix_socket(bool): use websocket or not
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, repository_id: RepositoryId, configuration: Configuration) -> None:
|
def __init__(self, repository_id: RepositoryId, configuration: Configuration) -> None:
|
||||||
@ -53,11 +52,13 @@ class WebClient(Client, SyncHttpClient):
|
|||||||
repository_id(RepositoryId): repository unique identifier
|
repository_id(RepositoryId): repository unique identifier
|
||||||
configuration(Configuration): configuration instance
|
configuration(Configuration): configuration instance
|
||||||
"""
|
"""
|
||||||
suppress_errors = configuration.getboolean("settings", "suppress_http_log_errors", fallback=False)
|
section, self.address = self.parse_address(configuration)
|
||||||
SyncHttpClient.__init__(self, configuration, "web", suppress_errors=suppress_errors)
|
suppress_errors = configuration.getboolean( # read old-style first and then fallback to new style
|
||||||
|
"settings", "suppress_http_log_errors",
|
||||||
|
fallback=configuration.getboolean("status", "suppress_http_log_errors", fallback=False))
|
||||||
|
SyncHttpClient.__init__(self, configuration, section, suppress_errors=suppress_errors)
|
||||||
|
|
||||||
self.repository_id = repository_id
|
self.repository_id = repository_id
|
||||||
self.address, self.use_unix_socket = self.parse_address(configuration)
|
|
||||||
|
|
||||||
@cached_property
|
@cached_property
|
||||||
def session(self) -> requests.Session:
|
def session(self) -> requests.Session:
|
||||||
@ -67,41 +68,7 @@ class WebClient(Client, SyncHttpClient):
|
|||||||
Returns:
|
Returns:
|
||||||
request.Session: created session object
|
request.Session: created session object
|
||||||
"""
|
"""
|
||||||
return self._create_session(use_unix_socket=self.use_unix_socket)
|
if urlparse(self.address).scheme == "http+unix":
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def parse_address(configuration: Configuration) -> tuple[str, bool]:
|
|
||||||
"""
|
|
||||||
parse address from configuration
|
|
||||||
|
|
||||||
Args:
|
|
||||||
configuration(Configuration): configuration instance
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
tuple[str, bool]: tuple of server address and socket flag (True in case if unix socket must be used)
|
|
||||||
"""
|
|
||||||
if (unix_socket := configuration.get("web", "unix_socket", fallback=None)) is not None:
|
|
||||||
# special pseudo-protocol which is used for unix sockets
|
|
||||||
return f"http+unix://{urlencode(unix_socket)}", True
|
|
||||||
address = configuration.get("web", "address", fallback=None)
|
|
||||||
if not address:
|
|
||||||
# build address from host and port directly
|
|
||||||
host = configuration.get("web", "host")
|
|
||||||
port = configuration.getint("web", "port")
|
|
||||||
address = f"http://{host}:{port}"
|
|
||||||
return address, False
|
|
||||||
|
|
||||||
def _create_session(self, *, use_unix_socket: bool) -> requests.Session:
|
|
||||||
"""
|
|
||||||
generate new request session
|
|
||||||
|
|
||||||
Args:
|
|
||||||
use_unix_socket(bool): if set to True then unix socket session will be generated instead of native requests
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
requests.Session: generated session object
|
|
||||||
"""
|
|
||||||
if use_unix_socket:
|
|
||||||
import requests_unixsocket # type: ignore[import-untyped]
|
import requests_unixsocket # type: ignore[import-untyped]
|
||||||
session: requests.Session = requests_unixsocket.Session()
|
session: requests.Session = requests_unixsocket.Session()
|
||||||
session.headers["User-Agent"] = f"ahriman/{__version__}"
|
session.headers["User-Agent"] = f"ahriman/{__version__}"
|
||||||
@ -113,6 +80,33 @@ class WebClient(Client, SyncHttpClient):
|
|||||||
|
|
||||||
return session
|
return session
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def parse_address(configuration: Configuration) -> tuple[str, str]:
|
||||||
|
"""
|
||||||
|
parse address from legacy configuration
|
||||||
|
|
||||||
|
Args:
|
||||||
|
configuration(Configuration): configuration instance
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
tuple[str, str]: tuple of section name and server address
|
||||||
|
"""
|
||||||
|
# new-style section
|
||||||
|
if (address := configuration.get("status", "address", fallback=None)) is not None:
|
||||||
|
return "status", address
|
||||||
|
|
||||||
|
# legacy-style section
|
||||||
|
if (unix_socket := configuration.get("web", "unix_socket", fallback=None)) is not None:
|
||||||
|
# special pseudo-protocol which is used for unix sockets
|
||||||
|
return "web", f"http+unix://{urlencode(unix_socket)}"
|
||||||
|
address = configuration.get("web", "address", fallback=None)
|
||||||
|
if not address:
|
||||||
|
# build address from host and port directly
|
||||||
|
host = configuration.get("web", "host")
|
||||||
|
port = configuration.getint("web", "port")
|
||||||
|
address = f"http://{host}:{port}"
|
||||||
|
return "web", address
|
||||||
|
|
||||||
def _login(self, session: requests.Session) -> None:
|
def _login(self, session: requests.Session) -> None:
|
||||||
"""
|
"""
|
||||||
process login to the service
|
process login to the service
|
||||||
|
@ -5,6 +5,7 @@ from pathlib import Path
|
|||||||
from pytest_mock import MockerFixture
|
from pytest_mock import MockerFixture
|
||||||
from typing import Any
|
from typing import Any
|
||||||
from unittest.mock import call as MockCall
|
from unittest.mock import call as MockCall
|
||||||
|
from urllib.parse import quote_plus as urlencode
|
||||||
|
|
||||||
from ahriman.application.handlers import Setup
|
from ahriman.application.handlers import Setup
|
||||||
from ahriman.core.configuration import Configuration
|
from ahriman.core.configuration import Configuration
|
||||||
@ -145,7 +146,9 @@ def test_configuration_create_ahriman(args: argparse.Namespace, configuration: C
|
|||||||
MockCall(Configuration.section_name("sign", repository_id.name, repository_id.architecture), "key",
|
MockCall(Configuration.section_name("sign", repository_id.name, repository_id.architecture), "key",
|
||||||
args.sign_key),
|
args.sign_key),
|
||||||
MockCall("web", "port", str(args.web_port)),
|
MockCall("web", "port", str(args.web_port)),
|
||||||
|
MockCall("status", "address", f"http://127.0.0.1:{str(args.web_port)}"),
|
||||||
MockCall("web", "unix_socket", str(args.web_unix_socket)),
|
MockCall("web", "unix_socket", str(args.web_unix_socket)),
|
||||||
|
MockCall("status", "address", f"http+unix://{urlencode(str(args.web_unix_socket))}"),
|
||||||
MockCall("auth", "salt", pytest.helpers.anyvar(str, strict=True)),
|
MockCall("auth", "salt", pytest.helpers.anyvar(str, strict=True)),
|
||||||
])
|
])
|
||||||
write_mock.assert_called_once_with(pytest.helpers.anyvar(int))
|
write_mock.assert_called_once_with(pytest.helpers.anyvar(int))
|
||||||
|
@ -30,9 +30,30 @@ def test_load_dummy_client_disabled(configuration: Configuration) -> None:
|
|||||||
assert not isinstance(Client.load(repository_id, configuration, report=False), WebClient)
|
assert not isinstance(Client.load(repository_id, configuration, report=False), WebClient)
|
||||||
|
|
||||||
|
|
||||||
def test_load_full_client(configuration: Configuration) -> None:
|
def test_load_dummy_client_disabled_in_configuration(configuration: Configuration) -> None:
|
||||||
"""
|
"""
|
||||||
must load full client if settings set
|
must load dummy client if disabled in configuration
|
||||||
|
"""
|
||||||
|
configuration.set_option("web", "host", "localhost")
|
||||||
|
configuration.set_option("web", "port", "8080")
|
||||||
|
configuration.set_option("status", "enabled", "no")
|
||||||
|
|
||||||
|
_, repository_id = configuration.check_loaded()
|
||||||
|
assert not isinstance(Client.load(repository_id, configuration, report=True), WebClient)
|
||||||
|
|
||||||
|
|
||||||
|
def test_load_full_client_from_address(configuration: Configuration) -> None:
|
||||||
|
"""
|
||||||
|
must load full client by using address
|
||||||
|
"""
|
||||||
|
configuration.set_option("status", "address", "http://localhost:8080")
|
||||||
|
_, repository_id = configuration.check_loaded()
|
||||||
|
assert isinstance(Client.load(repository_id, configuration, report=True), WebClient)
|
||||||
|
|
||||||
|
|
||||||
|
def test_load_full_client_from_legacy_host(configuration: Configuration) -> None:
|
||||||
|
"""
|
||||||
|
must load full client if host and port settings set
|
||||||
"""
|
"""
|
||||||
configuration.set_option("web", "host", "localhost")
|
configuration.set_option("web", "host", "localhost")
|
||||||
configuration.set_option("web", "port", "8080")
|
configuration.set_option("web", "port", "8080")
|
||||||
@ -41,16 +62,16 @@ def test_load_full_client(configuration: Configuration) -> None:
|
|||||||
assert isinstance(Client.load(repository_id, configuration, report=True), WebClient)
|
assert isinstance(Client.load(repository_id, configuration, report=True), WebClient)
|
||||||
|
|
||||||
|
|
||||||
def test_load_full_client_from_address(configuration: Configuration) -> None:
|
def test_load_full_client_from_legacy_address(configuration: Configuration) -> None:
|
||||||
"""
|
"""
|
||||||
must load full client by using address
|
must load full client by using legacy address
|
||||||
"""
|
"""
|
||||||
configuration.set_option("web", "address", "http://localhost:8080")
|
configuration.set_option("web", "address", "http://localhost:8080")
|
||||||
_, repository_id = configuration.check_loaded()
|
_, repository_id = configuration.check_loaded()
|
||||||
assert isinstance(Client.load(repository_id, configuration, report=True), WebClient)
|
assert isinstance(Client.load(repository_id, configuration, report=True), WebClient)
|
||||||
|
|
||||||
|
|
||||||
def test_load_full_client_from_unix_socket(configuration: Configuration) -> None:
|
def test_load_full_client_from_legacy_unix_socket(configuration: Configuration) -> None:
|
||||||
"""
|
"""
|
||||||
must load full client by using unix socket
|
must load full client by using unix socket
|
||||||
"""
|
"""
|
||||||
|
@ -15,42 +15,44 @@ from ahriman.models.package import Package
|
|||||||
from ahriman.models.user import User
|
from ahriman.models.user import User
|
||||||
|
|
||||||
|
|
||||||
|
def test_session(web_client: WebClient, mocker: MockerFixture) -> None:
|
||||||
|
"""
|
||||||
|
must create normal requests session
|
||||||
|
"""
|
||||||
|
login_mock = mocker.patch("ahriman.core.status.web_client.WebClient._login")
|
||||||
|
|
||||||
|
assert isinstance(web_client.session, requests.Session)
|
||||||
|
assert not isinstance(web_client.session, requests_unixsocket.Session)
|
||||||
|
login_mock.assert_called_once_with(pytest.helpers.anyvar(int))
|
||||||
|
|
||||||
|
|
||||||
|
def test_session_unix_socket(web_client: WebClient, mocker: MockerFixture) -> None:
|
||||||
|
"""
|
||||||
|
must create unix socket session
|
||||||
|
"""
|
||||||
|
login_mock = mocker.patch("ahriman.core.status.web_client.WebClient._login")
|
||||||
|
web_client.address = "http+unix://path"
|
||||||
|
|
||||||
|
assert isinstance(web_client.session, requests_unixsocket.Session)
|
||||||
|
login_mock.assert_not_called()
|
||||||
|
|
||||||
|
|
||||||
def test_parse_address(configuration: Configuration) -> None:
|
def test_parse_address(configuration: Configuration) -> None:
|
||||||
"""
|
"""
|
||||||
must extract address correctly
|
must extract address correctly
|
||||||
"""
|
"""
|
||||||
configuration.set_option("web", "host", "localhost")
|
configuration.set_option("web", "host", "localhost")
|
||||||
configuration.set_option("web", "port", "8080")
|
configuration.set_option("web", "port", "8080")
|
||||||
assert WebClient.parse_address(configuration) == ("http://localhost:8080", False)
|
assert WebClient.parse_address(configuration) == ("web", "http://localhost:8080")
|
||||||
|
|
||||||
configuration.set_option("web", "address", "http://localhost:8081")
|
configuration.set_option("web", "address", "http://localhost:8081")
|
||||||
assert WebClient.parse_address(configuration) == ("http://localhost:8081", False)
|
assert WebClient.parse_address(configuration) == ("web", "http://localhost:8081")
|
||||||
|
|
||||||
configuration.set_option("web", "unix_socket", "/run/ahriman.sock")
|
configuration.set_option("web", "unix_socket", "/run/ahriman.sock")
|
||||||
assert WebClient.parse_address(configuration) == ("http+unix://%2Frun%2Fahriman.sock", True)
|
assert WebClient.parse_address(configuration) == ("web", "http+unix://%2Frun%2Fahriman.sock")
|
||||||
|
|
||||||
|
configuration.set_option("status", "address", "http://localhost:8082")
|
||||||
def test_create_session(web_client: WebClient, mocker: MockerFixture) -> None:
|
assert WebClient.parse_address(configuration) == ("status", "http://localhost:8082")
|
||||||
"""
|
|
||||||
must create normal requests session
|
|
||||||
"""
|
|
||||||
login_mock = mocker.patch("ahriman.core.status.web_client.WebClient._login")
|
|
||||||
|
|
||||||
session = web_client._create_session(use_unix_socket=False)
|
|
||||||
assert isinstance(session, requests.Session)
|
|
||||||
assert not isinstance(session, requests_unixsocket.Session)
|
|
||||||
login_mock.assert_called_once_with(pytest.helpers.anyvar(int))
|
|
||||||
|
|
||||||
|
|
||||||
def test_create_session_unix_socket(web_client: WebClient, mocker: MockerFixture) -> None:
|
|
||||||
"""
|
|
||||||
must create unix socket session
|
|
||||||
"""
|
|
||||||
login_mock = mocker.patch("ahriman.core.status.web_client.WebClient._login")
|
|
||||||
|
|
||||||
session = web_client._create_session(use_unix_socket=True)
|
|
||||||
assert isinstance(session, requests_unixsocket.Session)
|
|
||||||
login_mock.assert_not_called()
|
|
||||||
|
|
||||||
|
|
||||||
def test_login(web_client: WebClient, user: User, mocker: MockerFixture) -> None:
|
def test_login(web_client: WebClient, user: User, mocker: MockerFixture) -> None:
|
||||||
|
@ -8,13 +8,11 @@ from ahriman.core.upload.remote_service import RemoteService
|
|||||||
from ahriman.models.package import Package
|
from ahriman.models.package import Package
|
||||||
|
|
||||||
|
|
||||||
def test_session(remote_service: RemoteService, mocker: MockerFixture) -> None:
|
def test_session(remote_service: RemoteService) -> None:
|
||||||
"""
|
"""
|
||||||
must generate ahriman session
|
must generate ahriman session
|
||||||
"""
|
"""
|
||||||
upload_mock = mocker.patch("ahriman.core.status.web_client.WebClient._create_session")
|
assert remote_service.session == remote_service.client.session
|
||||||
assert remote_service.session
|
|
||||||
upload_mock.assert_called_once_with(use_unix_socket=False)
|
|
||||||
|
|
||||||
|
|
||||||
def test_package_upload(remote_service: RemoteService, package_ahriman: Package, mocker: MockerFixture) -> None:
|
def test_package_upload(remote_service: RemoteService, package_ahriman: Package, mocker: MockerFixture) -> None:
|
||||||
|
Loading…
Reference in New Issue
Block a user