write patches via gitremote push trigger (#79)

* write patches via gitremote push trigger

* implement context variables intead of custom database class
This commit is contained in:
2022-12-25 01:10:38 +02:00
committed by GitHub
parent 9be4a89c08
commit 83e9d7c523
43 changed files with 616 additions and 181 deletions

View File

@ -50,5 +50,5 @@ class ApplicationProperties(LazyLogging):
self.configuration = configuration
self.architecture = architecture
self.database = SQLite.load(configuration)
self.repository = Repository(architecture, configuration, self.database,
report=report, unsafe=unsafe, refresh_pacman_database=refresh_pacman_database)
self.repository = Repository.load(architecture, configuration, self.database, report=report, unsafe=unsafe,
refresh_pacman_database=refresh_pacman_database)

View File

@ -17,3 +17,82 @@
# 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 contextvars import ContextVar
from typing import Any, Dict, Iterator, TypeVar
from ahriman.models.context_key import ContextKey
T = TypeVar("T")
class _Context:
"""
simple ahriman global context implementation
"""
def __init__(self) -> None:
"""
default constructor. Must not be used directly
"""
self._content: Dict[str, Any] = {}
def get(self, key: ContextKey[T]) -> T:
"""
get value for the specified key
Args:
key(ContextKey[T]): context key name
Returns:
T: value associated with the key
Raises:
KeyError: in case if the specified context variable was not found
ValueError: in case if type of value is not an instance of specified return type
"""
if key.key not in self._content:
raise KeyError(key.key)
value = self._content[key.key]
if not isinstance(value, key.return_type):
raise ValueError(f"Value {value} is not an instance of {key.return_type}")
return value
def set(self, key: ContextKey[T], value: T) -> None:
"""
set value for the specified key
Args:
key(ContextKey[T]): context key name
value(T): context value associated with the specified key
Raises:
KeyError: in case if the specified context variable already exists
ValueError: in case if type of value is not an instance of specified return type
"""
if key.key in self._content:
raise KeyError(key.key)
if not isinstance(value, key.return_type):
raise ValueError(f"Value {value} is not an instance of {key.return_type}")
self._content[key.key] = value
def __iter__(self) -> Iterator[str]:
"""
iterate over keys in local storage
Returns:
str: context key iterator
"""
return iter(self._content)
def __len__(self) -> int:
"""
get count of the context variables set
Returns:
int: count of stored context variables
"""
return len(self._content)
context = ContextVar("context", default=_Context())

View File

@ -25,6 +25,9 @@ from ahriman.core.triggers import Trigger
class RemotePullTrigger(Trigger):
"""
trigger based on pulling PKGBUILDs before the actions
Attributes:
targets(List[str]): git remote target list
"""
def __init__(self, architecture: str, configuration: Configuration) -> None:

View File

@ -25,6 +25,7 @@ from typing import Generator
from ahriman.core.build_tools.sources import Sources
from ahriman.core.configuration import Configuration
from ahriman.core.database import SQLite
from ahriman.core.exceptions import GitRemoteError
from ahriman.core.log import LazyLogging
from ahriman.models.package import Package
@ -39,17 +40,20 @@ class RemotePush(LazyLogging):
Attributes:
commit_author(Optional[str]): optional commit author in form of git config (i.e. ``user <user@host>``)
database(SQLite): database instance
remote_source(RemoteSource): repository remote source (remote pull url and branch)
"""
def __init__(self, configuration: Configuration, section: str) -> None:
def __init__(self, configuration: Configuration, database: SQLite, section: str) -> None:
"""
default constructor
Args:
configuration(Configuration): configuration instance
remote_push_trigger.py
database(SQLite): database instance
section(str): settings section name
"""
self.database = database
self.commit_author = configuration.get(section, "commit_author", fallback=None)
self.remote_source = RemoteSource(
git_url=configuration.get(section, "push_url"),
@ -59,8 +63,7 @@ class RemotePush(LazyLogging):
source=PackageSource.Local,
)
@staticmethod
def package_update(package: Package, target_dir: Path) -> str:
def package_update(self, package: Package, target_dir: Path) -> str:
"""
clone specified package and update its content in cloned PKGBUILD repository
@ -79,11 +82,14 @@ class RemotePush(LazyLogging):
Sources.fetch(package_target_dir, package.remote)
# ...and last, but not least, we remove the dot-git directory...
shutil.rmtree(package_target_dir / ".git", ignore_errors=True)
# ...copy all patches...
for patch in self.database.patches_get(package.base):
filename = f"ahriman-{package.base}.patch" if patch.key is None else f"ahriman-{patch.key}.patch"
patch.write(package_target_dir / filename)
# ...and finally return path to the copied directory
return package.base
@staticmethod
def packages_update(result: Result, target_dir: Path) -> Generator[str, None, None]:
def packages_update(self, result: Result, target_dir: Path) -> Generator[str, None, None]:
"""
update all packages from the build result
@ -95,7 +101,7 @@ class RemotePush(LazyLogging):
str: path to updated files
"""
for package in result.success:
yield RemotePush.package_update(package, target_dir)
yield self.package_update(package, target_dir)
def run(self, result: Result) -> None:
"""
@ -107,7 +113,7 @@ class RemotePush(LazyLogging):
try:
with TemporaryDirectory(ignore_cleanup_errors=True) as dir_name, (clone_dir := Path(dir_name)):
Sources.fetch(clone_dir, self.remote_source)
Sources.push(clone_dir, self.remote_source, *RemotePush.packages_update(result, clone_dir),
Sources.push(clone_dir, self.remote_source, *self.packages_update(result, clone_dir),
commit_author=self.commit_author)
except Exception:
self.logger.exception("git push failed")

View File

@ -19,9 +19,12 @@
#
from typing import Iterable
from ahriman.core import context
from ahriman.core.configuration import Configuration
from ahriman.core.database import SQLite
from ahriman.core.gitremote.remote_push import RemotePush
from ahriman.core.triggers import Trigger
from ahriman.models.context_key import ContextKey
from ahriman.models.package import Package
from ahriman.models.result import Result
@ -29,6 +32,9 @@ from ahriman.models.result import Result
class RemotePushTrigger(Trigger):
"""
trigger for syncing PKGBUILDs to remote repository
Attributes:
targets(List[str]): git remote target list
"""
def __init__(self, architecture: str, configuration: Configuration) -> None:
@ -49,8 +55,14 @@ class RemotePushTrigger(Trigger):
Args:
result(Result): build result
packages(Iterable[Package]): list of all available packages
Raises:
GitRemoteError: if database is not set in context
"""
ctx = context.get()
database = ctx.get(ContextKey("database", SQLite))
for target in self.targets:
section, _ = self.configuration.gettype(target, self.architecture)
runner = RemotePush(self.configuration, section)
runner = RemotePush(self.configuration, database, section)
runner.run(result)

View File

@ -17,12 +17,20 @@
# 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 pathlib import Path
from typing import Dict, Iterable, List, Optional
from __future__ import annotations
from pathlib import Path
from typing import Dict, Iterable, List, Optional, Type
from ahriman.core import context
from ahriman.core.alpm.pacman import Pacman
from ahriman.core.configuration import Configuration
from ahriman.core.database import SQLite
from ahriman.core.repository.executor import Executor
from ahriman.core.repository.update_handler import UpdateHandler
from ahriman.core.sign.gpg import GPG
from ahriman.core.util import package_like
from ahriman.models.context_key import ContextKey
from ahriman.models.package import Package
@ -39,7 +47,7 @@ class Repository(Executor, UpdateHandler):
>>>
>>> configuration = Configuration()
>>> database = SQLite.load(configuration)
>>> repository = Repository("x86_64", configuration, database, report=True, unsafe=False)
>>> repository = Repository.load("x86_64", configuration, database, report=True, unsafe=False)
>>> known_packages = repository.packages()
>>>
>>> build_result = repository.process_build(known_packages)
@ -49,6 +57,41 @@ class Repository(Executor, UpdateHandler):
>>> repository.triggers.on_result(update_result, repository.packages())
"""
@classmethod
def load(cls: Type[Repository], architecture: str, configuration: Configuration, database: SQLite, *,
report: bool, unsafe: bool, refresh_pacman_database: int = 0) -> Repository:
"""
load instance from argument list
Args:
architecture(str): repository architecture
configuration(Configuration): configuration instance
database(SQLite): database instance
report(bool): force enable or disable reporting
unsafe(bool): if set no user check will be performed before path creation
refresh_pacman_database(int, optional): pacman database syncronization level, ``0`` is disabled
(Default value = 0)
"""
instance = cls(architecture, configuration, database,
report=report, unsafe=unsafe, refresh_pacman_database=refresh_pacman_database)
instance._set_context()
return instance
def _set_context(self) -> None:
"""
set context variables
"""
ctx = context.get()
ctx.set(ContextKey("database", SQLite), self.database)
ctx.set(ContextKey("configuration", Configuration), self.configuration)
ctx.set(ContextKey("pacman", Pacman), self.pacman)
ctx.set(ContextKey("sign", GPG), self.sign)
ctx.set(ContextKey("repository", type(self)), self)
context.set(ctx)
def load_archives(self, packages: Iterable[Path]) -> List[Package]:
"""
load packages from list of archives

View File

@ -48,7 +48,7 @@ class RepositoryProperties(LazyLogging):
"""
def __init__(self, architecture: str, configuration: Configuration, database: SQLite, *,
report: bool, unsafe: bool, refresh_pacman_database: int = 0) -> None:
report: bool, unsafe: bool, refresh_pacman_database: int) -> None:
"""
default constructor
@ -59,7 +59,6 @@ class RepositoryProperties(LazyLogging):
report(bool): force enable or disable reporting
unsafe(bool): if set no user check will be performed before path creation
refresh_pacman_database(int, optional): pacman database syncronization level, ``0`` is disabled
(Default value = 0)
"""
self.architecture = architecture
self.configuration = configuration

View File

@ -55,7 +55,7 @@ class Watcher(LazyLogging):
"""
self.architecture = architecture
self.database = database
self.repository = Repository(architecture, configuration, database, report=False, unsafe=False)
self.repository = Repository.load(architecture, configuration, database, report=False, unsafe=False)
self.known: Dict[str, Tuple[Package, BuildStatus]] = {}
self.status = BuildStatus()

View File

@ -110,7 +110,7 @@ class Tree:
>>>
>>> configuration = Configuration()
>>> database = SQLite.load(configuration)
>>> repository = Repository("x86_64", configuration, database, report=True, unsafe=False)
>>> repository = Repository.load("x86_64", configuration, database, report=True, unsafe=False)
>>> packages = repository.packages()
>>>
>>> tree = Tree.load(packages, configuration.repository_paths, database)

View File

@ -69,11 +69,11 @@ class TriggerLoader(LazyLogging):
self.architecture = architecture
self.configuration = configuration
self._on_stop_requested = False
self.triggers = [
self.load_trigger(trigger)
for trigger in configuration.getlist("build", "triggers")
]
self._on_stop_requested = False
def __del__(self) -> None:
"""

View File

@ -0,0 +1,37 @@
#
# Copyright (c) 2021-2022 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 dataclasses import dataclass
from typing import Generic, Type, TypeVar
T = TypeVar("T")
@dataclass(frozen=True)
class ContextKey(Generic[T]):
"""
ahriman context key for typing purposes
Attributes:
key(str): context key to lookup
return_type(Type[T]): return type used for the specified context key
"""
key: str
return_type: Type[T]