add status command

This commit is contained in:
Evgenii Alekseev 2021-03-20 22:20:47 +03:00
parent 3d74b1485a
commit 15e3d2500c
16 changed files with 305 additions and 145 deletions

View File

@ -23,9 +23,9 @@ import sys
from multiprocessing import Pool
import ahriman.application.functions as functions
import ahriman.version as version
from ahriman.application.application import Application
from ahriman.application.lock import Lock
from ahriman.core.configuration import Configuration
@ -47,118 +47,6 @@ def _call(args: argparse.Namespace, architecture: str, config: Configuration) ->
return False
def add(args: argparse.Namespace, architecture: str, config: Configuration) -> None:
'''
add packages callback
:param args: command line args
:param architecture: repository architecture
:param config: configuration instance
'''
Application(architecture, config).add(args.package, args.without_dependencies)
def clean(args: argparse.Namespace, architecture: str, config: Configuration) -> None:
'''
clean caches callback
:param args: command line args
:param architecture: repository architecture
:param config: configuration instance
'''
Application(architecture, config).clean(args.no_build, args.no_cache, args.no_chroot,
args.no_manual, args.no_packages)
def dump_config(args: argparse.Namespace, architecture: str, config: Configuration) -> None:
'''
configuration dump callback
:param args: command line args
:param architecture: repository architecture
:param config: configuration instance
'''
del args
config_dump = config.dump(architecture)
for section, values in sorted(config_dump.items()):
print(f'[{section}]')
for key, value in sorted(values.items()):
print(f'{key} = {value}')
print()
def rebuild(args: argparse.Namespace, architecture: str, config: Configuration) -> None:
'''
world rebuild callback
:param args: command line args
:param architecture: repository architecture
:param config: configuration instance
'''
del args
app = Application(architecture, config)
packages = app.repository.packages()
app.update(packages)
def remove(args: argparse.Namespace, architecture: str, config: Configuration) -> None:
'''
remove packages callback
:param args: command line args
:param architecture: repository architecture
:param config: configuration instance
'''
Application(architecture, config).remove(args.package)
def report(args: argparse.Namespace, architecture: str, config: Configuration) -> None:
'''
generate report callback
:param args: command line args
:param architecture: repository architecture
:param config: configuration instance
'''
Application(architecture, config).report(args.target)
def sync(args: argparse.Namespace, architecture: str, config: Configuration) -> None:
'''
sync to remote server callback
:param args: command line args
:param architecture: repository architecture
:param config: configuration instance
'''
Application(architecture, config).sync(args.target)
def update(args: argparse.Namespace, architecture: str, config: Configuration) -> None:
'''
update packages callback
:param args: command line args
:param architecture: repository architecture
:param config: configuration instance
'''
# typing workaround
def log_fn(line: str) -> None:
return print(line) if args.dry_run else application.logger.info(line)
application = Application(architecture, config)
packages = application.get_updates(args.package, args.no_aur, args.no_manual, args.no_vcs, log_fn)
if args.dry_run:
return
application.update(packages)
def web(args: argparse.Namespace, architecture: str, config: Configuration) -> None:
'''
web server callback
:param args: command line args
:param architecture: repository architecture
:param config: configuration instance
'''
del args
from ahriman.web.web import run_server, setup_service
application = setup_service(architecture, config)
run_server(application, architecture)
if __name__ == '__main__':
parser = argparse.ArgumentParser(prog='ahriman', description='ArcHlinux ReposItory MANager')
parser.add_argument(
@ -176,14 +64,14 @@ if __name__ == '__main__':
subparsers = parser.add_subparsers(title='command')
add_parser = subparsers.add_parser('add', description='add package')
add_parser.add_argument('package', help='package name or archive path', nargs='+')
add_parser.add_argument('package', help='package base/name or archive path', nargs='+')
add_parser.add_argument('--without-dependencies', help='do not add dependencies', action='store_true')
add_parser.set_defaults(fn=add)
add_parser.set_defaults(fn=functions.add)
check_parser = subparsers.add_parser('check', description='check for updates. Same as update --dry-run --no-manual')
check_parser.add_argument('package', help='filter check by packages', nargs='*')
check_parser.add_argument('package', help='filter check by package base', nargs='*')
check_parser.add_argument('--no-vcs', help='do not check VCS packages', action='store_true')
check_parser.set_defaults(fn=update, no_aur=False, no_manual=True, dry_run=True)
check_parser.set_defaults(fn=functions.update, no_aur=False, no_manual=True, dry_run=True)
clean_parser = subparsers.add_parser('clean', description='clear all local caches')
clean_parser.add_argument('--no-build', help='do not clear directory with package sources', action='store_true')
@ -194,37 +82,42 @@ if __name__ == '__main__':
help='do not clear directory with manually added packages',
action='store_true')
clean_parser.add_argument('--no-packages', help='do not clear directory with built packages', action='store_true')
clean_parser.set_defaults(fn=clean)
clean_parser.set_defaults(fn=functions.clean)
config_parser = subparsers.add_parser('config', description='dump configuration for specified architecture')
config_parser.set_defaults(fn=dump_config)
config_parser.set_defaults(fn=functions.dump_config, lock=None, no_report=True)
rebuild_parser = subparsers.add_parser('rebuild', description='rebuild whole repository')
rebuild_parser.set_defaults(fn=rebuild)
rebuild_parser.set_defaults(fn=functions.rebuild)
remove_parser = subparsers.add_parser('remove', description='remove package')
remove_parser.add_argument('package', help='package name', nargs='+')
remove_parser.set_defaults(fn=remove)
remove_parser.add_argument('package', help='package name or base', nargs='+')
remove_parser.set_defaults(fn=functions.remove)
report_parser = subparsers.add_parser('report', description='generate report')
report_parser.add_argument('target', help='target to generate report', nargs='*')
report_parser.set_defaults(fn=report)
report_parser.set_defaults(fn=functions.report)
status_parser = subparsers.add_parser('status', description='request status of the package')
status_parser.add_argument('--ahriman', help='get service status itself', action='store_true')
status_parser.add_argument('package', help='filter status by package base', nargs='*')
status_parser.set_defaults(fn=functions.status, lock=None, no_report=True)
sync_parser = subparsers.add_parser('sync', description='sync packages to remote server')
sync_parser.add_argument('target', help='target to sync', nargs='*')
sync_parser.set_defaults(fn=sync)
sync_parser.set_defaults(fn=functions.sync)
update_parser = subparsers.add_parser('update', description='run updates')
update_parser.add_argument('package', help='filter check by packages', nargs='*')
update_parser.add_argument('package', help='filter check by package base', nargs='*')
update_parser.add_argument(
'--dry-run', help='just perform check for updates, same as check command', action='store_true')
update_parser.add_argument('--no-aur', help='do not check for AUR updates. Implies --no-vcs', action='store_true')
update_parser.add_argument('--no-manual', help='do not include manual updates', action='store_true')
update_parser.add_argument('--no-vcs', help='do not check VCS packages', action='store_true')
update_parser.set_defaults(fn=update)
update_parser.set_defaults(fn=functions.update)
web_parser = subparsers.add_parser('web', description='start web server')
web_parser.set_defaults(fn=web, lock=None, no_report=True)
web_parser.set_defaults(fn=functions.web, lock=None, no_report=True)
cmd_args = parser.parse_args()
if 'fn' not in cmd_args:

