mirror of
https://github.com/arcan1s/ahriman.git
synced 2025-04-23 23:07:17 +00:00
handle service status
This commit is contained in:
parent
3e0b3cdbaa
commit
374b3febc8
@ -23,7 +23,7 @@ optdepends=('aws-cli: sync to s3'
|
||||
source=("https://github.com/arcan1s/ahriman/releases/download/$pkgver/$pkgname-$pkgver-src.tar.xz"
|
||||
'ahriman.sysusers'
|
||||
'ahriman.tmpfiles')
|
||||
sha512sums=('d35053c7a52e5cc2dd8a3dca6c9d9f18788296d0059683b16238a54318a74fb4c42544d0727460250e7d0f0ce1009aca96e88d3e52e6bdffab8d45e5e4901b7b'
|
||||
sha512sums=('46f75bb230b7810d0459b58ea3956da45bfbc448cb3119982eacb78b6c24c252a99d29f7785922c0f8e715f16c09b3bdedb50cd0bd0962fff471fe0c6cc2626b'
|
||||
'13718afec2c6786a18f0b223ef8e58dccf0688bca4cdbe203f14071f5031ed20120eb0ce38b52c76cfd6e8b6581a9c9eaa2743eb11abbaca637451a84c33f075'
|
||||
'55b20f6da3d66e7bbf2add5d95a3b60632df121717d25a993e56e737d14f51fe063eb6f1b38bd81cc32e05db01c0c1d80aaa720c45cde87f238d8b46cdb8cbc4')
|
||||
backup=('etc/ahriman.ini'
|
||||
|
@ -11,7 +11,7 @@
|
||||
|
||||
<body>
|
||||
<div class="root">
|
||||
<h1>ahriman {{ version|e }} ({{ architecture|e }})</h1>
|
||||
<h1>ahriman {{ version|e }} ({{ architecture|e }})<sup class="service-{{ service.status|e }}" title="{{ service.timestamp }}">{{ service.status|e }}</sup></h1>
|
||||
|
||||
{% include "search-line.jinja2" %}
|
||||
|
||||
|
@ -75,4 +75,26 @@
|
||||
td.package-success {
|
||||
background-color: rgba(var(--color-success), 1.0);
|
||||
}
|
||||
|
||||
sup.service-unknown {
|
||||
font-weight: lighter;
|
||||
background-color: rgba(var(--color-unknown), 1.0);
|
||||
}
|
||||
sup.service-building {
|
||||
font-weight: lighter;
|
||||
background-color: rgba(var(--color-building), 1.0);
|
||||
animation-name: blink-building;
|
||||
animation-duration: 1s;
|
||||
animation-timing-function: linear;
|
||||
animation-iteration-count: infinite;
|
||||
animation-direction: alternate;
|
||||
}
|
||||
sup.service-failed {
|
||||
font-weight: lighter;
|
||||
background-color: rgba(var(--color-failed), 1.0);
|
||||
}
|
||||
sup.service-success {
|
||||
font-weight: lighter;
|
||||
background-color: rgba(var(--color-success), 1.0);
|
||||
}
|
||||
</style>
|
@ -26,58 +26,64 @@ from ahriman.application.lock import Lock
|
||||
from ahriman.core.configuration import Configuration
|
||||
|
||||
|
||||
def add(args: argparse.Namespace) -> None:
|
||||
def add(args: argparse.Namespace, config: Configuration) -> None:
|
||||
'''
|
||||
add packages callback
|
||||
:param args: command line args
|
||||
:param config: configuration instance
|
||||
'''
|
||||
Application.from_args(args).add(args.package, args.without_dependencies)
|
||||
Application.from_args(args, config).add(args.package, args.without_dependencies)
|
||||
|
||||
|
||||
def rebuild(args: argparse.Namespace) -> None:
|
||||
def rebuild(args: argparse.Namespace, config: Configuration) -> None:
|
||||
'''
|
||||
world rebuild callback
|
||||
:param args: command line args
|
||||
:param config: configuration instance
|
||||
'''
|
||||
app = Application.from_args(args)
|
||||
app = Application.from_args(args, config)
|
||||
packages = app.repository.packages()
|
||||
app.update(packages)
|
||||
|
||||
|
||||
def remove(args: argparse.Namespace) -> None:
|
||||
def remove(args: argparse.Namespace, config: Configuration) -> None:
|
||||
'''
|
||||
remove packages callback
|
||||
:param args: command line args
|
||||
:param config: configuration instance
|
||||
'''
|
||||
Application.from_args(args).remove(args.package)
|
||||
Application.from_args(args, config).remove(args.package)
|
||||
|
||||
|
||||
def report(args: argparse.Namespace) -> None:
|
||||
def report(args: argparse.Namespace, config: Configuration) -> None:
|
||||
'''
|
||||
generate report callback
|
||||
:param args: command line args
|
||||
:param config: configuration instance
|
||||
'''
|
||||
Application.from_args(args).report(args.target)
|
||||
Application.from_args(args, config).report(args.target)
|
||||
|
||||
|
||||
def sync(args: argparse.Namespace) -> None:
|
||||
def sync(args: argparse.Namespace, config: Configuration) -> None:
|
||||
'''
|
||||
sync to remote server callback
|
||||
:param args: command line args
|
||||
:param config: configuration instance
|
||||
'''
|
||||
Application.from_args(args).sync(args.target)
|
||||
Application.from_args(args, config).sync(args.target)
|
||||
|
||||
|
||||
def update(args: argparse.Namespace) -> None:
|
||||
def update(args: argparse.Namespace, config: Configuration) -> None:
|
||||
'''
|
||||
update packages callback
|
||||
:param args: command line args
|
||||
:param config: configuration instance
|
||||
'''
|
||||
# typing workaround
|
||||
def log_fn(line: str) -> None:
|
||||
return print(line) if args.dry_run else app.logger.info(line)
|
||||
|
||||
app = Application.from_args(args)
|
||||
app = Application.from_args(args, config)
|
||||
packages = app.get_updates(args.package, args.no_aur, args.no_manual, args.no_vcs, log_fn)
|
||||
if args.dry_run:
|
||||
return
|
||||
@ -85,13 +91,13 @@ def update(args: argparse.Namespace) -> None:
|
||||
app.update(packages)
|
||||
|
||||
|
||||
def web(args: argparse.Namespace) -> None:
|
||||
def web(args: argparse.Namespace, config: Configuration) -> None:
|
||||
'''
|
||||
web server callback
|
||||
:param args: command line args
|
||||
:param config: configuration instance
|
||||
'''
|
||||
from ahriman.web.web import run_server, setup_service
|
||||
config = Configuration.from_path(args.config)
|
||||
app = setup_service(args.architecture, config)
|
||||
run_server(app, args.architecture)
|
||||
|
||||
@ -146,5 +152,6 @@ if __name__ == '__main__':
|
||||
parser.print_help()
|
||||
exit(1)
|
||||
|
||||
with Lock(args.lock, args.architecture, args.force):
|
||||
args.fn(args)
|
||||
config = Configuration.from_path(args.config)
|
||||
with Lock(args.lock, args.architecture, args.force, config):
|
||||
args.fn(args, config)
|
||||
|
@ -54,13 +54,13 @@ class Application:
|
||||
self.repository = Repository(architecture, config)
|
||||
|
||||
@classmethod
|
||||
def from_args(cls: Type[Application], args: argparse.Namespace) -> Application:
|
||||
def from_args(cls: Type[Application], args: argparse.Namespace, config: Configuration) -> Application:
|
||||
'''
|
||||
constructor which has to be used to build instance from command line args
|
||||
:param args: command line args
|
||||
:param config: configuration instance
|
||||
:return: application instance
|
||||
'''
|
||||
config = Configuration.from_path(args.config)
|
||||
return cls(args.architecture, config)
|
||||
|
||||
def _known_packages(self) -> Set[str]:
|
||||
|
@ -24,7 +24,10 @@ import os
|
||||
from types import TracebackType
|
||||
from typing import Literal, Optional, Type
|
||||
|
||||
from ahriman.core.configuration import Configuration
|
||||
from ahriman.core.exceptions import DuplicateRun
|
||||
from ahriman.core.watcher.client import Client
|
||||
from ahriman.models.build_status import BuildStatusEnum
|
||||
|
||||
|
||||
class Lock:
|
||||
@ -32,29 +35,36 @@ class Lock:
|
||||
wrapper for application lock file
|
||||
:ivar force: remove lock file on start if any
|
||||
:ivar path: path to lock file if any
|
||||
:ivar reporter: build status reporter instance
|
||||
'''
|
||||
|
||||
def __init__(self, path: Optional[str], architecture: str, force: bool) -> None:
|
||||
def __init__(self, path: Optional[str], architecture: str, force: bool, config: Configuration) -> None:
|
||||
'''
|
||||
default constructor
|
||||
:param path: optional path to lock file, if empty no file lock will be used
|
||||
:param architecture: repository architecture
|
||||
:param force: remove lock file on start if any
|
||||
:param config: configuration instance
|
||||
'''
|
||||
self.path = f'{path}_{architecture}' if path is not None else None
|
||||
self.force = force
|
||||
|
||||
self.reporter = Client.load(architecture, config)
|
||||
|
||||
def __enter__(self) -> Lock:
|
||||
'''
|
||||
default workflow is the following
|
||||
* remove lock file if force flag is set
|
||||
* check if there is lock file
|
||||
* create lock file
|
||||
default workflow is the following:
|
||||
|
||||
remove lock file if force flag is set
|
||||
check if there is lock file
|
||||
create lock file
|
||||
report to web if enabled
|
||||
'''
|
||||
if self.force:
|
||||
self.remove()
|
||||
self.check()
|
||||
self.create()
|
||||
self.reporter.update_self(BuildStatusEnum.Building)
|
||||
return self
|
||||
|
||||
def __exit__(self, exc_type: Optional[Type[Exception]], exc_val: Optional[Exception],
|
||||
@ -67,6 +77,8 @@ class Lock:
|
||||
:return: always False (do not suppress any exception)
|
||||
'''
|
||||
self.remove()
|
||||
status = BuildStatusEnum.Success if exc_val is None else BuildStatusEnum.Failed
|
||||
self.reporter.update_self(status)
|
||||
return False
|
||||
|
||||
def check(self) -> None:
|
||||
|
@ -52,6 +52,13 @@ class Client:
|
||||
'''
|
||||
pass
|
||||
|
||||
def update_self(self, status: BuildStatusEnum) -> None:
|
||||
'''
|
||||
update ahriman status itself
|
||||
:param status: current ahriman status
|
||||
'''
|
||||
pass
|
||||
|
||||
def set_building(self, base: str) -> None:
|
||||
'''
|
||||
set package status to building
|
||||
|
@ -43,6 +43,7 @@ class Watcher:
|
||||
self.repository = Repository(architecture, config)
|
||||
|
||||
self.known: Dict[str, Tuple[Package, BuildStatus]] = {}
|
||||
self.status = BuildStatus()
|
||||
|
||||
@property
|
||||
def packages(self) -> List[Tuple[Package, BuildStatus]]:
|
||||
@ -82,3 +83,10 @@ class Watcher:
|
||||
package, _ = self.known[base]
|
||||
full_status = BuildStatus(status)
|
||||
self.known[base] = (package, full_status)
|
||||
|
||||
def update_self(self, status: BuildStatusEnum) -> None:
|
||||
'''
|
||||
update service status
|
||||
:param status: new service status
|
||||
'''
|
||||
self.status = BuildStatus(status)
|
||||
|
@ -46,7 +46,14 @@ class WebClient(Client):
|
||||
self.host = host
|
||||
self.port = port
|
||||
|
||||
def _url(self, base: str) -> str:
|
||||
def _ahriman_url(self) -> str:
|
||||
'''
|
||||
url generator
|
||||
:return: full url for web service for ahriman service itself
|
||||
'''
|
||||
return f'http://{self.host}:{self.port}/api/v1/ahriman'
|
||||
|
||||
def _package_url(self, base: str) -> str:
|
||||
'''
|
||||
url generator
|
||||
:param base: package base to generate url
|
||||
@ -66,7 +73,7 @@ class WebClient(Client):
|
||||
}
|
||||
|
||||
try:
|
||||
response = requests.post(self._url(package.base), json=payload)
|
||||
response = requests.post(self._package_url(package.base), json=payload)
|
||||
response.raise_for_status()
|
||||
except Exception:
|
||||
self.logger.exception(f'could not add {package.base}', exc_info=True)
|
||||
@ -77,7 +84,7 @@ class WebClient(Client):
|
||||
:param base: basename to remove
|
||||
'''
|
||||
try:
|
||||
response = requests.delete(self._url(base))
|
||||
response = requests.delete(self._package_url(base))
|
||||
response.raise_for_status()
|
||||
except Exception:
|
||||
self.logger.exception(f'could not delete {base}', exc_info=True)
|
||||
@ -91,7 +98,20 @@ class WebClient(Client):
|
||||
payload: Dict[str, Any] = {'status': status.value}
|
||||
|
||||
try:
|
||||
response = requests.post(self._url(base), json=payload)
|
||||
response = requests.post(self._package_url(base), json=payload)
|
||||
response.raise_for_status()
|
||||
except Exception:
|
||||
self.logger.exception(f'could not update {base}', exc_info=True)
|
||||
|
||||
def update_self(self, status: BuildStatusEnum) -> None:
|
||||
'''
|
||||
update ahriman status itself
|
||||
:param status: current ahriman status
|
||||
'''
|
||||
payload: Dict[str, Any] = {'status': status.value}
|
||||
|
||||
try:
|
||||
response = requests.post(self._ahriman_url(), json=payload)
|
||||
response.raise_for_status()
|
||||
except Exception:
|
||||
self.logger.exception(f'could not update service status', exc_info=True)
|
||||
|
@ -33,6 +33,8 @@ def setup_routes(application: Application) -> None:
|
||||
GET / get build status page
|
||||
GET /index.html same as above
|
||||
|
||||
POST /api/v1/ahriman update service status
|
||||
|
||||
POST /api/v1/packages force update every package from repository
|
||||
|
||||
POST /api/v1/package/:base update package base status
|
||||
|
51
src/ahriman/web/views/ahriman.py
Normal file
51
src/ahriman/web/views/ahriman.py
Normal file
@ -0,0 +1,51 @@
|
||||
#
|
||||
# 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/>.
|
||||
#
|
||||
from aiohttp.web import HTTPBadRequest, HTTPOk, Response
|
||||
|
||||
from ahriman.models.build_status import BuildStatusEnum
|
||||
from ahriman.web.views.base import BaseView
|
||||
|
||||
|
||||
class AhrimanView(BaseView):
|
||||
'''
|
||||
service status web view
|
||||
'''
|
||||
|
||||
async def post(self) -> Response:
|
||||
'''
|
||||
update service status
|
||||
|
||||
JSON body must be supplied, the following model is used:
|
||||
{
|
||||
"status": "unknown", # service status string, must be valid `BuildStatusEnum`
|
||||
}
|
||||
|
||||
:return: 200 on success
|
||||
'''
|
||||
data = await self.request.json()
|
||||
|
||||
try:
|
||||
status = BuildStatusEnum(data['status'])
|
||||
except Exception as e:
|
||||
raise HTTPBadRequest(text=str(e))
|
||||
|
||||
self.service.update_self(status)
|
||||
|
||||
return HTTPOk()
|
@ -36,6 +36,7 @@ class IndexView(BaseView):
|
||||
packages - sorted list of packages properties: base, packages (sorted list), status,
|
||||
timestamp, version, web_url. Required
|
||||
repository - repository name, string, required
|
||||
service - service status properties: status, timestamp. Required
|
||||
version - ahriman version, string, required
|
||||
'''
|
||||
|
||||
@ -56,10 +57,15 @@ class IndexView(BaseView):
|
||||
'web_url': package.web_url
|
||||
} for package, status in sorted(self.service.packages, key=lambda item: item[0].base)
|
||||
]
|
||||
service = {
|
||||
'status': self.service.status.status.value,
|
||||
'timestamp': self.service.status.timestamp
|
||||
}
|
||||
|
||||
return {
|
||||
'architecture': self.service.architecture,
|
||||
'packages': packages,
|
||||
'repository': self.service.repository.name,
|
||||
'service': service,
|
||||
'version': version.__version__,
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user