Compare commits

..

3 Commits

Author SHA1 Message Date
b36bcb194b move argument parsers to handlers themselves 2024-10-05 17:26:06 +03:00
b167df904b build: use tool.flit.external-data to distribute data 2024-10-05 17:17:16 +03:00
cd0ac7a7bd chore: replace passlib with bcrypt
passlib uses deprecated crypt module which is deprecated and scheduled
for removal in 3.13. Unfortunately, this module seems to be
unmaintained, so this commit replaces passlib with bcrypt, unfortunately
breaking current passwords
2024-10-05 16:35:27 +03:00
48 changed files with 742 additions and 747 deletions

View File

@ -10,7 +10,7 @@ echo -e '[arcanisrepo]\nServer = https://repo.arcanis.me/$arch\nSigLevel = Never
# refresh the image
pacman -Syyu --noconfirm
# main dependencies
pacman -S --noconfirm devtools git pyalpm python-inflection python-passlib python-pyelftools python-requests python-systemd sudo
pacman -S --noconfirm devtools git pyalpm python-bcrypt python-inflection python-pyelftools python-requests python-systemd sudo
# make dependencies
pacman -S --noconfirm --asdeps base-devel python-build python-flit python-installer python-tox python-wheel
# optional dependencies

View File

@ -35,8 +35,8 @@ RUN pacman -S --noconfirm --asdeps \
devtools \
git \
pyalpm \
python-bcrypt \
python-inflection \
python-passlib \
python-pyelftools \
python-requests \
&& \

View File

@ -147,7 +147,7 @@ There are multiple subdirectories, some of them are commons for any repository,
* ``pacman/{repository}/{architecture}`` is the repository and architecture specific caches for pacman's databases.
* ``repository/{repository}/{architecture}`` is a repository packages directory.
Normally you should avoid direct interaction with the application tree. For tree migration process refer to the :doc:`migration notes <migration>`.
Normally you should avoid direct interaction with the application tree. For tree migration process refer to the :doc:`migration notes <migrations/index>`.
Database
--------

View File

@ -34,7 +34,7 @@ Contents
configuration
command-line
faq/index
migration
migrations/index
architecture
advanced-usage
triggers

View File

@ -1,25 +1,5 @@
Manual migrations
=================
Normally the most of migrations are handled automatically after application start, however, some upgrades require manual interventions; this document describes them.
Upgrades to breakpoints
-----------------------
To 2.9.0
^^^^^^^^
This release includes major upgrade for the newest devtools and archlinux repository structure. In order to upgrade package need to:
#. Upgrade to the latest major release of python (3.11) (required by other changes).
#. Upgrade devtools to the latest release.
#. Backup local settings, ``/etc/ahriman.ini.d/00-setup-overrides.ini`` by default.
#. Run setup command (i.e. ``ahriman service-setup``) again with the same arguments as used before. This step can be done manually by moving ``devtools`` configuration (something like ``/usr/share/devtools/pacman-ahriman*.conf``) to new location ``/usr/share/devtools/pacman.conf.d/`` under name ``ahriman.conf``. After that make sure to remove any ``community`` mentions from configurations (e.g. ``/usr/share/devtools/pacman.conf.d/ahriman.conf``, ``/etc/ahriman.ini``) if there were any. The only thing which will change is ``devtools`` configuration.
#. Remove build chroot as it is incompatible, e.g. ``sudo ahriman service-clean --chroot``.
#. Run ``sudo -u ahriman ahriman update --no-aur --no-local --no-manual -yy`` in order to update local databases.
To 2.12.0
^^^^^^^^^
---------
This release includes paths migration. Unlike usual case, no automatic migration is performed because it might break user configuration. The following noticeable changes have been made:

View File

@ -0,0 +1,16 @@
To 2.16.0
---------
This release replaces ``passlib`` dependency with ``bcrypt``.
The reason behind this change is that python developers have deprecated and scheduled for removal ``crypt`` module, which is used by ``passlib``. (By the way, they recommend to use ``passlib`` as a replacement.) Unfortunately, it appears that ``passlib`` is unmaintained (see `the issue <https://foss.heptapod.net/python-libs/passlib/-/issues/187>`__), so the only solution is to migrate to anoher library.
Because passwords are stored as hashes, it is near to impossible to shadow change passwords in database, the manual intervention is required if:
#. Authentication is used.
#. Notification provider is ``configuration`` or a user with explicitly set password exists.
Manual steps might look as:
#. Get list of users with their roles ``ahriman user-list``.
#. For each user run update command, i.e. ``ahriman user-add <username> -R <role>``. Type password when it will be requested.

11
docs/migrations/2.9.0.rst Normal file
View File

@ -0,0 +1,11 @@
To 2.9.0
--------
This release includes major upgrade for the newest devtools and archlinux repository structure. In order to upgrade package need to:
#. Upgrade to the latest major release of python (3.11) (required by other changes).
#. Upgrade devtools to the latest release.
#. Backup local settings, ``/etc/ahriman.ini.d/00-setup-overrides.ini`` by default.
#. Run setup command (i.e. ``ahriman service-setup``) again with the same arguments as used before. This step can be done manually by moving ``devtools`` configuration (something like ``/usr/share/devtools/pacman-ahriman*.conf``) to new location ``/usr/share/devtools/pacman.conf.d/`` under name ``ahriman.conf``. After that make sure to remove any ``community`` mentions from configurations (e.g. ``/usr/share/devtools/pacman.conf.d/ahriman.conf``, ``/etc/ahriman.ini``) if there were any. The only thing which will change is ``devtools`` configuration.
#. Remove build chroot as it is incompatible, e.g. ``sudo ahriman service-clean --chroot``.
#. Run ``sudo -u ahriman ahriman update --no-aur --no-local --no-manual -yy`` in order to update local databases.

14
docs/migrations/index.rst Normal file
View File

@ -0,0 +1,14 @@
Manual migrations
=================
Normally the most of migrations are handled automatically after application start, however, some upgrades require manual interventions; this document describes them.
Upgrades to breakpoints
-----------------------
.. toctree::
:maxdepth: 2
2.9.0
2.12.0
2.16.0

View File

@ -7,7 +7,7 @@ pkgdesc="ArcH linux ReposItory MANager"
arch=('any')
url="https://github.com/arcan1s/ahriman"
license=('GPL3')
depends=('devtools>=1:1.0.0' 'git' 'pyalpm' 'python-inflection' 'python-passlib' 'python-pyelftools' 'python-requests')
depends=('devtools>=1:1.0.0' 'git' 'pyalpm' 'python-bcrypt' 'python-inflection' 'python-pyelftools' 'python-requests')
makedepends=('python-build' 'python-flit' 'python-installer' 'python-wheel')
optdepends=('python-aioauth-client: web server with OAuth2 authorization'
'python-aiohttp: web server'
@ -42,9 +42,6 @@ package() {
python -m installer --destdir="$pkgdir" "dist/$pkgname-$pkgver-py3-none-any.whl"
# thanks too PEP517, which we all wanted, you need to install data files manually nowadays
pushd package && find . \( -type f -or -type l \) -exec install -Dm644 "{}" "$pkgdir/usr/{}" \; && popd
# keep usr/share configs as reference and copy them to /etc
install -Dm644 "$pkgdir/usr/share/$pkgname/settings/ahriman.ini" "$pkgdir/etc/ahriman.ini"
install -Dm644 "$pkgdir/usr/share/$pkgname/settings/ahriman.ini.d/logging.ini" "$pkgdir/etc/ahriman.ini.d/logging.ini"
@ -52,7 +49,3 @@ package() {
install -Dm644 "$srcdir/$pkgname.sysusers" "$pkgdir/usr/lib/sysusers.d/$pkgname.conf"
install -Dm644 "$srcdir/$pkgname.tmpfiles" "$pkgdir/usr/lib/tmpfiles.d/$pkgname.conf"
}
sha512sums=('19841842641520b573cdde6cb80a7cfcd69756d323fdfeebc2eee2d264a1325ead4ab2f8383bb369f7896bfc1de59d7358f133f4afeb90a9b9f0695f482a58d0'
'53d37efec812afebf86281716259f9ea78a307b83897166c72777251c3eebcb587ecee375d907514781fb2a5c808cbb24ef9f3f244f12740155d0603bf213131'
'62b2eccc352d33853ef243c9cddd63663014aa97b87242f1b5bc5099a7dbd69ff3821f24ffc58e1b7f2387bd4e9e9712cc4c67f661b1724ad99cdf09b3717794')

View File

@ -21,7 +21,7 @@ It was found that there was an upgrade from old devtools package to the new one,
* remove build chroot, e.g.: ahriman service-clean --chroot;
* update local databases: ahriman update --no-aur --no-local --no-manual -yy.
For more information kindly refer to migration notes https://ahriman.readthedocs.io/en/stable/migration.html.
For more information kindly refer to migration notes https://ahriman.readthedocs.io/en/stable/migrations/2.9.0.html.
EOF
}
@ -37,6 +37,16 @@ Whereas old local tree is still supported it is highly recommended to migrate to
* enable web and timer services again by using x86_64-aur suffix,
where x86_64 is the repository architecture and aur is the repository name.
For more information kindly refer to migration notes https://ahriman.readthedocs.io/en/stable/migration.html.
For more information kindly refer to migration notes https://ahriman.readthedocs.io/en/stable/migrations/2.12.0.html.
EOF
}
_2_16_0_1_changes() {
cat << EOF
In order to prepare to python 3.13 the project now uses bcrypt instead of passlib for generating and validating
passwords, because the passlib seems to be unmaintained and will be broken since then. If you are using password
authentication, you'd need to generate passwords again.
For more information kindly refer to migration notes https://ahriman.readthedocs.io/en/stable/migrations/2.16.0.html.
EOF
}

View File

@ -17,8 +17,8 @@ authors = [
]
dependencies = [
"bcrypt",
"inflection",
"passlib",
"pyelftools",
"requests",
]
@ -100,3 +100,6 @@ include = [
exclude = [
"package/archlinux",
]
[tool.flit.external-data]
directory = "package"

View File

@ -89,7 +89,8 @@ Start web service (requires additional configuration):
handlers_root = Path(__file__).parent / "handlers"
for handler in implementations(handlers_root, "ahriman.application.handlers", Handler):
for subparser in handler.arguments(subparsers):
for subparser_parser in handler.arguments:
subparser = subparser_parser(subparsers)
subparser.formatter_class = _HelpFormatter
subparser.set_defaults(handler=handler, parser=_parser)

View File

@ -34,50 +34,6 @@ class Add(Handler):
add packages handler
"""
@classmethod
def arguments(cls, root: SubParserAction) -> list[argparse.ArgumentParser]:
"""
add parser(s) for the subcommand
Args:
root(SubParserAction): subparsers for the commands
Returns:
list[argparse.ArgumentParser]: created argument parser
"""
parser = root.add_parser("package-add", aliases=["add", "package-update"], help="add package",
description="add existing or new package to the build queue",
epilog="This subcommand should be used for new package addition. "
"It also supports flag --now in case if you would like to build "
"the package immediately. You can add new package from one of "
"supported sources:\n\n"
"1. If it is already built package you can specify the path to the archive.\n"
"2. You can also add built packages from the directory (e.g. during the "
"migration from another repository source).\n"
"3. It is also possible to add package from local PKGBUILD, but in this case "
"it will be ignored during the next automatic updates.\n"
"4. Ahriman supports downloading archives from remote (e.g. HTTP) sources.\n"
"5. Finally you can add package from AUR.")
parser.add_argument("package", help="package source (base name, path to local files, remote URL)", nargs="+")
parser.add_argument("--changes", help="calculate changes from the latest known commit if available",
action=argparse.BooleanOptionalAction, default=True)
parser.add_argument("--dependencies", help="process missing package dependencies",
action=argparse.BooleanOptionalAction, default=True)
parser.add_argument("-e", "--exit-code", help="return non-zero exit status if result is empty",
action="store_true")
parser.add_argument("--increment", help="increment package release (pkgrel) version on duplicate",
action=argparse.BooleanOptionalAction, default=True)
parser.add_argument("-n", "--now", help="run update function after", action="store_true")
parser.add_argument("-y", "--refresh", help="download fresh package databases from the mirror before actions, "
"-yy to force refresh even if up to date",
action="count", default=False)
parser.add_argument("-s", "--source", help="explicitly specify the package source for this command",
type=PackageSource, choices=enum_values(PackageSource), default=PackageSource.Auto)
parser.add_argument("-u", "--username", help="build as user", default=extract_user())
parser.add_argument("-v", "--variable", help="apply specified makepkg variables to the next build",
action="append")
return [parser]
@classmethod
def run(cls, args: argparse.Namespace, repository_id: RepositoryId, configuration: Configuration, *,
report: bool) -> None:
@ -112,3 +68,49 @@ class Add(Handler):
application.print_updates(packages, log_fn=application.logger.info)
result = application.update(packages, packagers, bump_pkgrel=args.increment)
Add.check_status(args.exit_code, not result.is_empty)
@staticmethod
def _set_package_add_parser(root: SubParserAction) -> argparse.ArgumentParser:
"""
add parser for package addition subcommand
Args:
root(SubParserAction): subparsers for the commands
Returns:
argparse.ArgumentParser: created argument parser
"""
parser = root.add_parser("package-add", aliases=["add", "package-update"], help="add package",
description="add existing or new package to the build queue",
epilog="This subcommand should be used for new package addition. "
"It also supports flag --now in case if you would like to build "
"the package immediately. You can add new package from one of "
"supported sources:\n\n"
"1. If it is already built package you can specify the path to the archive.\n"
"2. You can also add built packages from the directory (e.g. during the "
"migration from another repository source).\n"
"3. It is also possible to add package from local PKGBUILD, but in this case "
"it will be ignored during the next automatic updates.\n"
"4. Ahriman supports downloading archives from remote (e.g. HTTP) sources.\n"
"5. Finally you can add package from AUR.")
parser.add_argument("package", help="package source (base name, path to local files, remote URL)", nargs="+")
parser.add_argument("--changes", help="calculate changes from the latest known commit if available",
action=argparse.BooleanOptionalAction, default=True)
parser.add_argument("--dependencies", help="process missing package dependencies",
action=argparse.BooleanOptionalAction, default=True)
parser.add_argument("-e", "--exit-code", help="return non-zero exit status if result is empty",
action="store_true")
parser.add_argument("--increment", help="increment package release (pkgrel) version on duplicate",
action=argparse.BooleanOptionalAction, default=True)
parser.add_argument("-n", "--now", help="run update function after", action="store_true")
parser.add_argument("-y", "--refresh", help="download fresh package databases from the mirror before actions, "
"-yy to force refresh even if up to date",
action="count", default=False)
parser.add_argument("-s", "--source", help="explicitly specify the package source for this command",
type=PackageSource, choices=enum_values(PackageSource), default=PackageSource.Auto)
parser.add_argument("-u", "--username", help="build as user", default=extract_user())
parser.add_argument("-v", "--variable", help="apply specified makepkg variables to the next build",
action="append")
return parser
arguments = [_set_package_add_parser]

View File

@ -36,23 +36,6 @@ class Backup(Handler):
ALLOW_MULTI_ARCHITECTURE_RUN = False # system-wide action
@classmethod
def arguments(cls, root: SubParserAction) -> list[argparse.ArgumentParser]:
"""
add parser(s) for the subcommand
Args:
root(SubParserAction): subparsers for the commands
Returns:
list[argparse.ArgumentParser]: created argument parser
"""
parser = root.add_parser("repo-backup", help="backup repository data",
description="backup repository settings and database")
parser.add_argument("path", help="path of the output archive", type=Path)
parser.set_defaults(architecture="", lock=None, report=False, repository="", unsafe=True)
return [parser]
@classmethod
def run(cls, args: argparse.Namespace, repository_id: RepositoryId, configuration: Configuration, *,
report: bool) -> None:
@ -70,6 +53,23 @@ class Backup(Handler):
for backup_path in backup_paths:
archive.add(backup_path)
@staticmethod
def _set_repo_backup_parser(root: SubParserAction) -> argparse.ArgumentParser:
"""
add parser for repository backup subcommand
Args:
root(SubParserAction): subparsers for the commands
Returns:
argparse.ArgumentParser: created argument parser
"""
parser = root.add_parser("repo-backup", help="backup repository data",
description="backup repository settings and database")
parser.add_argument("path", help="path of the output archive", type=Path)
parser.set_defaults(architecture="", lock=None, report=False, repository="", unsafe=True)
return parser
@staticmethod
def get_paths(configuration: Configuration) -> set[Path]:
"""
@ -100,3 +100,5 @@ class Backup(Handler):
paths.add(gnupg_home)
return paths
arguments = [_set_repo_backup_parser]

View File

@ -35,19 +35,6 @@ class Change(Handler):
ALLOW_MULTI_ARCHITECTURE_RUN = False # conflicting io
@classmethod
def arguments(cls, root: SubParserAction) -> list[argparse.ArgumentParser]:
"""
add parser(s) for the subcommand
Args:
root(SubParserAction): subparsers for the commands
Returns:
list[argparse.ArgumentParser]: created argument parser
"""
return [cls._set_package_changes_parser(root), cls._set_package_changes_remove_parser(root)]
@classmethod
def run(cls, args: argparse.Namespace, repository_id: RepositoryId, configuration: Configuration, *,
report: bool) -> None:
@ -108,3 +95,5 @@ class Change(Handler):
parser.add_argument("package", help="package base")
parser.set_defaults(action=Action.Remove, exit_code=False, lock=None, quiet=True, report=False, unsafe=True)
return parser
arguments = [_set_package_changes_parser, _set_package_changes_remove_parser]

View File

@ -31,15 +31,32 @@ class Clean(Handler):
"""
@classmethod
def arguments(cls, root: SubParserAction) -> list[argparse.ArgumentParser]:
def run(cls, args: argparse.Namespace, repository_id: RepositoryId, configuration: Configuration, *,
report: bool) -> None:
"""
add parser(s) for the subcommand
callback for command line
Args:
args(argparse.Namespace): command line args
repository_id(RepositoryId): repository unique identifier
configuration(Configuration): configuration instance
report(bool): force enable or disable reporting
"""
application = Application(repository_id, configuration, report=report)
application.on_start()
application.clean(cache=args.cache, chroot=args.chroot, manual=args.manual, packages=args.packages,
pacman=args.pacman)
@staticmethod
def _set_service_clean_parser(root: SubParserAction) -> argparse.ArgumentParser:
"""
add parser for repository clean subcommand
Args:
root(SubParserAction): subparsers for the commands
Returns:
list[argparse.ArgumentParser]: created argument parser
argparse.ArgumentParser: created argument parser
"""
parser = root.add_parser("service-clean", aliases=["clean", "repo-clean"], help="clean local caches",
description="remove local caches",
@ -56,21 +73,6 @@ class Clean(Handler):
parser.add_argument("--pacman", help="clear directory with pacman local database cache",
action=argparse.BooleanOptionalAction, default=False)
parser.set_defaults(lock=None, quiet=True, unsafe=True)
return [parser]
return parser
@classmethod
def run(cls, args: argparse.Namespace, repository_id: RepositoryId, configuration: Configuration, *,
report: bool) -> None:
"""
callback for command line
Args:
args(argparse.Namespace): command line args
repository_id(RepositoryId): repository unique identifier
configuration(Configuration): configuration instance
report(bool): force enable or disable reporting
"""
application = Application(repository_id, configuration, report=report)
application.on_start()
application.clean(cache=args.cache, chroot=args.chroot, manual=args.manual, packages=args.packages,
pacman=args.pacman)
arguments = [_set_service_clean_parser]

View File

@ -35,26 +35,6 @@ class Copy(Handler):
ALLOW_MULTI_ARCHITECTURE_RUN = False # conflicting action
@classmethod
def arguments(cls, root: SubParserAction) -> list[argparse.ArgumentParser]:
"""
add parser(s) for the subcommand
Args:
root(SubParserAction): subparsers for the commands
Returns:
list[argparse.ArgumentParser]: created argument parser
"""
parser = root.add_parser("package-copy", aliases=["copy"], help="copy package from another repository",
description="copy package and its metadata from another repository")
parser.add_argument("source", help="source repository name")
parser.add_argument("package", help="package base", nargs="+")
parser.add_argument("-e", "--exit-code", help="return non-zero exit status if result is empty",
action="store_true")
parser.add_argument("--remove", help="remove package from the source repository after", action="store_true")
return [parser]
@classmethod
def run(cls, args: argparse.Namespace, repository_id: RepositoryId, configuration: Configuration, *,
report: bool) -> None:
@ -87,6 +67,26 @@ class Copy(Handler):
if args.remove:
source_application.remove(args.package)
@staticmethod
def _set_package_copy_parser(root: SubParserAction) -> argparse.ArgumentParser:
"""
add parser for package copy subcommand
Args:
root(SubParserAction): subparsers for the commands
Returns:
argparse.ArgumentParser: created argument parser
"""
parser = root.add_parser("package-copy", aliases=["copy"], help="copy package from another repository",
description="copy package and its metadata from another repository")
parser.add_argument("source", help="source repository name")
parser.add_argument("package", help="package base", nargs="+")
parser.add_argument("-e", "--exit-code", help="return non-zero exit status if result is empty",
action="store_true")
parser.add_argument("--remove", help="remove package from the source repository after", action="store_true")
return parser
@staticmethod
def copy_package(package: Package, application: Application, source_application: Application) -> None:
"""
@ -113,3 +113,5 @@ class Copy(Handler):
package.base, source_application.reporter.package_dependencies_get(package.base)
)
application.reporter.package_update(package, BuildStatusEnum.Pending)
arguments = [_set_package_copy_parser]

View File

@ -36,15 +36,40 @@ class Daemon(Handler):
"""
@classmethod
def arguments(cls, root: SubParserAction) -> list[argparse.ArgumentParser]:
def run(cls, args: argparse.Namespace, repository_id: RepositoryId, configuration: Configuration, *,
report: bool) -> None:
"""
add parser(s) for the subcommand
callback for command line
Args:
args(argparse.Namespace): command line args
repository_id(RepositoryId): repository unique identifier
configuration(Configuration): configuration instance
report(bool): force enable or disable reporting
"""
application = Application(repository_id, configuration, report=report, refresh_pacman_database=args.refresh)
if args.partitions:
iterator = UpdatesIterator(application, args.interval)
else:
iterator = FixedUpdatesIterator(application, args.interval)
for packages in iterator:
if packages is None:
continue # nothing to check case
args.package = packages
Update.run(args, repository_id, configuration, report=report)
@staticmethod
def _set_repo_daemon_parser(root: SubParserAction) -> argparse.ArgumentParser:
"""
add parser for daemon subcommand
Args:
root(SubParserAction): subparsers for the commands
Returns:
list[argparse.ArgumentParser]: created argument parser
argparse.ArgumentParser: created argument parser
"""
parser = root.add_parser("repo-daemon", aliases=["daemon"], help="run application as daemon",
description="start process which periodically will run update process")
@ -76,29 +101,6 @@ class Daemon(Handler):
"-yy to force refresh even if up to date",
action="count", default=False)
parser.set_defaults(exit_code=False, lock=Path("ahriman-daemon.pid"), package=[])
return [parser]
return parser
@classmethod
def run(cls, args: argparse.Namespace, repository_id: RepositoryId, configuration: Configuration, *,
report: bool) -> None:
"""
callback for command line
Args:
args(argparse.Namespace): command line args
repository_id(RepositoryId): repository unique identifier
configuration(Configuration): configuration instance
report(bool): force enable or disable reporting
"""
application = Application(repository_id, configuration, report=report, refresh_pacman_database=args.refresh)
if args.partitions:
iterator = UpdatesIterator(application, args.interval)
else:
iterator = FixedUpdatesIterator(application, args.interval)
for packages in iterator:
if packages is None:
continue # nothing to check case
args.package = packages
Update.run(args, repository_id, configuration, report=report)
arguments = [_set_repo_daemon_parser]

View File

@ -32,28 +32,6 @@ class Dump(Handler):
ALLOW_MULTI_ARCHITECTURE_RUN = False # conflicting io
@classmethod
def arguments(cls, root: SubParserAction) -> list[argparse.ArgumentParser]:
"""
add parser(s) for the subcommand
Args:
root(SubParserAction): subparsers for the commands
Returns:
list[argparse.ArgumentParser]: created argument parser
"""
parser = root.add_parser("service-config", aliases=["config", "repo-config"], help="dump configuration",
description="dump configuration for the specified architecture")
parser.add_argument("section", help="filter settings by section", nargs="?")
parser.add_argument("key", help="filter settings by key", nargs="?")
parser.add_argument("--info", help="show additional information, e.g. configuration files",
action=argparse.BooleanOptionalAction, default=True)
parser.add_argument("--secure", help="hide passwords and secrets from output",
action=argparse.BooleanOptionalAction, default=True)
parser.set_defaults(lock=None, quiet=True, report=False, unsafe=True)
return [parser]
@classmethod
def run(cls, args: argparse.Namespace, repository_id: RepositoryId, configuration: Configuration, *,
report: bool) -> None:
@ -81,3 +59,27 @@ class Dump(Handler):
case section, key: # key only
value = configuration.get(section, key, fallback="")
StringPrinter(value)(verbose=False)
@staticmethod
def _set_service_config_parser(root: SubParserAction) -> argparse.ArgumentParser:
"""
add parser for config subcommand
Args:
root(SubParserAction): subparsers for the commands
Returns:
argparse.ArgumentParser: created argument parser
"""
parser = root.add_parser("service-config", aliases=["config", "repo-config"], help="dump configuration",
description="dump configuration for the specified architecture")
parser.add_argument("section", help="filter settings by section", nargs="?")
parser.add_argument("key", help="filter settings by key", nargs="?")
parser.add_argument("--info", help="show additional information, e.g. configuration files",
action=argparse.BooleanOptionalAction, default=True)
parser.add_argument("--secure", help="hide passwords and secrets from output",
action=argparse.BooleanOptionalAction, default=True)
parser.set_defaults(lock=None, quiet=True, report=False, unsafe=True)
return parser
arguments = [_set_service_config_parser]

View File

@ -46,6 +46,8 @@ class Handler:
Attributes:
ALLOW_MULTI_ARCHITECTURE_RUN(bool): (class attribute) allow running with multiple architectures
arguments(list[Callable[[SubParserAction], argparse.ArgumentParser]]): (class attribute) argument parser
methods, which will be called to create command line parsers
Examples:
Wrapper for all command line actions, though each derived class implements :func:`run()` method, it usually
@ -57,22 +59,7 @@ class Handler:
"""
ALLOW_MULTI_ARCHITECTURE_RUN = True
@classmethod
def arguments(cls, root: SubParserAction) -> list[argparse.ArgumentParser]:
"""
add parser(s) for the subcommand
Args:
root(SubParserAction): subparsers for the commands
Returns:
list[argparse.ArgumentParser]: created argument parser
Raises:
NotImplementedError: not implemented method
"""
raise NotImplementedError
arguments: list[Callable[[SubParserAction], argparse.ArgumentParser]]
@classmethod
def call(cls, args: argparse.Namespace, repository_id: RepositoryId) -> bool:

View File

@ -31,23 +31,6 @@ class Help(Handler):
ALLOW_MULTI_ARCHITECTURE_RUN = False # system-wide action
@classmethod
def arguments(cls, root: SubParserAction) -> list[argparse.ArgumentParser]:
"""
add parser(s) for the subcommand
Args:
root(SubParserAction): subparsers for the commands
Returns:
list[argparse.ArgumentParser]: created argument parser
"""
parser = root.add_parser("help", help="show help message",
description="show help message for application or command and exit")
parser.add_argument("subcommand", help="show help message for specific command", nargs="?")
parser.set_defaults(architecture="", lock=None, quiet=True, report=False, repository="", unsafe=True)
return [parser]
@classmethod
def run(cls, args: argparse.Namespace, repository_id: RepositoryId, configuration: Configuration, *,
report: bool) -> None:
@ -65,3 +48,22 @@ class Help(Handler):
parser.parse_args(["--help"])
else:
parser.parse_args([args.subcommand, "--help"])
@staticmethod
def _set_help_parser(root: SubParserAction) -> argparse.ArgumentParser:
"""
add parser for listing help subcommand
Args:
root(SubParserAction): subparsers for the commands
Returns:
argparse.ArgumentParser: created argument parser
"""
parser = root.add_parser("help", help="show help message",
description="show help message for application or command and exit")
parser.add_argument("subcommand", help="show help message for specific command", nargs="?")
parser.set_defaults(architecture="", lock=None, quiet=True, report=False, repository="", unsafe=True)
return parser
arguments = [_set_help_parser]

View File

@ -32,28 +32,6 @@ class KeyImport(Handler):
ALLOW_MULTI_ARCHITECTURE_RUN = False # system-wide action
@classmethod
def arguments(cls, root: SubParserAction) -> list[argparse.ArgumentParser]:
"""
add parser(s) for the subcommand
Args:
root(SubParserAction): subparsers for the commands
Returns:
list[argparse.ArgumentParser]: created argument parser
"""
parser = root.add_parser("service-key-import", aliases=["key-import"], help="import PGP key",
description="import PGP key from public sources to the repository user",
epilog="By default ahriman runs build process with package sources validation "
"(in case if signature and keys are available in PKGBUILD). This process will "
"fail in case if key is not known for build user. This subcommand can be used "
"in order to import the PGP key to user keychain.")
parser.add_argument("--key-server", help="key server for key import", default="keyserver.ubuntu.com")
parser.add_argument("key", help="PGP key to import from public server")
parser.set_defaults(architecture="", lock=None, report=False, repository="")
return [parser]
@classmethod
def run(cls, args: argparse.Namespace, repository_id: RepositoryId, configuration: Configuration, *,
report: bool) -> None:
@ -68,3 +46,27 @@ class KeyImport(Handler):
"""
application = Application(repository_id, configuration, report=report)
application.repository.sign.key_import(args.key_server, args.key)
@staticmethod
def _set_service_key_import_parser(root: SubParserAction) -> argparse.ArgumentParser:
"""
add parser for key import subcommand
Args:
root(SubParserAction): subparsers for the commands
Returns:
argparse.ArgumentParser: created argument parser
"""
parser = root.add_parser("service-key-import", aliases=["key-import"], help="import PGP key",
description="import PGP key from public sources to the repository user",
epilog="By default ahriman runs build process with package sources validation "
"(in case if signature and keys are available in PKGBUILD). This process will "
"fail in case if key is not known for build user. This subcommand can be used "
"in order to import the PGP key to user keychain.")
parser.add_argument("--key-server", help="key server for key import", default="keyserver.ubuntu.com")
parser.add_argument("key", help="PGP key to import from public server")
parser.set_defaults(architecture="", lock=None, report=False, repository="")
return parser
arguments = [_set_service_key_import_parser]

View File

@ -40,24 +40,6 @@ class Patch(Handler):
ALLOW_MULTI_ARCHITECTURE_RUN = False # system-wide action
@classmethod
def arguments(cls, root: SubParserAction) -> list[argparse.ArgumentParser]:
"""
add parser(s) for the subcommand
Args:
root(SubParserAction): subparsers for the commands
Returns:
list[argparse.ArgumentParser]: created argument parser
"""
return [
cls._set_patch_add_parser(root),
cls._set_patch_list_parser(root),
cls._set_patch_remove_parser(root),
cls._set_patch_set_add_parser(root),
]
@classmethod
def run(cls, args: argparse.Namespace, repository_id: RepositoryId, configuration: Configuration, *,
report: bool) -> None:
@ -267,3 +249,10 @@ class Patch(Handler):
application.reporter.package_patches_remove(package_base, variable)
else:
application.reporter.package_patches_remove(package_base, None) # just pass as is
arguments = [
_set_patch_add_parser,
_set_patch_list_parser,
_set_patch_remove_parser,
_set_patch_set_add_parser,
]

View File

@ -34,38 +34,6 @@ class Rebuild(Handler):
make world handler
"""
@classmethod
def arguments(cls, root: SubParserAction) -> list[argparse.ArgumentParser]:
"""
add parser(s) for the subcommand
Args:
root(SubParserAction): subparsers for the commands
Returns:
list[argparse.ArgumentParser]: created argument parser
"""
parser = root.add_parser("repo-rebuild", aliases=["rebuild"], help="rebuild repository",
description="force rebuild whole repository")
parser.add_argument("--depends-on", help="only rebuild packages that depend on specified packages",
action="append")
parser.add_argument("--dry-run", help="just perform check for packages without rebuild process itself",
action="store_true")
parser.add_argument("--from-database",
help="read packages from database instead of filesystem. This feature in particular is "
"required in case if you would like to restore repository from another repository "
"instance. Note, however, that in order to restore packages you need to have original "
"ahriman instance run with web service and have run repo-update at least once.",
action="store_true")
parser.add_argument("--increment", help="increment package release (pkgrel) on duplicate",
action=argparse.BooleanOptionalAction, default=True)
parser.add_argument("-e", "--exit-code", help="return non-zero exit status if result is empty",
action="store_true")
parser.add_argument("-s", "--status", help="filter packages by status. Requires --from-database to be set",
type=BuildStatusEnum, choices=enum_values(BuildStatusEnum))
parser.add_argument("-u", "--username", help="build as user", default=extract_user())
return [parser]
@classmethod
def run(cls, args: argparse.Namespace, repository_id: RepositoryId, configuration: Configuration, *,
report: bool) -> None:
@ -92,6 +60,38 @@ class Rebuild(Handler):
result = application.update(packages, Packagers(args.username), bump_pkgrel=args.increment)
Rebuild.check_status(args.exit_code, not result.is_empty)
@staticmethod
def _set_repo_rebuild_parser(root: SubParserAction) -> argparse.ArgumentParser:
"""
add parser for repository rebuild subcommand
Args:
root(SubParserAction): subparsers for the commands
Returns:
argparse.ArgumentParser: created argument parser
"""
parser = root.add_parser("repo-rebuild", aliases=["rebuild"], help="rebuild repository",
description="force rebuild whole repository")
parser.add_argument("--depends-on", help="only rebuild packages that depend on specified packages",
action="append")
parser.add_argument("--dry-run", help="just perform check for packages without rebuild process itself",
action="store_true")
parser.add_argument("--from-database",
help="read packages from database instead of filesystem. This feature in particular is "
"required in case if you would like to restore repository from another repository "
"instance. Note, however, that in order to restore packages you need to have original "
"ahriman instance run with web service and have run repo-update at least once.",
action="store_true")
parser.add_argument("--increment", help="increment package release (pkgrel) on duplicate",
action=argparse.BooleanOptionalAction, default=True)
parser.add_argument("-e", "--exit-code", help="return non-zero exit status if result is empty",
action="store_true")
parser.add_argument("-s", "--status", help="filter packages by status. Requires --from-database to be set",
type=BuildStatusEnum, choices=enum_values(BuildStatusEnum))
parser.add_argument("-u", "--username", help="build as user", default=extract_user())
return parser
@staticmethod
def extract_packages(application: Application, status: BuildStatusEnum | None, *,
from_database: bool) -> list[Package]:
@ -114,3 +114,5 @@ class Rebuild(Handler):
]
return application.repository.packages()
arguments = [_set_repo_rebuild_parser]

View File

@ -30,22 +30,6 @@ class Remove(Handler):
remove packages handler
"""
@classmethod
def arguments(cls, root: SubParserAction) -> list[argparse.ArgumentParser]:
"""
add parser(s) for the subcommand
Args:
root(SubParserAction): subparsers for the commands
Returns:
list[argparse.ArgumentParser]: created argument parser
"""
parser = root.add_parser("package-remove", aliases=["remove"], help="remove package",
description="remove package from the repository")
parser.add_argument("package", help="package name or base", nargs="+")
return [parser]
@classmethod
def run(cls, args: argparse.Namespace, repository_id: RepositoryId, configuration: Configuration, *,
report: bool) -> None:
@ -61,3 +45,21 @@ class Remove(Handler):
application = Application(repository_id, configuration, report=report)
application.on_start()
application.remove(args.package)
@staticmethod
def _set_package_remove_parser(root: SubParserAction) -> argparse.ArgumentParser:
"""
add parser for package removal subcommand
Args:
root(SubParserAction): subparsers for the commands
Returns:
argparse.ArgumentParser: created argument parser
"""
parser = root.add_parser("package-remove", aliases=["remove"], help="remove package",
description="remove package from the repository")
parser.add_argument("package", help="package name or base", nargs="+")
return parser
arguments = [_set_package_remove_parser]

View File

@ -31,22 +31,6 @@ class RemoveUnknown(Handler):
remove unknown packages handler
"""
@classmethod
def arguments(cls, root: SubParserAction) -> list[argparse.ArgumentParser]:
"""
add parser(s) for the subcommand
Args:
root(SubParserAction): subparsers for the commands
Returns:
list[argparse.ArgumentParser]: created argument parser
"""
parser = root.add_parser("repo-remove-unknown", aliases=["remove-unknown"], help="remove unknown packages",
description="remove packages which are missing in AUR and do not have local PKGBUILDs")
parser.add_argument("--dry-run", help="just perform check for packages without removal", action="store_true")
return [parser]
@classmethod
def run(cls, args: argparse.Namespace, repository_id: RepositoryId, configuration: Configuration, *,
report: bool) -> None:
@ -69,3 +53,21 @@ class RemoveUnknown(Handler):
return
application.remove(unknown_packages)
@staticmethod
def _set_repo_remove_unknown_parser(root: SubParserAction) -> argparse.ArgumentParser:
"""
add parser for remove unknown packages subcommand
Args:
root(SubParserAction): subparsers for the commands
Returns:
argparse.ArgumentParser: created argument parser
"""
parser = root.add_parser("repo-remove-unknown", aliases=["remove-unknown"], help="remove unknown packages",
description="remove packages which are missing in AUR and do not have local PKGBUILDs")
parser.add_argument("--dry-run", help="just perform check for packages without removal", action="store_true")
return parser
arguments = [_set_repo_remove_unknown_parser]

View File

@ -32,24 +32,6 @@ class Repositories(Handler):
ALLOW_MULTI_ARCHITECTURE_RUN = False # system-wide action
@classmethod
def arguments(cls, root: SubParserAction) -> list[argparse.ArgumentParser]:
"""
add parser(s) for the subcommand
Args:
root(SubParserAction): subparsers for the commands
Returns:
list[argparse.ArgumentParser]: created argument parser
"""
parser = root.add_parser("service-repositories", help="show repositories",
description="list all available repositories")
parser.add_argument("--id-only", help="show machine readable identifier instead",
action=argparse.BooleanOptionalAction, default=False)
parser.set_defaults(architecture="", lock=None, report=False, repository="", unsafe=True)
return [parser]
@classmethod
def run(cls, args: argparse.Namespace, repository_id: RepositoryId, configuration: Configuration, *,
report: bool) -> None:
@ -70,3 +52,23 @@ class Repositories(Handler):
)
for repository in cls.repositories_extract(dummy_args):
RepositoryPrinter(repository)(verbose=not args.id_only)
@staticmethod
def _set_service_repositories(root: SubParserAction) -> argparse.ArgumentParser:
"""
add parser for repositories listing
Args:
root(SubParserAction): subparsers for the commands
Returns:
argparse.ArgumentParser: created argument parser
"""
parser = root.add_parser("service-repositories", help="show repositories",
description="list all available repositories")
parser.add_argument("--id-only", help="show machine readable identifier instead",
action=argparse.BooleanOptionalAction, default=False)
parser.set_defaults(architecture="", lock=None, report=False, repository="", unsafe=True)
return parser
arguments = [_set_service_repositories]

View File

@ -34,24 +34,6 @@ class Restore(Handler):
ALLOW_MULTI_ARCHITECTURE_RUN = False # system-wide action
@classmethod
def arguments(cls, root: SubParserAction) -> list[argparse.ArgumentParser]:
"""
add parser(s) for the subcommand
Args:
root(SubParserAction): subparsers for the commands
Returns:
list[argparse.ArgumentParser]: created argument parser
"""
parser = root.add_parser("repo-restore", help="restore repository data",
description="restore settings and database")
parser.add_argument("path", help="path of the input archive", type=Path)
parser.add_argument("-o", "--output", help="root path of the extracted files", type=Path, default=Path("/"))
parser.set_defaults(architecture="", lock=None, report=False, repository="", unsafe=True)
return [parser]
@classmethod
def run(cls, args: argparse.Namespace, repository_id: RepositoryId, configuration: Configuration, *,
report: bool) -> None:
@ -66,3 +48,23 @@ class Restore(Handler):
"""
with tarfile.open(args.path) as archive:
archive.extractall(path=args.output) # nosec
@staticmethod
def _set_repo_restore_parser(root: SubParserAction) -> argparse.ArgumentParser:
"""
add parser for repository restore subcommand
Args:
root(SubParserAction): subparsers for the commands
Returns:
argparse.ArgumentParser: created argument parser
"""
parser = root.add_parser("repo-restore", help="restore repository data",
description="restore settings and database")
parser.add_argument("path", help="path of the input archive", type=Path)
parser.add_argument("-o", "--output", help="root path of the extracted files", type=Path, default=Path("/"))
parser.set_defaults(architecture="", lock=None, report=False, repository="", unsafe=True)
return parser
arguments = [_set_repo_restore_parser]

View File

@ -32,25 +32,6 @@ class Run(Handler):
ALLOW_MULTI_ARCHITECTURE_RUN = False # system-wide action
@classmethod
def arguments(cls, root: SubParserAction) -> list[argparse.ArgumentParser]:
"""
add parser(s) for the subcommand
Args:
root(SubParserAction): subparsers for the commands
Returns:
list[argparse.ArgumentParser]: created argument parser
"""
parser = root.add_parser("service-run", aliases=["run"], help="run multiple commands",
description="run multiple commands on success run of the previous command",
epilog="Commands must be quoted by using usual bash rules. Processes will be spawned "
"under the same user as this command.")
parser.add_argument("command", help="command to be run (quoted) without ``ahriman``", nargs="+")
parser.set_defaults(architecture="", lock=None, report=False, repository="", unsafe=True)
return [parser]
@classmethod
def run(cls, args: argparse.Namespace, repository_id: RepositoryId, configuration: Configuration, *,
report: bool) -> None:
@ -68,6 +49,25 @@ class Run(Handler):
status = Run.run_command(shlex.split(command), parser)
Run.check_status(True, status)
@staticmethod
def _set_service_run(root: SubParserAction) -> argparse.ArgumentParser:
"""
add parser for multicommand
Args:
root(SubParserAction): subparsers for the commands
Returns:
argparse.ArgumentParser: created argument parser
"""
parser = root.add_parser("service-run", aliases=["run"], help="run multiple commands",
description="run multiple commands on success run of the previous command",
epilog="Commands must be quoted by using usual bash rules. Processes will be spawned "
"under the same user as this command.")
parser.add_argument("command", help="command to be run (quoted) without ``ahriman``", nargs="+")
parser.set_defaults(architecture="", lock=None, report=False, repository="", unsafe=True)
return parser
@staticmethod
def run_command(command: list[str], parser: argparse.ArgumentParser) -> bool:
"""
@ -83,3 +83,5 @@ class Run(Handler):
args = parser.parse_args(command)
handler: Handler = args.handler
return handler.execute(args) == 0
arguments = [_set_service_run]

View File

@ -46,33 +46,6 @@ class Search(Handler):
if field.default_factory is not list
}
@classmethod
def arguments(cls, root: SubParserAction) -> list[argparse.ArgumentParser]:
"""
add parser(s) for the subcommand
Args:
root(SubParserAction): subparsers for the commands
Returns:
list[argparse.ArgumentParser]: created argument parser
"""
parser = root.add_parser("aur-search", aliases=["search"], help="search for package",
description="search for package in AUR using API")
parser.add_argument("search",
help="search terms, can be specified multiple times, the result will match all terms",
nargs="+")
parser.add_argument("-e", "--exit-code", help="return non-zero exit status if result is empty",
action="store_true")
parser.add_argument("--info", help="show additional package information",
action=argparse.BooleanOptionalAction, default=False)
parser.add_argument("--sort-by",
help="sort field by this field. In case if two packages have the same value of "
"the specified field, they will be always sorted by name",
default="name", choices=sorted(Search.SORT_FIELDS))
parser.set_defaults(architecture="", lock=None, quiet=True, report=False, repository="", unsafe=True)
return [parser]
@classmethod
def run(cls, args: argparse.Namespace, repository_id: RepositoryId, configuration: Configuration, *,
report: bool) -> None:
@ -95,6 +68,33 @@ class Search(Handler):
for package in Search.sort(packages_list, args.sort_by):
AurPrinter(package)(verbose=args.info)
@staticmethod
def _set_aur_search_parser(root: SubParserAction) -> argparse.ArgumentParser:
"""
add parser for AUR search subcommand
Args:
root(SubParserAction): subparsers for the commands
Returns:
argparse.ArgumentParser: created argument parser
"""
parser = root.add_parser("aur-search", aliases=["search"], help="search for package",
description="search for package in AUR using API")
parser.add_argument("search",
help="search terms, can be specified multiple times, the result will match all terms",
nargs="+")
parser.add_argument("-e", "--exit-code", help="return non-zero exit status if result is empty",
action="store_true")
parser.add_argument("--info", help="show additional package information",
action=argparse.BooleanOptionalAction, default=False)
parser.add_argument("--sort-by",
help="sort field by this field. In case if two packages have the same value of "
"the specified field, they will be always sorted by name",
default="name", choices=sorted(Search.SORT_FIELDS))
parser.set_defaults(architecture="", lock=None, quiet=True, report=False, repository="", unsafe=True)
return parser
@staticmethod
def sort(packages: Iterable[AURPackage], sort_by: str) -> list[AURPackage]:
"""
@ -117,3 +117,5 @@ class Search(Handler):
comparator: Callable[[AURPackage], tuple[str, str]] =\
lambda package: (getattr(package, sort_by), package.name)
return sorted(packages, key=comparator)
arguments = [_set_aur_search_parser]

View File

@ -34,24 +34,6 @@ class ServiceUpdates(Handler):
ALLOW_MULTI_ARCHITECTURE_RUN = False # system-wide action
@classmethod
def arguments(cls, root: SubParserAction) -> list[argparse.ArgumentParser]:
"""
add parser(s) for the subcommand
Args:
root(SubParserAction): subparsers for the commands
Returns:
list[argparse.ArgumentParser]: created argument parser
"""
parser = root.add_parser("help-updates", help="check for service updates",
description="request AUR for current version and compare with current service version")
parser.add_argument("-e", "--exit-code", help="return non-zero exit code if updates available",
action="store_true")
parser.set_defaults(architecture="", lock=None, quiet=True, report=False, repository="", unsafe=True)
return [parser]
@classmethod
def run(cls, args: argparse.Namespace, repository_id: RepositoryId, configuration: Configuration, *,
report: bool) -> None:
@ -76,3 +58,23 @@ class ServiceUpdates(Handler):
UpdatePrinter(remote, local_version)(verbose=True, separator=" -> ")
ServiceUpdates.check_status(args.exit_code, same_version)
@staticmethod
def _set_help_updates_parser(root: SubParserAction) -> argparse.ArgumentParser:
"""
add parser for service update check subcommand
Args:
root(SubParserAction): subparsers for the commands
Returns:
argparse.ArgumentParser: created argument parser
"""
parser = root.add_parser("help-updates", help="check for service updates",
description="request AUR for current version and compare with current service version")
parser.add_argument("-e", "--exit-code", help="return non-zero exit code if updates available",
action="store_true")
parser.set_defaults(architecture="", lock=None, quiet=True, report=False, repository="", unsafe=True)
return parser
arguments = [_set_help_updates_parser]

View File

@ -50,44 +50,6 @@ class Setup(Handler):
MIRRORLIST_PATH = Path("/") / "etc" / "pacman.d" / "mirrorlist"
SUDOERS_DIR_PATH = Path("/") / "etc" / "sudoers.d"
@classmethod
def arguments(cls, root: SubParserAction) -> list[argparse.ArgumentParser]:
"""
add parser(s) for the subcommand
Args:
root(SubParserAction): subparsers for the commands
Returns:
list[argparse.ArgumentParser]: created argument parser
"""
parser = root.add_parser("service-setup", aliases=["init", "repo-init", "repo-setup", "setup"],
help="initial service configuration",
description="create initial service configuration, requires root",
epilog="Create minimal configuration for the service according to provided options.")
parser.add_argument("--build-as-user", help="force makepkg user to the specific one")
parser.add_argument("--from-configuration", help="path to default devtools pacman configuration",
type=Path,
default=Path("/") / "usr" / "share" / "devtools" / "pacman.conf.d" / "extra.conf")
parser.add_argument("--generate-salt", help="generate salt for user passwords",
action=argparse.BooleanOptionalAction, default=False)
parser.add_argument("--makeflags-jobs",
help="append MAKEFLAGS variable with parallelism set to number of cores",
action=argparse.BooleanOptionalAction, default=True)
parser.add_argument("--mirror", help="use the specified explicitly mirror instead of including mirrorlist")
parser.add_argument("--multilib", help="add or do not multilib repository",
action=argparse.BooleanOptionalAction, default=True)
parser.add_argument("--packager", help="packager name and email", required=True)
parser.add_argument("--server", help="server to be used for devtools. If none set, local files will be used")
parser.add_argument("--sign-key", help="sign key id")
parser.add_argument("--sign-target", help="sign options", action="append",
type=SignSettings.from_option, choices=enum_values(SignSettings))
parser.add_argument("--web-port", help="port of the web service", type=int)
parser.add_argument("--web-unix-socket", help="path to unix socket used for interprocess communications",
type=Path)
parser.set_defaults(lock=None, quiet=True, report=False, unsafe=True)
return [parser]
@classmethod
def run(cls, args: argparse.Namespace, repository_id: RepositoryId, configuration: Configuration, *,
report: bool) -> None:
@ -120,6 +82,44 @@ class Setup(Handler):
# lazy database sync
application.repository.pacman.handle # pylint: disable=pointless-statement
@staticmethod
def _set_service_setup_parser(root: SubParserAction) -> argparse.ArgumentParser:
"""
add parser for setup subcommand
Args:
root(SubParserAction): subparsers for the commands
Returns:
argparse.ArgumentParser: created argument parser
"""
parser = root.add_parser("service-setup", aliases=["init", "repo-init", "repo-setup", "setup"],
help="initial service configuration",
description="create initial service configuration, requires root",
epilog="Create minimal configuration for the service according to provided options.")
parser.add_argument("--build-as-user", help="force makepkg user to the specific one")
parser.add_argument("--from-configuration", help="path to default devtools pacman configuration",
type=Path,
default=Path("/") / "usr" / "share" / "devtools" / "pacman.conf.d" / "extra.conf")
parser.add_argument("--generate-salt", help="generate salt for user passwords",
action=argparse.BooleanOptionalAction, default=False)
parser.add_argument("--makeflags-jobs",
help="append MAKEFLAGS variable with parallelism set to number of cores",
action=argparse.BooleanOptionalAction, default=True)
parser.add_argument("--mirror", help="use the specified explicitly mirror instead of including mirrorlist")
parser.add_argument("--multilib", help="add or do not multilib repository",
action=argparse.BooleanOptionalAction, default=True)
parser.add_argument("--packager", help="packager name and email", required=True)
parser.add_argument("--server", help="server to be used for devtools. If none set, local files will be used")
parser.add_argument("--sign-key", help="sign key id")
parser.add_argument("--sign-target", help="sign options", action="append",
type=SignSettings.from_option, choices=enum_values(SignSettings))
parser.add_argument("--web-port", help="port of the web service", type=int)
parser.add_argument("--web-unix-socket", help="path to unix socket used for interprocess communications",
type=Path)
parser.set_defaults(lock=None, quiet=True, report=False, unsafe=True)
return parser
@staticmethod
def build_command(root: Path, repository_id: RepositoryId) -> Path:
"""
@ -280,3 +280,5 @@ class Setup(Handler):
command.unlink(missing_ok=True)
command.symlink_to(Setup.ARCHBUILD_COMMAND_PATH)
paths.chown(command) # we would like to keep owner inside ahriman's home
arguments = [_set_service_setup_parser]

View File

@ -36,24 +36,6 @@ class Shell(Handler):
ALLOW_MULTI_ARCHITECTURE_RUN = False # conflicting io
@classmethod
def arguments(cls, root: SubParserAction) -> list[argparse.ArgumentParser]:
"""
add parser(s) for the subcommand
Args:
root(SubParserAction): subparsers for the commands
Returns:
list[argparse.ArgumentParser]: created argument parser
"""
parser = root.add_parser("service-shell", aliases=["shell"], help="invoke python shell",
description="drop into python shell")
parser.add_argument("code", help="instead of dropping into shell, just execute the specified code", nargs="?")
parser.add_argument("-v", "--verbose", help=argparse.SUPPRESS, action="store_true")
parser.set_defaults(lock=None, report=False)
return [parser]
@classmethod
def run(cls, args: argparse.Namespace, repository_id: RepositoryId, configuration: Configuration, *,
report: bool) -> None:
@ -81,3 +63,23 @@ class Shell(Handler):
code.interact(local=local_variables)
else:
code.InteractiveConsole(locals=local_variables).runcode(args.code)
@staticmethod
def _set_service_shell_parser(root: SubParserAction) -> argparse.ArgumentParser:
"""
add parser for shell subcommand
Args:
root(SubParserAction): subparsers for the commands
Returns:
argparse.ArgumentParser: created argument parser
"""
parser = root.add_parser("service-shell", aliases=["shell"], help="invoke python shell",
description="drop into python shell")
parser.add_argument("code", help="instead of dropping into shell, just execute the specified code", nargs="?")
parser.add_argument("-v", "--verbose", help=argparse.SUPPRESS, action="store_true")
parser.set_defaults(lock=None, report=False)
return parser
arguments = [_set_service_shell_parser]

View File

@ -30,23 +30,6 @@ class Sign(Handler):
(re-)sign handler
"""
@classmethod
def arguments(cls, root: SubParserAction) -> list[argparse.ArgumentParser]:
"""
add parser(s) for the subcommand
Args:
root(SubParserAction): subparsers for the commands
Returns:
list[argparse.ArgumentParser]: created argument parser
"""
parser = root.add_parser("repo-sign", aliases=["sign"], help="sign packages",
description="(re-)sign packages and repository database according to current settings",
epilog="Sign repository and/or packages as configured.")
parser.add_argument("package", help="sign only specified packages", nargs="*")
return [parser]
@classmethod
def run(cls, args: argparse.Namespace, repository_id: RepositoryId, configuration: Configuration, *,
report: bool) -> None:
@ -60,3 +43,22 @@ class Sign(Handler):
report(bool): force enable or disable reporting
"""
Application(repository_id, configuration, report=report).sign(args.package)
@staticmethod
def _set_repo_sign_parser(root: SubParserAction) -> argparse.ArgumentParser:
"""
add parser for sign subcommand
Args:
root(SubParserAction): subparsers for the commands
Returns:
argparse.ArgumentParser: created argument parser
"""
parser = root.add_parser("repo-sign", aliases=["sign"], help="sign packages",
description="(re-)sign packages and repository database according to current settings",
epilog="Sign repository and/or packages as configured.")
parser.add_argument("package", help="sign only specified packages", nargs="*")
return parser
arguments = [_set_repo_sign_parser]

View File

@ -40,30 +40,6 @@ class Statistics(Handler):
ALLOW_MULTI_ARCHITECTURE_RUN = False # conflicting io
@classmethod
def arguments(cls, root: SubParserAction) -> list[argparse.ArgumentParser]:
"""
add parser(s) for the subcommand
Args:
root(SubParserAction): subparsers for the commands
Returns:
list[argparse.ArgumentParser]: created argument parser
"""
parser = root.add_parser("repo-statistics", help="repository statistics",
description="fetch repository statistics")
parser.add_argument("package", help="fetch only events for the specified package", nargs="?")
parser.add_argument("--chart", help="create updates chart and save it to the specified path", type=Path)
parser.add_argument("-e", "--event", help="event type filter",
type=EventType, choices=enum_values(EventType), default=EventType.PackageUpdated)
parser.add_argument("--from-date", help="only fetch events which are newer than the date")
parser.add_argument("--limit", help="limit response by specified amount of events", type=int, default=-1)
parser.add_argument("--offset", help="skip specified amount of events", type=int, default=0)
parser.add_argument("--to-date", help="only fetch events which are older than the date")
parser.set_defaults(lock=None, quiet=True, report=False, unsafe=True)
return [parser]
@classmethod
def run(cls, args: argparse.Namespace, repository_id: RepositoryId, configuration: Configuration, *,
report: bool) -> None:
@ -92,6 +68,30 @@ class Statistics(Handler):
case _:
Statistics.stats_for_package(args.event, events, args.chart)
@staticmethod
def _set_repo_statistics_parser(root: SubParserAction) -> argparse.ArgumentParser:
"""
add parser for repository statistics subcommand
Args:
root(SubParserAction): subparsers for the commands
Returns:
argparse.ArgumentParser: created argument parser
"""
parser = root.add_parser("repo-statistics", help="repository statistics",
description="fetch repository statistics")
parser.add_argument("package", help="fetch only events for the specified package", nargs="?")
parser.add_argument("--chart", help="create updates chart and save it to the specified path", type=Path)
parser.add_argument("-e", "--event", help="event type filter",
type=EventType, choices=enum_values(EventType), default=EventType.PackageUpdated)
parser.add_argument("--from-date", help="only fetch events which are newer than the date")
parser.add_argument("--limit", help="limit response by specified amount of events", type=int, default=-1)
parser.add_argument("--offset", help="skip specified amount of events", type=int, default=0)
parser.add_argument("--to-date", help="only fetch events which are older than the date")
parser.set_defaults(lock=None, quiet=True, report=False, unsafe=True)
return parser
@staticmethod
def event_stats(event_type: str, events: list[Event]) -> None:
"""
@ -192,3 +192,5 @@ class Statistics(Handler):
# chart if enabled
if chart_path is not None:
Statistics.plot_packages(event_type, by_object_id, chart_path)
arguments = [_set_repo_statistics_parser]

View File

@ -38,32 +38,6 @@ class Status(Handler):
ALLOW_MULTI_ARCHITECTURE_RUN = False # conflicting io
@classmethod
def arguments(cls, root: SubParserAction) -> list[argparse.ArgumentParser]:
"""
add parser(s) for the subcommand
Args:
root(SubParserAction): subparsers for the commands
Returns:
list[argparse.ArgumentParser]: created argument parser
"""
parser = root.add_parser("package-status", aliases=["status"], help="get package status",
description="request status of the package",
epilog="This command requests package status from the web interface "
"if it is available.")
parser.add_argument("package", help="filter status by package base", nargs="*")
parser.add_argument("--ahriman", help="get service status itself", action="store_true")
parser.add_argument("-e", "--exit-code", help="return non-zero exit status if result is empty",
action="store_true")
parser.add_argument("--info", help="show additional package information",
action=argparse.BooleanOptionalAction, default=False)
parser.add_argument("-s", "--status", help="filter packages by status",
type=BuildStatusEnum, choices=enum_values(BuildStatusEnum))
parser.set_defaults(lock=None, quiet=True, report=False, unsafe=True)
return [parser]
@classmethod
def run(cls, args: argparse.Namespace, repository_id: RepositoryId, configuration: Configuration, *,
report: bool) -> None:
@ -95,3 +69,31 @@ class Status(Handler):
lambda item: args.status is None or item[1].status == args.status
for package, package_status in sorted(filter(filter_fn, packages), key=comparator):
PackagePrinter(package, package_status)(verbose=args.info)
@staticmethod
def _set_package_status_parser(root: SubParserAction) -> argparse.ArgumentParser:
"""
add parser for package status subcommand
Args:
root(SubParserAction): subparsers for the commands
Returns:
argparse.ArgumentParser: created argument parser
"""
parser = root.add_parser("package-status", aliases=["status"], help="get package status",
description="request status of the package",
epilog="This command requests package status from the web interface "
"if it is available.")
parser.add_argument("package", help="filter status by package base", nargs="*")
parser.add_argument("--ahriman", help="get service status itself", action="store_true")
parser.add_argument("-e", "--exit-code", help="return non-zero exit status if result is empty",
action="store_true")
parser.add_argument("--info", help="show additional package information",
action=argparse.BooleanOptionalAction, default=False)
parser.add_argument("-s", "--status", help="filter packages by status",
type=BuildStatusEnum, choices=enum_values(BuildStatusEnum))
parser.set_defaults(lock=None, quiet=True, report=False, unsafe=True)
return parser
arguments = [_set_package_status_parser]

View File

@ -35,23 +35,6 @@ class StatusUpdate(Handler):
ALLOW_MULTI_ARCHITECTURE_RUN = False # conflicting io
@classmethod
def arguments(cls, root: SubParserAction) -> list[argparse.ArgumentParser]:
"""
add parser(s) for the subcommand
Args:
root(SubParserAction): subparsers for the commands
Returns:
list[argparse.ArgumentParser]: created argument parser
"""
return [
cls._set_package_status_remove_parser(root),
cls._set_package_status_update_parser(root),
cls._set_repo_status_update_parser(root),
]
@classmethod
def run(cls, args: argparse.Namespace, repository_id: RepositoryId, configuration: Configuration, *,
report: bool) -> None:
@ -136,3 +119,9 @@ class StatusUpdate(Handler):
type=BuildStatusEnum, choices=enum_values(BuildStatusEnum), default=BuildStatusEnum.Success)
parser.set_defaults(action=Action.Update, lock=None, package=[], quiet=True, report=False, unsafe=True)
return parser
arguments = [
_set_package_status_remove_parser,
_set_package_status_update_parser,
_set_repo_status_update_parser,
]

View File

@ -34,24 +34,6 @@ class Structure(Handler):
ALLOW_MULTI_ARCHITECTURE_RUN = False # conflicting io
@classmethod
def arguments(cls, root: SubParserAction) -> list[argparse.ArgumentParser]:
"""
add parser(s) for the subcommand
Args:
root(SubParserAction): subparsers for the commands
Returns:
list[argparse.ArgumentParser]: created argument parser
"""
parser = root.add_parser("repo-tree", help="dump repository tree",
description="dump repository tree based on packages dependencies")
parser.add_argument("-p", "--partitions", help="also divide packages by independent partitions",
type=int, default=1)
parser.set_defaults(lock=None, quiet=True, report=False, unsafe=True)
return [parser]
@classmethod
def run(cls, args: argparse.Namespace, repository_id: RepositoryId, configuration: Configuration, *,
report: bool) -> None:
@ -76,3 +58,23 @@ class Structure(Handler):
# empty line
StringPrinter("")(verbose=False)
@staticmethod
def _set_repo_tree_parser(root: SubParserAction) -> argparse.ArgumentParser:
"""
add parser for repository tree subcommand
Args:
root(SubParserAction): subparsers for the commands
Returns:
argparse.ArgumentParser: created argument parser
"""
parser = root.add_parser("repo-tree", help="dump repository tree",
description="dump repository tree based on packages dependencies")
parser.add_argument("-p", "--partitions", help="also divide packages by independent partitions",
type=int, default=1)
parser.set_defaults(lock=None, quiet=True, report=False, unsafe=True)
return parser
arguments = [_set_repo_tree_parser]

View File

@ -30,22 +30,6 @@ class TreeMigrate(Handler):
tree migration handler
"""
@classmethod
def arguments(cls, root: SubParserAction) -> list[argparse.ArgumentParser]:
"""
add parser(s) for the subcommand
Args:
root(SubParserAction): subparsers for the commands
Returns:
list[argparse.ArgumentParser]: created argument parser
"""
parser = root.add_parser("service-tree-migrate", help="migrate repository tree",
description="migrate repository tree between versions")
parser.set_defaults(lock=None, quiet=True, report=False)
return [parser]
@classmethod
def run(cls, args: argparse.Namespace, repository_id: RepositoryId, configuration: Configuration, *,
report: bool) -> None:
@ -66,6 +50,22 @@ class TreeMigrate(Handler):
# perform migration
TreeMigrate.tree_move(current_tree, target_tree)
@staticmethod
def _set_service_tree_migrate_parser(root: SubParserAction) -> argparse.ArgumentParser:
"""
add parser for tree migration subcommand
Args:
root(SubParserAction): subparsers for the commands
Returns:
argparse.ArgumentParser: created argument parser
"""
parser = root.add_parser("service-tree-migrate", help="migrate repository tree",
description="migrate repository tree between versions")
parser.set_defaults(lock=None, quiet=True, report=False)
return parser
@staticmethod
def tree_move(from_tree: RepositoryPaths, to_tree: RepositoryPaths) -> None:
"""
@ -82,3 +82,5 @@ class TreeMigrate(Handler):
RepositoryPaths.repository,
):
attribute.fget(from_tree).rename(attribute.fget(to_tree)) # type: ignore[attr-defined]
arguments = [_set_service_tree_migrate_parser]

View File

@ -31,25 +31,6 @@ class Triggers(Handler):
triggers handlers
"""
@classmethod
def arguments(cls, root: SubParserAction) -> list[argparse.ArgumentParser]:
"""
add parser(s) for the subcommand
Args:
root(SubParserAction): subparsers for the commands
Returns:
list[argparse.ArgumentParser]: created argument parser
"""
return [
cls._set_repo_create_keyring_parser(root),
cls._set_repo_create_mirrorlist_parser(root),
cls._set_repo_report_parser(root),
cls._set_repo_sync_parser(root),
cls._set_repo_triggers_parser(root),
]
@classmethod
def run(cls, args: argparse.Namespace, repository_id: RepositoryId, configuration: Configuration, *,
report: bool) -> None:
@ -155,3 +136,11 @@ class Triggers(Handler):
parser.add_argument("trigger", help="instead of running all triggers as set by configuration, just process "
"specified ones in order of mention", nargs="*")
return parser
arguments = [
_set_repo_create_keyring_parser,
_set_repo_create_mirrorlist_parser,
_set_repo_report_parser,
_set_repo_sync_parser,
_set_repo_triggers_parser,
]

View File

@ -32,25 +32,6 @@ class UnsafeCommands(Handler):
ALLOW_MULTI_ARCHITECTURE_RUN = False # system-wide action
@classmethod
def arguments(cls, root: SubParserAction) -> list[argparse.ArgumentParser]:
"""
add parser(s) for the subcommand
Args:
root(SubParserAction): subparsers for the commands
Returns:
list[argparse.ArgumentParser]: created argument parser
"""
parser = root.add_parser("help-commands-unsafe", help="list unsafe commands",
description="list unsafe commands as defined in default args")
parser.add_argument("subcommand",
help="instead of showing commands, just test command line for unsafe subcommand "
"and return 0 in case if command is safe and 1 otherwise", nargs="*")
parser.set_defaults(architecture="", lock=None, quiet=True, report=False, repository="", unsafe=True)
return [parser]
@classmethod
def run(cls, args: argparse.Namespace, repository_id: RepositoryId, configuration: Configuration, *,
report: bool) -> None:
@ -71,6 +52,25 @@ class UnsafeCommands(Handler):
for command in unsafe_commands:
StringPrinter(command)(verbose=True)
@staticmethod
def _set_help_commands_unsafe_parser(root: SubParserAction) -> argparse.ArgumentParser:
"""
add parser for listing unsafe commands
Args:
root(SubParserAction): subparsers for the commands
Returns:
argparse.ArgumentParser: created argument parser
"""
parser = root.add_parser("help-commands-unsafe", help="list unsafe commands",
description="list unsafe commands as defined in default args")
parser.add_argument("subcommand",
help="instead of showing commands, just test command line for unsafe subcommand "
"and return 0 in case if command is safe and 1 otherwise", nargs="*")
parser.set_defaults(architecture="", lock=None, quiet=True, report=False, repository="", unsafe=True)
return parser
@staticmethod
def check_unsafe(command: list[str], unsafe_commands: list[str], parser: argparse.ArgumentParser) -> None:
"""
@ -100,3 +100,5 @@ class UnsafeCommands(Handler):
subparser = next((action for action in parser._actions if isinstance(action, argparse._SubParsersAction)), None)
actions = subparser.choices if subparser is not None else {}
return sorted(action_name for action_name, action in actions.items() if action.get_default("unsafe"))
arguments = [_set_help_commands_unsafe_parser]

View File

@ -34,19 +34,6 @@ class Update(Handler):
package update handler
"""
@classmethod
def arguments(cls, root: SubParserAction) -> list[argparse.ArgumentParser]:
"""
add parser(s) for the subcommand
Args:
root(SubParserAction): subparsers for the commands
Returns:
list[argparse.ArgumentParser]: created argument parser
"""
return [cls._set_repo_check_parser(root), cls._set_repo_update_parser(root)]
@classmethod
def run(cls, args: argparse.Namespace, repository_id: RepositoryId, configuration: Configuration, *,
report: bool) -> None:
@ -165,3 +152,8 @@ class Update(Handler):
def inner(line: str) -> None:
return print(line) if dry_run else application.logger.info(line) # pylint: disable=bad-builtin
return inner
arguments = [
_set_repo_check_parser,
_set_repo_update_parser,
]

View File

@ -39,23 +39,6 @@ class Users(Handler):
ALLOW_MULTI_ARCHITECTURE_RUN = False # system-wide action
@classmethod
def arguments(cls, root: SubParserAction) -> list[argparse.ArgumentParser]:
"""
add parser(s) for the subcommand
Args:
root(SubParserAction): subparsers for the commands
Returns:
list[argparse.ArgumentParser]: created argument parser
"""
return [
cls._set_user_add_parser(root),
cls._set_user_list_parser(root),
cls._set_user_remove_parser(root),
]
@classmethod
def run(cls, args: argparse.Namespace, repository_id: RepositoryId, configuration: Configuration, *,
report: bool) -> None:
@ -178,3 +161,9 @@ class Users(Handler):
return User(username=args.username, password=password, access=args.role,
packager_id=args.packager, key=args.key)
arguments = [
_set_user_add_parser,
_set_user_list_parser,
_set_user_remove_parser,
]

View File

@ -38,25 +38,6 @@ class Validate(Handler):
ALLOW_MULTI_ARCHITECTURE_RUN = False # conflicting io
@classmethod
def arguments(cls, root: SubParserAction) -> list[argparse.ArgumentParser]:
"""
add parser(s) for the subcommand
Args:
root(SubParserAction): subparsers for the commands
Returns:
list[argparse.ArgumentParser]: created argument parser
"""
parser = root.add_parser("service-config-validate", aliases=["config-validate", "repo-config-validate"],
help="validate system configuration",
description="validate configuration and print found errors")
parser.add_argument("-e", "--exit-code", help="return non-zero exit status if configuration is invalid",
action="store_true")
parser.set_defaults(lock=None, quiet=True, report=False, unsafe=True)
return [parser]
@classmethod
def run(cls, args: argparse.Namespace, repository_id: RepositoryId, configuration: Configuration, *,
report: bool) -> None:
@ -82,6 +63,25 @@ class Validate(Handler):
# as we reach this part it means that we always have errors
Validate.check_status(args.exit_code, False)
@staticmethod
def _set_service_config_validate_parser(root: SubParserAction) -> argparse.ArgumentParser:
"""
add parser for config validation subcommand
Args:
root(SubParserAction): subparsers for the commands
Returns:
argparse.ArgumentParser: created argument parser
"""
parser = root.add_parser("service-config-validate", aliases=["config-validate", "repo-config-validate"],
help="validate system configuration",
description="validate configuration and print found errors")
parser.add_argument("-e", "--exit-code", help="return non-zero exit status if configuration is invalid",
action="store_true")
parser.set_defaults(lock=None, quiet=True, report=False, unsafe=True)
return parser
@staticmethod
def schema(repository_id: RepositoryId, configuration: Configuration) -> ConfigurationSchema:
"""
@ -155,3 +155,5 @@ class Validate(Handler):
Validate.schema_merge(value, schema[key])
return schema
arguments = [_set_service_config_validate_parser]

View File

@ -42,22 +42,6 @@ class Versions(Handler):
ALLOW_MULTI_ARCHITECTURE_RUN = False # system-wide action
PEP423_PACKAGE_NAME = re.compile(r"^[A-Za-z0-9._-]+")
@classmethod
def arguments(cls, root: SubParserAction) -> list[argparse.ArgumentParser]:
"""
add parser(s) for the subcommand
Args:
root(SubParserAction): subparsers for the commands
Returns:
list[argparse.ArgumentParser]: created argument parser
"""
parser = root.add_parser("help-version", aliases=["version"], help="application version",
description="print application and its dependencies versions")
parser.set_defaults(architecture="", lock=None, quiet=True, report=False, repository="", unsafe=True)
return [parser]
@classmethod
def run(cls, args: argparse.Namespace, repository_id: RepositoryId, configuration: Configuration, *,
report: bool) -> None:
@ -75,6 +59,22 @@ class Versions(Handler):
packages = Versions.package_dependencies("ahriman")
VersionPrinter("Installed packages", dict(packages))(verbose=False, separator=" ")
@staticmethod
def _set_help_version_parser(root: SubParserAction) -> argparse.ArgumentParser:
"""
add parser for version subcommand
Args:
root(SubParserAction): subparsers for the commands
Returns:
argparse.ArgumentParser: created argument parser
"""
parser = root.add_parser("help-version", aliases=["version"], help="application version",
description="print application and its dependencies versions")
parser.set_defaults(architecture="", lock=None, quiet=True, report=False, repository="", unsafe=True)
return parser
@staticmethod
def package_dependencies(root: str) -> Generator[tuple[str, str], None, None]:
"""
@ -112,3 +112,5 @@ class Versions(Handler):
yield distribution.name, distribution.version
except metadata.PackageNotFoundError:
continue
arguments = [_set_help_version_parser]

View File

@ -36,21 +36,6 @@ class Web(Handler):
ALLOW_MULTI_ARCHITECTURE_RUN = False # system-wide action
@classmethod
def arguments(cls, root: SubParserAction) -> list[argparse.ArgumentParser]:
"""
add parser(s) for the subcommand
Args:
root(SubParserAction): subparsers for the commands
Returns:
list[argparse.ArgumentParser]: created argument parser
"""
parser = root.add_parser("web", help="web server", description="start web server")
parser.set_defaults(architecture="", lock=Path("ahriman-web.pid"), report=False, repository="")
return [parser]
@classmethod
def run(cls, args: argparse.Namespace, repository_id: RepositoryId, configuration: Configuration, *,
report: bool) -> None:
@ -87,6 +72,21 @@ class Web(Handler):
spawner.stop()
spawner.join()
@staticmethod
def _set_web_parser(root: SubParserAction) -> argparse.ArgumentParser:
"""
add parser for web subcommand
Args:
root(SubParserAction): subparsers for the commands
Returns:
argparse.ArgumentParser: created argument parser
"""
parser = root.add_parser("web", help="web server", description="start web server")
parser.set_defaults(architecture="", lock=Path("ahriman-web.pid"), report=False, repository="")
return parser
@staticmethod
def extract_arguments(args: argparse.Namespace, configuration: Configuration) -> Generator[str, None, None]:
"""
@ -116,3 +116,5 @@ class Web(Handler):
# arguments from configuration
if (wait_timeout := configuration.getint("web", "wait_timeout", fallback=None)) is not None:
yield from ["--wait-timeout", str(wait_timeout)]
arguments = [_set_web_parser]

View File

@ -17,8 +17,9 @@
# 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 bcrypt
from dataclasses import dataclass, replace
from passlib.hash import sha512_crypt
from secrets import token_urlsafe as generate_password
from typing import Self
@ -67,8 +68,6 @@ class User:
packager_id: str | None = None
key: str | None = None
_HASHER = sha512_crypt
def __post_init__(self) -> None:
"""
remove empty fields
@ -101,10 +100,9 @@ class User:
bool: ``True`` in case if password matches, ``False`` otherwise
"""
try:
verified: bool = self._HASHER.verify(password + salt, self.password)
return bcrypt.checkpw((password + salt).encode("utf8"), self.password.encode("utf8"))
except ValueError:
verified = False # the absence of evidence is not the evidence of absence (c) Gin Rummy
return verified
return False # the absence of evidence is not the evidence of absence (c) Gin Rummy
def hash_password(self, salt: str) -> Self:
"""
@ -120,8 +118,8 @@ class User:
# in case of empty password we leave it empty. This feature is used by any external (like OAuth) provider
# when we do not store any password here
return self
password_hash: str = self._HASHER.hash(self.password + salt)
return replace(self, password=password_hash)
password_hash = bcrypt.hashpw((self.password + salt).encode("utf8"), bcrypt.gensalt())
return replace(self, password=password_hash.decode("utf8"))
def verify_access(self, required: UserAccess) -> bool:
"""

View File

@ -3,7 +3,6 @@ import pytest
from pathlib import Path
from pytest_mock import MockerFixture
from unittest.mock import MagicMock
from ahriman.application.handlers.handler import Handler
from ahriman.core.configuration import Configuration
@ -12,14 +11,6 @@ from ahriman.models.log_handler import LogHandler
from ahriman.models.repository_id import RepositoryId
def test_arguments() -> None:
"""
must raise NotImplemented for missing arguments method
"""
with pytest.raises(NotImplementedError):
Handler.arguments(MagicMock())
def test_call(args: argparse.Namespace, configuration: Configuration, mocker: MockerFixture) -> None:
"""
must call inside lock