View File

@ -25,7 +25,7 @@ from typing import Callable, Iterable, List, Optional, Set
from ahriman.core.build_tools.task import Task
from ahriman.core.configuration import Configuration
from ahriman.repository.repository import Repository
from ahriman.core.repository.repository import Repository
from ahriman.core.tree import Tree
from ahriman.models.package import Package

View File

@ -0,0 +1,163 @@
#
# Copyright (c) 2021 Evgenii Alekseev.
#
# 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/>.
#
import argparse
from typing import Iterable, Tuple
from ahriman.application.application import Application
from ahriman.core.configuration import Configuration
from ahriman.models.build_status import BuildStatus
from ahriman.models.package import Package
def add(args: argparse.Namespace, architecture: str, config: Configuration) -> None:
'''
add packages callback
:param args: command line args
:param architecture: repository architecture
:param config: configuration instance
'''
Application(architecture, config).add(args.package, args.without_dependencies)
def clean(args: argparse.Namespace, architecture: str, config: Configuration) -> None:
'''
clean caches callback
:param args: command line args
:param architecture: repository architecture
:param config: configuration instance
'''
Application(architecture, config).clean(args.no_build, args.no_cache, args.no_chroot,
args.no_manual, args.no_packages)
def dump_config(args: argparse.Namespace, architecture: str, config: Configuration) -> None:
'''
configuration dump callback
:param args: command line args
:param architecture: repository architecture
:param config: configuration instance
'''
del args
config_dump = config.dump(architecture)
for section, values in sorted(config_dump.items()):
print(f'[{section}]')
for key, value in sorted(values.items()):
print(f'{key} = {value}')
print()
def rebuild(args: argparse.Namespace, architecture: str, config: Configuration) -> None:
'''
world rebuild callback
:param args: command line args
:param architecture: repository architecture
:param config: configuration instance
'''
del args
app = Application(architecture, config)
packages = app.repository.packages()
app.update(packages)
def remove(args: argparse.Namespace, architecture: str, config: Configuration) -> None:
'''
remove packages callback
:param args: command line args
:param architecture: repository architecture
:param config: configuration instance
'''
Application(architecture, config).remove(args.package)
def report(args: argparse.Namespace, architecture: str, config: Configuration) -> None:
'''
generate report callback
:param args: command line args
:param architecture: repository architecture
:param config: configuration instance
'''
Application(architecture, config).report(args.target)
def status(args: argparse.Namespace, architecture: str, config: Configuration) -> None:
'''
package status callback
:param args: command line args
:param architecture: repository architecture
:param config: configuration instance
'''
application = Application(architecture, config)
if args.ahriman:
ahriman = application.repository.reporter.get_self()
print(ahriman.pretty_print())
print()
if args.package:
packages: Iterable[Tuple[Package, BuildStatus]] = sum(
[application.repository.reporter.get(base) for base in args.package],
start=[])
else:
packages = application.repository.reporter.get(None)
for package, package_status in sorted(packages, key=lambda item: item[0].base):
print(package.pretty_print())
print(f'\t{package.version}')
print(f'\t{package_status.pretty_print()}')
def sync(args: argparse.Namespace, architecture: str, config: Configuration) -> None:
'''
sync to remote server callback
:param args: command line args
:param architecture: repository architecture
:param config: configuration instance
'''
Application(architecture, config).sync(args.target)
def update(args: argparse.Namespace, architecture: str, config: Configuration) -> None:
'''
update packages callback
:param args: command line args
:param architecture: repository architecture
:param config: configuration instance
'''
# typing workaround
def log_fn(line: str) -> None:
return print(line) if args.dry_run else application.logger.info(line)
application = Application(architecture, config)
packages = application.get_updates(args.package, args.no_aur, args.no_manual, args.no_vcs, log_fn)
if args.dry_run:
return
application.update(packages)
def web(args: argparse.Namespace, architecture: str, config: Configuration) -> None:
'''
web server callback
:param args: command line args
:param architecture: repository architecture
:param config: configuration instance
'''
del args
from ahriman.web.web import run_server, setup_service
application = setup_service(architecture, config)
run_server(application, architecture)

