From 20962f03855fc1fcba6933c29123ebae87453333 Mon Sep 17 00:00:00 2001 From: Evgeniy Alekseev Date: Sun, 17 Oct 2021 05:33:23 +0300 Subject: [PATCH] allow to use multiple upload and report targets with the same name In this feature target option must allways point to section name instead of type. Type will be read from type option. In case if type option is not presented it will try to check if section with architecture exists (e.g. target = email, section = email:x86_64); if it does, the correct section name and type will be used. Otherwise it will check if the specified section exists; if it does, seection name and type will be returned. --- docs/configuration.md | 43 ++++--- docs/faq.md | 60 ++++++++++ src/ahriman/core/configuration.py | 24 +++- src/ahriman/core/report/email.py | 25 +++-- src/ahriman/core/report/html.py | 9 +- src/ahriman/core/report/report.py | 9 +- src/ahriman/core/upload/github.py | 9 +- src/ahriman/core/upload/rsync.py | 7 +- src/ahriman/core/upload/s3.py | 17 +-- src/ahriman/core/upload/upload.py | 9 +- tests/ahriman/core/report/test_email.py | 22 ++-- tests/ahriman/core/report/test_html.py | 2 +- tests/ahriman/core/report/test_report.py | 9 +- tests/ahriman/core/test_configuration.py | 137 ++++++++++++++--------- tests/ahriman/core/upload/conftest.py | 6 +- tests/ahriman/core/upload/test_upload.py | 10 +- tests/testresources/core/ahriman.ini | 7 +- 17 files changed, 274 insertions(+), 131 deletions(-) diff --git a/docs/configuration.md b/docs/configuration.md index d1366e9b..374ffec0 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -69,12 +69,19 @@ Settings for signing packages or repository. Group name must refer to architectu Report generation settings. -* `target` - list of reports to be generated, space separated list of strings, required. Allowed values are `html`, `email`. +* `target` - list of reports to be generated, space separated list of strings, required. It must point to valid section (or to section with architecture), e.g. `somerandomname` must point to existing section, `email` must point to one of `email` of `email:x86_64` (with architecture it has higher priority). -### `email:*` groups +Type will be read from several ways: -Group name must refer to architecture, e.g. it should be `email:x86_64` for x86_64 architecture. +* In case if `type` option set inside the section, it will be used. +* Otherwise, it will look for type from section name removing architecture name. +* And finally, it will use section name as type. +### `email` type + +Section name must be either `email` (plus optional architecture name, e.g. `email:x86_64`) or random name with `type` set. + +* `type` - type of the report, string, optional, must be set to `email` if exists. * `full_template_path` - path to Jinja2 template for full package description index, string, optional. * `homepage` - link to homepage, string, optional. * `host` - SMTP host for sending emails, string, required. @@ -88,10 +95,11 @@ Group name must refer to architecture, e.g. it should be `email:x86_64` for x86_ * `template_path` - path to Jinja2 template, string, required. * `user` - SMTP user to authenticate, string, optional. -### `html:*` groups +### `html` type -Group name must refer to architecture, e.g. it should be `html:x86_64` for x86_64 architecture. +Section name must be either `html` (plus optional architecture name, e.g. `html:x86_64`) or random name with `type` set. +* `type` - type of the report, string, optional, must be set to `html` if exists. * `path` - path to html report file, string, required. * `homepage` - link to homepage, string, optional. * `link_path` - prefix for HTML links, string, required. @@ -101,12 +109,19 @@ Group name must refer to architecture, e.g. it should be `html:x86_64` for x86_6 Remote synchronization settings. -* `target` - list of synchronizations to be used, space separated list of strings, required. Allowed values are `rsync`, `s3`, `github`. +* `target` - list of synchronizations to be used, space separated list of strings, required. It must point to valid section (or to section with architecture), e.g. `somerandomname` must point to existing section, `github` must point to one of `github` of `github:x86_64` (with architecture it has higher priority). -### `github:*` groups +Type will be read from several ways: -Group name must refer to architecture, e.g. it should be `github:x86_64` for x86_64 architecture. This feature requires Github key creation (see below). +* In case if `type` option set inside the section, it will be used. +* Otherwise, it will look for type from section name removing architecture name. +* And finally, it will use section name as type. +### `github` type + +This feature requires Github key creation (see below). Section name must be either `github` (plus optional architecture name, e.g. `github:x86_64`) or random name with `type` set. + +* `type` - type of the upload, string, optional, must be set to `github` if exists. * `owner` - Github repository owner, string, required. * `password` - created Github API key. In order to create it do the following: 1. Go to [settings page](https://github.com/settings/profile). @@ -116,17 +131,19 @@ Group name must refer to architecture, e.g. it should be `github:x86_64` for x86 * `repository` - Github repository name, string, required. Repository must be created before any action and must have active branch (e.g. with readme). * `username` - Github authorization user, string, required. Basically the same as `owner`. -### `rsync:*` groups +### `rsync` type -Group name must refer to architecture, e.g. it should be `rsync:x86_64` for x86_64 architecture. Requires `rsync` package to be installed. Do not forget to configure ssh for user `ahriman`. +Requires `rsync` package to be installed. Do not forget to configure ssh for user `ahriman`. Section name must be either `rsync` (plus optional architecture name, e.g. `rsync:x86_64`) or random name with `type` set. +* `type` - type of the upload, string, optional, must be set to `rsync` if exists. * `command` - rsync command to run, space separated list of string, required. * `remote` - remote server to rsync (e.g. `1.2.3.4:path/to/sync`), string, required. -### `s3:*` groups +### `s3` type -Group name must refer to architecture, e.g. it should be `s3:x86_64` for x86_64 architecture. +Requires `boto3` library to be installed. Section name must be either `s3` (plus optional architecture name, e.g. `s3:x86_64`) or random name with `type` set. +* `type` - type of the upload, string, optional, must be set to `github` if exists. * `access_key` - AWS access key ID, string, required. * `bucket` - bucket name (e.g. `bucket`), string, required. * `chunk_size` - chunk size for calculating entity tags, int, optional, default 8 * 1024 * 1024. @@ -135,7 +152,7 @@ Group name must refer to architecture, e.g. it should be `s3:x86_64` for x86_64 ## `web:*` groups -Web server settings. If any of `host`/`port` is not set, web integration will be disabled. Group name must refer to architecture, e.g. it should be `web:x86_64` for x86_64 architecture. +Web server settings. If any of `host`/`port` is not set, web integration will be disabled. Group name must refer to architecture, e.g. it should be `web:x86_64` for x86_64 architecture. This feature requires `aiohttp` libraries to be installed. * `address` - optional address in form `proto://host:port` (`port` can be omitted in case of default `proto` ports), will be used instead of `http://{host}:{port}` in case if set, string, optional. This option is required in case if `OAuth` provider is used. * `debug` - enable debug toolbar, boolean, optional, default `no`. diff --git a/docs/faq.md b/docs/faq.md index 15212dee..704a9e37 100644 --- a/docs/faq.md +++ b/docs/faq.md @@ -21,6 +21,66 @@ systemctl enable --now ahriman@x86_64.timer The idea is to install the package as usual, create working directory tree, create configuration for `sudo` and `devtools`. Detailed description of the setup instruction can be found [here](setup.md). +### What does "architecture specific" mean? / How to configure for different architectures? + +Some sections can be configured per architecture. The service will merge architecture specific values into common settings. In order to specify settings for specific architecture you must point it in section name. + +For example, the section + +```ini +[build] +build_command = extra-x86_64-build +``` + +states that default build command is `extra-x86_64-build`. But if there is section + +```ini +[build:i686] +build_command = extra-i686-build +``` + +the `extra-i686-build` command will be used for `i686` architecture. + +### How to use reporter/upload settings? + +Normally you probably like to generate only one report for the specific type, e.g. only one email report. In order to do it you will need to have the following configuration: + +```ini +[report] +target = email + +[email] +... +``` + +or in case of multiple architectures and _different_ reporting settings: + +```ini +[report] +target = email + +[email:i686] +... + +[email:x86_64] +... +``` + +But for some cases you would like to have multiple different reports with the same type (e.g. sending different templates to different addresses). For these cases you will need to specify section name in target and type in section, e.g. the following configuration can be used: + +```ini +[report] +target = email_1 email_2 + +[email_1] +type = email +... + +[email_2] +type = email +... +``` + ### Okay, I've installed ahriman, how do I add new package? ```shell diff --git a/src/ahriman/core/configuration.py b/src/ahriman/core/configuration.py index 4f4d7074..a2107a5d 100644 --- a/src/ahriman/core/configuration.py +++ b/src/ahriman/core/configuration.py @@ -24,7 +24,7 @@ import logging from logging.config import fileConfig from pathlib import Path -from typing import Any, Dict, List, Optional, Type +from typing import Any, Dict, List, Optional, Tuple, Type from ahriman.core.exceptions import InitializeException @@ -42,7 +42,7 @@ class Configuration(configparser.RawConfigParser): DEFAULT_LOG_FORMAT = "[%(levelname)s %(asctime)s] [%(filename)s:%(lineno)d] [%(funcName)s]: %(message)s" DEFAULT_LOG_LEVEL = logging.DEBUG - ARCHITECTURE_SPECIFIC_SECTIONS = ["build", "html", "rsync", "s3", "sign", "web"] + ARCHITECTURE_SPECIFIC_SECTIONS = ["build", "sign", "web"] def __init__(self) -> None: """ @@ -121,6 +121,26 @@ class Configuration(configparser.RawConfigParser): def getpath(self, *args: Any, **kwargs: Any) -> Path: ... + def gettype(self, section: str, architecture: str) -> Tuple[str, str]: + """ + get type variable with fallback to old logic + Despite the fact that it has same semantics as other get* methods, but it has different argument list + :param section: section name + :param architecture: repository architecture + :return: section name and found type name + """ + group_type = self.get(section, "type", fallback=None) # new-style logic + if group_type is not None: + return section, group_type + # okay lets check for the section with architecture name + full_section = self.section_name(section, architecture) + if self.has_section(full_section): + return full_section, section + # okay lets just use section as type + if not self.has_section(section): + raise configparser.NoSectionError(section) + return section, section + def load(self, path: Path) -> None: """ fully load configuration diff --git a/src/ahriman/core/report/email.py b/src/ahriman/core/report/email.py index 1600b1a6..64f784ea 100644 --- a/src/ahriman/core/report/email.py +++ b/src/ahriman/core/report/email.py @@ -45,27 +45,28 @@ class Email(Report, JinjaTemplate): :ivar user: username to authenticate via SMTP """ - def __init__(self, architecture: str, configuration: Configuration) -> None: + def __init__(self, architecture: str, configuration: Configuration, section: str) -> None: """ default constructor :param architecture: repository architecture :param configuration: configuration instance + :param section: settings section name """ Report.__init__(self, architecture, configuration) - JinjaTemplate.__init__(self, "email", configuration) + JinjaTemplate.__init__(self, section, configuration) - self.full_template_path = configuration.getpath("email", "full_template_path", fallback=None) - self.template_path = configuration.getpath("email", "template_path") + self.full_template_path = configuration.getpath(section, "full_template_path", fallback=None) + self.template_path = configuration.getpath(section, "template_path") # base smtp settings - self.host = configuration.get("email", "host") - self.no_empty_report = configuration.getboolean("email", "no_empty_report", fallback=True) - self.password = configuration.get("email", "password", fallback=None) - self.port = configuration.getint("email", "port") - self.receivers = configuration.getlist("email", "receivers") - self.sender = configuration.get("email", "sender") - self.ssl = SmtpSSLSettings.from_option(configuration.get("email", "ssl", fallback="disabled")) - self.user = configuration.get("email", "user", fallback=None) + self.host = configuration.get(section, "host") + self.no_empty_report = configuration.getboolean(section, "no_empty_report", fallback=True) + self.password = configuration.get(section, "password", fallback=None) + self.port = configuration.getint(section, "port") + self.receivers = configuration.getlist(section, "receivers") + self.sender = configuration.get(section, "sender") + self.ssl = SmtpSSLSettings.from_option(configuration.get(section, "ssl", fallback="disabled")) + self.user = configuration.get(section, "user", fallback=None) def _send(self, text: str, attachment: Dict[str, str]) -> None: """ diff --git a/src/ahriman/core/report/html.py b/src/ahriman/core/report/html.py index ee660ea1..e38084fd 100644 --- a/src/ahriman/core/report/html.py +++ b/src/ahriman/core/report/html.py @@ -31,17 +31,18 @@ class HTML(Report, JinjaTemplate): :ivar report_path: output path to html report """ - def __init__(self, architecture: str, configuration: Configuration) -> None: + def __init__(self, architecture: str, configuration: Configuration, section: str) -> None: """ default constructor :param architecture: repository architecture :param configuration: configuration instance + :param section: settings section name """ Report.__init__(self, architecture, configuration) - JinjaTemplate.__init__(self, "html", configuration) + JinjaTemplate.__init__(self, section, configuration) - self.report_path = configuration.getpath("html", "path") - self.template_path = configuration.getpath("html", "template_path") + self.report_path = configuration.getpath(section, "path") + self.template_path = configuration.getpath(section, "template_path") def generate(self, packages: Iterable[Package], built_packages: Iterable[Package]) -> None: """ diff --git a/src/ahriman/core/report/report.py b/src/ahriman/core/report/report.py index 86e79c51..e8f60a9c 100644 --- a/src/ahriman/core/report/report.py +++ b/src/ahriman/core/report/report.py @@ -53,16 +53,17 @@ class Report: load client from settings :param architecture: repository architecture :param configuration: configuration instance - :param target: target to generate report (e.g. html) + :param target: target to generate report aka section name (e.g. html) :return: client according to current settings """ - provider = ReportSettings.from_option(target) + section, provider_name = configuration.gettype(target, architecture) + provider = ReportSettings.from_option(provider_name) if provider == ReportSettings.HTML: from ahriman.core.report.html import HTML - return HTML(architecture, configuration) + return HTML(architecture, configuration, section) if provider == ReportSettings.Email: from ahriman.core.report.email import Email - return Email(architecture, configuration) + return Email(architecture, configuration, section) return cls(architecture, configuration) # should never happen def generate(self, packages: Iterable[Package], built_packages: Iterable[Package]) -> None: diff --git a/src/ahriman/core/upload/github.py b/src/ahriman/core/upload/github.py index 0b84aeab..1ffb9fd4 100644 --- a/src/ahriman/core/upload/github.py +++ b/src/ahriman/core/upload/github.py @@ -36,15 +36,16 @@ class Github(HttpUpload): :ivar gh_repository: github repository name """ - def __init__(self, architecture: str, configuration: Configuration) -> None: + def __init__(self, architecture: str, configuration: Configuration, section: str) -> None: """ default constructor :param architecture: repository architecture :param configuration: configuration instance + :param section: settings section name """ - HttpUpload.__init__(self, architecture, configuration, "github") - self.gh_owner = configuration.get("github", "owner") - self.gh_repository = configuration.get("github", "repository") + HttpUpload.__init__(self, architecture, configuration, section) + self.gh_owner = configuration.get(section, "owner") + self.gh_repository = configuration.get(section, "repository") def asset_remove(self, release: Dict[str, Any], name: str) -> None: """ diff --git a/src/ahriman/core/upload/rsync.py b/src/ahriman/core/upload/rsync.py index a978ae0b..e4507eae 100644 --- a/src/ahriman/core/upload/rsync.py +++ b/src/ahriman/core/upload/rsync.py @@ -35,15 +35,16 @@ class Rsync(Upload): _check_output = check_output - def __init__(self, architecture: str, configuration: Configuration) -> None: + def __init__(self, architecture: str, configuration: Configuration, section: str) -> None: """ default constructor :param architecture: repository architecture :param configuration: configuration instance + :param section: settings section name """ Upload.__init__(self, architecture, configuration) - self.command = configuration.getlist("rsync", "command") - self.remote = configuration.get("rsync", "remote") + self.command = configuration.getlist(section, "command") + self.remote = configuration.get(section, "remote") def sync(self, path: Path, built_packages: Iterable[Package]) -> None: """ diff --git a/src/ahriman/core/upload/s3.py b/src/ahriman/core/upload/s3.py index 7a8a3bcf..3ba6fcf2 100644 --- a/src/ahriman/core/upload/s3.py +++ b/src/ahriman/core/upload/s3.py @@ -37,15 +37,15 @@ class S3(Upload): :ivar chunk_size: chunk size for calculating checksums """ - def __init__(self, architecture: str, configuration: Configuration) -> None: + def __init__(self, architecture: str, configuration: Configuration, section: str) -> None: """ default constructor :param architecture: repository architecture :param configuration: configuration instance """ Upload.__init__(self, architecture, configuration) - self.bucket = self.get_bucket(configuration) - self.chunk_size = configuration.getint("s3", "chunk_size", fallback=8 * 1024 * 1024) + self.bucket = self.get_bucket(configuration, section) + self.chunk_size = configuration.getint(section, "chunk_size", fallback=8 * 1024 * 1024) @staticmethod def calculate_etag(path: Path, chunk_size: int) -> str: @@ -70,17 +70,18 @@ class S3(Upload): return f"{checksum.hexdigest()}{suffix}" @staticmethod - def get_bucket(configuration: Configuration) -> Any: + def get_bucket(configuration: Configuration, section: str) -> Any: """ create resource client from configuration :param configuration: configuration instance + :param section: settings section name :return: amazon client """ client = boto3.resource(service_name="s3", - region_name=configuration.get("s3", "region"), - aws_access_key_id=configuration.get("s3", "access_key"), - aws_secret_access_key=configuration.get("s3", "secret_key")) - return client.Bucket(configuration.get("s3", "bucket")) + region_name=configuration.get(section, "region"), + aws_access_key_id=configuration.get(section, "access_key"), + aws_secret_access_key=configuration.get(section, "secret_key")) + return client.Bucket(configuration.get(section, "bucket")) @staticmethod def files_remove(local_files: Dict[Path, str], remote_objects: Dict[Path, Any]) -> None: diff --git a/src/ahriman/core/upload/upload.py b/src/ahriman/core/upload/upload.py index 5aaa3926..99c8255e 100644 --- a/src/ahriman/core/upload/upload.py +++ b/src/ahriman/core/upload/upload.py @@ -57,16 +57,17 @@ class Upload: :param target: target to run sync (e.g. s3) :return: client according to current settings """ - provider = UploadSettings.from_option(target) + section, provider_name = configuration.gettype(target, architecture) + provider = UploadSettings.from_option(provider_name) if provider == UploadSettings.Rsync: from ahriman.core.upload.rsync import Rsync - return Rsync(architecture, configuration) + return Rsync(architecture, configuration, section) if provider == UploadSettings.S3: from ahriman.core.upload.s3 import S3 - return S3(architecture, configuration) + return S3(architecture, configuration, section) if provider == UploadSettings.Github: from ahriman.core.upload.github import Github - return Github(architecture, configuration) + return Github(architecture, configuration, section) return cls(architecture, configuration) # should never happen def run(self, path: Path, built_packages: Iterable[Package]) -> None: diff --git a/tests/ahriman/core/report/test_email.py b/tests/ahriman/core/report/test_email.py index eece27c0..b0f05aec 100644 --- a/tests/ahriman/core/report/test_email.py +++ b/tests/ahriman/core/report/test_email.py @@ -11,7 +11,7 @@ def test_send(configuration: Configuration, mocker: MockerFixture) -> None: """ smtp_mock = mocker.patch("smtplib.SMTP") - report = Email("x86_64", configuration) + report = Email("x86_64", configuration, "email") report._send("a text", {"attachment.html": "an attachment"}) smtp_mock.return_value.starttls.assert_not_called() smtp_mock.return_value.login.assert_not_called() @@ -27,7 +27,7 @@ def test_send_auth(configuration: Configuration, mocker: MockerFixture) -> None: configuration.set_option("email", "password", "password") smtp_mock = mocker.patch("smtplib.SMTP") - report = Email("x86_64", configuration) + report = Email("x86_64", configuration, "email") report._send("a text", {"attachment.html": "an attachment"}) smtp_mock.return_value.login.assert_called_once() @@ -39,7 +39,7 @@ def test_send_auth_no_password(configuration: Configuration, mocker: MockerFixtu configuration.set_option("email", "user", "username") smtp_mock = mocker.patch("smtplib.SMTP") - report = Email("x86_64", configuration) + report = Email("x86_64", configuration, "email") report._send("a text", {"attachment.html": "an attachment"}) smtp_mock.return_value.login.assert_not_called() @@ -51,7 +51,7 @@ def test_send_auth_no_user(configuration: Configuration, mocker: MockerFixture) configuration.set_option("email", "password", "password") smtp_mock = mocker.patch("smtplib.SMTP") - report = Email("x86_64", configuration) + report = Email("x86_64", configuration, "email") report._send("a text", {"attachment.html": "an attachment"}) smtp_mock.return_value.login.assert_not_called() @@ -63,7 +63,7 @@ def test_send_ssl_tls(configuration: Configuration, mocker: MockerFixture) -> No configuration.set_option("email", "ssl", "ssl") smtp_mock = mocker.patch("smtplib.SMTP_SSL") - report = Email("x86_64", configuration) + report = Email("x86_64", configuration, "email") report._send("a text", {"attachment.html": "an attachment"}) smtp_mock.return_value.starttls.assert_not_called() smtp_mock.return_value.login.assert_not_called() @@ -78,7 +78,7 @@ def test_send_starttls(configuration: Configuration, mocker: MockerFixture) -> N configuration.set_option("email", "ssl", "starttls") smtp_mock = mocker.patch("smtplib.SMTP") - report = Email("x86_64", configuration) + report = Email("x86_64", configuration, "email") report._send("a text", {"attachment.html": "an attachment"}) smtp_mock.return_value.starttls.assert_called_once() @@ -89,7 +89,7 @@ def test_generate(configuration: Configuration, package_ahriman: Package, mocker """ send_mock = mocker.patch("ahriman.core.report.email.Email._send") - report = Email("x86_64", configuration) + report = Email("x86_64", configuration, "email") report.generate([package_ahriman], []) send_mock.assert_called_once() @@ -100,7 +100,7 @@ def test_generate_with_built(configuration: Configuration, package_ahriman: Pack """ send_mock = mocker.patch("ahriman.core.report.email.Email._send") - report = Email("x86_64", configuration) + report = Email("x86_64", configuration, "email") report.generate([package_ahriman], [package_ahriman]) send_mock.assert_called_once() @@ -114,7 +114,7 @@ def test_generate_with_built_and_full_path( """ send_mock = mocker.patch("ahriman.core.report.email.Email._send") - report = Email("x86_64", configuration) + report = Email("x86_64", configuration, "email") report.full_template_path = report.template_path report.generate([package_ahriman], [package_ahriman]) send_mock.assert_called_once() @@ -127,7 +127,7 @@ def test_generate_no_empty(configuration: Configuration, package_ahriman: Packag configuration.set_option("email", "no_empty_report", "yes") send_mock = mocker.patch("ahriman.core.report.email.Email._send") - report = Email("x86_64", configuration) + report = Email("x86_64", configuration, "email") report.generate([package_ahriman], []) send_mock.assert_not_called() @@ -140,6 +140,6 @@ def test_generate_no_empty_with_built(configuration: Configuration, package_ahri configuration.set_option("email", "no_empty_report", "yes") send_mock = mocker.patch("ahriman.core.report.email.Email._send") - report = Email("x86_64", configuration) + report = Email("x86_64", configuration, "email") report.generate([package_ahriman], [package_ahriman]) send_mock.assert_called_once() diff --git a/tests/ahriman/core/report/test_html.py b/tests/ahriman/core/report/test_html.py index 4b611324..d086fd67 100644 --- a/tests/ahriman/core/report/test_html.py +++ b/tests/ahriman/core/report/test_html.py @@ -11,6 +11,6 @@ def test_generate(configuration: Configuration, package_ahriman: Package, mocker """ write_mock = mocker.patch("pathlib.Path.write_text") - report = HTML("x86_64", configuration) + report = HTML("x86_64", configuration, "html") report.generate([package_ahriman], []) write_mock.assert_called_once() diff --git a/tests/ahriman/core/report/test_report.py b/tests/ahriman/core/report/test_report.py index 4300bde4..bda074b7 100644 --- a/tests/ahriman/core/report/test_report.py +++ b/tests/ahriman/core/report/test_report.py @@ -1,6 +1,5 @@ import pytest -from pathlib import Path from pytest_mock import MockerFixture from ahriman.core.configuration import Configuration @@ -15,7 +14,7 @@ def test_report_failure(configuration: Configuration, mocker: MockerFixture) -> """ mocker.patch("ahriman.core.report.html.HTML.generate", side_effect=Exception()) with pytest.raises(ReportFailed): - Report.load("x86_64", configuration, ReportSettings.HTML.name).run(Path("path"), []) + Report.load("x86_64", configuration, "html").run([], []) def test_report_dummy(configuration: Configuration, mocker: MockerFixture) -> None: @@ -24,7 +23,7 @@ def test_report_dummy(configuration: Configuration, mocker: MockerFixture) -> No """ mocker.patch("ahriman.models.report_settings.ReportSettings.from_option", return_value=ReportSettings.Disabled) report_mock = mocker.patch("ahriman.core.report.report.Report.generate") - Report.load("x86_64", configuration, ReportSettings.Disabled.name).run(Path("path"), []) + Report.load("x86_64", configuration, "disabled").run([], []) report_mock.assert_called_once() @@ -33,7 +32,7 @@ def test_report_email(configuration: Configuration, mocker: MockerFixture) -> No must generate email report """ report_mock = mocker.patch("ahriman.core.report.email.Email.generate") - Report.load("x86_64", configuration, ReportSettings.Email.name).run(Path("path"), []) + Report.load("x86_64", configuration, "email").run([], []) report_mock.assert_called_once() @@ -42,5 +41,5 @@ def test_report_html(configuration: Configuration, mocker: MockerFixture) -> Non must generate html report """ report_mock = mocker.patch("ahriman.core.report.html.HTML.generate") - Report.load("x86_64", configuration, ReportSettings.HTML.name).run(Path("path"), []) + Report.load("x86_64", configuration, "html").run([], []) report_mock.assert_called_once() diff --git a/tests/ahriman/core/test_configuration.py b/tests/ahriman/core/test_configuration.py index 22ebff8f..7c1695ff 100644 --- a/tests/ahriman/core/test_configuration.py +++ b/tests/ahriman/core/test_configuration.py @@ -1,7 +1,7 @@ import configparser -from pathlib import Path - import pytest + +from pathlib import Path from pytest_mock import MockerFixture from ahriman.core.configuration import Configuration @@ -24,53 +24,6 @@ def test_from_path(mocker: MockerFixture) -> None: load_logging_mock.assert_called_once() -def test_section_name(configuration: Configuration) -> None: - """ - must return architecture specific group - """ - assert configuration.section_name("build", "x86_64") == "build:x86_64" - - -def test_absolute_path_for_absolute(configuration: Configuration) -> None: - """ - must not change path for absolute path in settings - """ - path = Path("/a/b/c") - configuration.set_option("build", "path", str(path)) - assert configuration.getpath("build", "path") == path - - -def test_absolute_path_for_relative(configuration: Configuration) -> None: - """ - must prepend root path to relative path - """ - path = Path("a") - configuration.set_option("build", "path", str(path)) - result = configuration.getpath("build", "path") - assert result.is_absolute() - assert result.parent == configuration.path.parent - assert result.name == path.name - - -def test_path_with_fallback(configuration: Configuration) -> None: - """ - must return fallback path - """ - path = Path("a") - assert configuration.getpath("some", "option", fallback=path).name == str(path) - assert configuration.getpath("some", "option", fallback=None) is None - - -def test_path_without_fallback(configuration: Configuration) -> None: - """ - must raise exception without fallback - """ - with pytest.raises(configparser.NoSectionError): - assert configuration.getpath("some", "option") - with pytest.raises(configparser.NoOptionError): - assert configuration.getpath("build", "option") - - def test_dump(configuration: Configuration) -> None: """ dump must not be empty @@ -93,6 +46,53 @@ def test_dump_architecture_specific(configuration: Configuration) -> None: assert dump["build"]["archbuild_flags"] == "hello flag" +def test_section_name(configuration: Configuration) -> None: + """ + must return architecture specific group + """ + assert configuration.section_name("build", "x86_64") == "build:x86_64" + + +def test_getpath_absolute_to_absolute(configuration: Configuration) -> None: + """ + must not change path for absolute path in settings + """ + path = Path("/a/b/c") + configuration.set_option("build", "path", str(path)) + assert configuration.getpath("build", "path") == path + + +def test_getpath_absolute_to_relative(configuration: Configuration) -> None: + """ + must prepend root path to relative path + """ + path = Path("a") + configuration.set_option("build", "path", str(path)) + result = configuration.getpath("build", "path") + assert result.is_absolute() + assert result.parent == configuration.path.parent + assert result.name == path.name + + +def test_getpath_with_fallback(configuration: Configuration) -> None: + """ + must return fallback path + """ + path = Path("a") + assert configuration.getpath("some", "option", fallback=path).name == str(path) + assert configuration.getpath("some", "option", fallback=None) is None + + +def test_getpath_without_fallback(configuration: Configuration) -> None: + """ + must raise exception without fallback + """ + with pytest.raises(configparser.NoSectionError): + assert configuration.getpath("some", "option") + with pytest.raises(configparser.NoOptionError): + assert configuration.getpath("build", "option") + + def test_getlist(configuration: Configuration) -> None: """ must return list of string correctly @@ -119,6 +119,43 @@ def test_getlist_single(configuration: Configuration) -> None: assert configuration.getlist("build", "test_list") == ["a"] +def test_gettype(configuration: Configuration) -> None: + """ + must extract type from variable + """ + section, provider = configuration.gettype("customs3", "x86_64") + assert section == "customs3" + assert provider == "s3" + + +def test_gettype_from_section(configuration: Configuration) -> None: + """ + must extract type from section name + """ + section, provider = configuration.gettype("rsync", "x86_64") + assert section == "rsync" + assert provider == "rsync" + + +def test_gettype_from_section_with_architecture(configuration: Configuration) -> None: + """ + must extract type from section name with architecture + """ + section, provider = configuration.gettype("github", "x86_64") + assert section == "github:x86_64" + assert provider == "github" + + +def test_gettype_from_section_no_section(configuration: Configuration) -> None: + """ + must extract type from section name with architecture + """ + # technically rsync:x86_64 is valid section + # but in current configuration it must be considered as missing section + with pytest.raises(configparser.NoSectionError): + configuration.gettype("rsync:x86_64", "x86_64") + + def test_load_includes_missing(configuration: Configuration) -> None: """ must not fail if not include directory found @@ -137,7 +174,7 @@ def test_load_includes_no_option(configuration: Configuration) -> None: def test_load_includes_no_section(configuration: Configuration) -> None: """ - must not fail if no option set + must not fail if no section set """ configuration.remove_section("settings") configuration.load_includes() diff --git a/tests/ahriman/core/upload/conftest.py b/tests/ahriman/core/upload/conftest.py index 141b88a7..3cf1f2a4 100644 --- a/tests/ahriman/core/upload/conftest.py +++ b/tests/ahriman/core/upload/conftest.py @@ -20,7 +20,7 @@ def github(configuration: Configuration) -> Github: :param configuration: configuration fixture :return: github test instance """ - return Github("x86_64", configuration) + return Github("x86_64", configuration, "github:x86_64") @pytest.fixture @@ -50,7 +50,7 @@ def rsync(configuration: Configuration) -> Rsync: :param configuration: configuration fixture :return: rsync test instance """ - return Rsync("x86_64", configuration) + return Rsync("x86_64", configuration, "rsync") @pytest.fixture @@ -60,7 +60,7 @@ def s3(configuration: Configuration) -> S3: :param configuration: configuration fixture :return: S3 test instance """ - return S3("x86_64", configuration) + return S3("x86_64", configuration, "customs3") @pytest.fixture diff --git a/tests/ahriman/core/upload/test_upload.py b/tests/ahriman/core/upload/test_upload.py index 6c6beb04..9ff502b8 100644 --- a/tests/ahriman/core/upload/test_upload.py +++ b/tests/ahriman/core/upload/test_upload.py @@ -15,7 +15,7 @@ def test_upload_failure(configuration: Configuration, mocker: MockerFixture) -> """ mocker.patch("ahriman.core.upload.rsync.Rsync.sync", side_effect=Exception()) with pytest.raises(SyncFailed): - Upload.load("x86_64", configuration, UploadSettings.Rsync.name).run(Path("path"), []) + Upload.load("x86_64", configuration, "rsync").run(Path("path"), []) def test_report_dummy(configuration: Configuration, mocker: MockerFixture) -> None: @@ -24,7 +24,7 @@ def test_report_dummy(configuration: Configuration, mocker: MockerFixture) -> No """ mocker.patch("ahriman.models.upload_settings.UploadSettings.from_option", return_value=UploadSettings.Disabled) upload_mock = mocker.patch("ahriman.core.upload.upload.Upload.sync") - Upload.load("x86_64", configuration, UploadSettings.Disabled.name).run(Path("path"), []) + Upload.load("x86_64", configuration, "disabled").run(Path("path"), []) upload_mock.assert_called_once() @@ -33,7 +33,7 @@ def test_upload_rsync(configuration: Configuration, mocker: MockerFixture) -> No must upload via rsync """ upload_mock = mocker.patch("ahriman.core.upload.rsync.Rsync.sync") - Upload.load("x86_64", configuration, UploadSettings.Rsync.name).run(Path("path"), []) + Upload.load("x86_64", configuration, "rsync").run(Path("path"), []) upload_mock.assert_called_once() @@ -42,7 +42,7 @@ def test_upload_s3(configuration: Configuration, mocker: MockerFixture) -> None: must upload via s3 """ upload_mock = mocker.patch("ahriman.core.upload.s3.S3.sync") - Upload.load("x86_64", configuration, UploadSettings.S3.name).run(Path("path"), []) + Upload.load("x86_64", configuration, "customs3").run(Path("path"), []) upload_mock.assert_called_once() @@ -51,5 +51,5 @@ def test_upload_github(configuration: Configuration, mocker: MockerFixture) -> N must upload via github """ upload_mock = mocker.patch("ahriman.core.upload.github.Github.sync") - Upload.load("x86_64", configuration, UploadSettings.Github.name).run(Path("path"), []) + Upload.load("x86_64", configuration, "github").run(Path("path"), []) upload_mock.assert_called_once() diff --git a/tests/testresources/core/ahriman.ini b/tests/testresources/core/ahriman.ini index af4c20e2..93e81e11 100644 --- a/tests/testresources/core/ahriman.ini +++ b/tests/testresources/core/ahriman.ini @@ -55,13 +55,16 @@ target = command = rsync --archive --verbose --compress --partial --delete remote = -[s3] +[disabled] + +[customs3] +type = s3 access_key = bucket = bucket region = eu-central-1 secret_key = -[github] +[github:x86_64] owner = arcan1s password = repository = ahriman