Compare commits

...

6 Commits

11 changed files with 138 additions and 56 deletions

View File

@ -1,7 +1,7 @@
# Maintainer: Evgeniy Alekseev # Maintainer: Evgeniy Alekseev
pkgname='ahriman' pkgname='ahriman'
pkgver=0.12.0 pkgver=0.12.2
pkgrel=1 pkgrel=1
pkgdesc="ArcHlinux ReposItory MANager" pkgdesc="ArcHlinux ReposItory MANager"
arch=('any') arch=('any')
@ -23,7 +23,7 @@ optdepends=('aws-cli: sync to s3'
source=("https://github.com/arcan1s/ahriman/releases/download/$pkgver/$pkgname-$pkgver-src.tar.xz" source=("https://github.com/arcan1s/ahriman/releases/download/$pkgver/$pkgname-$pkgver-src.tar.xz"
'ahriman.sysusers' 'ahriman.sysusers'
'ahriman.tmpfiles') 'ahriman.tmpfiles')
sha512sums=('8cbb9798329dc273bbb076724693118ff74cef1d05545d0b766b997810de8f9dfee4d13fc3b8c8c60436eae1dad53a4357bd845eaef26cf82fbd43adad6622a2' sha512sums=('8a140f11819a103b50bc8e8477f0d49a2c7468da4fbcaebc9b0519b29d964b9ca04224d171e3a6ecf00e371d09427c45d363e781d2df82cf0c6cbe9d9c829cfe'
'13718afec2c6786a18f0b223ef8e58dccf0688bca4cdbe203f14071f5031ed20120eb0ce38b52c76cfd6e8b6581a9c9eaa2743eb11abbaca637451a84c33f075' '13718afec2c6786a18f0b223ef8e58dccf0688bca4cdbe203f14071f5031ed20120eb0ce38b52c76cfd6e8b6581a9c9eaa2743eb11abbaca637451a84c33f075'
'55b20f6da3d66e7bbf2add5d95a3b60632df121717d25a993e56e737d14f51fe063eb6f1b38bd81cc32e05db01c0c1d80aaa720c45cde87f238d8b46cdb8cbc4') '55b20f6da3d66e7bbf2add5d95a3b60632df121717d25a993e56e737d14f51fe063eb6f1b38bd81cc32e05db01c0c1d80aaa720c45cde87f238d8b46cdb8cbc4')
backup=('etc/ahriman.ini' backup=('etc/ahriman.ini'

View File

@ -11,7 +11,11 @@
<body> <body>
<div class="root"> <div class="root">
<h1>ahriman {{ version|e }} ({{ architecture|e }})<sup class="service-{{ service.status|e }}" title="{{ service.timestamp }}">{{ service.status|e }}</sup></h1> <h1>ahriman
<img src="https://img.shields.io/badge/version-{{ version|e }}-informational" alt="{{ version|e }}">
<img src="https://img.shields.io/badge/architecture-{{ architecture|e }}-informational" alt="{{ architecture|e }}">
<img src="https://img.shields.io/badge/service%20status-{{ service.status|e }}-{{ service.status_color|e }}" alt="{{ service.status|e }}" title="{{ service.timestamp|e }}">
</h1>
{% include "search-line.jinja2" %} {% include "search-line.jinja2" %}
@ -31,11 +35,19 @@
<td class="include-search">{{ package.packages|join("<br>"|safe) }}</td> <td class="include-search">{{ package.packages|join("<br>"|safe) }}</td>
<td>{{ package.version|e }}</td> <td>{{ package.version|e }}</td>
<td>{{ package.timestamp|e }}</td> <td>{{ package.timestamp|e }}</td>
<td class="package-{{ package.status|e }}">{{ package.status|e }}</td> <td class="status package-{{ package.status|e }}">{{ package.status|e }}</td>
</tr> </tr>
{% endfor %} {% endfor %}
</table> </table>
</section> </section>
<footer>
<ul class="navigation">
<li><a href="https://github.com/arcan1s/ahriman" title="sources">ahriman</a></li>
<li><a href="https://github.com/arcan1s/ahriman/releases" title="releases list">releases</a></li>
<li><a href="https://github.com/arcan1s/ahriman/issues" title="issues tracker">report a bug</a></li>
</ul>
</footer>
</div> </div>
</body> </body>

View File

@ -46,9 +46,13 @@
</table> </table>
</section> </section>
{% if homepage is not none %} <footer>
<footer><a href="{{ homepage|e }}" title="homepage">Homepage</a></footer> <ul class="navigation">
{% endif %} {% if homepage is not none %}
<li><a href="{{ homepage|e }}" title="homepage">Homepage</a></li>
{% endif %}
</ul>
</footer>
</div> </div>
</body> </body>
</html> </html>

View File

@ -5,6 +5,11 @@
--color-pending: 255, 255, 146; --color-pending: 255, 255, 146;
--color-success: 94, 255, 94; --color-success: 94, 255, 94;
--color-unknown: 225, 225, 225; --color-unknown: 225, 225, 225;
--color-header: 200, 200, 255;
--color-hover: 255, 255, 225;
--color-line-blue: 235, 235, 255;
--color-line-white: 255, 255, 255;
} }
@keyframes blink-building { @keyframes blink-building {
@ -26,7 +31,7 @@
padding: 15px 15% 0; padding: 15px 15% 0;
} }
section.element { section.element, footer {
width: 100%; width: 100%;
padding: 10px 0; padding: 10px 0;
} }
@ -40,19 +45,23 @@
} }
tr.package:nth-child(odd) { tr.package:nth-child(odd) {
background-color: rgba(255, 255, 255, 1); background-color: rgba(var(--color-line-white), 1.0);
} }
tr.package:nth-child(even) { tr.package:nth-child(even) {
background-color: rgba(235, 235, 255, 1); background-color: rgba(var(--color-line-blue), 1.0);
} }
tr.package:hover { tr.package:hover {
background-color: rgba(255, 255, 225, 1); background-color: rgba(var(--color-hover), 1.0);
} }
tr.header{ tr.header{
background-color: rgba(200, 200, 255, 1); background-color: rgba(var(--color-header), 1.0);
}
td.status {
text-align: center;
} }
td.package-unknown { td.package-unknown {
@ -76,12 +85,10 @@
background-color: rgba(var(--color-success), 1.0); background-color: rgba(var(--color-success), 1.0);
} }
sup.service-unknown { li.service-unknown {
font-weight: lighter;
background-color: rgba(var(--color-unknown), 1.0); background-color: rgba(var(--color-unknown), 1.0);
} }
sup.service-building { li.service-building {
font-weight: lighter;
background-color: rgba(var(--color-building), 1.0); background-color: rgba(var(--color-building), 1.0);
animation-name: blink-building; animation-name: blink-building;
animation-duration: 1s; animation-duration: 1s;
@ -89,12 +96,41 @@
animation-iteration-count: infinite; animation-iteration-count: infinite;
animation-direction: alternate; animation-direction: alternate;
} }
sup.service-failed { li.service-failed {
font-weight: lighter;
background-color: rgba(var(--color-failed), 1.0); background-color: rgba(var(--color-failed), 1.0);
} }
sup.service-success { li.service-success {
font-weight: lighter;
background-color: rgba(var(--color-success), 1.0); background-color: rgba(var(--color-success), 1.0);
} }
ul.navigation {
list-style-type: none;
margin: 0;
padding: 0;
overflow: hidden;
background-color: rgba(var(--color-header), 1.0);
}
ul.navigation li {
float: left;
}
ul.navigation li.status {
display: block;
text-align: center;
text-decoration: none;
padding: 14px 16px;
}
ul.navigation li a {
display: block;
color: black;
text-align: center;
text-decoration: none;
padding: 14px 16px;
}
ul.navigation li a:hover {
background-color: rgba(var(--color-hover), 1.0);
}
</style> </style>

