mirror of
https://github.com/arcan1s/ahriman.git
synced 2025-07-31 06:39:56 +00:00
Compare commits
9 Commits
2536b8dc1f
...
2.0.0rc6
Author | SHA1 | Date | |
---|---|---|---|
a6c8d64053 | |||
fd78f2b5e2 | |||
900907cdaa | |||
5ff2f43506 | |||
dd521b49b5 | |||
5b1f5a8473 | |||
86af13f09e | |||
733c014229 | |||
783c16b2ed |
@ -13,7 +13,7 @@ Wrapper for managing custom repository inspired by [repo-scripts](https://github
|
||||
* Multi-architecture support.
|
||||
* VCS packages support.
|
||||
* Sign support with gpg (repository, package, per package settings).
|
||||
* Synchronization to remote services (rsync, s3 and github) and report generation (html).
|
||||
* Synchronization to remote services (rsync, s3 and github) and report generation (email, html, telegram).
|
||||
* Dependency manager.
|
||||
* Ability to patch AUR packages and even create package from local PKGBUILDs.
|
||||
* Repository status interface with optional authorization and control options:
|
||||
|
File diff suppressed because it is too large
Load Diff
Before Width: | Height: | Size: 503 KiB After Width: | Height: | Size: 509 KiB |
@ -3,7 +3,7 @@
|
||||
ahriman
|
||||
.SH SYNOPSIS
|
||||
.B ahriman
|
||||
[-h] [-a ARCHITECTURE] [-c CONFIGURATION] [--force] [-l LOCK] [--no-report] [-q] [--unsafe] [-v] {aur-search,search,help,help-commands-unsafe,key-import,package-add,add,package-update,package-remove,remove,package-status,status,package-status-remove,package-status-update,status-update,patch-add,patch-list,patch-remove,repo-check,check,repo-clean,clean,repo-config,config,repo-rebuild,rebuild,repo-remove-unknown,remove-unknown,repo-report,report,repo-setup,init,repo-init,setup,repo-sign,sign,repo-status-update,repo-sync,sync,repo-update,update,user-add,user-list,user-remove,web} ...
|
||||
[-h] [-a ARCHITECTURE] [-c CONFIGURATION] [--force] [-l LOCK] [--no-report] [-q] [--unsafe] [-v] {aur-search,search,help,help-commands-unsafe,key-import,package-add,add,package-update,package-remove,remove,package-status,status,package-status-remove,package-status-update,status-update,patch-add,patch-list,patch-remove,repo-check,check,repo-clean,clean,repo-config,config,repo-rebuild,rebuild,repo-remove-unknown,remove-unknown,repo-report,report,repo-restore,restore,repo-setup,init,repo-init,setup,repo-sign,sign,repo-status-update,repo-sync,sync,repo-update,update,user-add,user-list,user-remove,web} ...
|
||||
.SH DESCRIPTION
|
||||
ArcH Linux ReposItory MANager
|
||||
.SH OPTIONS
|
||||
@ -97,6 +97,9 @@ remove unknown packages
|
||||
\fBahriman\fR \fI\,repo-report\/\fR
|
||||
generate report
|
||||
.TP
|
||||
\fBahriman\fR \fI\,repo-restore\/\fR
|
||||
restore repository
|
||||
.TP
|
||||
\fBahriman\fR \fI\,repo-setup\/\fR
|
||||
initial service configuration
|
||||
.TP
|
||||
@ -207,7 +210,7 @@ key server for key import
|
||||
|
||||
.SH OPTIONS 'ahriman package-add'
|
||||
usage: ahriman package-add [-h] [-e] [-n]
|
||||
[-s {PackageSource.Auto,PackageSource.Archive,PackageSource.AUR,PackageSource.Directory,PackageSource.Local,PackageSource.Remote}]
|
||||
[-s {PackageSource.Auto,PackageSource.Archive,PackageSource.AUR,PackageSource.Directory,PackageSource.Local,PackageSource.Remote,PackageSource.Repository}]
|
||||
[--without-dependencies]
|
||||
package [package ...]
|
||||
|
||||
@ -226,7 +229,7 @@ return non\-zero exit status if result is empty
|
||||
run update function after
|
||||
|
||||
.TP
|
||||
\fB\-s\fR {PackageSource.Auto,PackageSource.Archive,PackageSource.AUR,PackageSource.Directory,PackageSource.Local,PackageSource.Remote}, \fB\-\-source\fR {PackageSource.Auto,PackageSource.Archive,PackageSource.AUR,PackageSource.Directory,PackageSource.Local,PackageSource.Remote}
|
||||
\fB\-s\fR {PackageSource.Auto,PackageSource.Archive,PackageSource.AUR,PackageSource.Directory,PackageSource.Local,PackageSource.Remote,PackageSource.Repository}, \fB\-\-source\fR {PackageSource.Auto,PackageSource.Archive,PackageSource.AUR,PackageSource.Directory,PackageSource.Local,PackageSource.Remote,PackageSource.Repository}
|
||||
explicitly specify the package source for this command
|
||||
|
||||
.TP
|
||||
@ -235,7 +238,7 @@ do not add dependencies
|
||||
|
||||
.SH OPTIONS 'ahriman add'
|
||||
usage: ahriman package-add [-h] [-e] [-n]
|
||||
[-s {PackageSource.Auto,PackageSource.Archive,PackageSource.AUR,PackageSource.Directory,PackageSource.Local,PackageSource.Remote}]
|
||||
[-s {PackageSource.Auto,PackageSource.Archive,PackageSource.AUR,PackageSource.Directory,PackageSource.Local,PackageSource.Remote,PackageSource.Repository}]
|
||||
[--without-dependencies]
|
||||
package [package ...]
|
||||
|
||||
@ -254,7 +257,7 @@ return non\-zero exit status if result is empty
|
||||
run update function after
|
||||
|
||||
.TP
|
||||
\fB\-s\fR {PackageSource.Auto,PackageSource.Archive,PackageSource.AUR,PackageSource.Directory,PackageSource.Local,PackageSource.Remote}, \fB\-\-source\fR {PackageSource.Auto,PackageSource.Archive,PackageSource.AUR,PackageSource.Directory,PackageSource.Local,PackageSource.Remote}
|
||||
\fB\-s\fR {PackageSource.Auto,PackageSource.Archive,PackageSource.AUR,PackageSource.Directory,PackageSource.Local,PackageSource.Remote,PackageSource.Repository}, \fB\-\-source\fR {PackageSource.Auto,PackageSource.Archive,PackageSource.AUR,PackageSource.Directory,PackageSource.Local,PackageSource.Remote,PackageSource.Repository}
|
||||
explicitly specify the package source for this command
|
||||
|
||||
.TP
|
||||
@ -263,7 +266,7 @@ do not add dependencies
|
||||
|
||||
.SH OPTIONS 'ahriman package-update'
|
||||
usage: ahriman package-add [-h] [-e] [-n]
|
||||
[-s {PackageSource.Auto,PackageSource.Archive,PackageSource.AUR,PackageSource.Directory,PackageSource.Local,PackageSource.Remote}]
|
||||
[-s {PackageSource.Auto,PackageSource.Archive,PackageSource.AUR,PackageSource.Directory,PackageSource.Local,PackageSource.Remote,PackageSource.Repository}]
|
||||
[--without-dependencies]
|
||||
package [package ...]
|
||||
|
||||
@ -282,7 +285,7 @@ return non\-zero exit status if result is empty
|
||||
run update function after
|
||||
|
||||
.TP
|
||||
\fB\-s\fR {PackageSource.Auto,PackageSource.Archive,PackageSource.AUR,PackageSource.Directory,PackageSource.Local,PackageSource.Remote}, \fB\-\-source\fR {PackageSource.Auto,PackageSource.Archive,PackageSource.AUR,PackageSource.Directory,PackageSource.Local,PackageSource.Remote}
|
||||
\fB\-s\fR {PackageSource.Auto,PackageSource.Archive,PackageSource.AUR,PackageSource.Directory,PackageSource.Local,PackageSource.Remote,PackageSource.Repository}, \fB\-\-source\fR {PackageSource.Auto,PackageSource.Archive,PackageSource.AUR,PackageSource.Directory,PackageSource.Local,PackageSource.Remote,PackageSource.Repository}
|
||||
explicitly specify the package source for this command
|
||||
|
||||
.TP
|
||||
@ -615,6 +618,42 @@ generate repository report according to current settings
|
||||
target to generate report
|
||||
|
||||
|
||||
.SH OPTIONS 'ahriman repo-restore'
|
||||
usage: ahriman repo-restore [-h] [-e] [-n] [--without-dependencies]
|
||||
|
||||
restore repository from database file
|
||||
|
||||
|
||||
.TP
|
||||
\fB\-e\fR, \fB\-\-exit\-code\fR
|
||||
return non\-zero exit status if result is empty
|
||||
|
||||
.TP
|
||||
\fB\-n\fR, \fB\-\-now\fR
|
||||
run update function after
|
||||
|
||||
.TP
|
||||
\fB\-\-without\-dependencies\fR
|
||||
do not add dependencies
|
||||
|
||||
.SH OPTIONS 'ahriman restore'
|
||||
usage: ahriman repo-restore [-h] [-e] [-n] [--without-dependencies]
|
||||
|
||||
restore repository from database file
|
||||
|
||||
|
||||
.TP
|
||||
\fB\-e\fR, \fB\-\-exit\-code\fR
|
||||
return non\-zero exit status if result is empty
|
||||
|
||||
.TP
|
||||
\fB\-n\fR, \fB\-\-now\fR
|
||||
run update function after
|
||||
|
||||
.TP
|
||||
\fB\-\-without\-dependencies\fR
|
||||
do not add dependencies
|
||||
|
||||
.SH OPTIONS 'ahriman repo-setup'
|
||||
usage: ahriman repo-setup [-h] [--build-as-user BUILD_AS_USER] [--build-command BUILD_COMMAND]
|
||||
[--from-configuration FROM_CONFIGURATION] [--no-multilib] --packager PACKAGER --repository
|
||||
|
@ -114,6 +114,18 @@ Section name must be either `html` (plus optional architecture name, e.g. `html:
|
||||
* `link_path` - prefix for HTML links, string, required.
|
||||
* `template_path` - path to Jinja2 template, string, required.
|
||||
|
||||
### `telegram` type
|
||||
|
||||
Section name must be either `telegram` (plus optional architecture name, e.g. `telegram:x86_64`) or random name with `type` set.
|
||||
|
||||
* `type` - type of the report, string, optional, must be set to `telegram` if exists.
|
||||
* `api_key` - telegram bot API key, string, required. Please refer FAQ about how to create chat and bot
|
||||
* `chat_id` - telegram chat id, either string with `@` or integer value, required.
|
||||
* `homepage` - link to homepage, string, optional.
|
||||
* `link_path` - prefix for HTML links, string, required.
|
||||
* `template_path` - path to Jinja2 template, string, required.
|
||||
* `template_type` - `parse_mode` to be passed to telegram API, one of `MarkdownV2`, `HTML`, `Markdown`, string, optional, default `HTML`.
|
||||
|
||||
## `upload` group
|
||||
|
||||
Remote synchronization settings.
|
||||
|
40
docs/faq.md
40
docs/faq.md
@ -402,6 +402,46 @@ There are several choices:
|
||||
|
||||
After these steps `index.html` file will be automatically synced to S3
|
||||
|
||||
### I would like to get messages to my telegram account/channel
|
||||
|
||||
1. It still requires additional dependencies:
|
||||
|
||||
```shell
|
||||
yay -S python-jinja
|
||||
```
|
||||
|
||||
2. Register bot in telegram. You can do it by using by talking with [@BotFather](https://t.me/botfather). For more details please refer to [official documentation](https://core.telegram.org/bots).
|
||||
|
||||
3. Optionally (if you want to post message in chat):
|
||||
|
||||
1. Create telegram channel.
|
||||
2. Invite your bot into the channel.
|
||||
3. Make your channel public
|
||||
|
||||
4. Get chat id if you want to use by numerical id or just use id prefixed with `@` (e.g. `@ahriman`). If you are not using chat the chat id is your user id. If you don't want to make channel public you can use [this guide](https://stackoverflow.com/a/33862907).
|
||||
|
||||
5. Configure the service:
|
||||
|
||||
```ini
|
||||
[report]
|
||||
target = telegram
|
||||
|
||||
[telegram]
|
||||
api_key = aaAAbbBBccCC
|
||||
chat_id = @ahriman
|
||||
link_path = http://example.com/x86_64
|
||||
```
|
||||
|
||||
`api_key` is the one sent by [@BotFather](https://t.me/botfather), `chat_id` is the value retrieved from previous step.
|
||||
|
||||
If you did everything fine you should receive the message with the next update. Quick credentials check can be done by using the following command:
|
||||
|
||||
```shell
|
||||
curl 'https://api.telegram.org/bot${CHAT_ID}/sendMessage?chat_id=${API_KEY}&text=hello'
|
||||
```
|
||||
|
||||
(replace `${CHAT_ID}` and `${API_KEY}` with the values from configuration).
|
||||
|
||||
## Web service
|
||||
|
||||
### Readme mentions web interface, how do I use it?
|
||||
|
@ -1,7 +1,7 @@
|
||||
# Maintainer: Evgeniy Alekseev
|
||||
|
||||
pkgname='ahriman'
|
||||
pkgver=2.0.0rc3
|
||||
pkgver=2.0.0rc6
|
||||
pkgrel=1
|
||||
pkgdesc="ArcH Linux ReposItory MANager"
|
||||
arch=('any')
|
||||
@ -38,7 +38,7 @@ build() {
|
||||
package() {
|
||||
cd "$pkgname"
|
||||
|
||||
python -m installer --destdir="$pkgdir" dist/*.whl
|
||||
python -m installer --destdir="$pkgdir" "dist/$pkgname-$pkgver-py3-none-any.whl"
|
||||
|
||||
# python-installer actually thinks that you cannot just copy files to root
|
||||
# thus we need to copy them manually
|
||||
|
@ -45,6 +45,9 @@ ssl = disabled
|
||||
[html]
|
||||
template_path = /usr/share/ahriman/templates/repo-index.jinja2
|
||||
|
||||
[telegram]
|
||||
template_path = /usr/share/ahriman/templates/telegram-index.jinja2
|
||||
|
||||
[upload]
|
||||
target =
|
||||
|
||||
|
4
package/share/ahriman/templates/telegram-index.jinja2
Normal file
4
package/share/ahriman/templates/telegram-index.jinja2
Normal file
@ -0,0 +1,4 @@
|
||||
{#simplified version of full report#}
|
||||
<b>{{ repository }} update</b>
|
||||
{% for package in packages %}
|
||||
<a href="{{ link_path }}/{{ package.filename }}">{{ package.name }}</a> {{ package.version }}{% endfor %}
|
1
setup.py
1
setup.py
@ -67,6 +67,7 @@ setup(
|
||||
"package/share/ahriman/templates/build-status.jinja2",
|
||||
"package/share/ahriman/templates/email-index.jinja2",
|
||||
"package/share/ahriman/templates/repo-index.jinja2",
|
||||
"package/share/ahriman/templates/telegram-index.jinja2",
|
||||
]),
|
||||
("share/ahriman/templates/build-status", [
|
||||
"package/share/ahriman/templates/build-status/login-modal.jinja2",
|
||||
|
@ -86,7 +86,8 @@ class Sources:
|
||||
Sources.logger.warning("%s is not initialized, but no remote provided", sources_dir)
|
||||
else:
|
||||
Sources.logger.info("clone remote %s to %s", remote, sources_dir)
|
||||
Sources._check_output("git", "clone", remote, str(sources_dir), exception=None, logger=Sources.logger)
|
||||
Sources._check_output("git", "clone", remote, str(sources_dir),
|
||||
exception=None, cwd=sources_dir, logger=Sources.logger)
|
||||
# and now force reset to our branch
|
||||
Sources._check_output("git", "checkout", "--force", Sources._branch,
|
||||
exception=None, cwd=sources_dir, logger=Sources.logger)
|
||||
|
@ -32,9 +32,9 @@ def migrate_users_data(connection: Connection, configuration: Configuration) ->
|
||||
for option, value in configuration[section].items():
|
||||
if not section.startswith("auth:"):
|
||||
continue
|
||||
permission = section[5:]
|
||||
access = section[5:]
|
||||
connection.execute(
|
||||
"""insert into users (username, permission, password) values (:username, :permission, :password)""",
|
||||
{"username": option.lower(), "permission": permission, "password": value})
|
||||
"""insert into users (username, access, password) values (:username, :access, :password)""",
|
||||
{"username": option.lower(), "access": access, "password": value})
|
||||
|
||||
connection.commit()
|
||||
|
@ -101,7 +101,7 @@ class JinjaTemplate:
|
||||
"name": package,
|
||||
"url": properties.url or "",
|
||||
"version": base.version
|
||||
} for base in result.updated for package, properties in base.packages.items()
|
||||
} for base in result.success for package, properties in base.packages.items()
|
||||
]
|
||||
comparator: Callable[[Dict[str, str]], str] = lambda item: item["filename"]
|
||||
|
||||
|
@ -68,6 +68,9 @@ class Report:
|
||||
if provider == ReportSettings.Console:
|
||||
from ahriman.core.report.console import Console
|
||||
return Console(architecture, configuration, section)
|
||||
if provider == ReportSettings.Telegram:
|
||||
from ahriman.core.report.telegram import Telegram
|
||||
return Telegram(architecture, configuration, section)
|
||||
return cls(architecture, configuration) # should never happen
|
||||
|
||||
def generate(self, packages: Iterable[Package], result: Result) -> None:
|
||||
|
95
src/ahriman/core/report/telegram.py
Normal file
95
src/ahriman/core/report/telegram.py
Normal file
@ -0,0 +1,95 @@
|
||||
#
|
||||
# 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/>.
|
||||
#
|
||||
# technically we could use python-telegram-bot, but it is just a single request, cmon
|
||||
import requests
|
||||
|
||||
from typing import Iterable
|
||||
|
||||
from ahriman.core.configuration import Configuration
|
||||
from ahriman.core.report.jinja_template import JinjaTemplate
|
||||
from ahriman.core.report.report import Report
|
||||
from ahriman.core.util import exception_response_text
|
||||
from ahriman.models.package import Package
|
||||
from ahriman.models.result import Result
|
||||
|
||||
|
||||
class Telegram(Report, JinjaTemplate):
|
||||
"""
|
||||
telegram report generator
|
||||
:cvar TELEGRAM_API_URL: telegram api base url
|
||||
:cvar TELEGRAM_MAX_CONTENT_LENGTH: max content length of the message
|
||||
:ivar api_key: bot api key
|
||||
:ivar chat_id: chat id to post message, either string with @ or integer
|
||||
:ivar template_path: path to template for built packages
|
||||
:ivar template_type: template message type to be used in parse mode, one of MarkdownV2, HTML, Markdown
|
||||
"""
|
||||
|
||||
TELEGRAM_API_URL = "https://api.telegram.org"
|
||||
TELEGRAM_MAX_CONTENT_LENGTH = 4096
|
||||
|
||||
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, section, configuration)
|
||||
|
||||
self.api_key = configuration.get(section, "api_key")
|
||||
self.chat_id = configuration.get(section, "chat_id")
|
||||
self.template_path = configuration.getpath(section, "template_path")
|
||||
self.template_type = configuration.get(section, "template_type", fallback="HTML")
|
||||
|
||||
def _send(self, text: str) -> None:
|
||||
"""
|
||||
send message to telegram channel
|
||||
:param text: message body text
|
||||
"""
|
||||
try:
|
||||
response = requests.post(
|
||||
f"{self.TELEGRAM_API_URL}/bot{self.api_key}/sendMessage",
|
||||
data={"chat_id": self.chat_id, "text": text, "parse_mode": self.template_type})
|
||||
response.raise_for_status()
|
||||
except requests.HTTPError as e:
|
||||
self.logger.exception("could not perform request: %s", exception_response_text(e))
|
||||
raise
|
||||
except Exception:
|
||||
self.logger.exception("could not perform request")
|
||||
raise
|
||||
|
||||
def generate(self, packages: Iterable[Package], result: Result) -> None:
|
||||
"""
|
||||
generate report for the specified packages
|
||||
:param packages: list of packages to generate report
|
||||
:param result: build result
|
||||
"""
|
||||
if not result.success:
|
||||
return
|
||||
text = self.make_html(result, self.template_path)
|
||||
# telegram content is limited by 4096 symbols, so we are going to split the message by new lines
|
||||
# to fit into this restriction
|
||||
if len(text) > self.TELEGRAM_MAX_CONTENT_LENGTH:
|
||||
position = text.rfind("\n", 0, self.TELEGRAM_MAX_CONTENT_LENGTH)
|
||||
portion, text = text[:position], text[position + 1:] # +1 to exclude newline we split
|
||||
self._send(portion)
|
||||
# send remaining (or full in case if size is less than max length) text
|
||||
self._send(text)
|
@ -19,14 +19,11 @@
|
||||
#
|
||||
from __future__ import annotations
|
||||
|
||||
import shutil
|
||||
import tempfile
|
||||
|
||||
from pathlib import Path
|
||||
from typing import Iterable, List, Set, Type
|
||||
|
||||
from ahriman.core.build_tools.sources import Sources
|
||||
from ahriman.core.database.sqlite import SQLite
|
||||
from ahriman.core.util import tmpdir
|
||||
from ahriman.models.package import Package
|
||||
|
||||
|
||||
@ -61,12 +58,9 @@ class Leaf:
|
||||
:param database: database instance
|
||||
:return: loaded class
|
||||
"""
|
||||
clone_dir = Path(tempfile.mkdtemp())
|
||||
try:
|
||||
with tmpdir() as clone_dir:
|
||||
Sources.load(clone_dir, package.git_url, database.patches_get(package.base))
|
||||
dependencies = Package.dependencies(clone_dir)
|
||||
finally:
|
||||
shutil.rmtree(clone_dir, ignore_errors=True)
|
||||
return cls(package, dependencies)
|
||||
|
||||
def is_root(self, packages: Iterable[Leaf]) -> bool:
|
||||
|
@ -211,23 +211,25 @@ class Package:
|
||||
:return: list of package dependencies including makedepends array, but excluding packages from this base
|
||||
"""
|
||||
# additional function to remove versions from dependencies
|
||||
def trim_version(name: str) -> str:
|
||||
def extract_packages(raw_packages_list: List[str]) -> Set[str]:
|
||||
return {trim_version(package_name) for package_name in raw_packages_list}
|
||||
|
||||
def trim_version(package_name: str) -> str:
|
||||
for symbol in ("<", "=", ">"):
|
||||
name = name.split(symbol)[0]
|
||||
return name
|
||||
package_name = package_name.split(symbol)[0]
|
||||
return package_name
|
||||
|
||||
srcinfo, errors = parse_srcinfo((path / ".SRCINFO").read_text())
|
||||
if errors:
|
||||
raise InvalidPackageInfo(errors)
|
||||
makedepends = srcinfo.get("makedepends", [])
|
||||
makedepends = extract_packages(srcinfo.get("makedepends", []))
|
||||
# sum over each package
|
||||
depends: List[str] = srcinfo.get("depends", [])
|
||||
depends = extract_packages(srcinfo.get("depends", []))
|
||||
for package in srcinfo["packages"].values():
|
||||
depends.extend(package.get("depends", []))
|
||||
depends |= extract_packages(package.get("depends", []))
|
||||
# we are not interested in dependencies inside pkgbase
|
||||
packages = set(srcinfo["packages"].keys())
|
||||
full_list = set(depends + makedepends) - packages
|
||||
return {trim_version(package_name) for package_name in full_list}
|
||||
return (depends | makedepends) - packages
|
||||
|
||||
def actual_version(self, paths: RepositoryPaths) -> str:
|
||||
"""
|
||||
|
@ -32,12 +32,14 @@ class ReportSettings(Enum):
|
||||
:cvar HTML: html report generation
|
||||
:cvar Email: email report generation
|
||||
:cvar Console: print result to console
|
||||
:cvar Telegram: markdown report to telegram channel
|
||||
"""
|
||||
|
||||
Disabled = "disabled" # for testing purpose
|
||||
HTML = "html"
|
||||
Email = "email"
|
||||
Console = "console"
|
||||
Telegram = "telegram"
|
||||
|
||||
@classmethod
|
||||
def from_option(cls: Type[ReportSettings], value: str) -> ReportSettings:
|
||||
@ -52,4 +54,6 @@ class ReportSettings(Enum):
|
||||
return cls.Email
|
||||
if value.lower() in ("console",):
|
||||
return cls.Console
|
||||
if value.lower() in ("telegram",):
|
||||
return cls.Telegram
|
||||
raise InvalidOption(value)
|
||||
|
@ -62,13 +62,6 @@ class Result:
|
||||
"""
|
||||
return list(self._success.values())
|
||||
|
||||
@property
|
||||
def updated(self) -> List[Package]:
|
||||
"""
|
||||
:return: list of updated packages inclding both success and failed
|
||||
"""
|
||||
return self.success + self.failed
|
||||
|
||||
def add_failed(self, package: Package) -> None:
|
||||
"""
|
||||
add new package to failed built
|
||||
|
@ -17,4 +17,4 @@
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
__version__ = "2.0.0rc3"
|
||||
__version__ = "2.0.0rc6"
|
||||
|
@ -87,7 +87,7 @@ def test_fetch_new(mocker: MockerFixture) -> None:
|
||||
local = Path("local")
|
||||
Sources.fetch(local, "remote")
|
||||
check_output_mock.assert_has_calls([
|
||||
mock.call("git", "clone", "remote", str(local), exception=None, logger=pytest.helpers.anyvar(int)),
|
||||
mock.call("git", "clone", "remote", str(local), exception=None, cwd=local, logger=pytest.helpers.anyvar(int)),
|
||||
mock.call("git", "checkout", "--force", Sources._branch,
|
||||
exception=None, cwd=local, logger=pytest.helpers.anyvar(int)),
|
||||
mock.call("git", "reset", "--hard", f"origin/{Sources._branch}",
|
||||
|
@ -53,3 +53,12 @@ def test_report_html(configuration: Configuration, result: Result, mocker: Mocke
|
||||
report_mock = mocker.patch("ahriman.core.report.html.HTML.generate")
|
||||
Report.load("x86_64", configuration, "html").run([], result)
|
||||
report_mock.assert_called_once_with([], result)
|
||||
|
||||
|
||||
def test_report_telegram(configuration: Configuration, result: Result, mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must generate telegram report
|
||||
"""
|
||||
report_mock = mocker.patch("ahriman.core.report.telegram.Telegram.generate")
|
||||
Report.load("x86_64", configuration, "telegram").run([], result)
|
||||
report_mock.assert_called_once_with([], result)
|
||||
|
83
tests/ahriman/core/report/test_telegram.py
Normal file
83
tests/ahriman/core/report/test_telegram.py
Normal file
@ -0,0 +1,83 @@
|
||||
import pytest
|
||||
import requests
|
||||
|
||||
from pytest_mock import MockerFixture
|
||||
from unittest import mock
|
||||
|
||||
from ahriman.core.configuration import Configuration
|
||||
from ahriman.core.report.telegram import Telegram
|
||||
from ahriman.models.package import Package
|
||||
from ahriman.models.result import Result
|
||||
|
||||
|
||||
def test_send(configuration: Configuration, mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must send a message
|
||||
"""
|
||||
request_mock = mocker.patch("requests.post")
|
||||
report = Telegram("x86_64", configuration, "telegram")
|
||||
|
||||
report._send("a text")
|
||||
request_mock.assert_called_once_with(
|
||||
pytest.helpers.anyvar(str, strict=True),
|
||||
data={"chat_id": pytest.helpers.anyvar(str, strict=True), "text": "a text", "parse_mode": "HTML"})
|
||||
|
||||
|
||||
def test_send_failed(configuration: Configuration, mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must reraise generic exception
|
||||
"""
|
||||
mocker.patch("requests.post", side_effect=Exception())
|
||||
report = Telegram("x86_64", configuration, "telegram")
|
||||
|
||||
with pytest.raises(Exception):
|
||||
report._send("a text")
|
||||
|
||||
|
||||
def test_make_request_failed_http_error(configuration: Configuration, mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must reraise http exception
|
||||
"""
|
||||
mocker.patch("requests.post", side_effect=requests.exceptions.HTTPError())
|
||||
report = Telegram("x86_64", configuration, "telegram")
|
||||
|
||||
with pytest.raises(requests.exceptions.HTTPError):
|
||||
report._send("a text")
|
||||
|
||||
|
||||
def test_generate(configuration: Configuration, package_ahriman: Package, result: Result,
|
||||
mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must generate report
|
||||
"""
|
||||
send_mock = mocker.patch("ahriman.core.report.telegram.Telegram._send")
|
||||
|
||||
report = Telegram("x86_64", configuration, "telegram")
|
||||
report.generate([package_ahriman], result)
|
||||
send_mock.assert_called_once_with(pytest.helpers.anyvar(int))
|
||||
|
||||
|
||||
def test_generate_big_text(configuration: Configuration, package_ahriman: Package, result: Result,
|
||||
mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must generate report with big text
|
||||
"""
|
||||
mocker.patch("ahriman.core.report.jinja_template.JinjaTemplate.make_html", return_value="a\n" * 4096)
|
||||
send_mock = mocker.patch("ahriman.core.report.telegram.Telegram._send")
|
||||
|
||||
report = Telegram("x86_64", configuration, "telegram")
|
||||
report.generate([package_ahriman], result)
|
||||
send_mock.assert_has_calls([
|
||||
mock.call(pytest.helpers.anyvar(str, strict=True)), mock.call(pytest.helpers.anyvar(str, strict=True))
|
||||
])
|
||||
|
||||
|
||||
def test_generate_no_empty(configuration: Configuration, package_ahriman: Package, mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must generate report
|
||||
"""
|
||||
send_mock = mocker.patch("ahriman.core.report.telegram.Telegram._send")
|
||||
|
||||
report = Telegram("x86_64", configuration, "telegram")
|
||||
report.generate([package_ahriman], Result())
|
||||
send_mock.assert_not_called()
|
@ -317,6 +317,7 @@ def test_walk(resource_path_root: Path) -> None:
|
||||
resource_path_root / "models" / "package_ahriman_aur",
|
||||
resource_path_root / "models" / "package_akonadi_aur",
|
||||
resource_path_root / "models" / "package_ahriman_srcinfo",
|
||||
resource_path_root / "models" / "package_gcc10_srcinfo",
|
||||
resource_path_root / "models" / "package_tpacpi-bat-git_srcinfo",
|
||||
resource_path_root / "models" / "package_yay_srcinfo",
|
||||
resource_path_root / "web" / "templates" / "build-status" / "login-modal.jinja2",
|
||||
@ -328,6 +329,7 @@ def test_walk(resource_path_root: Path) -> None:
|
||||
resource_path_root / "web" / "templates" / "build-status.jinja2",
|
||||
resource_path_root / "web" / "templates" / "email-index.jinja2",
|
||||
resource_path_root / "web" / "templates" / "repo-index.jinja2",
|
||||
resource_path_root / "web" / "templates" / "telegram-index.jinja2",
|
||||
])
|
||||
local_files = list(sorted(walk(resource_path_root)))
|
||||
assert local_files == expected
|
||||
|
@ -261,6 +261,16 @@ def test_dependencies_with_version(mocker: MockerFixture, resource_path_root: Pa
|
||||
assert Package.dependencies(Path("path")) == {"git", "go", "pacman"}
|
||||
|
||||
|
||||
def test_dependencies_with_version_and_overlap(mocker: MockerFixture, resource_path_root: Path) -> None:
|
||||
"""
|
||||
must load correct list of dependencies with version
|
||||
"""
|
||||
srcinfo = (resource_path_root / "models" / "package_gcc10_srcinfo").read_text()
|
||||
mocker.patch("pathlib.Path.read_text", return_value=srcinfo)
|
||||
|
||||
assert Package.dependencies(Path("path")) == {"glibc", "doxygen", "binutils", "git", "libmpc", "python", "zstd"}
|
||||
|
||||
|
||||
def test_actual_version(package_ahriman: Package, repository_paths: RepositoryPaths) -> None:
|
||||
"""
|
||||
must return same actual_version as version is
|
||||
|
@ -24,3 +24,6 @@ def test_from_option_valid() -> None:
|
||||
|
||||
assert ReportSettings.from_option("console") == ReportSettings.Console
|
||||
assert ReportSettings.from_option("conSOle") == ReportSettings.Console
|
||||
|
||||
assert ReportSettings.from_option("telegram") == ReportSettings.Telegram
|
||||
assert ReportSettings.from_option("TElegraM") == ReportSettings.Telegram
|
||||
|
@ -52,6 +52,13 @@ homepage =
|
||||
link_path =
|
||||
template_path = ../web/templates/repo-index.jinja2
|
||||
|
||||
[telegram]
|
||||
api_key = apikey
|
||||
chat_id = @ahrimantestchat
|
||||
homepage =
|
||||
link_path =
|
||||
template_path = ../web/templates/telegram-index.jinja2
|
||||
|
||||
[upload]
|
||||
target =
|
||||
|
||||
|
57
tests/testresources/models/package_gcc10_srcinfo
Normal file
57
tests/testresources/models/package_gcc10_srcinfo
Normal file
@ -0,0 +1,57 @@
|
||||
pkgbase = gcc10
|
||||
pkgdesc = The GNU Compiler Collection (10.x.x)
|
||||
pkgver = 10.3.0
|
||||
pkgrel = 2
|
||||
url = https://gcc.gnu.org
|
||||
arch = x86_64
|
||||
license = GPL
|
||||
license = LGPL
|
||||
license = FDL
|
||||
license = custom
|
||||
checkdepends = dejagnu
|
||||
checkdepends = inetutils
|
||||
makedepends = binutils
|
||||
makedepends = doxygen
|
||||
makedepends = git
|
||||
makedepends = libmpc
|
||||
makedepends = python
|
||||
options = !emptydirs
|
||||
options = !lto
|
||||
source = https://sourceware.org/pub/gcc/releases/gcc-10.3.0/gcc-10.3.0.tar.xz
|
||||
source = https://sourceware.org/pub/gcc/releases/gcc-10.3.0/gcc-10.3.0.tar.xz.sig
|
||||
source = https://mirror.sobukus.de/files/src/isl/isl-0.24.tar.xz
|
||||
source = c89
|
||||
source = c99
|
||||
validpgpkeys = F3691687D867B81B51CE07D9BBE43771487328A9
|
||||
validpgpkeys = 86CFFCA918CF3AF47147588051E8B148A9999C34
|
||||
validpgpkeys = 13975A70E63C361C73AE69EF6EEB81F8981C74C7
|
||||
validpgpkeys = D3A93CAD751C2AF4F8C7AD516C35B99309B5FA62
|
||||
b2sums = ac7898f5eb8a7c5f151a526d1bb38913a68b50a65e4d010ac09fa20b6c801c671c790d780f23ccb8e4ecdfc686f4aa588082ccc9eb5c80c7b0e30788f824c1eb
|
||||
b2sums = SKIP
|
||||
b2sums = 39cbfd18ad05778e3a5a44429261b45e4abc3efe7730ee890674d968890fe5e52c73bc1f8d271c7c3bc72d5754e3f7fcb209bd139e823d19cb9ea4ce1440164d
|
||||
b2sums = a76d19c7830b0a141302890522086fc1548c177611501caac7e66d576e541b64ca3f6e977de715268a9872dfdd6368a011b92e01f7944ec0088f899ac0d2a2a5
|
||||
b2sums = 02b655b5668f7dea51c3b3e4ff46d5a4aee5a04ed5e26b98a6470f39c2e98ddc0519bffeeedd982c31ef3c171457e4d1beaff32767d1aedd9346837aac4ec3ee
|
||||
|
||||
pkgname = gcc10
|
||||
pkgdesc = The GNU Compiler Collection - C and C++ frontends (10.x.x)
|
||||
depends = gcc10-libs=10.3.0-2
|
||||
depends = binutils>=2.28
|
||||
depends = libmpc
|
||||
depends = zstd
|
||||
options = !emptydirs
|
||||
options = staticlibs
|
||||
|
||||
pkgname = gcc10-libs
|
||||
pkgdesc = Runtime libraries shipped by GCC (10.x.x)
|
||||
depends = glibc>=2.27
|
||||
provides = libgfortran.so
|
||||
provides = libubsan.so
|
||||
provides = libasan.so
|
||||
provides = libtsan.so
|
||||
provides = liblsan.so
|
||||
options = !emptydirs
|
||||
options = !strip
|
||||
|
||||
pkgname = gcc10-fortran
|
||||
pkgdesc = Fortran front-end for GCC (10.x.x)
|
||||
depends = gcc10=10.3.0-2
|
Reference in New Issue
Block a user