mirror of
https://github.com/arcan1s/ahriman.git
synced 2025-07-15 23:09:56 +00:00
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:
@ -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)
|
||||
|
@ -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())
|
||||
|
@ -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:
|
||||
|
@ -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")
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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()
|
||||
|
@ -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)
|
||||
|
@ -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:
|
||||
"""
|
||||
|
37
src/ahriman/models/context_key.py
Normal file
37
src/ahriman/models/context_key.py
Normal 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]
|
Reference in New Issue
Block a user