View File

@ -19,6 +19,8 @@
# #
import argparse import argparse
from multiprocessing import Pool
import ahriman.version as version import ahriman.version as version
from ahriman.application.application import Application from ahriman.application.application import Application
@ -26,64 +28,81 @@ from ahriman.application.lock import Lock
from ahriman.core.configuration import Configuration from ahriman.core.configuration import Configuration
def add(args: argparse.Namespace, config: Configuration) -> None: def _call(args: argparse.Namespace, architecture: str, config: Configuration) -> None:
'''
additional function to wrap all calls for multiprocessing library
:param args: command line args
:param architecture: repository architecture
:param config: configuration instance
'''
with Lock(args.lock, architecture, args.force, config):
args.fn(args, architecture, config)
def add(args: argparse.Namespace, architecture: str, config: Configuration) -> None:
''' '''
add packages callback add packages callback
:param args: command line args :param args: command line args
:param architecture: repository architecture
:param config: configuration instance :param config: configuration instance
''' '''
Application.from_args(args, config).add(args.package, args.without_dependencies) Application(architecture, config).add(args.package, args.without_dependencies)
def rebuild(args: argparse.Namespace, config: Configuration) -> None: def rebuild(args: argparse.Namespace, architecture: str, config: Configuration) -> None:
''' '''
world rebuild callback world rebuild callback
:param args: command line args :param args: command line args
:param architecture: repository architecture
:param config: configuration instance :param config: configuration instance
''' '''
app = Application.from_args(args, config) app = Application(architecture, config)
packages = app.repository.packages() packages = app.repository.packages()
app.update(packages) app.update(packages)
def remove(args: argparse.Namespace, config: Configuration) -> None: def remove(args: argparse.Namespace, architecture: str, config: Configuration) -> None:
''' '''
remove packages callback remove packages callback
:param args: command line args :param args: command line args
:param architecture: repository architecture
:param config: configuration instance :param config: configuration instance
''' '''
Application.from_args(args, config).remove(args.package) Application(architecture, config).remove(args.package)
def report(args: argparse.Namespace, config: Configuration) -> None: def report(args: argparse.Namespace, architecture: str, config: Configuration) -> None:
''' '''
generate report callback generate report callback
:param args: command line args :param args: command line args
:param architecture: repository architecture
:param config: configuration instance :param config: configuration instance
''' '''
Application.from_args(args, config).report(args.target) Application(architecture, config).report(args.target)
def sync(args: argparse.Namespace, config: Configuration) -> None: def sync(args: argparse.Namespace, architecture: str, config: Configuration) -> None:
''' '''
sync to remote server callback sync to remote server callback
:param args: command line args :param args: command line args
:param architecture: repository architecture
:param config: configuration instance :param config: configuration instance
''' '''
Application.from_args(args, config).sync(args.target) Application(architecture, config).sync(args.target)
def update(args: argparse.Namespace, config: Configuration) -> None: def update(args: argparse.Namespace, architecture: str, config: Configuration) -> None:
''' '''
update packages callback update packages callback
:param args: command line args :param args: command line args
:param architecture: repository architecture
:param config: configuration instance :param config: configuration instance
''' '''
# typing workaround # typing workaround
def log_fn(line: str) -> None: def log_fn(line: str) -> None:
return print(line) if args.dry_run else app.logger.info(line) return print(line) if args.dry_run else app.logger.info(line)
app = Application.from_args(args, config) app = Application(architecture, config)
packages = app.get_updates(args.package, args.no_aur, args.no_manual, args.no_vcs, log_fn) packages = app.get_updates(args.package, args.no_aur, args.no_manual, args.no_vcs, log_fn)
if args.dry_run: if args.dry_run:
return return
@ -91,20 +110,21 @@ def update(args: argparse.Namespace, config: Configuration) -> None:
app.update(packages) app.update(packages)
def web(args: argparse.Namespace, config: Configuration) -> None: def web(args: argparse.Namespace, architecture: str, config: Configuration) -> None:
''' '''
web server callback web server callback
:param args: command line args :param args: command line args
:param architecture: repository architecture
:param config: configuration instance :param config: configuration instance
''' '''
from ahriman.web.web import run_server, setup_service from ahriman.web.web import run_server, setup_service
app = setup_service(args.architecture, config) app = setup_service(architecture, config)
run_server(app, args.architecture) run_server(app, architecture)
if __name__ == '__main__': if __name__ == '__main__':
parser = argparse.ArgumentParser(prog='ahriman', description='ArcHlinux ReposItory MANager') parser = argparse.ArgumentParser(prog='ahriman', description='ArcHlinux ReposItory MANager')
parser.add_argument('-a', '--architecture', help='target architecture', required=True) parser.add_argument('-a', '--architecture', help='target architectures', action='append')
parser.add_argument('-c', '--config', help='configuration path', default='/etc/ahriman.ini') parser.add_argument('-c', '--config', help='configuration path', default='/etc/ahriman.ini')
parser.add_argument('--force', help='force run, remove file lock', action='store_true') parser.add_argument('--force', help='force run, remove file lock', action='store_true')
parser.add_argument('--lock', help='lock file', default='/tmp/ahriman.lock') parser.add_argument('--lock', help='lock file', default='/tmp/ahriman.lock')
@ -118,7 +138,8 @@ if __name__ == '__main__':
check_parser = subparsers.add_parser('check', description='check for updates') check_parser = subparsers.add_parser('check', description='check for updates')
check_parser.add_argument('package', help='filter check by packages', nargs='*') check_parser.add_argument('package', help='filter check by packages', nargs='*')
check_parser.set_defaults(fn=update, no_aur=False, no_manual=True, no_vcs=False, dry_run=True) 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)
rebuild_parser = subparsers.add_parser('rebuild', description='rebuild whole repository') rebuild_parser = subparsers.add_parser('rebuild', description='rebuild whole repository')
rebuild_parser.set_defaults(fn=rebuild) rebuild_parser.set_defaults(fn=rebuild)
@ -153,5 +174,5 @@ if __name__ == '__main__':
exit(1) exit(1)
config = Configuration.from_path(args.config) config = Configuration.from_path(args.config)
with Lock(args.lock, args.architecture, args.force, config): with Pool(len(args.architecture)) as pool:
args.fn(args, config) pool.starmap(_call, [(args, architecture, config) for architecture in args.architecture])

View File

@ -19,7 +19,6 @@
# #
from __future__ import annotations from __future__ import annotations
import argparse
import logging import logging
import os import os
import shutil import shutil
@ -53,16 +52,6 @@ class Application:
self.architecture = architecture self.architecture = architecture
self.repository = Repository(architecture, config) self.repository = Repository(architecture, config)
@classmethod
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
'''
return cls(args.architecture, config)
def _known_packages(self) -> Set[str]: def _known_packages(self) -> Set[str]:
''' '''
load packages from repository and pacman repositories load packages from repository and pacman repositories

View File

@ -39,6 +39,21 @@ class BuildStatusEnum(Enum):
Failed = 'failed' Failed = 'failed'
Success = 'success' Success = 'success'
def badges_color(self) -> str:
'''
convert itself to shield.io badges color
:return: shields.io color
'''
if self == BuildStatusEnum.Pending:
return 'yellow'
elif self == BuildStatusEnum.Building:
return 'yellow'
elif self == BuildStatusEnum.Failed:
return 'critical'
elif self == BuildStatusEnum.Success:
return 'success'
return 'inactive'
class BuildStatus: class BuildStatus:
''' '''

View File

@ -25,7 +25,7 @@ from dataclasses import dataclass
@dataclass @dataclass
class RepositoryPaths: class RepositoryPaths:
''' '''
repository paths holder repository paths holder. For the most operations with paths you want to use this object
:ivar root: repository root (i.e. ahriman home) :ivar root: repository root (i.e. ahriman home)
:ivar architecture: repository architecture :ivar architecture: repository architecture
''' '''
@ -38,6 +38,7 @@ class RepositoryPaths:
''' '''
:return: directory for devtools chroot :return: directory for devtools chroot
''' '''
# for the chroot directory devtools will create own tree and we don't have to specify architecture here
return os.path.join(self.root, 'chroot') return os.path.join(self.root, 'chroot')
@property @property
@ -45,14 +46,14 @@ class RepositoryPaths:
''' '''
:return: directory for manual updates (i.e. from add command) :return: directory for manual updates (i.e. from add command)
''' '''
return os.path.join(self.root, 'manual') return os.path.join(self.root, 'manual', self.architecture)
@property @property
def packages(self) -> str: def packages(self) -> str:
''' '''
:return: directory for built packages :return: directory for built packages
''' '''
return os.path.join(self.root, 'packages') return os.path.join(self.root, 'packages', self.architecture)
@property @property
def repository(self) -> str: def repository(self) -> str:
@ -66,7 +67,7 @@ class RepositoryPaths:
''' '''
:return: directory for downloaded PKGBUILDs for current build :return: directory for downloaded PKGBUILDs for current build
''' '''
return os.path.join(self.root, 'sources') return os.path.join(self.root, 'sources', self.architecture)
def create_tree(self) -> None: def create_tree(self) -> None:
''' '''

View File

@ -17,4 +17,4 @@
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
# #
__version__ = '0.12.0' __version__ = '0.12.2'

View File

@ -19,6 +19,7 @@
# #
from aiohttp.web import Application from aiohttp.web import Application
from ahriman.web.views.ahriman import AhrimanView
from ahriman.web.views.index import IndexView from ahriman.web.views.index import IndexView
from ahriman.web.views.package import PackageView from ahriman.web.views.package import PackageView
from ahriman.web.views.packages import PackagesView from ahriman.web.views.packages import PackagesView
@ -45,6 +46,8 @@ def setup_routes(application: Application) -> None:
application.router.add_get('/', IndexView) application.router.add_get('/', IndexView)
application.router.add_get('/index.html', IndexView) application.router.add_get('/index.html', IndexView)
application.router.add_post('/api/v1/ahriman', AhrimanView)
application.router.add_post('/api/v1/packages', PackagesView) application.router.add_post('/api/v1/packages', PackagesView)
application.router.add_delete('/api/v1/packages/{package}', PackageView) application.router.add_delete('/api/v1/packages/{package}', PackageView)

View File

@ -36,7 +36,7 @@ class IndexView(BaseView):
packages - sorted list of packages properties: base, packages (sorted list), status, packages - sorted list of packages properties: base, packages (sorted list), status,
timestamp, version, web_url. Required timestamp, version, web_url. Required
repository - repository name, string, required repository - repository name, string, required
service - service status properties: status, timestamp. Required service - service status properties: status, status_color, timestamp. Required
version - ahriman version, string, required version - ahriman version, string, required
''' '''
@ -59,6 +59,7 @@ class IndexView(BaseView):
] ]
service = { service = {
'status': self.service.status.status.value, 'status': self.service.status.status.value,
'status_color': self.service.status.status.badges_color(),
'timestamp': self.service.status.timestamp 'timestamp': self.service.status.timestamp
} }