View File

@ -22,7 +22,7 @@ import shutil
from typing import List
from ahriman.repository.properties import Properties
from ahriman.core.repository.properties import Properties
class Cleaner(Properties):

View File

@ -24,9 +24,9 @@ from typing import Dict, Iterable, List, Optional
from ahriman.core.build_tools.task import Task
from ahriman.core.report.report import Report
from ahriman.core.repository.cleaner import Cleaner
from ahriman.core.upload.uploader import Uploader
from ahriman.models.package import Package
from ahriman.repository.cleaner import Cleaner
class Executor(Cleaner):

View File

@ -21,10 +21,10 @@ import os
from typing import Dict, List
from ahriman.core.repository.executor import Executor
from ahriman.core.repository.update_handler import UpdateHandler
from ahriman.core.util import package_like
from ahriman.models.package import Package
from ahriman.repository.executor import Executor
from ahriman.repository.update_handler import UpdateHandler
class Repository(Executor, UpdateHandler):

View File

@ -21,8 +21,8 @@ import os
from typing import Iterable, List
from ahriman.core.repository.cleaner import Cleaner
from ahriman.models.package import Package
from ahriman.repository.cleaner import Cleaner
class UpdateHandler(Cleaner):

View File

@ -19,8 +19,10 @@
#
from __future__ import annotations
from typing import List, Optional, Tuple
from ahriman.core.configuration import Configuration
from ahriman.models.build_status import BuildStatusEnum
from ahriman.models.build_status import BuildStatus, BuildStatusEnum
from ahriman.models.package import Package
@ -36,10 +38,28 @@ class Client:
:param status: current package build status
'''
# pylint: disable=R0201
def get(self, base: Optional[str]) -> List[Tuple[Package, BuildStatus]]:
'''
get package status
:param base: package base to get
:return: list of current package description and status if it has been found
'''
del base
return []
# pylint: disable=R0201
def get_self(self) -> BuildStatus:
'''
get ahriman status itself
:return: current ahriman status
'''
return BuildStatus()
def remove(self, base: str) -> None:
'''
remove packages from watcher
:param base: basename to remove
:param base: package base to remove
'''
def update(self, base: str, status: BuildStatusEnum) -> None:

View File

@ -24,7 +24,7 @@ import os
from typing import Any, Dict, List, Optional, Tuple
from ahriman.core.configuration import Configuration
from ahriman.repository.repository import Repository
from ahriman.core.repository.repository import Repository
from ahriman.models.build_status import BuildStatus, BuildStatusEnum
from ahriman.models.package import Package
@ -73,7 +73,7 @@ class Watcher:
'''
def parse_single(properties: Dict[str, Any]) -> None:
package = Package.from_json(properties['package'])
status = BuildStatus(**properties['status'])
status = BuildStatus.from_json(properties['status'])
if package.base in self.known:
self.known[package.base] = (package, status)

