restore behavior of the httploghandler

This commit is contained in:
Evgenii Alekseev 2023-08-20 02:38:39 +03:00
parent c83936d4d9
commit 7d7fd691b4
12 changed files with 174 additions and 101 deletions

View File

@ -20,6 +20,14 @@ ahriman.core.configuration.schema module
:no-undoc-members: :no-undoc-members:
:show-inheritance: :show-inheritance:
ahriman.core.configuration.shell\_interpolator module
-----------------------------------------------------
.. automodule:: ahriman.core.configuration.shell_interpolator
:members:
:no-undoc-members:
:show-inheritance:
ahriman.core.configuration.validator module ahriman.core.configuration.validator module
------------------------------------------- -------------------------------------------

View File

@ -84,6 +84,14 @@ ahriman.core.database.migrations.m009\_local\_source module
:no-undoc-members: :no-undoc-members:
:show-inheritance: :show-inheritance:
ahriman.core.database.migrations.m010\_version\_based\_logs\_removal module
---------------------------------------------------------------------------
.. automodule:: ahriman.core.database.migrations.m010_version_based_logs_removal
:members:
:no-undoc-members:
:show-inheritance:
Module contents Module contents
--------------- ---------------

View File

@ -58,11 +58,11 @@ _shtab_ahriman_config_validate_option_strings=('-h' '--help' '-e' '--exit-code')
_shtab_ahriman_repo_config_validate_option_strings=('-h' '--help' '-e' '--exit-code') _shtab_ahriman_repo_config_validate_option_strings=('-h' '--help' '-e' '--exit-code')
_shtab_ahriman_service_key_import_option_strings=('-h' '--help' '--key-server') _shtab_ahriman_service_key_import_option_strings=('-h' '--help' '--key-server')
_shtab_ahriman_key_import_option_strings=('-h' '--help' '--key-server') _shtab_ahriman_key_import_option_strings=('-h' '--help' '--key-server')
_shtab_ahriman_service_setup_option_strings=('-h' '--help' '--build-as-user' '--build-command' '--from-configuration' '--generate-salt' '--no-generate-salt' '--makeflags-jobs' '--no-makeflags-jobs' '--mirror' '--multilib' '--no-multilib' '--packager' '--repository' '--sign-key' '--sign-target' '--web-port' '--web-unix-socket') _shtab_ahriman_service_setup_option_strings=('-h' '--help' '--build-as-user' '--build-command' '--from-configuration' '--generate-salt' '--no-generate-salt' '--makeflags-jobs' '--no-makeflags-jobs' '--mirror' '--multilib' '--no-multilib' '--packager' '--repository' '--server' '--sign-key' '--sign-target' '--web-port' '--web-unix-socket')
_shtab_ahriman_init_option_strings=('-h' '--help' '--build-as-user' '--build-command' '--from-configuration' '--generate-salt' '--no-generate-salt' '--makeflags-jobs' '--no-makeflags-jobs' '--mirror' '--multilib' '--no-multilib' '--packager' '--repository' '--sign-key' '--sign-target' '--web-port' '--web-unix-socket') _shtab_ahriman_init_option_strings=('-h' '--help' '--build-as-user' '--build-command' '--from-configuration' '--generate-salt' '--no-generate-salt' '--makeflags-jobs' '--no-makeflags-jobs' '--mirror' '--multilib' '--no-multilib' '--packager' '--repository' '--server' '--sign-key' '--sign-target' '--web-port' '--web-unix-socket')
_shtab_ahriman_repo_init_option_strings=('-h' '--help' '--build-as-user' '--build-command' '--from-configuration' '--generate-salt' '--no-generate-salt' '--makeflags-jobs' '--no-makeflags-jobs' '--mirror' '--multilib' '--no-multilib' '--packager' '--repository' '--sign-key' '--sign-target' '--web-port' '--web-unix-socket') _shtab_ahriman_repo_init_option_strings=('-h' '--help' '--build-as-user' '--build-command' '--from-configuration' '--generate-salt' '--no-generate-salt' '--makeflags-jobs' '--no-makeflags-jobs' '--mirror' '--multilib' '--no-multilib' '--packager' '--repository' '--server' '--sign-key' '--sign-target' '--web-port' '--web-unix-socket')
_shtab_ahriman_repo_setup_option_strings=('-h' '--help' '--build-as-user' '--build-command' '--from-configuration' '--generate-salt' '--no-generate-salt' '--makeflags-jobs' '--no-makeflags-jobs' '--mirror' '--multilib' '--no-multilib' '--packager' '--repository' '--sign-key' '--sign-target' '--web-port' '--web-unix-socket') _shtab_ahriman_repo_setup_option_strings=('-h' '--help' '--build-as-user' '--build-command' '--from-configuration' '--generate-salt' '--no-generate-salt' '--makeflags-jobs' '--no-makeflags-jobs' '--mirror' '--multilib' '--no-multilib' '--packager' '--repository' '--server' '--sign-key' '--sign-target' '--web-port' '--web-unix-socket')
_shtab_ahriman_setup_option_strings=('-h' '--help' '--build-as-user' '--build-command' '--from-configuration' '--generate-salt' '--no-generate-salt' '--makeflags-jobs' '--no-makeflags-jobs' '--mirror' '--multilib' '--no-multilib' '--packager' '--repository' '--sign-key' '--sign-target' '--web-port' '--web-unix-socket') _shtab_ahriman_setup_option_strings=('-h' '--help' '--build-as-user' '--build-command' '--from-configuration' '--generate-salt' '--no-generate-salt' '--makeflags-jobs' '--no-makeflags-jobs' '--mirror' '--multilib' '--no-multilib' '--packager' '--repository' '--server' '--sign-key' '--sign-target' '--web-port' '--web-unix-socket')
_shtab_ahriman_service_shell_option_strings=('-h' '--help') _shtab_ahriman_service_shell_option_strings=('-h' '--help')
_shtab_ahriman_shell_option_strings=('-h' '--help') _shtab_ahriman_shell_option_strings=('-h' '--help')
_shtab_ahriman_user_add_option_strings=('-h' '--help' '--key' '--packager' '-p' '--password' '-r' '--role') _shtab_ahriman_user_add_option_strings=('-h' '--help' '--key' '--packager' '-p' '--password' '-r' '--role')

View File

@ -1,4 +1,4 @@
.TH AHRIMAN "1" "2023\-08\-13" "ahriman" "Generated Python Manual" .TH AHRIMAN "1" "2023\-08\-19" "ahriman" "Generated Python Manual"
.SH NAME .SH NAME
ahriman ahriman
.SH SYNOPSIS .SH SYNOPSIS
@ -689,7 +689,7 @@ key server for key import
usage: ahriman service\-setup [\-h] [\-\-build\-as\-user BUILD_AS_USER] [\-\-build\-command BUILD_COMMAND] usage: ahriman service\-setup [\-h] [\-\-build\-as\-user BUILD_AS_USER] [\-\-build\-command BUILD_COMMAND]
[\-\-from\-configuration FROM_CONFIGURATION] [\-\-generate\-salt | \-\-no\-generate\-salt] [\-\-from\-configuration FROM_CONFIGURATION] [\-\-generate\-salt | \-\-no\-generate\-salt]
[\-\-makeflags\-jobs | \-\-no\-makeflags\-jobs] [\-\-mirror MIRROR] [\-\-multilib | \-\-no\-multilib] [\-\-makeflags\-jobs | \-\-no\-makeflags\-jobs] [\-\-mirror MIRROR] [\-\-multilib | \-\-no\-multilib]
\-\-packager PACKAGER \-\-repository REPOSITORY [\-\-sign\-key SIGN_KEY] \-\-packager PACKAGER \-\-repository REPOSITORY [\-\-server SERVER] [\-\-sign\-key SIGN_KEY]
[\-\-sign\-target {disabled,packages,repository}] [\-\-web\-port WEB_PORT] [\-\-sign\-target {disabled,packages,repository}] [\-\-web\-port WEB_PORT]
[\-\-web\-unix\-socket WEB_UNIX_SOCKET] [\-\-web\-unix\-socket WEB_UNIX_SOCKET]
@ -732,6 +732,10 @@ packager name and email
\fB\-\-repository\fR \fI\,REPOSITORY\/\fR \fB\-\-repository\fR \fI\,REPOSITORY\/\fR
repository name repository name
.TP
\fB\-\-server\fR \fI\,SERVER\/\fR
server to be used for devtools. If none set, local files will be used
.TP .TP
\fB\-\-sign\-key\fR \fI\,SIGN_KEY\/\fR \fB\-\-sign\-key\fR \fI\,SIGN_KEY\/\fR
sign key id sign key id

View File

@ -177,6 +177,7 @@ _shtab_ahriman_init_options=(
{--multilib,--no-multilib}"[add or do not multilib repository (default\: True)]:multilib:" {--multilib,--no-multilib}"[add or do not multilib repository (default\: True)]:multilib:"
"--packager[packager name and email (default\: None)]:packager:" "--packager[packager name and email (default\: None)]:packager:"
"--repository[repository name (default\: None)]:repository:" "--repository[repository name (default\: None)]:repository:"
"--server[server to be used for devtools. If none set, local files will be used (default\: None)]:server:"
"--sign-key[sign key id (default\: None)]:sign_key:" "--sign-key[sign key id (default\: None)]:sign_key:"
"*--sign-target[sign options (default\: None)]:sign_target:(disabled packages repository)" "*--sign-target[sign options (default\: None)]:sign_target:(disabled packages repository)"
"--web-port[port of the web service (default\: None)]:web_port:" "--web-port[port of the web service (default\: None)]:web_port:"
@ -347,6 +348,7 @@ _shtab_ahriman_repo_init_options=(
{--multilib,--no-multilib}"[add or do not multilib repository (default\: True)]:multilib:" {--multilib,--no-multilib}"[add or do not multilib repository (default\: True)]:multilib:"
"--packager[packager name and email (default\: None)]:packager:" "--packager[packager name and email (default\: None)]:packager:"
"--repository[repository name (default\: None)]:repository:" "--repository[repository name (default\: None)]:repository:"
"--server[server to be used for devtools. If none set, local files will be used (default\: None)]:server:"
"--sign-key[sign key id (default\: None)]:sign_key:" "--sign-key[sign key id (default\: None)]:sign_key:"
"*--sign-target[sign options (default\: None)]:sign_target:(disabled packages repository)" "*--sign-target[sign options (default\: None)]:sign_target:(disabled packages repository)"
"--web-port[port of the web service (default\: None)]:web_port:" "--web-port[port of the web service (default\: None)]:web_port:"
@ -390,6 +392,7 @@ _shtab_ahriman_repo_setup_options=(
{--multilib,--no-multilib}"[add or do not multilib repository (default\: True)]:multilib:" {--multilib,--no-multilib}"[add or do not multilib repository (default\: True)]:multilib:"
"--packager[packager name and email (default\: None)]:packager:" "--packager[packager name and email (default\: None)]:packager:"
"--repository[repository name (default\: None)]:repository:" "--repository[repository name (default\: None)]:repository:"
"--server[server to be used for devtools. If none set, local files will be used (default\: None)]:server:"
"--sign-key[sign key id (default\: None)]:sign_key:" "--sign-key[sign key id (default\: None)]:sign_key:"
"*--sign-target[sign options (default\: None)]:sign_target:(disabled packages repository)" "*--sign-target[sign options (default\: None)]:sign_target:(disabled packages repository)"
"--web-port[port of the web service (default\: None)]:web_port:" "--web-port[port of the web service (default\: None)]:web_port:"
@ -482,6 +485,7 @@ _shtab_ahriman_service_setup_options=(
{--multilib,--no-multilib}"[add or do not multilib repository (default\: True)]:multilib:" {--multilib,--no-multilib}"[add or do not multilib repository (default\: True)]:multilib:"
"--packager[packager name and email (default\: None)]:packager:" "--packager[packager name and email (default\: None)]:packager:"
"--repository[repository name (default\: None)]:repository:" "--repository[repository name (default\: None)]:repository:"
"--server[server to be used for devtools. If none set, local files will be used (default\: None)]:server:"
"--sign-key[sign key id (default\: None)]:sign_key:" "--sign-key[sign key id (default\: None)]:sign_key:"
"*--sign-target[sign options (default\: None)]:sign_target:(disabled packages repository)" "*--sign-target[sign options (default\: None)]:sign_target:(disabled packages repository)"
"--web-port[port of the web service (default\: None)]:web_port:" "--web-port[port of the web service (default\: None)]:web_port:"
@ -504,6 +508,7 @@ _shtab_ahriman_setup_options=(
{--multilib,--no-multilib}"[add or do not multilib repository (default\: True)]:multilib:" {--multilib,--no-multilib}"[add or do not multilib repository (default\: True)]:multilib:"
"--packager[packager name and email (default\: None)]:packager:" "--packager[packager name and email (default\: None)]:packager:"
"--repository[repository name (default\: None)]:repository:" "--repository[repository name (default\: None)]:repository:"
"--server[server to be used for devtools. If none set, local files will be used (default\: None)]:server:"
"--sign-key[sign key id (default\: None)]:sign_key:" "--sign-key[sign key id (default\: None)]:sign_key:"
"*--sign-target[sign options (default\: None)]:sign_target:(disabled packages repository)" "*--sign-target[sign options (default\: None)]:sign_target:(disabled packages repository)"
"--web-port[port of the web service (default\: None)]:web_port:" "--web-port[port of the web service (default\: None)]:web_port:"

View File

@ -66,9 +66,9 @@ class LogsOperations(Operations):
connection.execute( connection.execute(
""" """
insert into logs insert into logs
(package_base, created, version, record) (package_base, version, created, record)
values values
(:package_base, :created, :version, :record) (:package_base, :version, :created, :record)
""", """,
{ {
"package_base": log_record_id.package_base, "package_base": log_record_id.package_base,

View File

@ -31,15 +31,17 @@ class HttpLogHandler(logging.Handler):
Attributes: Attributes:
reporter(Client): build status reporter instance reporter(Client): build status reporter instance
suppress_errors(bool): suppress logging errors (e.g. if no web server available)
""" """
def __init__(self, configuration: Configuration, *, report: bool) -> None: def __init__(self, configuration: Configuration, *, report: bool, suppress_errors: bool) -> None:
""" """
default constructor default constructor
Args: Args:
configuration(Configuration): configuration instance configuration(Configuration): configuration instance
report(bool): force enable or disable reporting report(bool): force enable or disable reporting
suppress_errors(bool): suppress logging errors (e.g. if no web server available)
""" """
# we don't really care about those parameters because they will be handled by the reporter # we don't really care about those parameters because they will be handled by the reporter
logging.Handler.__init__(self) logging.Handler.__init__(self)
@ -47,6 +49,7 @@ class HttpLogHandler(logging.Handler):
# client has to be imported here because of circular imports # client has to be imported here because of circular imports
from ahriman.core.status.client import Client from ahriman.core.status.client import Client
self.reporter = Client.load(configuration, report=report) self.reporter = Client.load(configuration, report=report)
self.suppress_errors = suppress_errors
@classmethod @classmethod
def load(cls, configuration: Configuration, *, report: bool) -> Self: def load(cls, configuration: Configuration, *, report: bool) -> Self:
@ -65,7 +68,8 @@ 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
handler = cls(configuration, report=report) suppress_errors = configuration.getboolean("settings", "suppress_http_log_errors", fallback=False)
handler = cls(configuration, report=report, suppress_errors=suppress_errors)
root.addHandler(handler) root.addHandler(handler)
return handler return handler
@ -81,4 +85,9 @@ class HttpLogHandler(logging.Handler):
if log_record_id is None: if log_record_id is None:
return # in case if no package base supplied we need just skip log message return # in case if no package base supplied we need just skip log message
self.reporter.package_logs(log_record_id, record) try:
self.reporter.package_logs(log_record_id, record)
except Exception:
if self.suppress_errors:
return
self.handleError(record)

View File

@ -17,6 +17,8 @@
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
# #
import requests
from ahriman.core.configuration import Configuration from ahriman.core.configuration import Configuration
from ahriman.core.report.report import Report from ahriman.core.report.report import Report
from ahriman.core.status.web_client import WebClient from ahriman.core.status.web_client import WebClient
@ -77,43 +79,41 @@ class RemoteCall(Report):
Returns: 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
""" """
response = self.client.make_request("GET", f"/api/v1/service/process/{process_id}") try:
if response is None: response = self.client.make_request("GET", f"/api/v1/service/process/{process_id}")
return False except requests.RequestException as e:
if e.response is not None and e.response.status_code == 404:
return False
raise
response_json = response.json() response_json = response.json()
is_alive: bool = response_json["is_alive"] is_alive: bool = response_json["is_alive"]
return is_alive return is_alive
def remote_update(self) -> str | None: def remote_update(self) -> str:
""" """
call remote server for update call remote server for update
Returns: Returns:
str | None: remote process id on success and ``None`` otherwise str: remote process id
""" """
response = self.client.make_request("POST", "/api/v1/service/update", json={ response = self.client.make_request("POST", "/api/v1/service/update", json={
"aur": self.update_aur, "aur": self.update_aur,
"local": self.update_local, "local": self.update_local,
"manual": self.update_manual, "manual": self.update_manual,
}) })
if response is None:
return None # request terminated with error
response_json = response.json() response_json = response.json()
process_id: str = response_json["process_id"] process_id: str = response_json["process_id"]
return process_id return process_id
def remote_wait(self, process_id: str | None) -> None: def remote_wait(self, process_id: str) -> None:
""" """
wait for remote process termination wait for remote process termination
Args: Args:
process_id(str | None): remote process id process_id(str): remote process id
""" """
if process_id is None:
return # nothing to track
waiter = Waiter(self.wait_timeout) waiter = Waiter(self.wait_timeout)
waiter.wait(self.is_process_alive, process_id) waiter.wait(self.is_process_alive, process_id)

View File

@ -21,7 +21,6 @@ import contextlib
import logging import logging
import requests import requests
from collections.abc import Generator
from functools import cached_property from functools import cached_property
from typing import Any, IO, Literal from typing import Any, IO, Literal
from urllib.parse import quote_plus as urlencode from urllib.parse import quote_plus as urlencode
@ -129,32 +128,6 @@ class WebClient(Client, LazyLogging):
address = f"http://{host}:{port}" address = f"http://{host}:{port}"
return address, False return address, False
@contextlib.contextmanager
def __get_session(self, session: requests.Session | None = None) -> Generator[requests.Session, None, None]:
"""
execute request and handle exceptions
Args:
session(requests.Session | None, optional): session to be used or stored instance property otherwise
(Default value = None)
Yields:
requests.Session: session for requests
"""
try:
if session is not None:
yield session # use session from arguments
else:
yield self.session # use instance generated session
except requests.RequestException as e:
if self.suppress_errors:
return
self.logger.exception("could not perform http request: %s", exception_response_text(e))
except Exception:
if self.suppress_errors:
return
self.logger.exception("could not perform http request")
def _create_session(self, *, use_unix_socket: bool) -> requests.Session: def _create_session(self, *, use_unix_socket: bool) -> requests.Session:
""" """
generate new request session generate new request session
@ -191,13 +164,15 @@ class WebClient(Client, LazyLogging):
"username": self.user.username, "username": self.user.username,
"password": self.user.password "password": self.user.password
} }
self.make_request("POST", self._login_url, json=payload, session=session) with contextlib.suppress(Exception):
self.make_request("POST", self._login_url, json=payload, session=session)
def make_request(self, method: Literal["DELETE", "GET", "POST"], url: str, *, def make_request(self, method: Literal["DELETE", "GET", "POST"], url: str, *,
params: list[tuple[str, str]] | None = None, params: list[tuple[str, str]] | None = None,
json: dict[str, Any] | None = None, json: dict[str, Any] | None = None,
files: dict[str, MultipartType] | None = None, files: dict[str, MultipartType] | None = None,
session: requests.Session | None = None) -> requests.Response | None: session: requests.Session | None = None,
suppress_errors: bool | None = None) -> requests.Response:
""" """
perform request with specified parameters perform request with specified parameters
@ -208,17 +183,30 @@ class WebClient(Client, LazyLogging):
json(dict[str, Any] | None, optional): request json parameters (Default value = None) json(dict[str, Any] | None, optional): request json parameters (Default value = None)
files(dict[str, MultipartType] | None, optional): multipart upload (Default value = None) files(dict[str, MultipartType] | None, optional): multipart upload (Default value = None)
session(requests.Session | None, optional): session object if any (Default value = None) session(requests.Session | None, optional): session object if any (Default value = None)
suppress_errors(bool | None, optional): suppress logging errors (e.g. if no web server available). If none
set, the instance-wide value will be used (Default value = None)
Returns: Returns:
requests.Response | None: response object or None in case of errors requests.Response: response object
""" """
with self.__get_session(session) as _session: # defaults
response = _session.request(method, f"{self.address}{url}", params=params, json=json, files=files) if suppress_errors is None:
suppress_errors = self.suppress_errors
if session is None:
session = self.session
try:
response = session.request(method, f"{self.address}{url}", params=params, json=json, files=files)
response.raise_for_status() response.raise_for_status()
return response return response
except requests.RequestException as e:
# noinspection PyUnreachableCode if not suppress_errors:
return None self.logger.exception("could not perform http request: %s", exception_response_text(e))
raise
except Exception:
if not suppress_errors:
self.logger.exception("could not perform http request")
raise
def package_add(self, package: Package, status: BuildStatusEnum) -> None: def package_add(self, package: Package, status: BuildStatusEnum) -> None:
""" """
@ -232,7 +220,8 @@ class WebClient(Client, LazyLogging):
"status": status.value, "status": status.value,
"package": package.view() "package": package.view()
} }
self.make_request("POST", self._package_url(package.base), json=payload) with contextlib.suppress(Exception):
self.make_request("POST", self._package_url(package.base), json=payload)
def package_get(self, package_base: str | None) -> list[tuple[Package, BuildStatus]]: def package_get(self, package_base: str | None) -> list[tuple[Package, BuildStatus]]:
""" """
@ -244,15 +233,16 @@ class WebClient(Client, LazyLogging):
Returns: Returns:
list[tuple[Package, BuildStatus]]: list of current package description and status if it has been found list[tuple[Package, BuildStatus]]: list of current package description and status if it has been found
""" """
response = self.make_request("GET", self._package_url(package_base or "")) with contextlib.suppress(Exception):
if response is None: response = self.make_request("GET", self._package_url(package_base or ""))
return [] response_json = response.json()
response_json = response.json() return [
return [ (Package.from_json(package["package"]), BuildStatus.from_json(package["status"]))
(Package.from_json(package["package"]), BuildStatus.from_json(package["status"])) for package in response_json
for package in response_json ]
]
return []
def package_logs(self, log_record_id: LogRecordId, record: logging.LogRecord) -> None: def package_logs(self, log_record_id: LogRecordId, record: logging.LogRecord) -> None:
""" """
@ -267,7 +257,11 @@ class WebClient(Client, LazyLogging):
"message": record.getMessage(), "message": record.getMessage(),
"version": log_record_id.version, "version": log_record_id.version,
} }
self.make_request("POST", self._logs_url(log_record_id.package_base), json=payload)
# this is special case, because we would like to do not suppress exception here
# in case of exception raised it will be handled by upstream HttpLogHandler
# In the other hand, we force to suppress all http logs here to avoid cyclic reporting
self.make_request("POST", self._logs_url(log_record_id.package_base), json=payload, suppress_errors=True)
def package_remove(self, package_base: str) -> None: def package_remove(self, package_base: str) -> None:
""" """
@ -276,7 +270,8 @@ class WebClient(Client, LazyLogging):
Args: Args:
package_base(str): basename to remove package_base(str): basename to remove
""" """
self.make_request("DELETE", self._package_url(package_base)) with contextlib.suppress(Exception):
self.make_request("DELETE", self._package_url(package_base))
def package_update(self, package_base: str, status: BuildStatusEnum) -> None: def package_update(self, package_base: str, status: BuildStatusEnum) -> None:
""" """
@ -287,7 +282,8 @@ class WebClient(Client, LazyLogging):
status(BuildStatusEnum): current package build status status(BuildStatusEnum): current package build status
""" """
payload = {"status": status.value} payload = {"status": status.value}
self.make_request("POST", self._package_url(package_base), json=payload) with contextlib.suppress(Exception):
self.make_request("POST", self._package_url(package_base), json=payload)
def status_get(self) -> InternalStatus: def status_get(self) -> InternalStatus:
""" """
@ -296,12 +292,13 @@ class WebClient(Client, LazyLogging):
Returns: Returns:
InternalStatus: current internal (web) service status InternalStatus: current internal (web) service status
""" """
response = self.make_request("GET", self._status_url) with contextlib.suppress(Exception):
if response is None: response = self.make_request("GET", self._status_url)
return InternalStatus(status=BuildStatus()) response_json = response.json()
response_json = response.json() return InternalStatus.from_json(response_json)
return InternalStatus.from_json(response_json)
return InternalStatus(status=BuildStatus())
def status_update(self, status: BuildStatusEnum) -> None: def status_update(self, status: BuildStatusEnum) -> None:
""" """
@ -311,4 +308,5 @@ class WebClient(Client, LazyLogging):
status(BuildStatusEnum): current ahriman status status(BuildStatusEnum): current ahriman status
""" """
payload = {"status": status.value} payload = {"status": status.value}
self.make_request("POST", self._status_url, json=payload) with contextlib.suppress(Exception):
self.make_request("POST", self._status_url, json=payload)

View File

@ -43,18 +43,46 @@ def test_emit(configuration: Configuration, log_record: logging.LogRecord, packa
log_record_id = log_record.package_id = LogRecordId(package_ahriman.base, package_ahriman.version) log_record_id = log_record.package_id = LogRecordId(package_ahriman.base, package_ahriman.version)
log_mock = mocker.patch("ahriman.core.status.client.Client.package_logs") log_mock = mocker.patch("ahriman.core.status.client.Client.package_logs")
handler = HttpLogHandler(configuration, report=False) handler = HttpLogHandler(configuration, report=False, suppress_errors=False)
handler.emit(log_record) handler.emit(log_record)
log_mock.assert_called_once_with(log_record_id, log_record) log_mock.assert_called_once_with(log_record_id, log_record)
def test_emit_failed(configuration: Configuration, log_record: logging.LogRecord, package_ahriman: Package,
mocker: MockerFixture) -> None:
"""
must call handle error on exception
"""
log_record.package_id = LogRecordId(package_ahriman.base, package_ahriman.version)
mocker.patch("ahriman.core.status.client.Client.package_logs", side_effect=Exception())
handle_error_mock = mocker.patch("logging.Handler.handleError")
handler = HttpLogHandler(configuration, report=False, suppress_errors=False)
handler.emit(log_record)
handle_error_mock.assert_called_once_with(log_record)
def test_emit_suppress_failed(configuration: Configuration, log_record: logging.LogRecord, package_ahriman: Package,
mocker: MockerFixture) -> None:
"""
must not call handle error on exception if suppress flag is set
"""
log_record.package_id = LogRecordId(package_ahriman.base, package_ahriman.version)
mocker.patch("ahriman.core.status.client.Client.package_logs", side_effect=Exception())
handle_error_mock = mocker.patch("logging.Handler.handleError")
handler = HttpLogHandler(configuration, report=False, suppress_errors=True)
handler.emit(log_record)
handle_error_mock.assert_not_called()
def test_emit_skip(configuration: Configuration, log_record: logging.LogRecord, mocker: MockerFixture) -> None: def test_emit_skip(configuration: Configuration, log_record: logging.LogRecord, mocker: MockerFixture) -> None:
""" """
must skip log record posting if no package base set must skip log record posting if no package base set
""" """
log_mock = mocker.patch("ahriman.core.status.client.Client.package_logs") log_mock = mocker.patch("ahriman.core.status.client.Client.package_logs")
handler = HttpLogHandler(configuration, report=False) handler = HttpLogHandler(configuration, report=False, suppress_errors=False)
handler.emit(log_record) handler.emit(log_record)
log_mock.assert_not_called() log_mock.assert_not_called()

View File

@ -37,10 +37,37 @@ def test_is_process_alive_unknown(remote_call: RemoteCall, mocker: MockerFixture
""" """
must correctly define if process is unknown must correctly define if process is unknown
""" """
mocker.patch("ahriman.core.status.web_client.WebClient.make_request", return_value=None) response = requests.Response()
response.status_code = 404
mocker.patch("ahriman.core.status.web_client.WebClient.make_request",
side_effect=requests.RequestException(response=response))
assert not remote_call.is_process_alive("id") assert not remote_call.is_process_alive("id")
def test_is_process_alive_error(remote_call: RemoteCall, mocker: MockerFixture) -> None:
"""
must reraise exception on process request
"""
mocker.patch("ahriman.core.status.web_client.WebClient.make_request", side_effect=Exception)
with pytest.raises(Exception):
remote_call.is_process_alive("id")
def test_is_process_alive_http_error(remote_call: RemoteCall, mocker: MockerFixture) -> None:
"""
must reraise http exception on process request
"""
response = requests.Response()
response.status_code = 500
mocker.patch("ahriman.core.status.web_client.WebClient.make_request",
side_effect=requests.RequestException(response=response))
with pytest.raises(requests.RequestException):
remote_call.is_process_alive("id")
def test_remote_update(remote_call: RemoteCall, mocker: MockerFixture) -> None: def test_remote_update(remote_call: RemoteCall, mocker: MockerFixture) -> None:
""" """
must call remote server for update process must call remote server for update process
@ -59,14 +86,6 @@ def test_remote_update(remote_call: RemoteCall, mocker: MockerFixture) -> None:
}) })
def test_remote_update_failed(remote_call: RemoteCall, mocker: MockerFixture) -> None:
"""
must return empty process id in case of errors
"""
mocker.patch("ahriman.core.status.web_client.WebClient.make_request", return_value=None)
assert remote_call.generate([], Result()) is None
def test_remote_wait(remote_call: RemoteCall, mocker: MockerFixture) -> None: def test_remote_wait(remote_call: RemoteCall, mocker: MockerFixture) -> None:
""" """
must wait for remote process to success must wait for remote process to success
@ -74,12 +93,3 @@ def test_remote_wait(remote_call: RemoteCall, mocker: MockerFixture) -> None:
wait_mock = mocker.patch("ahriman.models.waiter.Waiter.wait") wait_mock = mocker.patch("ahriman.models.waiter.Waiter.wait")
remote_call.remote_wait("id") remote_call.remote_wait("id")
wait_mock.assert_called_once_with(pytest.helpers.anyvar(int), "id") wait_mock.assert_called_once_with(pytest.helpers.anyvar(int), "id")
def test_remote_wait_skip(remote_call: RemoteCall, mocker: MockerFixture) -> None:
"""
must skip wait if process id is unknown
"""
wait_mock = mocker.patch("ahriman.models.waiter.Waiter.wait")
remote_call.remote_wait(None)
wait_mock.assert_not_called()

View File

@ -163,7 +163,8 @@ def test_make_request_failed(web_client: WebClient, mocker: MockerFixture) -> No
must make HTTP request must make HTTP request
""" """
mocker.patch("requests.Session.request", side_effect=Exception()) mocker.patch("requests.Session.request", side_effect=Exception())
assert web_client.make_request("GET", "url") is None with pytest.raises(Exception):
web_client.make_request("GET", "url")
def test_package_add(web_client: WebClient, package_ahriman: Package, mocker: MockerFixture) -> None: def test_package_add(web_client: WebClient, package_ahriman: Package, mocker: MockerFixture) -> None:
@ -296,7 +297,8 @@ def test_package_logs_failed(web_client: WebClient, log_record: logging.LogRecor
""" """
mocker.patch("requests.Session.request", side_effect=Exception()) mocker.patch("requests.Session.request", side_effect=Exception())
log_record.package_base = package_ahriman.base log_record.package_base = package_ahriman.base
web_client.package_logs(LogRecordId(package_ahriman.base, package_ahriman.version), log_record) with pytest.raises(Exception):
web_client.package_logs(LogRecordId(package_ahriman.base, package_ahriman.version), log_record)
def test_package_logs_failed_http_error(web_client: WebClient, log_record: logging.LogRecord, package_ahriman: Package, def test_package_logs_failed_http_error(web_client: WebClient, log_record: logging.LogRecord, package_ahriman: Package,
@ -306,7 +308,8 @@ def test_package_logs_failed_http_error(web_client: WebClient, log_record: loggi
""" """
mocker.patch("requests.Session.request", side_effect=requests.exceptions.HTTPError()) mocker.patch("requests.Session.request", side_effect=requests.exceptions.HTTPError())
log_record.package_base = package_ahriman.base log_record.package_base = package_ahriman.base
web_client.package_logs(LogRecordId(package_ahriman.base, package_ahriman.version), log_record) with pytest.raises(Exception):
web_client.package_logs(LogRecordId(package_ahriman.base, package_ahriman.version), log_record)
def test_package_remove(web_client: WebClient, package_ahriman: Package, mocker: MockerFixture) -> None: def test_package_remove(web_client: WebClient, package_ahriman: Package, mocker: MockerFixture) -> None: