feat: add separated switch for status reporting

This commit is contained in:
Evgenii Alekseev 2023-11-11 00:15:44 +02:00
parent 95056cfbe7
commit 62dd77317d
12 changed files with 164 additions and 99 deletions

View File

@ -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.

View File

@ -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

View File

@ -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

View File

@ -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))

View File

@ -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": {

View File

@ -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)

View File

@ -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()

View File

@ -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

View File

@ -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))

View File

@ -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
""" """

View File

@ -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:

View File

@ -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: