mirror of
https://github.com/arcan1s/ahriman.git
synced 2025-04-24 15:27:17 +00:00
implement keyring package generator
This commit is contained in:
parent
21ea9a4dd1
commit
070d1d6d62
@ -108,6 +108,24 @@ Web server settings. If any of ``host``/``port`` is not set, web integration wil
|
|||||||
* ``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.
|
* ``username`` - username to authorize in web service in order to update service status, string, required in case if authorization enabled.
|
||||||
|
|
||||||
|
``keyring`` group
|
||||||
|
--------------------
|
||||||
|
|
||||||
|
Keyring package generator plugin.
|
||||||
|
|
||||||
|
* ``target`` - list of generator settings sections, space separated list of strings, required. It must point to valid section name.
|
||||||
|
|
||||||
|
Keyring generator plugin
|
||||||
|
^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
* ``description`` - keyring package description, string, optional, default is ``repo PGP keyring``, where ``repo`` is the repository name.
|
||||||
|
* ``homepage`` - url to homepage location if any, string, optional.
|
||||||
|
* ``license`` - list of licenses which are applied to this package, space separated list of strings, optional, default is ``Unlicense``.
|
||||||
|
* ``package`` - keyring package name, string, optional, default is ``repo-keyring``, where ``repo`` is the repository name.
|
||||||
|
* ``packagers`` - list of packagers keys, space separated list of strings, optional, if not set, the ``key_*`` options from ``sign`` group will be used.
|
||||||
|
* ``revoked`` - list of revoked packagers keys, space separated list of strings, optional.
|
||||||
|
* ``trusted`` - list of master keys, space separated list of strings, optional, if not set, the ``key`` option from ``sign`` group will be used.
|
||||||
|
|
||||||
``mirrorlist`` group
|
``mirrorlist`` group
|
||||||
--------------------
|
--------------------
|
||||||
|
|
||||||
|
@ -25,7 +25,7 @@ ignore_packages =
|
|||||||
makechrootpkg_flags =
|
makechrootpkg_flags =
|
||||||
makepkg_flags = --nocolor --ignorearch
|
makepkg_flags = --nocolor --ignorearch
|
||||||
triggers = ahriman.core.gitremote.RemotePullTrigger ahriman.core.report.ReportTrigger ahriman.core.upload.UploadTrigger ahriman.core.gitremote.RemotePushTrigger
|
triggers = ahriman.core.gitremote.RemotePullTrigger ahriman.core.report.ReportTrigger ahriman.core.upload.UploadTrigger ahriman.core.gitremote.RemotePushTrigger
|
||||||
triggers_known = ahriman.core.support.MirrorlistTrigger
|
triggers_known = ahriman.core.support.KeyringTrigger ahriman.core.support.MirrorlistTrigger
|
||||||
vcs_allowed_age = 604800
|
vcs_allowed_age = 604800
|
||||||
|
|
||||||
[repository]
|
[repository]
|
||||||
@ -35,6 +35,9 @@ root = /var/lib/ahriman
|
|||||||
[sign]
|
[sign]
|
||||||
target =
|
target =
|
||||||
|
|
||||||
|
[keyring]
|
||||||
|
target =
|
||||||
|
|
||||||
[mirrorlist]
|
[mirrorlist]
|
||||||
target =
|
target =
|
||||||
|
|
||||||
|
@ -100,6 +100,7 @@ def _parser() -> argparse.ArgumentParser:
|
|||||||
_set_patch_set_add_parser(subparsers)
|
_set_patch_set_add_parser(subparsers)
|
||||||
_set_repo_backup_parser(subparsers)
|
_set_repo_backup_parser(subparsers)
|
||||||
_set_repo_check_parser(subparsers)
|
_set_repo_check_parser(subparsers)
|
||||||
|
_set_repo_create_keyring_parser(subparsers)
|
||||||
_set_repo_create_mirrorlist_parser(subparsers)
|
_set_repo_create_mirrorlist_parser(subparsers)
|
||||||
_set_repo_daemon_parser(subparsers)
|
_set_repo_daemon_parser(subparsers)
|
||||||
_set_repo_rebuild_parser(subparsers)
|
_set_repo_rebuild_parser(subparsers)
|
||||||
@ -479,6 +480,25 @@ def _set_repo_check_parser(root: SubParserAction) -> argparse.ArgumentParser:
|
|||||||
return parser
|
return parser
|
||||||
|
|
||||||
|
|
||||||
|
def _set_repo_create_keyring_parser(root: SubParserAction) -> argparse.ArgumentParser:
|
||||||
|
"""
|
||||||
|
add parser for create-keyring subcommand
|
||||||
|
|
||||||
|
Args:
|
||||||
|
root(SubParserAction): subparsers for the commands
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
argparse.ArgumentParser: created argument parser
|
||||||
|
"""
|
||||||
|
parser = root.add_parser("repo-create-keyring", help="create keyring package",
|
||||||
|
description="create package which contains list of trusted keys as set by "
|
||||||
|
"configuration. Note, that this action will only create package, the package "
|
||||||
|
"itself has to be built manually",
|
||||||
|
formatter_class=_formatter)
|
||||||
|
parser.set_defaults(handler=handlers.Triggers, trigger=["ahriman.core.support.KeyringTrigger"])
|
||||||
|
return parser
|
||||||
|
|
||||||
|
|
||||||
def _set_repo_create_mirrorlist_parser(root: SubParserAction) -> argparse.ArgumentParser:
|
def _set_repo_create_mirrorlist_parser(root: SubParserAction) -> argparse.ArgumentParser:
|
||||||
"""
|
"""
|
||||||
add parser for create-mirrorlist subcommand
|
add parser for create-mirrorlist subcommand
|
||||||
|
@ -194,6 +194,18 @@ class PasswordError(ValueError):
|
|||||||
ValueError.__init__(self, f"Password error: {details}")
|
ValueError.__init__(self, f"Password error: {details}")
|
||||||
|
|
||||||
|
|
||||||
|
class PkgbuildGeneratorError(RuntimeError):
|
||||||
|
"""
|
||||||
|
exception class for support type triggers
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self) -> None:
|
||||||
|
"""
|
||||||
|
default constructor
|
||||||
|
"""
|
||||||
|
RuntimeError.__init__(self, "Could not generate package")
|
||||||
|
|
||||||
|
|
||||||
class ReportError(RuntimeError):
|
class ReportError(RuntimeError):
|
||||||
"""
|
"""
|
||||||
report generation exception
|
report generation exception
|
||||||
|
@ -57,4 +57,4 @@ class HTML(Report, JinjaTemplate):
|
|||||||
result(Result): build result
|
result(Result): build result
|
||||||
"""
|
"""
|
||||||
html = self.make_html(Result(success=packages), self.template_path)
|
html = self.make_html(Result(success=packages), self.template_path)
|
||||||
self.report_path.write_text(html)
|
self.report_path.write_text(html, encoding="utf8")
|
||||||
|
@ -79,7 +79,7 @@ class RepositoryProperties(LazyLogging):
|
|||||||
|
|
||||||
self.ignore_list = configuration.getlist("build", "ignore_packages", fallback=[])
|
self.ignore_list = configuration.getlist("build", "ignore_packages", fallback=[])
|
||||||
self.pacman = Pacman(architecture, configuration, refresh_database=refresh_pacman_database)
|
self.pacman = Pacman(architecture, configuration, refresh_database=refresh_pacman_database)
|
||||||
self.sign = GPG(architecture, configuration)
|
self.sign = GPG(configuration)
|
||||||
self.repo = Repo(self.name, self.paths, self.sign.repository_sign_args)
|
self.repo = Repo(self.name, self.paths, self.sign.repository_sign_args)
|
||||||
self.reporter = Client.load(configuration, report=report)
|
self.reporter = Client.load(configuration, report=report)
|
||||||
self.triggers = TriggerLoader.load(architecture, configuration)
|
self.triggers = TriggerLoader.load(architecture, configuration)
|
||||||
|
@ -19,6 +19,7 @@
|
|||||||
#
|
#
|
||||||
import requests
|
import requests
|
||||||
|
|
||||||
|
from collections.abc import Generator
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
from ahriman.core.configuration import Configuration
|
from ahriman.core.configuration import Configuration
|
||||||
@ -34,7 +35,6 @@ class GPG(LazyLogging):
|
|||||||
|
|
||||||
Attributes:
|
Attributes:
|
||||||
DEFAULT_TIMEOUT(int): (class attribute) HTTP request timeout in seconds
|
DEFAULT_TIMEOUT(int): (class attribute) HTTP request timeout in seconds
|
||||||
architecture(str): repository architecture
|
|
||||||
configuration(Configuration): configuration instance
|
configuration(Configuration): configuration instance
|
||||||
default_key(str | None): default PGP key ID to use
|
default_key(str | None): default PGP key ID to use
|
||||||
targets(set[SignSettings]): list of targets to sign (repository, package etc)
|
targets(set[SignSettings]): list of targets to sign (repository, package etc)
|
||||||
@ -43,15 +43,13 @@ class GPG(LazyLogging):
|
|||||||
_check_output = check_output
|
_check_output = check_output
|
||||||
DEFAULT_TIMEOUT = 30
|
DEFAULT_TIMEOUT = 30
|
||||||
|
|
||||||
def __init__(self, architecture: str, configuration: Configuration) -> None:
|
def __init__(self, configuration: Configuration) -> None:
|
||||||
"""
|
"""
|
||||||
default constructor
|
default constructor
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
architecture(str): repository architecture
|
|
||||||
configuration(Configuration): configuration instance
|
configuration(Configuration): configuration instance
|
||||||
"""
|
"""
|
||||||
self.architecture = architecture
|
|
||||||
self.configuration = configuration
|
self.configuration = configuration
|
||||||
self.targets, self.default_key = self.sign_options(configuration)
|
self.targets, self.default_key = self.sign_options(configuration)
|
||||||
|
|
||||||
@ -128,6 +126,34 @@ class GPG(LazyLogging):
|
|||||||
raise
|
raise
|
||||||
return response.text
|
return response.text
|
||||||
|
|
||||||
|
def key_export(self, key: str) -> str:
|
||||||
|
"""
|
||||||
|
export public key from stored keychain
|
||||||
|
|
||||||
|
Args:
|
||||||
|
key(str): key ID to export
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
str: PGP key in .asc format
|
||||||
|
"""
|
||||||
|
return GPG._check_output("gpg", "--armor", "--no-emit-version", "--export", key, logger=self.logger)
|
||||||
|
|
||||||
|
def key_fingerprint(self, key: str) -> str:
|
||||||
|
"""
|
||||||
|
get full key fingerprint from short key id
|
||||||
|
|
||||||
|
Args:
|
||||||
|
key(str): key ID to lookup
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
str: full PGP key fingerprint
|
||||||
|
"""
|
||||||
|
metadata = GPG._check_output("gpg", "--with-colons", "--fingerprint", key, logger=self.logger)
|
||||||
|
# fingerprint line will be like
|
||||||
|
# fpr:::::::::43A663569A07EE1E4ECC55CC7E3A4240CE3C45C2:
|
||||||
|
fingerprint = next(filter(lambda line: line[:3] == "fpr", metadata.splitlines()))
|
||||||
|
return fingerprint.split(":")[-2]
|
||||||
|
|
||||||
def key_import(self, server: str, key: str) -> None:
|
def key_import(self, server: str, key: str) -> None:
|
||||||
"""
|
"""
|
||||||
import key to current user and sign it locally
|
import key to current user and sign it locally
|
||||||
@ -139,6 +165,21 @@ class GPG(LazyLogging):
|
|||||||
key_body = self.key_download(server, key)
|
key_body = self.key_download(server, key)
|
||||||
GPG._check_output("gpg", "--import", input_data=key_body, logger=self.logger)
|
GPG._check_output("gpg", "--import", input_data=key_body, logger=self.logger)
|
||||||
|
|
||||||
|
def keys(self) -> list[str]:
|
||||||
|
"""
|
||||||
|
extract list of keys described in configuration
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
list[str]: list of unique keys which are set in configuration
|
||||||
|
"""
|
||||||
|
def generator() -> Generator[str, None, None]:
|
||||||
|
if self.default_key is not None:
|
||||||
|
yield self.default_key
|
||||||
|
for _, value in filter(lambda pair: pair[0].startswith("key_"), self.configuration["sign"].items()):
|
||||||
|
yield value
|
||||||
|
|
||||||
|
return sorted(set(generator()))
|
||||||
|
|
||||||
def process(self, path: Path, key: str) -> list[Path]:
|
def process(self, path: Path, key: str) -> list[Path]:
|
||||||
"""
|
"""
|
||||||
gpg command wrapper
|
gpg command wrapper
|
||||||
|
@ -17,4 +17,5 @@
|
|||||||
# 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/>.
|
||||||
#
|
#
|
||||||
|
from ahriman.core.support.keyring_trigger import KeyringTrigger
|
||||||
from ahriman.core.support.mirrorlist_trigger import MirrorlistTrigger
|
from ahriman.core.support.mirrorlist_trigger import MirrorlistTrigger
|
||||||
|
114
src/ahriman/core/support/keyring_trigger.py
Normal file
114
src/ahriman/core/support/keyring_trigger.py
Normal file
@ -0,0 +1,114 @@
|
|||||||
|
#
|
||||||
|
# Copyright (c) 2021-2023 ahriman team.
|
||||||
|
#
|
||||||
|
# This file is part of ahriman
|
||||||
|
# (see https://github.com/arcan1s/ahriman).
|
||||||
|
#
|
||||||
|
# This program is free software: you can redistribute it and/or modify
|
||||||
|
# it under the terms of the GNU General Public License as published by
|
||||||
|
# the Free Software Foundation, either version 3 of the License, or
|
||||||
|
# (at your option) any later version.
|
||||||
|
#
|
||||||
|
# This program is distributed in the hope that it will be useful,
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
# GNU General Public License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU General Public License
|
||||||
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
#
|
||||||
|
from ahriman.core import context
|
||||||
|
from ahriman.core.configuration import Configuration
|
||||||
|
from ahriman.core.sign.gpg import GPG
|
||||||
|
from ahriman.core.support.package_creator import PackageCreator
|
||||||
|
from ahriman.core.support.pkgbuild.keyring_generator import KeyringGenerator
|
||||||
|
from ahriman.core.triggers import Trigger
|
||||||
|
from ahriman.models.context_key import ContextKey
|
||||||
|
|
||||||
|
|
||||||
|
class KeyringTrigger(Trigger):
|
||||||
|
"""
|
||||||
|
keyring generator trigger
|
||||||
|
|
||||||
|
Attributes:
|
||||||
|
targets(list[str]): git remote target list
|
||||||
|
"""
|
||||||
|
|
||||||
|
CONFIGURATION_SCHEMA = {
|
||||||
|
"keyring": {
|
||||||
|
"type": "dict",
|
||||||
|
"schema": {
|
||||||
|
"target": {
|
||||||
|
"type": "list",
|
||||||
|
"coerce": "list",
|
||||||
|
"schema": {"type": "string"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"keyring_generator": {
|
||||||
|
"type": "dict",
|
||||||
|
"schema": {
|
||||||
|
"description": {
|
||||||
|
"type": "string",
|
||||||
|
},
|
||||||
|
"homepage": {
|
||||||
|
"type": "string",
|
||||||
|
},
|
||||||
|
"license": {
|
||||||
|
"type": "list",
|
||||||
|
"coerce": "list",
|
||||||
|
},
|
||||||
|
"package": {
|
||||||
|
"type": "string",
|
||||||
|
},
|
||||||
|
"packagers": {
|
||||||
|
"type": "list",
|
||||||
|
"coerce": "list",
|
||||||
|
},
|
||||||
|
"revoked": {
|
||||||
|
"type": "list",
|
||||||
|
"coerce": "list",
|
||||||
|
},
|
||||||
|
"trusted": {
|
||||||
|
"type": "list",
|
||||||
|
"coerce": "list",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
def __init__(self, architecture: str, configuration: Configuration) -> None:
|
||||||
|
"""
|
||||||
|
default constructor
|
||||||
|
|
||||||
|
Args:
|
||||||
|
architecture(str): repository architecture
|
||||||
|
configuration(Configuration): configuration instance
|
||||||
|
"""
|
||||||
|
Trigger.__init__(self, architecture, configuration)
|
||||||
|
self.targets = self.configuration_sections(configuration)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def configuration_sections(cls, configuration: Configuration) -> list[str]:
|
||||||
|
"""
|
||||||
|
extract configuration sections from configuration
|
||||||
|
|
||||||
|
Args:
|
||||||
|
configuration(Configuration): configuration instance
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
list[str]: read configuration sections belong to this trigger
|
||||||
|
"""
|
||||||
|
return configuration.getlist("keyring", "target", fallback=[])
|
||||||
|
|
||||||
|
def on_start(self) -> None:
|
||||||
|
"""
|
||||||
|
trigger action which will be called at the start of the application
|
||||||
|
"""
|
||||||
|
ctx = context.get()
|
||||||
|
sign = ctx.get(ContextKey("sign", GPG))
|
||||||
|
|
||||||
|
for target in self.targets:
|
||||||
|
generator = KeyringGenerator(sign, self.configuration, target)
|
||||||
|
runner = PackageCreator(self.configuration, generator)
|
||||||
|
runner.run()
|
@ -23,14 +23,13 @@ from ahriman.core import context
|
|||||||
from ahriman.core.build_tools.sources import Sources
|
from ahriman.core.build_tools.sources import Sources
|
||||||
from ahriman.core.configuration import Configuration
|
from ahriman.core.configuration import Configuration
|
||||||
from ahriman.core.database import SQLite
|
from ahriman.core.database import SQLite
|
||||||
from ahriman.core.log import LazyLogging
|
|
||||||
from ahriman.core.support.pkgbuild.pkgbuild_generator import PkgbuildGenerator
|
from ahriman.core.support.pkgbuild.pkgbuild_generator import PkgbuildGenerator
|
||||||
from ahriman.models.build_status import BuildStatus
|
from ahriman.models.build_status import BuildStatus
|
||||||
from ahriman.models.context_key import ContextKey
|
from ahriman.models.context_key import ContextKey
|
||||||
from ahriman.models.package import Package
|
from ahriman.models.package import Package
|
||||||
|
|
||||||
|
|
||||||
class PackageCreator(LazyLogging):
|
class PackageCreator:
|
||||||
"""
|
"""
|
||||||
helper which creates packages based on pkgbuild generator
|
helper which creates packages based on pkgbuild generator
|
||||||
|
|
||||||
|
194
src/ahriman/core/support/pkgbuild/keyring_generator.py
Normal file
194
src/ahriman/core/support/pkgbuild/keyring_generator.py
Normal file
@ -0,0 +1,194 @@
|
|||||||
|
#
|
||||||
|
# Copyright (c) 2021-2023 ahriman team.
|
||||||
|
#
|
||||||
|
# This file is part of ahriman
|
||||||
|
# (see https://github.com/arcan1s/ahriman).
|
||||||
|
#
|
||||||
|
# This program is free software: you can redistribute it and/or modify
|
||||||
|
# it under the terms of the GNU General Public License as published by
|
||||||
|
# the Free Software Foundation, either version 3 of the License, or
|
||||||
|
# (at your option) any later version.
|
||||||
|
#
|
||||||
|
# This program is distributed in the hope that it will be useful,
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
# GNU General Public License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU General Public License
|
||||||
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
#
|
||||||
|
from collections.abc import Callable
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
from ahriman.core.configuration import Configuration
|
||||||
|
from ahriman.core.exceptions import PkgbuildGeneratorError
|
||||||
|
from ahriman.core.sign.gpg import GPG
|
||||||
|
from ahriman.core.support.pkgbuild.pkgbuild_generator import PkgbuildGenerator
|
||||||
|
|
||||||
|
|
||||||
|
class KeyringGenerator(PkgbuildGenerator):
|
||||||
|
"""
|
||||||
|
generator for keyring PKGBUILD
|
||||||
|
|
||||||
|
Attributes:
|
||||||
|
sign(GPG): GPG wrapper instance
|
||||||
|
name(str): repository name
|
||||||
|
packagers(list[str]): list of packagers PGP keys
|
||||||
|
pkgbuild_license(list[str]): keyring package license
|
||||||
|
pkgbuild_pkgdesc(str): keyring package description
|
||||||
|
pkgbuild_pkgname(str): keyring package name
|
||||||
|
pkgbuild_url(str): keyring package home page
|
||||||
|
revoked(list[str]): list of revoked PGP keys
|
||||||
|
trusted(list[str]): lif of trusted PGP keys
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, sign: GPG, configuration: Configuration, section: str) -> None:
|
||||||
|
"""
|
||||||
|
default constructor
|
||||||
|
|
||||||
|
Args:
|
||||||
|
sign(GPG): GPG wrapper instance
|
||||||
|
configuration(Configuration): configuration instance
|
||||||
|
section(str): settings section name
|
||||||
|
"""
|
||||||
|
self.sign = sign
|
||||||
|
self.name = configuration.repository_name
|
||||||
|
|
||||||
|
# configuration fields
|
||||||
|
self.packagers = configuration.getlist(section, "packagers", fallback=sign.keys())
|
||||||
|
self.revoked = configuration.getlist(section, "revoked", fallback=[])
|
||||||
|
self.trusted = configuration.getlist(
|
||||||
|
section, "trusted", fallback=[sign.default_key] if sign.default_key is not None else [])
|
||||||
|
# pkgbuild description fields
|
||||||
|
self.pkgbuild_pkgname = configuration.get(section, "package", fallback=f"{self.name}-keyring")
|
||||||
|
self.pkgbuild_pkgdesc = configuration.get(section, "description", fallback=f"{self.name} PGP keyring")
|
||||||
|
self.pkgbuild_license = configuration.getlist(section, "license", fallback=["Unlicense"])
|
||||||
|
self.pkgbuild_url = configuration.get(section, "homepage", fallback="")
|
||||||
|
|
||||||
|
@property
|
||||||
|
def license(self) -> list[str]:
|
||||||
|
"""
|
||||||
|
package licenses list
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
list[str]: package licenses as PKGBUILD property
|
||||||
|
"""
|
||||||
|
return self.pkgbuild_license
|
||||||
|
|
||||||
|
@property
|
||||||
|
def pkgdesc(self) -> str:
|
||||||
|
"""
|
||||||
|
package description
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
str: package description as PKGBUILD property
|
||||||
|
"""
|
||||||
|
return self.pkgbuild_pkgdesc
|
||||||
|
|
||||||
|
@property
|
||||||
|
def pkgname(self) -> str:
|
||||||
|
"""
|
||||||
|
package name
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
str: package name as PKGBUILD property
|
||||||
|
"""
|
||||||
|
return self.pkgbuild_pkgname
|
||||||
|
|
||||||
|
@property
|
||||||
|
def url(self) -> str:
|
||||||
|
"""
|
||||||
|
package upstream url
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
str: package upstream url as PKGBUILD property
|
||||||
|
"""
|
||||||
|
return self.pkgbuild_url
|
||||||
|
|
||||||
|
def _generate_gpg(self, source_path: Path) -> None:
|
||||||
|
"""
|
||||||
|
generate GPG keychain
|
||||||
|
|
||||||
|
Args:
|
||||||
|
source_path(Path): destination of the file content
|
||||||
|
"""
|
||||||
|
with source_path.open("w") as source_file:
|
||||||
|
for key in sorted(set(self.trusted + self.packagers + self.revoked)):
|
||||||
|
public_key = self.sign.key_export(key)
|
||||||
|
source_file.write(public_key)
|
||||||
|
source_file.write("\n")
|
||||||
|
|
||||||
|
def _generate_revoked(self, source_path: Path) -> None:
|
||||||
|
"""
|
||||||
|
generate revoked PGP keys
|
||||||
|
|
||||||
|
Args:
|
||||||
|
source_path(Path): destination of the file content
|
||||||
|
"""
|
||||||
|
with source_path.open("w") as source_file:
|
||||||
|
for key in sorted(set(self.revoked)):
|
||||||
|
fingerprint = self.sign.key_fingerprint(key)
|
||||||
|
source_file.write(fingerprint)
|
||||||
|
source_file.write("\n")
|
||||||
|
|
||||||
|
def _generate_trusted(self, source_path: Path) -> None:
|
||||||
|
"""
|
||||||
|
generate trusted PGP keys
|
||||||
|
|
||||||
|
Args:
|
||||||
|
source_path(Path): destination of the file content
|
||||||
|
"""
|
||||||
|
if not self.trusted:
|
||||||
|
raise PkgbuildGeneratorError
|
||||||
|
with source_path.open("w") as source_file:
|
||||||
|
for key in sorted(set(self.trusted)):
|
||||||
|
fingerprint = self.sign.key_fingerprint(key)
|
||||||
|
source_file.write(fingerprint)
|
||||||
|
source_file.write(":4:\n")
|
||||||
|
|
||||||
|
def install(self) -> str | None:
|
||||||
|
"""
|
||||||
|
content of the install functions
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
str | None: content of the install functions if any
|
||||||
|
"""
|
||||||
|
# copy-paste from archlinux-keyring
|
||||||
|
return f"""post_upgrade() {{
|
||||||
|
if usr/bin/pacman-key -l >/dev/null 2>&1; then
|
||||||
|
usr/bin/pacman-key --populate {self.name}
|
||||||
|
usr/bin/pacman-key --updatedb
|
||||||
|
fi
|
||||||
|
}}
|
||||||
|
|
||||||
|
post_install() {{
|
||||||
|
if [ -x usr/bin/pacman-key ]; then
|
||||||
|
post_upgrade
|
||||||
|
fi
|
||||||
|
}}"""
|
||||||
|
|
||||||
|
def package(self) -> str:
|
||||||
|
"""
|
||||||
|
package function generator
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
str: package() function for PKGBUILD
|
||||||
|
"""
|
||||||
|
return f"""{{
|
||||||
|
install -Dm644 "{Path("$srcdir") / f"{self.name}.gpg"}" "{Path("$pkgdir") / "usr" / "share" / "pacman" / "keyrings" / f"{self.name}.gpg"}"
|
||||||
|
install -Dm644 "{Path("$srcdir") / f"{self.name}-revoked"}" "{Path("$pkgdir") / "usr" / "share" / "pacman" / "keyrings" / f"{self.name}-revoked"}"
|
||||||
|
install -Dm644 "{Path("$srcdir") / f"{self.name}-trusted"}" "{Path("$pkgdir") / "usr" / "share" / "pacman" / "keyrings" / f"{self.name}-trusted"}"
|
||||||
|
}}"""
|
||||||
|
|
||||||
|
def sources(self) -> dict[str, Callable[[Path], None]]:
|
||||||
|
"""
|
||||||
|
return list of sources for the package
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
dict[str, Callable[[Path], None]]: map of source identifier (e.g. filename) to its generator function
|
||||||
|
"""
|
||||||
|
return {
|
||||||
|
f"{self.name}.gpg": self._generate_gpg,
|
||||||
|
f"{self.name}-revoked": self._generate_revoked,
|
||||||
|
f"{self.name}-trusted": self._generate_trusted,
|
||||||
|
}
|
@ -106,8 +106,8 @@ class MirrorlistGenerator(PkgbuildGenerator):
|
|||||||
Args:
|
Args:
|
||||||
source_path(Path): destination of the mirrorlist content
|
source_path(Path): destination of the mirrorlist content
|
||||||
"""
|
"""
|
||||||
with source_path.open("w") as source_file:
|
content = "".join([f"Server = {server}\n" for server in self.servers])
|
||||||
source_file.writelines([f"Server = {server}\n" for server in self.servers])
|
source_path.write_text(content, encoding="utf8")
|
||||||
|
|
||||||
def package(self) -> str:
|
def package(self) -> str:
|
||||||
"""
|
"""
|
||||||
@ -139,5 +139,5 @@ class MirrorlistGenerator(PkgbuildGenerator):
|
|||||||
dict[str, Callable[[Path], None]]: map of source identifier (e.g. filename) to its generator function
|
dict[str, Callable[[Path], None]]: map of source identifier (e.g. filename) to its generator function
|
||||||
"""
|
"""
|
||||||
return {
|
return {
|
||||||
"mirrorlist": self._generate_mirrorlist
|
"mirrorlist": self._generate_mirrorlist,
|
||||||
}
|
}
|
||||||
|
@ -23,12 +23,11 @@ import itertools
|
|||||||
from collections.abc import Callable, Generator
|
from collections.abc import Callable, Generator
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
from ahriman.core.log import LazyLogging
|
|
||||||
from ahriman.core.util import utcnow
|
from ahriman.core.util import utcnow
|
||||||
from ahriman.models.pkgbuild_patch import PkgbuildPatch
|
from ahriman.models.pkgbuild_patch import PkgbuildPatch
|
||||||
|
|
||||||
|
|
||||||
class PkgbuildGenerator(LazyLogging):
|
class PkgbuildGenerator:
|
||||||
"""
|
"""
|
||||||
main class for generating PKGBUILDs
|
main class for generating PKGBUILDs
|
||||||
|
|
||||||
@ -97,6 +96,14 @@ class PkgbuildGenerator(LazyLogging):
|
|||||||
"""
|
"""
|
||||||
return ""
|
return ""
|
||||||
|
|
||||||
|
def install(self) -> str | None:
|
||||||
|
"""
|
||||||
|
content of the install functions
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
str | None: content of the install functions if any
|
||||||
|
"""
|
||||||
|
|
||||||
def package(self) -> str:
|
def package(self) -> str:
|
||||||
"""
|
"""
|
||||||
package function generator
|
package function generator
|
||||||
@ -127,6 +134,24 @@ class PkgbuildGenerator(LazyLogging):
|
|||||||
"""
|
"""
|
||||||
return {}
|
return {}
|
||||||
|
|
||||||
|
def write_install(self, source_dir: Path) -> list[PkgbuildPatch]:
|
||||||
|
"""
|
||||||
|
generate content of install file
|
||||||
|
|
||||||
|
Args:
|
||||||
|
source_dir(Path): path to directory in which sources must be generated
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
list[PkgbuildPatch]: patch for the pkgbuild if install file exists and empty list otherwise
|
||||||
|
"""
|
||||||
|
content: str | None = self.install()
|
||||||
|
if content is None:
|
||||||
|
return []
|
||||||
|
|
||||||
|
source_path = source_dir / f"{self.pkgname}.install"
|
||||||
|
source_path.write_text(content)
|
||||||
|
return [PkgbuildPatch("install", source_path.name)]
|
||||||
|
|
||||||
def write_pkgbuild(self, source_dir: Path) -> None:
|
def write_pkgbuild(self, source_dir: Path) -> None:
|
||||||
"""
|
"""
|
||||||
generate PKGBUILD content to the specified path
|
generate PKGBUILD content to the specified path
|
||||||
@ -143,6 +168,7 @@ class PkgbuildGenerator(LazyLogging):
|
|||||||
PkgbuildPatch("url", self.url),
|
PkgbuildPatch("url", self.url),
|
||||||
]) # ...main properties as defined by derived class...
|
]) # ...main properties as defined by derived class...
|
||||||
patches.extend(self.patches()) # ...optional properties as defined by derived class...
|
patches.extend(self.patches()) # ...optional properties as defined by derived class...
|
||||||
|
patches.extend(self.write_install(source_dir)) # ...install function...
|
||||||
patches.append(PkgbuildPatch("package()", self.package())) # ...package function...
|
patches.append(PkgbuildPatch("package()", self.package())) # ...package function...
|
||||||
|
|
||||||
patches.extend(self.write_sources(source_dir)) # ...and finally source files
|
patches.extend(self.write_sources(source_dir)) # ...and finally source files
|
||||||
|
@ -62,6 +62,8 @@ def test_schema(configuration: Configuration) -> None:
|
|||||||
assert schema.pop("email")
|
assert schema.pop("email")
|
||||||
assert schema.pop("github")
|
assert schema.pop("github")
|
||||||
assert schema.pop("html")
|
assert schema.pop("html")
|
||||||
|
assert schema.pop("keyring")
|
||||||
|
assert schema.pop("keyring_generator")
|
||||||
assert schema.pop("mirrorlist")
|
assert schema.pop("mirrorlist")
|
||||||
assert schema.pop("mirrorlist_generator")
|
assert schema.pop("mirrorlist_generator")
|
||||||
assert schema.pop("report")
|
assert schema.pop("report")
|
||||||
|
@ -373,6 +373,24 @@ def test_subparsers_repo_check_option_refresh(parser: argparse.ArgumentParser) -
|
|||||||
assert args.refresh == 2
|
assert args.refresh == 2
|
||||||
|
|
||||||
|
|
||||||
|
def test_subparsers_repo_create_keyring(parser: argparse.ArgumentParser) -> None:
|
||||||
|
"""
|
||||||
|
repo-create-keyring command must imply trigger
|
||||||
|
"""
|
||||||
|
args = parser.parse_args(["repo-create-keyring"])
|
||||||
|
assert args.trigger == ["ahriman.core.support.KeyringTrigger"]
|
||||||
|
|
||||||
|
|
||||||
|
def test_subparsers_repo_create_keyring_architecture(parser: argparse.ArgumentParser) -> None:
|
||||||
|
"""
|
||||||
|
repo-create-keyring command must correctly parse architecture list
|
||||||
|
"""
|
||||||
|
args = parser.parse_args(["repo-create-keyring"])
|
||||||
|
assert args.architecture is None
|
||||||
|
args = parser.parse_args(["-a", "x86_64", "repo-create-keyring"])
|
||||||
|
assert args.architecture == ["x86_64"]
|
||||||
|
|
||||||
|
|
||||||
def test_subparsers_repo_create_mirrorlist(parser: argparse.ArgumentParser) -> None:
|
def test_subparsers_repo_create_mirrorlist(parser: argparse.ArgumentParser) -> None:
|
||||||
"""
|
"""
|
||||||
repo-create-mirrorlist command must imply trigger
|
repo-create-mirrorlist command must imply trigger
|
||||||
|
@ -4,6 +4,7 @@ import pytest
|
|||||||
from ahriman.core.alpm.repo import Repo
|
from ahriman.core.alpm.repo import Repo
|
||||||
from ahriman.core.build_tools.task import Task
|
from ahriman.core.build_tools.task import Task
|
||||||
from ahriman.core.configuration import Configuration
|
from ahriman.core.configuration import Configuration
|
||||||
|
from ahriman.core.sign.gpg import GPG
|
||||||
from ahriman.core.tree import Leaf
|
from ahriman.core.tree import Leaf
|
||||||
from ahriman.models.package import Package
|
from ahriman.models.package import Package
|
||||||
from ahriman.models.repository_paths import RepositoryPaths
|
from ahriman.models.repository_paths import RepositoryPaths
|
||||||
@ -63,6 +64,20 @@ def repo(configuration: Configuration, repository_paths: RepositoryPaths) -> Rep
|
|||||||
return Repo(configuration.get("repository", "name"), repository_paths, [])
|
return Repo(configuration.get("repository", "name"), repository_paths, [])
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def gpg(configuration: Configuration) -> GPG:
|
||||||
|
"""
|
||||||
|
fixture for empty GPG
|
||||||
|
|
||||||
|
Args:
|
||||||
|
configuration(Configuration): configuration fixture
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
GPG: GPG test instance
|
||||||
|
"""
|
||||||
|
return GPG(configuration)
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def task_ahriman(package_ahriman: Package, configuration: Configuration, repository_paths: RepositoryPaths) -> Task:
|
def task_ahriman(package_ahriman: Package, configuration: Configuration, repository_paths: RepositoryPaths) -> Task:
|
||||||
"""
|
"""
|
||||||
|
@ -16,4 +16,4 @@ def test_generate(configuration: Configuration, package_ahriman: Package, mocker
|
|||||||
|
|
||||||
report = HTML("x86_64", configuration, "html")
|
report = HTML("x86_64", configuration, "html")
|
||||||
report.generate([package_ahriman], Result())
|
report.generate([package_ahriman], Result())
|
||||||
write_mock.assert_called_once_with(pytest.helpers.anyvar(int))
|
write_mock.assert_called_once_with(pytest.helpers.anyvar(int), encoding="utf8")
|
||||||
|
@ -1,23 +1,8 @@
|
|||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from ahriman.core.configuration import Configuration
|
|
||||||
from ahriman.core.sign.gpg import GPG
|
from ahriman.core.sign.gpg import GPG
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
|
||||||
def gpg(configuration: Configuration) -> GPG:
|
|
||||||
"""
|
|
||||||
fixture for empty GPG
|
|
||||||
|
|
||||||
Args:
|
|
||||||
configuration(Configuration): configuration fixture
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
GPG: GPG test instance
|
|
||||||
"""
|
|
||||||
return GPG("x86_64", configuration)
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def gpg_with_key(gpg: GPG) -> GPG:
|
def gpg_with_key(gpg: GPG) -> GPG:
|
||||||
"""
|
"""
|
||||||
|
@ -97,6 +97,33 @@ def test_key_download_failure(gpg: GPG, mocker: MockerFixture) -> None:
|
|||||||
gpg.key_download("keyserver.ubuntu.com", "0xE989490C")
|
gpg.key_download("keyserver.ubuntu.com", "0xE989490C")
|
||||||
|
|
||||||
|
|
||||||
|
def test_key_export(gpg: GPG, mocker: MockerFixture) -> None:
|
||||||
|
"""
|
||||||
|
must export gpg key correctly
|
||||||
|
"""
|
||||||
|
check_output_mock = mocker.patch("ahriman.core.sign.gpg.GPG._check_output", return_value="key")
|
||||||
|
assert gpg.key_export("k") == "key"
|
||||||
|
check_output_mock.assert_called_once_with("gpg", "--armor", "--no-emit-version", "--export", "k",
|
||||||
|
logger=pytest.helpers.anyvar(int))
|
||||||
|
|
||||||
|
|
||||||
|
def test_key_fingerprint(gpg: GPG, mocker: MockerFixture) -> None:
|
||||||
|
"""
|
||||||
|
must extract fingerprint
|
||||||
|
"""
|
||||||
|
check_output_mock = mocker.patch(
|
||||||
|
"ahriman.core.sign.gpg.GPG._check_output",
|
||||||
|
return_value="""tru::1:1576103830:0:3:1:5
|
||||||
|
fpr:::::::::C6EBB9222C3C8078631A0DE4BD2AC8C5E989490C:
|
||||||
|
sub:-:4096:1:7E3A4240CE3C45C2:1615121387::::::e::::::23:
|
||||||
|
fpr:::::::::43A663569A07EE1E4ECC55CC7E3A4240CE3C45C2:""")
|
||||||
|
|
||||||
|
key = "0xCE3C45C2"
|
||||||
|
assert gpg.key_fingerprint(key) == "C6EBB9222C3C8078631A0DE4BD2AC8C5E989490C"
|
||||||
|
check_output_mock.assert_called_once_with("gpg", "--with-colons", "--fingerprint", key,
|
||||||
|
logger=pytest.helpers.anyvar(int))
|
||||||
|
|
||||||
|
|
||||||
def test_key_import(gpg: GPG, mocker: MockerFixture) -> None:
|
def test_key_import(gpg: GPG, mocker: MockerFixture) -> None:
|
||||||
"""
|
"""
|
||||||
must import PGP key from the server
|
must import PGP key from the server
|
||||||
@ -108,6 +135,21 @@ def test_key_import(gpg: GPG, mocker: MockerFixture) -> None:
|
|||||||
check_output_mock.assert_called_once_with("gpg", "--import", input_data="key", logger=pytest.helpers.anyvar(int))
|
check_output_mock.assert_called_once_with("gpg", "--import", input_data="key", logger=pytest.helpers.anyvar(int))
|
||||||
|
|
||||||
|
|
||||||
|
def test_keys(gpg: GPG) -> None:
|
||||||
|
"""
|
||||||
|
must extract keys
|
||||||
|
"""
|
||||||
|
assert gpg.keys() == []
|
||||||
|
|
||||||
|
gpg.default_key = "key"
|
||||||
|
assert gpg.keys() == [gpg.default_key]
|
||||||
|
|
||||||
|
gpg.configuration.set_option("sign", "key_a", "key1")
|
||||||
|
gpg.configuration.set_option("sign", "key_b", "key1")
|
||||||
|
gpg.configuration.set_option("sign", "key_c", "key2")
|
||||||
|
assert gpg.keys() == ["key", "key1", "key2"]
|
||||||
|
|
||||||
|
|
||||||
def test_process(gpg_with_key: GPG, mocker: MockerFixture) -> None:
|
def test_process(gpg_with_key: GPG, mocker: MockerFixture) -> None:
|
||||||
"""
|
"""
|
||||||
must call process method correctly
|
must call process method correctly
|
||||||
|
@ -1,8 +1,26 @@
|
|||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
|
from ahriman.core.configuration import Configuration
|
||||||
|
from ahriman.core.sign.gpg import GPG
|
||||||
|
from ahriman.core.support.pkgbuild.keyring_generator import KeyringGenerator
|
||||||
from ahriman.core.support.pkgbuild.pkgbuild_generator import PkgbuildGenerator
|
from ahriman.core.support.pkgbuild.pkgbuild_generator import PkgbuildGenerator
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def keyring_generator(gpg: GPG, configuration: Configuration) -> KeyringGenerator:
|
||||||
|
"""
|
||||||
|
fixture for keyring pkgbuild generator
|
||||||
|
|
||||||
|
Args:
|
||||||
|
gpg(GPG): empty GPG fixture
|
||||||
|
configuration(Configuration): configuration fixture
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
KeyringGenerator: keyring generator test instance
|
||||||
|
"""
|
||||||
|
return KeyringGenerator(gpg, configuration, "keyring")
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def pkgbuild_generator() -> PkgbuildGenerator:
|
def pkgbuild_generator() -> PkgbuildGenerator:
|
||||||
"""
|
"""
|
||||||
|
185
tests/ahriman/core/support/pkgbuild/test_keyring_generator.py
Normal file
185
tests/ahriman/core/support/pkgbuild/test_keyring_generator.py
Normal file
@ -0,0 +1,185 @@
|
|||||||
|
import pytest
|
||||||
|
|
||||||
|
from pathlib import Path
|
||||||
|
from pytest_mock import MockerFixture
|
||||||
|
from unittest.mock import MagicMock, call as MockCall
|
||||||
|
|
||||||
|
from ahriman.core.configuration import Configuration
|
||||||
|
from ahriman.core.exceptions import PkgbuildGeneratorError
|
||||||
|
from ahriman.core.sign.gpg import GPG
|
||||||
|
from ahriman.core.support.pkgbuild.keyring_generator import KeyringGenerator
|
||||||
|
|
||||||
|
|
||||||
|
def test_init_packagers(gpg: GPG, configuration: Configuration, mocker: MockerFixture) -> None:
|
||||||
|
"""
|
||||||
|
must extract packagers keys
|
||||||
|
"""
|
||||||
|
mocker.patch("ahriman.core.sign.gpg.GPG.keys", return_value=["key"])
|
||||||
|
|
||||||
|
assert KeyringGenerator(gpg, configuration, "keyring").packagers == ["key"]
|
||||||
|
|
||||||
|
configuration.set_option("keyring", "packagers", "key1")
|
||||||
|
assert KeyringGenerator(gpg, configuration, "keyring").packagers == ["key1"]
|
||||||
|
|
||||||
|
|
||||||
|
def test_init_revoked(gpg: GPG, configuration: Configuration) -> None:
|
||||||
|
"""
|
||||||
|
must extract revoked keys
|
||||||
|
"""
|
||||||
|
assert KeyringGenerator(gpg, configuration, "keyring").revoked == []
|
||||||
|
|
||||||
|
configuration.set_option("keyring", "revoked", "key1")
|
||||||
|
assert KeyringGenerator(gpg, configuration, "keyring").revoked == ["key1"]
|
||||||
|
|
||||||
|
|
||||||
|
def test_init_trusted(gpg: GPG, configuration: Configuration) -> None:
|
||||||
|
"""
|
||||||
|
must extract trusted keys
|
||||||
|
"""
|
||||||
|
assert KeyringGenerator(gpg, configuration, "keyring").trusted == []
|
||||||
|
|
||||||
|
gpg.default_key = "key"
|
||||||
|
assert KeyringGenerator(gpg, configuration, "keyring").trusted == ["key"]
|
||||||
|
|
||||||
|
configuration.set_option("keyring", "trusted", "key1")
|
||||||
|
assert KeyringGenerator(gpg, configuration, "keyring").trusted == ["key1"]
|
||||||
|
|
||||||
|
|
||||||
|
def test_license(gpg: GPG, configuration: Configuration) -> None:
|
||||||
|
"""
|
||||||
|
must generate correct licenses list
|
||||||
|
"""
|
||||||
|
assert KeyringGenerator(gpg, configuration, "keyring").license == ["Unlicense"]
|
||||||
|
|
||||||
|
configuration.set_option("keyring", "license", "GPL MPL")
|
||||||
|
assert KeyringGenerator(gpg, configuration, "keyring").license == ["GPL", "MPL"]
|
||||||
|
|
||||||
|
|
||||||
|
def test_pkgdesc(gpg: GPG, configuration: Configuration) -> None:
|
||||||
|
"""
|
||||||
|
must generate correct pkgdesc property
|
||||||
|
"""
|
||||||
|
assert KeyringGenerator(gpg, configuration, "keyring").pkgdesc == "aur-clone PGP keyring"
|
||||||
|
|
||||||
|
configuration.set_option("keyring", "description", "description")
|
||||||
|
assert KeyringGenerator(gpg, configuration, "keyring").pkgdesc == "description"
|
||||||
|
|
||||||
|
|
||||||
|
def test_pkgname(gpg: GPG, configuration: Configuration) -> None:
|
||||||
|
"""
|
||||||
|
must generate correct pkgname property
|
||||||
|
"""
|
||||||
|
assert KeyringGenerator(gpg, configuration, "keyring").pkgname == "aur-clone-keyring"
|
||||||
|
|
||||||
|
configuration.set_option("keyring", "package", "keyring")
|
||||||
|
assert KeyringGenerator(gpg, configuration, "keyring").pkgname == "keyring"
|
||||||
|
|
||||||
|
|
||||||
|
def test_url(gpg: GPG, configuration: Configuration) -> None:
|
||||||
|
"""
|
||||||
|
must generate correct url property
|
||||||
|
"""
|
||||||
|
assert KeyringGenerator(gpg, configuration, "keyring").url == ""
|
||||||
|
|
||||||
|
configuration.set_option("keyring", "homepage", "homepage")
|
||||||
|
assert KeyringGenerator(gpg, configuration, "keyring").url == "homepage"
|
||||||
|
|
||||||
|
|
||||||
|
def test_generate_gpg(keyring_generator: KeyringGenerator, mocker: MockerFixture) -> None:
|
||||||
|
"""
|
||||||
|
must correctly generate file with all PGP keys
|
||||||
|
"""
|
||||||
|
file_mock = MagicMock()
|
||||||
|
export_mock = mocker.patch("ahriman.core.sign.gpg.GPG.key_export", side_effect=lambda key: key)
|
||||||
|
open_mock = mocker.patch("pathlib.Path.open")
|
||||||
|
open_mock.return_value.__enter__.return_value = file_mock
|
||||||
|
keyring_generator.packagers = ["key"]
|
||||||
|
keyring_generator.revoked = ["revoked"]
|
||||||
|
keyring_generator.trusted = ["trusted", "key"]
|
||||||
|
|
||||||
|
keyring_generator._generate_gpg(Path("local"))
|
||||||
|
open_mock.assert_called_once_with("w")
|
||||||
|
export_mock.assert_has_calls([MockCall("key"), MockCall("revoked"), MockCall("trusted")])
|
||||||
|
file_mock.write.assert_has_calls([
|
||||||
|
MockCall("key"), MockCall("\n"),
|
||||||
|
MockCall("revoked"), MockCall("\n"),
|
||||||
|
MockCall("trusted"), MockCall("\n"),
|
||||||
|
])
|
||||||
|
|
||||||
|
|
||||||
|
def test_generate_revoked(keyring_generator: KeyringGenerator, mocker: MockerFixture) -> None:
|
||||||
|
"""
|
||||||
|
must correctly generate file with revoked keys
|
||||||
|
"""
|
||||||
|
file_mock = MagicMock()
|
||||||
|
fingerprint_mock = mocker.patch("ahriman.core.sign.gpg.GPG.key_fingerprint", side_effect=lambda key: key)
|
||||||
|
open_mock = mocker.patch("pathlib.Path.open")
|
||||||
|
open_mock.return_value.__enter__.return_value = file_mock
|
||||||
|
keyring_generator.revoked = ["revoked"]
|
||||||
|
|
||||||
|
keyring_generator._generate_revoked(Path("local"))
|
||||||
|
open_mock.assert_called_once_with("w")
|
||||||
|
fingerprint_mock.assert_called_once_with("revoked")
|
||||||
|
file_mock.write.assert_has_calls([MockCall("revoked"), MockCall("\n")])
|
||||||
|
|
||||||
|
|
||||||
|
def test_generate_trusted(keyring_generator: KeyringGenerator, mocker: MockerFixture) -> None:
|
||||||
|
"""
|
||||||
|
must correctly generate file with trusted keys
|
||||||
|
"""
|
||||||
|
file_mock = MagicMock()
|
||||||
|
fingerprint_mock = mocker.patch("ahriman.core.sign.gpg.GPG.key_fingerprint", side_effect=lambda key: key)
|
||||||
|
open_mock = mocker.patch("pathlib.Path.open")
|
||||||
|
open_mock.return_value.__enter__.return_value = file_mock
|
||||||
|
keyring_generator.trusted = ["trusted", "trusted"]
|
||||||
|
|
||||||
|
keyring_generator._generate_trusted(Path("local"))
|
||||||
|
open_mock.assert_called_once_with("w")
|
||||||
|
fingerprint_mock.assert_called_once_with("trusted")
|
||||||
|
file_mock.write.assert_has_calls([MockCall("trusted"), MockCall(":4:\n")])
|
||||||
|
|
||||||
|
|
||||||
|
def test_generate_trusted_empty(keyring_generator: KeyringGenerator) -> None:
|
||||||
|
"""
|
||||||
|
must raise PkgbuildGeneratorError if no trusted keys set
|
||||||
|
"""
|
||||||
|
with pytest.raises(PkgbuildGeneratorError):
|
||||||
|
keyring_generator._generate_trusted(Path("local"))
|
||||||
|
|
||||||
|
|
||||||
|
def test_install(keyring_generator: KeyringGenerator) -> None:
|
||||||
|
"""
|
||||||
|
must return install functions
|
||||||
|
"""
|
||||||
|
assert keyring_generator.install() == """post_upgrade() {
|
||||||
|
if usr/bin/pacman-key -l >/dev/null 2>&1; then
|
||||||
|
usr/bin/pacman-key --populate aur-clone
|
||||||
|
usr/bin/pacman-key --updatedb
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
post_install() {
|
||||||
|
if [ -x usr/bin/pacman-key ]; then
|
||||||
|
post_upgrade
|
||||||
|
fi
|
||||||
|
}"""
|
||||||
|
|
||||||
|
|
||||||
|
def test_package(keyring_generator: KeyringGenerator) -> None:
|
||||||
|
"""
|
||||||
|
must generate package function correctly
|
||||||
|
"""
|
||||||
|
assert keyring_generator.package() == """{
|
||||||
|
install -Dm644 "$srcdir/aur-clone.gpg" "$pkgdir/usr/share/pacman/keyrings/aur-clone.gpg"
|
||||||
|
install -Dm644 "$srcdir/aur-clone-revoked" "$pkgdir/usr/share/pacman/keyrings/aur-clone-revoked"
|
||||||
|
install -Dm644 "$srcdir/aur-clone-trusted" "$pkgdir/usr/share/pacman/keyrings/aur-clone-trusted"
|
||||||
|
}"""
|
||||||
|
|
||||||
|
|
||||||
|
def test_sources(keyring_generator: KeyringGenerator) -> None:
|
||||||
|
"""
|
||||||
|
must return valid sources files list
|
||||||
|
"""
|
||||||
|
assert keyring_generator.sources().get("aur-clone.gpg")
|
||||||
|
assert keyring_generator.sources().get("aur-clone-revoked")
|
||||||
|
assert keyring_generator.sources().get("aur-clone-trusted")
|
@ -1,6 +1,4 @@
|
|||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from unittest.mock import MagicMock
|
|
||||||
|
|
||||||
from pytest_mock import MockerFixture
|
from pytest_mock import MockerFixture
|
||||||
|
|
||||||
from ahriman.core.configuration import Configuration
|
from ahriman.core.configuration import Configuration
|
||||||
@ -61,14 +59,9 @@ def test_generate_mirrorlist(mirrorlist_generator: MirrorlistGenerator, mocker:
|
|||||||
"""
|
"""
|
||||||
must correctly generate mirrorlist file
|
must correctly generate mirrorlist file
|
||||||
"""
|
"""
|
||||||
path = Path("local")
|
write_mock = mocker.patch("pathlib.Path.write_text")
|
||||||
file_mock = MagicMock()
|
mirrorlist_generator._generate_mirrorlist(Path("local"))
|
||||||
open_mock = mocker.patch("pathlib.Path.open")
|
write_mock.assert_called_once_with("Server = http://localhost\n", encoding="utf8")
|
||||||
open_mock.return_value.__enter__.return_value = file_mock
|
|
||||||
|
|
||||||
mirrorlist_generator._generate_mirrorlist(path)
|
|
||||||
open_mock.assert_called_once_with("w")
|
|
||||||
file_mock.writelines.assert_called_once_with(["Server = http://localhost\n"])
|
|
||||||
|
|
||||||
|
|
||||||
def test_package(mirrorlist_generator: MirrorlistGenerator) -> None:
|
def test_package(mirrorlist_generator: MirrorlistGenerator) -> None:
|
||||||
|
@ -48,6 +48,13 @@ def test_url(pkgbuild_generator: PkgbuildGenerator) -> None:
|
|||||||
assert pkgbuild_generator.url == ""
|
assert pkgbuild_generator.url == ""
|
||||||
|
|
||||||
|
|
||||||
|
def test_install(pkgbuild_generator: PkgbuildGenerator) -> None:
|
||||||
|
"""
|
||||||
|
must return empty install function
|
||||||
|
"""
|
||||||
|
assert pkgbuild_generator.install() is None
|
||||||
|
|
||||||
|
|
||||||
def test_package(pkgbuild_generator: PkgbuildGenerator) -> None:
|
def test_package(pkgbuild_generator: PkgbuildGenerator) -> None:
|
||||||
"""
|
"""
|
||||||
must raise NotImplementedError on missing package function
|
must raise NotImplementedError on missing package function
|
||||||
@ -70,6 +77,25 @@ def test_sources(pkgbuild_generator: PkgbuildGenerator) -> None:
|
|||||||
assert pkgbuild_generator.sources() == {}
|
assert pkgbuild_generator.sources() == {}
|
||||||
|
|
||||||
|
|
||||||
|
def test_write_install(pkgbuild_generator: PkgbuildGenerator, mocker: MockerFixture) -> None:
|
||||||
|
"""
|
||||||
|
must write install file
|
||||||
|
"""
|
||||||
|
mocker.patch.object(PkgbuildGenerator, "pkgname", "package")
|
||||||
|
mocker.patch("ahriman.core.support.pkgbuild.pkgbuild_generator.PkgbuildGenerator.install", return_value="content")
|
||||||
|
write_mock = mocker.patch("pathlib.Path.write_text")
|
||||||
|
|
||||||
|
assert pkgbuild_generator.write_install(Path("local")) == [PkgbuildPatch("install", "package.install")]
|
||||||
|
write_mock.assert_called_once_with("content")
|
||||||
|
|
||||||
|
|
||||||
|
def test_write_install_empty(pkgbuild_generator: PkgbuildGenerator) -> None:
|
||||||
|
"""
|
||||||
|
must return empty patch list for missing install function
|
||||||
|
"""
|
||||||
|
assert pkgbuild_generator.write_install(Path("local")) == []
|
||||||
|
|
||||||
|
|
||||||
def test_write_pkgbuild(pkgbuild_generator: PkgbuildGenerator, mocker: MockerFixture) -> None:
|
def test_write_pkgbuild(pkgbuild_generator: PkgbuildGenerator, mocker: MockerFixture) -> None:
|
||||||
"""
|
"""
|
||||||
must write PKGBUILD content to file
|
must write PKGBUILD content to file
|
||||||
@ -80,14 +106,17 @@ def test_write_pkgbuild(pkgbuild_generator: PkgbuildGenerator, mocker: MockerFix
|
|||||||
mocker.patch("ahriman.core.support.pkgbuild.pkgbuild_generator.PkgbuildGenerator.package", return_value="{}")
|
mocker.patch("ahriman.core.support.pkgbuild.pkgbuild_generator.PkgbuildGenerator.package", return_value="{}")
|
||||||
patches_mock = mocker.patch("ahriman.core.support.pkgbuild.pkgbuild_generator.PkgbuildGenerator.patches",
|
patches_mock = mocker.patch("ahriman.core.support.pkgbuild.pkgbuild_generator.PkgbuildGenerator.patches",
|
||||||
return_value=[PkgbuildPatch("property", "value")])
|
return_value=[PkgbuildPatch("property", "value")])
|
||||||
|
install_mock = mocker.patch("ahriman.core.support.pkgbuild.pkgbuild_generator.PkgbuildGenerator.write_install",
|
||||||
|
return_value=[PkgbuildPatch("install", "pkgname.install")])
|
||||||
sources_mock = mocker.patch("ahriman.core.support.pkgbuild.pkgbuild_generator.PkgbuildGenerator.write_sources",
|
sources_mock = mocker.patch("ahriman.core.support.pkgbuild.pkgbuild_generator.PkgbuildGenerator.write_sources",
|
||||||
return_value=[PkgbuildPatch("source", []), PkgbuildPatch("sha512sums", [])])
|
return_value=[PkgbuildPatch("source", []), PkgbuildPatch("sha512sums", [])])
|
||||||
write_mock = mocker.patch("ahriman.models.pkgbuild_patch.PkgbuildPatch.write")
|
write_mock = mocker.patch("ahriman.models.pkgbuild_patch.PkgbuildPatch.write")
|
||||||
|
|
||||||
pkgbuild_generator.write_pkgbuild(path)
|
pkgbuild_generator.write_pkgbuild(path)
|
||||||
patches_mock.assert_called_once_with()
|
patches_mock.assert_called_once_with()
|
||||||
|
install_mock.assert_called_once_with(path)
|
||||||
sources_mock.assert_called_once_with(path)
|
sources_mock.assert_called_once_with(path)
|
||||||
write_mock.assert_has_calls([MockCall(path / "PKGBUILD")] * 11)
|
write_mock.assert_has_calls([MockCall(path / "PKGBUILD")] * 12)
|
||||||
|
|
||||||
|
|
||||||
def test_write_sources(pkgbuild_generator: PkgbuildGenerator, mocker: MockerFixture) -> None:
|
def test_write_sources(pkgbuild_generator: PkgbuildGenerator, mocker: MockerFixture) -> None:
|
||||||
|
30
tests/ahriman/core/support/test_keyring_trigger.py
Normal file
30
tests/ahriman/core/support/test_keyring_trigger.py
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
from pytest_mock import MockerFixture
|
||||||
|
|
||||||
|
from ahriman.core.configuration import Configuration
|
||||||
|
from ahriman.core.sign.gpg import GPG
|
||||||
|
from ahriman.core.support import KeyringTrigger
|
||||||
|
from ahriman.models.context_key import ContextKey
|
||||||
|
|
||||||
|
|
||||||
|
def test_configuration_sections(configuration: Configuration) -> None:
|
||||||
|
"""
|
||||||
|
must correctly parse target list
|
||||||
|
"""
|
||||||
|
configuration.set_option("keyring", "target", "a b c")
|
||||||
|
assert KeyringTrigger.configuration_sections(configuration) == ["a", "b", "c"]
|
||||||
|
|
||||||
|
configuration.remove_option("keyring", "target")
|
||||||
|
assert KeyringTrigger.configuration_sections(configuration) == []
|
||||||
|
|
||||||
|
|
||||||
|
def test_on_start(configuration: Configuration, mocker: MockerFixture) -> None:
|
||||||
|
"""
|
||||||
|
must run report for specified targets
|
||||||
|
"""
|
||||||
|
gpg_mock = mocker.patch("ahriman.core._Context.get")
|
||||||
|
run_mock = mocker.patch("ahriman.core.support.package_creator.PackageCreator.run")
|
||||||
|
|
||||||
|
trigger = KeyringTrigger("x86_64", configuration)
|
||||||
|
trigger.on_start()
|
||||||
|
gpg_mock.assert_called_once_with(ContextKey("sign", GPG))
|
||||||
|
run_mock.assert_called_once_with()
|
@ -19,7 +19,6 @@ def test_on_start(configuration: Configuration, mocker: MockerFixture) -> None:
|
|||||||
"""
|
"""
|
||||||
must run report for specified targets
|
must run report for specified targets
|
||||||
"""
|
"""
|
||||||
configuration.set_option("mirrorlist", "target", "mirrorlist")
|
|
||||||
run_mock = mocker.patch("ahriman.core.support.package_creator.PackageCreator.run")
|
run_mock = mocker.patch("ahriman.core.support.package_creator.PackageCreator.run")
|
||||||
|
|
||||||
trigger = MirrorlistTrigger("x86_64", configuration)
|
trigger = MirrorlistTrigger("x86_64", configuration)
|
||||||
|
@ -25,7 +25,7 @@ ignore_packages =
|
|||||||
makechrootpkg_flags =
|
makechrootpkg_flags =
|
||||||
makepkg_flags = --skippgpcheck
|
makepkg_flags = --skippgpcheck
|
||||||
triggers = ahriman.core.report.ReportTrigger ahriman.core.upload.UploadTrigger
|
triggers = ahriman.core.report.ReportTrigger ahriman.core.upload.UploadTrigger
|
||||||
triggers_known = ahriman.core.support.MirrorlistTrigger
|
triggers_known = ahriman.core.support.KeyringTrigger ahriman.core.support.MirrorlistTrigger
|
||||||
|
|
||||||
[repository]
|
[repository]
|
||||||
name = aur-clone
|
name = aur-clone
|
||||||
@ -34,6 +34,9 @@ root = ../../../
|
|||||||
[sign]
|
[sign]
|
||||||
target =
|
target =
|
||||||
|
|
||||||
|
[keyring]
|
||||||
|
target = keyring
|
||||||
|
|
||||||
[mirrorlist]
|
[mirrorlist]
|
||||||
target = mirrorlist
|
target = mirrorlist
|
||||||
servers = http://localhost
|
servers = http://localhost
|
||||||
|
Loading…
Reference in New Issue
Block a user