View File

@ -18,10 +18,12 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
import logging
from typing import List, Optional, Tuple
import requests
from ahriman.core.watcher.client import Client
from ahriman.models.build_status import BuildStatusEnum
from ahriman.models.build_status import BuildStatusEnum, BuildStatus
from ahriman.models.package import Package
@ -50,7 +52,7 @@ class WebClient(Client):
'''
return f'http://{self.host}:{self.port}/api/v1/ahriman'
def _package_url(self, base: str) -> str:
def _package_url(self, base: str = '') -> str:
'''
url generator
:param base: package base to generate url
@ -77,6 +79,44 @@ class WebClient(Client):
except Exception:
self.logger.exception(f'could not add {package.base}', exc_info=True)
def get(self, base: Optional[str]) -> List[Tuple[Package, BuildStatus]]:
'''
get package status
:param base: package base to get
:return: list of current package description and status if it has been found
'''
try:
response = requests.get(self._package_url(base or ''))
response.raise_for_status()
status_json = response.json()
return [
(Package.from_json(package['package']), BuildStatus.from_json(package['status']))
for package in status_json
]
except requests.exceptions.HTTPError as e:
self.logger.exception(f'could not get {base}: {e.response.text}', exc_info=True)
except Exception:
self.logger.exception(f'could not get {base}', exc_info=True)
return []
def get_self(self) -> BuildStatus:
'''
get ahriman status itself
:return: current ahriman status
'''
try:
response = requests.get(self._ahriman_url())
response.raise_for_status()
status_json = response.json()
return BuildStatus.from_json(status_json)
except requests.exceptions.HTTPError as e:
self.logger.exception(f'could not get service status: {e.response.text}', exc_info=True)
except Exception:
self.logger.exception('could not get service status', exc_info=True)
return BuildStatus()
def remove(self, base: str) -> None:
'''
remove packages from watcher

View File

@ -17,10 +17,14 @@
# 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 __future__ import annotations
import datetime
from enum import Enum
from typing import Any, Dict, Optional, Union
from typing import Any, Dict, Optional, Type, Union
from ahriman.core.util import pretty_datetime
class BuildStatusEnum(Enum):
@ -72,6 +76,22 @@ class BuildStatus:
self.status = BuildStatusEnum(status) if status else BuildStatusEnum.Unknown
self.timestamp = timestamp or int(datetime.datetime.utcnow().timestamp())
@classmethod
def from_json(cls: Type[BuildStatus], dump: Dict[str, Any]) -> BuildStatus:
'''
construct status properties from json dump
:param dump: json dump body
:return: status properties
'''
return cls(dump.get('status'), dump.get('timestamp'))
def pretty_print(self) -> str:
'''
generate pretty string representation
:return: print-friendly string
'''
return f'{self.status.value} ({pretty_datetime(self.timestamp)})'
def view(self) -> Dict[str, Any]:
'''
generate json status view
@ -81,3 +101,10 @@ class BuildStatus:
'status': self.status.value,
'timestamp': self.timestamp
}
def __repr__(self) -> str:
'''
generate string representation of object
:return: unique string representation
'''
return f'BuildStatus(status={self.status.value}, timestamp={self.timestamp})'

View File

@ -57,6 +57,13 @@ class Package:
'''
return f'{self.aur_url}/{self.base}.git'
@property
def is_single_package(self) -> bool:
'''
:return: true in case if this base has only one package with the same name
'''
return self.base in self.packages and len(self.packages) == 1
@property
def is_vcs(self) -> bool:
'''
@ -224,6 +231,14 @@ class Package:
result: int = vercmp(self.version, remote_version)
return result < 0
def pretty_print(self) -> str:
'''
generate pretty string representation
:return: print-friendly string
'''
details = '' if self.is_single_package else f''' ({' '.join(sorted(self.packages.keys()))})'''
return f'{self.base}{details}'
def view(self) -> Dict[str, Any]:
'''
generate json package view

View File

@ -41,7 +41,7 @@ class IndexView(BaseView):
version - ahriman version, string, required
'''
@aiohttp_jinja2.template("build-status.jinja2")
@aiohttp_jinja2.template('build-status.jinja2')
async def get(self) -> Dict[str, Any]:
'''
process get request. No parameters supported here

View File

@ -41,10 +41,12 @@ class PackageView(BaseView):
except KeyError:
raise HTTPNotFound()
response = {
'package': package.view(),
'status': status.view()
}
response = [
{
'package': package.view(),
'status': status.view()
}
]
return json_response(response)
async def delete(self) -> Response: