mirror of
https://github.com/arcan1s/ahriman.git
synced 2025-04-24 07:17:17 +00:00
feat: allow to use single web instance for all repositories (#114)
* Allow to use single web instance for any repository * some improvements * drop includes from user home directory, introduce new variables to docker The old solution didn't actually work as expected, because devtools configuration belongs to filesystem (as well as sudo one), so it was still required to run setup command. In order to handle additional repositories, the POSTSETUP and PRESETUP commands variables have been introduced. FAQ has been updated as well * raise 404 in case if repository is unknown
This commit is contained in:
parent
bf9a46936c
commit
1e00bf9398
2
.github/workflows/setup.sh
vendored
2
.github/workflows/setup.sh
vendored
@ -42,7 +42,7 @@ ahriman -a x86_64 -r "github" service-setup --packager "ahriman bot <ahriman@exa
|
||||
# validate configuration
|
||||
ahriman service-config-validate --exit-code
|
||||
# enable services
|
||||
systemctl enable ahriman-web@x86_64-github
|
||||
systemctl enable ahriman-web
|
||||
systemctl enable ahriman@x86_64-github.timer
|
||||
if [[ -z $MINIMAL_INSTALL ]]; then
|
||||
# run web service (detached)
|
||||
|
@ -202,6 +202,7 @@ Again, the most checks can be performed by `make check` command, though some add
|
||||
400: {"description": "Bad data is supplied", "schema": ErrorSchema}, # exception raised by this method
|
||||
401: {"description": "Authorization required", "schema": ErrorSchema}, # should be always presented
|
||||
403: {"description": "Access is forbidden", "schema": ErrorSchema}, # should be always presented
|
||||
404: {"description": "Repository is unknown", "schema": ErrorSchema}, # include if BaseView.service() method is called
|
||||
500: {"description": "Internal server error", "schema": ErrorSchema}, # should be always presented
|
||||
},
|
||||
security=[{"token": [POST_PERMISSION]}],
|
||||
|
@ -10,6 +10,8 @@ ENV AHRIMAN_OUTPUT=""
|
||||
ENV AHRIMAN_PACKAGER="ahriman bot <ahriman@example.com>"
|
||||
ENV AHRIMAN_PACMAN_MIRROR=""
|
||||
ENV AHRIMAN_PORT=""
|
||||
ENV AHRIMAN_POSTSETUP_COMMAND=""
|
||||
ENV AHRIMAN_PRESETUP_COMMAND=""
|
||||
ENV AHRIMAN_REPOSITORY="aur-clone"
|
||||
ENV AHRIMAN_REPOSITORY_SERVER=""
|
||||
ENV AHRIMAN_REPOSITORY_ROOT="/var/lib/ahriman/ahriman"
|
||||
|
@ -49,7 +49,10 @@ fi
|
||||
if [ -n "$AHRIMAN_UNIX_SOCKET" ]; then
|
||||
AHRIMAN_SETUP_ARGS+=("--web-unix-socket" "$AHRIMAN_UNIX_SOCKET")
|
||||
fi
|
||||
|
||||
[ -n "$AHRIMAN_PRESETUP_COMMAND" ] && eval "$AHRIMAN_PRESETUP_COMMAND"
|
||||
ahriman "${AHRIMAN_DEFAULT_ARGS[@]}" service-setup "${AHRIMAN_SETUP_ARGS[@]}"
|
||||
[ -n "$AHRIMAN_POSTSETUP_COMMAND" ] && eval "$AHRIMAN_POSTSETUP_COMMAND"
|
||||
|
||||
# validate configuration if set
|
||||
[ -n "$AHRIMAN_VALIDATE_CONFIGURATION" ] && ahriman "${AHRIMAN_DEFAULT_ARGS[@]}" service-config-validate --exit-code
|
||||
|
@ -100,6 +100,14 @@ ahriman.application.handlers.remove\_unknown module
|
||||
:no-undoc-members:
|
||||
:show-inheritance:
|
||||
|
||||
ahriman.application.handlers.repositories module
|
||||
------------------------------------------------
|
||||
|
||||
.. automodule:: ahriman.application.handlers.repositories
|
||||
:members:
|
||||
:no-undoc-members:
|
||||
:show-inheritance:
|
||||
|
||||
ahriman.application.handlers.restore module
|
||||
-------------------------------------------
|
||||
|
||||
|
@ -60,6 +60,14 @@ ahriman.core.formatters.printer module
|
||||
:no-undoc-members:
|
||||
:show-inheritance:
|
||||
|
||||
ahriman.core.formatters.repository\_printer module
|
||||
--------------------------------------------------
|
||||
|
||||
.. automodule:: ahriman.core.formatters.repository_printer
|
||||
:members:
|
||||
:no-undoc-members:
|
||||
:show-inheritance:
|
||||
|
||||
ahriman.core.formatters.status\_printer module
|
||||
----------------------------------------------
|
||||
|
||||
|
@ -140,6 +140,14 @@ ahriman.models.pkgbuild\_patch module
|
||||
:no-undoc-members:
|
||||
:show-inheritance:
|
||||
|
||||
ahriman.models.process\_status module
|
||||
-------------------------------------
|
||||
|
||||
.. automodule:: ahriman.models.process_status
|
||||
:members:
|
||||
:no-undoc-members:
|
||||
:show-inheritance:
|
||||
|
||||
ahriman.models.property module
|
||||
------------------------------
|
||||
|
||||
|
@ -172,6 +172,14 @@ ahriman.web.schemas.remote\_schema module
|
||||
:no-undoc-members:
|
||||
:show-inheritance:
|
||||
|
||||
ahriman.web.schemas.repository\_id\_schema module
|
||||
-------------------------------------------------
|
||||
|
||||
.. automodule:: ahriman.web.schemas.repository_id_schema
|
||||
:members:
|
||||
:no-undoc-members:
|
||||
:show-inheritance:
|
||||
|
||||
ahriman.web.schemas.search\_schema module
|
||||
-----------------------------------------
|
||||
|
||||
|
@ -28,6 +28,14 @@ ahriman.web.views.v1.status.packages module
|
||||
:no-undoc-members:
|
||||
:show-inheritance:
|
||||
|
||||
ahriman.web.views.v1.status.repositories module
|
||||
-----------------------------------------------
|
||||
|
||||
.. automodule:: ahriman.web.views.v1.status.repositories
|
||||
:members:
|
||||
:no-undoc-members:
|
||||
:show-inheritance:
|
||||
|
||||
ahriman.web.views.v1.status.status module
|
||||
-----------------------------------------
|
||||
|
||||
|
@ -41,7 +41,7 @@ Base configuration settings.
|
||||
|
||||
* ``apply_migrations`` - perform migrations on application start, boolean, optional, default ``yes``. Useful if you are using git version. Note, however, that this option must be changed only if you know what to do and going to handle migrations automatically.
|
||||
* ``database`` - path to SQLite database, string, required.
|
||||
* ``include`` - path to directory with configuration files overrides, string, required.
|
||||
* ``include`` - path to directory with configuration files overrides, string, optional.
|
||||
* ``logging`` - path to logging configuration, string, required. Check ``logging.ini`` for reference.
|
||||
* ``suppress_http_log_errors`` - suppress http log errors, boolean, optional, default ``no``. If set to ``yes``, any http log errors (e.g. if web server is not available, but http logging is enabled) will be suppressed.
|
||||
|
||||
@ -103,10 +103,10 @@ Settings for signing packages or repository. Group name can refer to architectur
|
||||
* ``target`` - configuration flag to enable signing, space separated list of strings, required. Allowed values are ``package`` (sign each package separately), ``repository`` (sign repository database file).
|
||||
* ``key`` - default PGP key, string, required. This key will also be used for database signing if enabled.
|
||||
|
||||
``web:*`` groups
|
||||
----------------
|
||||
``web`` group
|
||||
-------------
|
||||
|
||||
Web server settings. If any of ``host``/``port`` is not set, web integration will be disabled. Group name can refer to architecture, e.g. ``web:x86_64`` can be used for x86_64 architecture specific settings. This feature requires ``aiohttp`` libraries to be installed.
|
||||
Web server settings. If any of ``host``/``port`` is not set, web integration will be disabled. This feature requires ``aiohttp`` libraries to be installed.
|
||||
|
||||
* ``address`` - optional address in form ``proto://host:port`` (``port`` can be omitted in case of default ``proto`` ports), will be used instead of ``http://{host}:{port}`` in case if set, string, optional. This option is required in case if ``OAuth`` provider is used.
|
||||
* ``debug`` - enable debug toolbar, boolean, optional, default ``no``.
|
||||
|
24
docs/faq.rst
24
docs/faq.rst
@ -402,6 +402,8 @@ The following environment variables are supported:
|
||||
* ``AHRIMAN_PACKAGER`` - packager name from which packages will be built, default is ``ahriman bot <ahriman@example.com>``.
|
||||
* ``AHRIMAN_PACMAN_MIRROR`` - override pacman mirror server if set.
|
||||
* ``AHRIMAN_PORT`` - HTTP server port if any, default is empty.
|
||||
* ``AHRIMAN_POSTSETUP_COMMAND`` - if set, the command which will be called (as root) after the setup command, but before any other actions.
|
||||
* ``AHRIMAN_PRESETUP_COMMAND`` - if set, the command which will be called (as root) right before the setup command.
|
||||
* ``AHRIMAN_REPOSITORY`` - repository name, default is ``aur-clone``.
|
||||
* ``AHRIMAN_REPOSITORY_SERVER`` - optional override for the repository url. Useful if you would like to download packages from remote instead of local filesystem.
|
||||
* ``AHRIMAN_REPOSITORY_ROOT`` - repository root. Because of filesystem rights it is required to override default repository root. By default, it uses ``ahriman`` directory inside ahriman's home, which can be passed as mount volume.
|
||||
@ -429,7 +431,7 @@ This command uses same rules as ``repo-update``, thus, e.g. requires ``--privile
|
||||
Web service setup
|
||||
^^^^^^^^^^^^^^^^^
|
||||
|
||||
Well for that you would need to have web container instance running forever; it can be achieved by the following command:
|
||||
For that you would need to have web container instance running forever; it can be achieved by the following command:
|
||||
|
||||
.. code-block:: shell
|
||||
|
||||
@ -451,6 +453,20 @@ Otherwise, you would need to pass ``AHRIMAN_PORT`` and mount container network t
|
||||
|
||||
docker run --privileged --net=host -e AHRIMAN_PORT=8080 -v /path/to/local/repo:/var/lib/ahriman arcan1s/ahriman:latest
|
||||
|
||||
Mutli-repository web service
|
||||
""""""""""""""""""""""""""""
|
||||
|
||||
Idea is pretty same as to just run web service. However, it is required to run setup commands for each repository, except for one which is specified by ``AHRIMAN_REPOSITORY`` and ``AHRIMAN_ARCHITECTURE`` variables.
|
||||
|
||||
In order to create configuration for additional repositories, the ``AHRIMAN_POSTSETUP_COMMAND`` variable should be used, e.g.:
|
||||
|
||||
.. code-block:: shell
|
||||
|
||||
docker run --privileged -p 8080:8080 -e AHRIMAN_PORT=8080 -e AHRIMAN_UNIX_SOCKET=/var/lib/ahriman/ahriman/ahriman-web.sock -e AHRIMAN_POSTSETUP_COMMAND="ahriman --architecture x86_64 --repository aur-clone-v2 service-setup --build-as-user ahriman --packager 'ahriman bot <ahriman@example.com>'" -v /path/to/local/repo:/var/lib/ahriman arcan1s/ahriman:latest
|
||||
|
||||
The command above will also create configuration for the repository named ``aur-clone-v2``.
|
||||
|
||||
Note, however, that the command above is only required in case if the service is going to be used to run subprocesses. Otherwise, everything else (web interface, status, etc) will be handled as usual.
|
||||
|
||||
Non-x86_64 architecture setup
|
||||
-----------------------------
|
||||
@ -1073,7 +1089,7 @@ How to setup web service
|
||||
port = 8080
|
||||
|
||||
#.
|
||||
Start the web service ``systemctl enable --now ahriman-web@x86_64-aur-clone``.
|
||||
Start the web service ``systemctl enable --now ahriman-web``.
|
||||
|
||||
How to enable basic authorization
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
@ -1130,7 +1146,7 @@ How to enable basic authorization
|
||||
sudo -u ahriman ahriman user-add -r full my-first-user
|
||||
|
||||
#.
|
||||
Restart web service ``systemctl restart ahriman-web@x86_64-aur-clone``.
|
||||
Restart web service ``systemctl restart ahriman-web``.
|
||||
|
||||
How to enable OAuth authorization
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
@ -1176,7 +1192,7 @@ How to enable OAuth authorization
|
||||
When it will ask for the password leave it blank.
|
||||
|
||||
#.
|
||||
Restart web service ``systemctl restart ahriman-web@x86_64-aur-clone``.
|
||||
Restart web service ``systemctl restart ahriman-web``.
|
||||
|
||||
How to implement own interface
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
@ -54,6 +54,8 @@ In order to migrate to new filesystem tree the following actions are required:
|
||||
|
||||
Alternatively it can be done by running ``service-setup`` command again.
|
||||
|
||||
#. If you didn't run setup command on the previous step, make sure to remove architecture reference from ``web`` section (if any).
|
||||
|
||||
#.
|
||||
Make sure to update remote synchronization services if any. Almost all of them rely on current repository tree by default, so you need to setup either redirects or configure to synchronize to the old locations (e.g. ``object_path`` option for S3 synchronization).
|
||||
|
||||
@ -63,4 +65,4 @@ In order to migrate to new filesystem tree the following actions are required:
|
||||
.. code-block:: shell
|
||||
|
||||
sudo systemctl enable --now ahriman@x86_64-aur-clone.timer
|
||||
sudo systemctl enable --now ahriman-web@x86_64-aur-clone
|
||||
sudo systemctl enable --now ahriman-web
|
||||
|
@ -81,7 +81,7 @@ Initial setup
|
||||
|
||||
.. code-block:: shell
|
||||
|
||||
systemctl enable --now ahriman-web@x86_64-aur-clone
|
||||
systemctl enable --now ahriman-web
|
||||
|
||||
#.
|
||||
Add packages by using ``ahriman package-add {package}`` command:
|
||||
|
@ -46,7 +46,7 @@ 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 -exec install -Dm644 "{}" "$pkgdir/usr/{}" \; && popd
|
||||
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"
|
||||
|
12
package/lib/systemd/system/ahriman-web.service
Normal file
12
package/lib/systemd/system/ahriman-web.service
Normal file
@ -0,0 +1,12 @@
|
||||
[Unit]
|
||||
Description=ArcH linux ReposItory MANager web server
|
||||
After=network.target
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
ExecStart=/usr/bin/ahriman web
|
||||
User=ahriman
|
||||
Group=ahriman
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
@ -1,12 +0,0 @@
|
||||
[Unit]
|
||||
Description=ArcH linux ReposItory MANager web server (%i)
|
||||
After=network.target
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
ExecStart=/usr/bin/ahriman --repository-id "%I" web
|
||||
User=ahriman
|
||||
Group=ahriman
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
1
package/lib/systemd/system/ahriman-web@.service
Symbolic link
1
package/lib/systemd/system/ahriman-web@.service
Symbolic link
@ -0,0 +1 @@
|
||||
ahriman-web.service
|
@ -1,7 +1,7 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<title>{{ repository }}</title>
|
||||
<title>ahriman</title>
|
||||
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
|
||||
@ -16,7 +16,22 @@
|
||||
{% include "utils/bootstrap-scripts.jinja2" %}
|
||||
|
||||
<div class="container">
|
||||
<h1 id="badge-repository">ahriman</h1>
|
||||
<nav class="navbar navbar-expand-lg">
|
||||
<div class="navbar-brand"><img src="/static/logo.svg" width="30" height="30" alt=""> ahriman</div>
|
||||
<button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbar-supported-content" aria-controls="navbar-supported-content" aria-expanded="false" aria-label="Toggle navigation">
|
||||
<span class="navbar-toggler-icon"></span>
|
||||
</button>
|
||||
|
||||
<div id="navbar-supported-content" class="collapse navbar-collapse">
|
||||
<ul id="repositories" class="nav nav-tabs">
|
||||
{% for repository in repositories %}
|
||||
<li class="nav-item">
|
||||
<a id="{{ repository.id }}-lnk" class="nav-link" href="#{{ repository.id }}" data-repository="{{ repository.repository }}" data-architecture="{{ repository.architecture }}">{{ repository.repository }} ({{ repository.architecture }})</a>
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</div>
|
||||
</nav>
|
||||
</div>
|
||||
|
||||
<div id="alert-placeholder" class="toast-container p3 top-0 start-50 translate-middle-x"></div>
|
||||
@ -31,28 +46,28 @@
|
||||
</button>
|
||||
<ul class="dropdown-menu">
|
||||
<li>
|
||||
<button id="package-add-btn" class="btn dropdown-item" data-bs-toggle="modal" data-bs-target="#package-add-modal" hidden>
|
||||
<button id="package-add-button" class="btn dropdown-item" data-bs-toggle="modal" data-bs-target="#package-add-modal" hidden>
|
||||
<i class="bi bi-plus"></i> add
|
||||
</button>
|
||||
</li>
|
||||
<li>
|
||||
<button id="package-update-btn" class="btn dropdown-item" onclick="updatePackages()" hidden>
|
||||
<button id="package-update-button" class="btn dropdown-item" onclick="updatePackages()" hidden>
|
||||
<i class="bi bi-play"></i> update
|
||||
</button>
|
||||
</li>
|
||||
<li>
|
||||
<button id="package-rebuild-btn" class="btn dropdown-item" data-bs-toggle="modal" data-bs-target="#package-rebuild-modal" hidden>
|
||||
<button id="package-rebuild-button" class="btn dropdown-item" data-bs-toggle="modal" data-bs-target="#package-rebuild-modal" hidden>
|
||||
<i class="bi bi-arrow-clockwise"></i> rebuild
|
||||
</button>
|
||||
</li>
|
||||
<li>
|
||||
<button id="package-remove-btn" class="btn dropdown-item" onclick="removePackages()" disabled hidden>
|
||||
<button id="package-remove-button" class="btn dropdown-item" onclick="removePackages()" disabled hidden>
|
||||
<i class="bi bi-trash"></i> remove
|
||||
</button>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<button id="key-import-btn" type="button" class="btn btn-info" data-bs-toggle="modal" data-bs-target="#key-import-modal" hidden>
|
||||
<button id="key-import-button" type="button" class="btn btn-info" data-bs-toggle="modal" data-bs-target="#key-import-modal" hidden>
|
||||
<i class="bi bi-key"></i> import key
|
||||
</button>
|
||||
{% endif %}
|
||||
@ -117,7 +132,7 @@
|
||||
{% if auth.enabled %}
|
||||
<ul class="nav">
|
||||
{% if auth.username is none %}
|
||||
<li>{{ auth.control|safe }}</li>
|
||||
<li>{{ auth.control | safe }}</li>
|
||||
{% else %}
|
||||
<li>
|
||||
<form action="/api/v1/logout" method="post">
|
||||
|
@ -8,21 +8,21 @@
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="form-group row">
|
||||
<label for="key-fingerprint-input" class="col-sm-2 col-form-label">fingerprint</label>
|
||||
<label for="key-import-fingerprint-input" class="col-sm-2 col-form-label">fingerprint</label>
|
||||
<div class="col-sm-10">
|
||||
<input id="key-fingerprint-input" type="text" class="form-control" placeholder="PGP key fingerprint" name="key" required>
|
||||
<input id="key-import-fingerprint-input" type="text" class="form-control" placeholder="PGP key fingerprint" name="key" required>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group row">
|
||||
<label for="key-server-input" class="col-sm-2 col-form-label">key server</label>
|
||||
<label for="key-import-server-input" class="col-sm-2 col-form-label">key server</label>
|
||||
<div class="col-sm-10">
|
||||
<input id="key-server-input" type="text" class="form-control" placeholder="PGP key server" name="server" value="keyserver.ubuntu.com" required>
|
||||
<input id="key-import-server-input" type="text" class="form-control" placeholder="PGP key server" name="server" value="keyserver.ubuntu.com" required>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group row">
|
||||
<div class="col-sm-2"></div>
|
||||
<div class="col-sm-10">
|
||||
<pre class="language-less"><samp id="key-body-input" class="pre-scrollable language-less"></samp><button id="key-copy-btn" type="button" class="btn language-less" onclick="copyPgpKey()"><i class="bi bi-clipboard"></i> copy</button></pre>
|
||||
<pre class="language-less"><samp id="key-import-body-input" class="pre-scrollable language-less"></samp><button id="key-import-copy-button" type="button" class="btn language-less" onclick="copyPgpKey()"><i class="bi bi-clipboard"></i> copy</button></pre>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -39,24 +39,24 @@
|
||||
const keyImportModal = $("#key-import-modal");
|
||||
const keyImportForm = $("#key-import-form");
|
||||
keyImportModal.on("hidden.bs.modal", () => {
|
||||
keyBodyInput.text("");
|
||||
keyImportBodyInput.text("");
|
||||
keyImportForm.trigger("reset");
|
||||
});
|
||||
|
||||
const keyBodyInput = $("#key-body-input");
|
||||
const keyCopyButton = $("#key-copy-btn");
|
||||
const keyImportBodyInput = $("#key-import-body-input");
|
||||
const keyImportCopyButton = $("#key-import-copy-button");
|
||||
|
||||
const keyFingerprintInput = $("#key-fingerprint-input");
|
||||
const keyServerInput = $("#key-server-input");
|
||||
const keyImportFingerprintInput = $("#key-import-fingerprint-input");
|
||||
const keyImportServerInput = $("#key-import-server-input");
|
||||
|
||||
async function copyPgpKey() {
|
||||
const logs = keyBodyInput.text();
|
||||
await copyToClipboard(logs, keyCopyButton);
|
||||
const logs = keyImportBodyInput.text();
|
||||
await copyToClipboard(logs, keyImportCopyButton);
|
||||
}
|
||||
|
||||
function fetchPgpKey() {
|
||||
const key = keyFingerprintInput.val();
|
||||
const server = keyServerInput.val();
|
||||
const key = keyImportFingerprintInput.val();
|
||||
const server = keyImportServerInput.val();
|
||||
|
||||
if (key && server) {
|
||||
$.ajax({
|
||||
@ -64,14 +64,14 @@
|
||||
data: {"key": key, "server": server},
|
||||
type: "GET",
|
||||
dataType: "json",
|
||||
success: response => { keyBodyInput.text(response.key); },
|
||||
success: response => { keyImportBodyInput.text(response.key); },
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function importPgpKey() {
|
||||
const key = keyFingerprintInput.val();
|
||||
const server = keyServerInput.val();
|
||||
const key = keyImportFingerprintInput.val();
|
||||
const server = keyImportServerInput.val();
|
||||
|
||||
if (key && server) {
|
||||
$.ajax({
|
||||
|
@ -8,18 +8,18 @@
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="form-group row">
|
||||
<label for="username" class="col-sm-2 col-form-label">username</label>
|
||||
<div class="col-sm-10">
|
||||
<input id="username" type="text" class="form-control" placeholder="enter username" name="username" required>
|
||||
<label for="login-username" class="col-sm-4 col-form-label">username</label>
|
||||
<div class="col-sm-8">
|
||||
<input id="login-username" type="text" class="form-control" placeholder="enter username" name="username" required>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group row">
|
||||
<label for="password" class="col-sm-2 col-form-label">password</label>
|
||||
<div class="col-sm-10">
|
||||
<label for="login-password" class="col-sm-4 col-form-label">password</label>
|
||||
<div class="col-sm-8">
|
||||
<div class="input-group">
|
||||
<input id="password" type="password" class="form-control" placeholder="enter password" name="password" required>
|
||||
<input id="login-password" type="password" class="form-control" placeholder="enter password" name="password" required>
|
||||
<div class="input-group-append">
|
||||
<button class="btn btn-outline-secondary" type="button" onclick="showPassword()"><i id="show-hide-password-btn" class="bi bi-eye"></i></button>
|
||||
<button class="btn btn-outline-secondary" type="button" onclick="showPassword()"><i id="login-show-hide-password-button" class="bi bi-eye"></i></button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -34,8 +34,8 @@
|
||||
</div>
|
||||
|
||||
<script>
|
||||
const passwordInput = $("#password");
|
||||
const showHidePasswordButton = $("#show-hide-password-btn");
|
||||
const passwordInput = $("#login-password");
|
||||
const showHidePasswordButton = $("#login-show-hide-password-button");
|
||||
|
||||
function showPassword() {
|
||||
if (passwordInput.attr("type") === "password") {
|
||||
|
@ -8,10 +8,20 @@
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="form-group row">
|
||||
<label for="package-input" class="col-sm-2 col-form-label">package</label>
|
||||
<div class="col-sm-10">
|
||||
<input id="package-input" type="text" list="known-packages-dlist" autocomplete="off" class="form-control" placeholder="AUR package" name="package" required>
|
||||
<datalist id="known-packages-dlist"></datalist>
|
||||
<label for="package-add-repository-input" class="col-sm-4 col-form-label">repository</label>
|
||||
<div class="col-sm-8">
|
||||
<select id="package-add-repository-input" class="form-control" name="repository" required>
|
||||
{% for repository in repositories %}
|
||||
<option value="{{ repository.id }}" data-repository="{{ repository.repository }}" data-architecture="{{ repository.architecture }}">{{ repository.repository }} ({{ repository.architecture }})</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group row">
|
||||
<label for="package-add-input" class="col-sm-4 col-form-label">package</label>
|
||||
<div class="col-sm-8">
|
||||
<input id="package-add-input" type="text" list="known-packages-dlist" autocomplete="off" class="form-control" placeholder="AUR package" name="package" required>
|
||||
<datalist id="package-add-known-packages-dlist"></datalist>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -27,14 +37,19 @@
|
||||
<script>
|
||||
const packageAddModal = $("#package-add-modal");
|
||||
const packageAddForm = $("#package-add-form");
|
||||
packageAddModal.on("shown.bs.modal", () => {
|
||||
$(`#package-add-repository-input option[value="${repository.architecture}-${repository.repository}"]`).prop("selected", true);
|
||||
|
||||
});
|
||||
packageAddModal.on("hidden.bs.modal", () => { packageAddForm.trigger("reset"); });
|
||||
|
||||
const packageInput = $("#package-input");
|
||||
const knownPackagesList = $("#known-packages-dlist");
|
||||
packageInput.keyup(() => {
|
||||
clearTimeout(packageInput.data("timeout"));
|
||||
packageInput.data("timeout", setTimeout($.proxy(() => {
|
||||
const value = packageInput.val();
|
||||
const packageAddInput = $("#package-add-input");
|
||||
const packageAddRepositoryInput = $("#package-add-repository-input");
|
||||
const packageAddKnownPackagesList = $("#package-add-known-packages-dlist");
|
||||
packageAddInput.keyup(() => {
|
||||
clearTimeout(packageAddInput.data("timeout"));
|
||||
packageAddInput.data("timeout", setTimeout($.proxy(() => {
|
||||
const value = packageAddInput.val();
|
||||
|
||||
if (value.length >= 3) {
|
||||
$.ajax({
|
||||
@ -49,7 +64,7 @@
|
||||
option.innerText = `${pkg.package} (${pkg.description})`;
|
||||
return option;
|
||||
});
|
||||
knownPackagesList.empty().append(options);
|
||||
packageAddKnownPackagesList.empty().append(options);
|
||||
},
|
||||
});
|
||||
}
|
||||
@ -57,22 +72,24 @@
|
||||
});
|
||||
|
||||
function packagesAdd() {
|
||||
const packages = packageInput.val();
|
||||
const packages = packageAddInput.val();
|
||||
const repository = getRepositorySelector(packageAddRepositoryInput);
|
||||
if (packages) {
|
||||
packageAddModal.modal("hide");
|
||||
const onSuccess = update => `Packages ${update} have been added`;
|
||||
const onFailure = error => `Package addition failed: ${error}`;
|
||||
doPackageAction("/api/v1/service/add", [packages], onSuccess, onFailure);
|
||||
doPackageAction("/api/v1/service/add", [packages], repository, onSuccess, onFailure);
|
||||
}
|
||||
}
|
||||
|
||||
function packagesRequest() {
|
||||
const packages = packageInput.val();
|
||||
const packages = packageAddInput.val();
|
||||
const repository = getRepositorySelector(packageAddRepositoryInput);
|
||||
if (packages) {
|
||||
packageAddModal.modal("hide");
|
||||
const onSuccess = update => `Packages ${update} have been requested`;
|
||||
const onFailure = error => `Package request failed: ${error}`;
|
||||
doPackageAction("/api/v1/service/request", [packages], onSuccess, onFailure);
|
||||
doPackageAction("/api/v1/service/request", [packages], repository, onSuccess, onFailure);
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
@ -6,7 +6,7 @@
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="close"></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<pre class="language-logs"><samp id="package-info-logs-input" class="pre-scrollable language-logs"></samp><button id="logs-copy-btn" type="button" class="btn language-logs" onclick="copyLogs()"><i class="bi bi-clipboard"></i> copy</button></pre>
|
||||
<pre class="language-logs"><samp id="package-info-logs-input" class="pre-scrollable language-logs"></samp><button id="package-info-logs-copy-button" type="button" class="btn language-logs" onclick="copyLogs()"><i class="bi bi-clipboard"></i> copy</button></pre>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" onclick="showLogs()"><i class="bi bi-arrow-clockwise"></i> reload</button>
|
||||
@ -22,7 +22,7 @@
|
||||
const packageInfo = $("#package-info");
|
||||
|
||||
const packageInfoLogsInput = $("#package-info-logs-input");
|
||||
const packageInfoLogsCopyButton = $("#logs-copy-btn");
|
||||
const packageInfoLogsCopyButton = $("#package-info-logs-copy-button");
|
||||
|
||||
async function copyLogs() {
|
||||
const logs = packageInfoLogsInput.text();
|
||||
@ -46,6 +46,10 @@
|
||||
|
||||
$.ajax({
|
||||
url: `/api/v2/packages/${packageBase}/logs`,
|
||||
data: {
|
||||
architecture: repository.architecture,
|
||||
repository: repository.repository,
|
||||
},
|
||||
type: "GET",
|
||||
dataType: "json",
|
||||
success: response => {
|
||||
|
@ -8,9 +8,19 @@
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="form-group row">
|
||||
<label for="dependency-input" class="col-sm-4 col-form-label">dependency</label>
|
||||
<label for="package-rebuild-repository-input" class="col-sm-4 col-form-label">repository</label>
|
||||
<div class="col-sm-8">
|
||||
<input id="dependency-input" type="text" class="form-control" placeholder="packages dependency" name="package" required>
|
||||
<select id="package-rebuild-repository-input" class="form-control" name="repository" required>
|
||||
{% for repository in repositories %}
|
||||
<option value="{{ repository.id }}" data-repository="{{ repository.repository }}" data-architecture="{{ repository.architecture }}">{{ repository.repository }} ({{ repository.architecture }})</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group row">
|
||||
<label for="package-rebuild-dependency-input" class="col-sm-4 col-form-label">dependency</label>
|
||||
<div class="col-sm-8">
|
||||
<input id="package-rebuild-dependency-input" type="text" class="form-control" placeholder="packages dependency" name="package" required>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -25,17 +35,23 @@
|
||||
<script>
|
||||
const packageRebuildModal = $("#package-rebuild-modal");
|
||||
const packageRebuildForm = $("#package-rebuild-form");
|
||||
packageRebuildModal.on("shown.bs.modal", () => {
|
||||
$(`#package-rebuild-repository-input option[value="${repository.architecture}-${repository.repository}"]`).prop("selected", true);
|
||||
|
||||
});
|
||||
packageRebuildModal.on("hidden.bs.modal", () => { packageRebuildForm.trigger("reset"); });
|
||||
|
||||
const dependencyInput = $("#dependency-input");
|
||||
const packageRebuildDependencyInput = $("#package-rebuild-dependency-input");
|
||||
const packageRebuildRepositoryInput = $("#package-rebuild-repository-input");
|
||||
|
||||
function packagesRebuild() {
|
||||
const packages = dependencyInput.val();
|
||||
const packages = packageRebuildDependencyInput.val();
|
||||
const repository = getRepositorySelector(packageRebuildRepositoryInput);
|
||||
if (packages) {
|
||||
packageRebuildModal.modal("hide");
|
||||
const onSuccess = update => `Repository rebuild has been run for packages which depend on ${update}`;
|
||||
const onFailure = error => `Repository rebuild failed: ${error}`;
|
||||
doPackageAction("/api/v1/service/rebuild", [packages], onSuccess, onFailure);
|
||||
doPackageAction("/api/v1/service/rebuild", [packages], repository, onSuccess, onFailure);
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
@ -1,9 +1,21 @@
|
||||
<script>
|
||||
const keyImportButton = $("#key-import-btn");
|
||||
const packageAddButton = $("#package-add-btn");
|
||||
const packageRebuildButton = $("#package-rebuild-btn");
|
||||
const packageRemoveButton = $("#package-remove-btn");
|
||||
const packageUpdateButton = $("#package-update-btn");
|
||||
const keyImportButton = $("#key-import-button");
|
||||
const packageAddButton = $("#package-add-button");
|
||||
const packageRebuildButton = $("#package-rebuild-button");
|
||||
const packageRemoveButton = $("#package-remove-button");
|
||||
const packageUpdateButton = $("#package-update-button");
|
||||
|
||||
let repository = null;
|
||||
$("#repositories a").on("click", (event) => {
|
||||
const element = event.target;
|
||||
repository = {
|
||||
architecture: element.dataset.architecture,
|
||||
repository: element.dataset.repository,
|
||||
};
|
||||
packageUpdateButton.html(`<i class="bi bi-play"></i> update ${safe(repository.repository)} (${safe(repository.architecture)})`);
|
||||
$(`#${element.id}`).tab("show");
|
||||
reload();
|
||||
});
|
||||
|
||||
const table = $("#packages");
|
||||
table.on("check.bs.table uncheck.bs.table check-all.bs.table uncheck-all.bs.table", () => {
|
||||
@ -35,13 +47,17 @@
|
||||
});
|
||||
});
|
||||
|
||||
const repositoryBadge = $("#badge-repository");
|
||||
const statusBadge = $("#badge-status");
|
||||
const versionBadge = $("#badge-version");
|
||||
|
||||
function doPackageAction(uri, packages, successText, failureText) {
|
||||
function doPackageAction(uri, packages, repository, successText, failureText) {
|
||||
const queryParams = $.param({
|
||||
architecture: repository.architecture,
|
||||
repository: repository.repository,
|
||||
}); // it will never be empty btw
|
||||
|
||||
$.ajax({
|
||||
url: uri,
|
||||
url: `${uri}?${queryParams}`,
|
||||
data: JSON.stringify({packages: packages}),
|
||||
type: "POST",
|
||||
contentType: "application/json",
|
||||
@ -62,16 +78,22 @@
|
||||
function removePackages() {
|
||||
const onSuccess = update => `Packages ${update} have been removed`;
|
||||
const onFailure = error => `Could not remove packages: ${error}`;
|
||||
doPackageAction("/api/v1/service/remove", getSelection(), onSuccess, onFailure);
|
||||
doPackageAction("/api/v1/service/remove", getSelection(), repository, onSuccess, onFailure);
|
||||
}
|
||||
|
||||
function selectRepository() {
|
||||
const fragment = window.location.hash.replace("#", "") || "{{ repositories[0].id }}";
|
||||
const element = $(`#${fragment}-lnk`);
|
||||
element.click();
|
||||
}
|
||||
|
||||
function updatePackages() {
|
||||
const currentSelection = getSelection();
|
||||
const [url, onSuccess] = currentSelection.length === 0
|
||||
? ["/api/v1/service/update", _ => "Repository update has been run"]
|
||||
? ["/api/v1/service/update", _ => `Repository update has been run`]
|
||||
: ["/api/v1/service/add", update => `Run update for packages ${update}`];
|
||||
const onFailure = error => `Packages update failed: ${error}`;
|
||||
doPackageAction(url, currentSelection, onSuccess, onFailure);
|
||||
doPackageAction(url, currentSelection, repository, onSuccess, onFailure);
|
||||
}
|
||||
|
||||
function hideControls(hidden) {
|
||||
@ -95,6 +117,10 @@
|
||||
|
||||
$.ajax({
|
||||
url: "/api/v1/packages",
|
||||
data: {
|
||||
architecture: repository.architecture,
|
||||
repository: repository.repository,
|
||||
},
|
||||
type: "GET",
|
||||
dataType: "json",
|
||||
success: response => {
|
||||
@ -149,10 +175,13 @@
|
||||
|
||||
$.ajax({
|
||||
url: "/api/v1/status",
|
||||
data: {
|
||||
architecture: repository.architecture,
|
||||
repository: repository.repository,
|
||||
},
|
||||
type: "GET",
|
||||
dataType: "json",
|
||||
success: response => {
|
||||
repositoryBadge.text(`${response.repository} ${response.architecture}`);
|
||||
versionBadge.html(`<i class="bi bi-github"></i> ahriman ${safe(response.version)}`);
|
||||
|
||||
statusBadge
|
||||
@ -189,9 +218,17 @@
|
||||
return extractDataList(table.bootstrapTable("getData"), "packager");
|
||||
}
|
||||
|
||||
function getRepositorySelector(selector) {
|
||||
const selected = selector.find(":selected");
|
||||
return {
|
||||
architecture: selected.data("architecture"),
|
||||
repository: selected.data("repository"),
|
||||
};
|
||||
}
|
||||
|
||||
$(() => {
|
||||
table.bootstrapTable({});
|
||||
statusBadge.popover();
|
||||
reload();
|
||||
selectRepository();
|
||||
});
|
||||
</script>
|
70
package/share/ahriman/templates/static/logo.svg
Normal file
70
package/share/ahriman/templates/static/logo.svg
Normal file
@ -0,0 +1,70 @@
|
||||
<?xml version="1.0" standalone="no"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 20010904//EN"
|
||||
"http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
|
||||
<svg version="1.0" xmlns="http://www.w3.org/2000/svg"
|
||||
width="256.000000pt" height="256.000000pt" viewBox="0 0 256.000000 256.000000"
|
||||
preserveAspectRatio="xMidYMid meet">
|
||||
|
||||
<g transform="translate(0.000000,256.000000) scale(0.100000,-0.100000)"
|
||||
fill="#000000" stroke="none">
|
||||
<path d="M1019 2546 c-51 -18 -92 -49 -136 -103 -34 -41 -83 -73 -112 -73 -6
|
||||
0 4 22 20 49 17 27 30 54 28 59 -7 17 -153 -102 -176 -143 -13 -23 -30 -40
|
||||
-40 -40 -15 0 -18 9 -18 61 0 49 6 73 33 125 l33 64 -42 -40 c-50 -49 -82 -97
|
||||
-98 -150 -31 -102 -43 -125 -77 -146 -19 -11 -48 -42 -65 -67 -17 -26 -62 -76
|
||||
-100 -112 -69 -66 -69 -66 -69 -120 0 -30 5 -62 10 -70 5 -8 10 -10 10 -4 0 7
|
||||
21 35 46 63 69 77 154 95 154 33 0 -42 -27 -126 -50 -155 l-21 -26 25 -7 c34
|
||||
-8 63 10 86 56 27 53 74 105 127 142 42 29 44 32 38 68 -6 33 -3 40 22 60 25
|
||||
20 36 22 96 17 83 -7 121 5 137 43 7 16 19 31 28 34 10 3 -9 3 -40 1 -65 -6
|
||||
-84 8 -48 37 34 27 118 24 180 -7 57 -29 115 -32 150 -8 l24 17 -24 6 c-14 3
|
||||
-27 14 -28 24 -4 17 1 18 46 13 58 -7 118 -34 147 -67 38 -44 107 -80 149 -80
|
||||
36 1 38 2 21 12 -41 23 -115 108 -115 133 0 13 58 -27 94 -64 42 -43 86 -58
|
||||
133 -46 28 7 28 7 6 18 -13 5 -23 19 -23 29 0 14 6 19 23 16 17 -2 32 -23 62
|
||||
-85 48 -99 63 -115 123 -141 97 -41 101 -40 50 16 -54 60 -80 113 -76 149 l3
|
||||
26 58 -47 c61 -50 87 -89 87 -131 0 -43 27 -93 59 -109 38 -20 37 -21 36 11 0
|
||||
45 24 38 47 -14 19 -41 22 -63 20 -146 -2 -91 0 -103 25 -148 45 -83 54 -83
|
||||
47 2 -6 74 11 155 32 153 20 -2 47 -101 48 -179 1 -65 -3 -88 -22 -122 -24
|
||||
-46 -28 -98 -10 -138 14 -30 24 -32 31 -5 8 30 37 25 37 -6 0 -53 -23 -113
|
||||
-62 -160 -52 -63 -58 -81 -58 -167 l0 -72 35 65 c40 75 98 135 103 107 4 -19
|
||||
-37 -114 -69 -158 -11 -16 -49 -46 -85 -66 -64 -37 -94 -76 -94 -122 0 -20 1
|
||||
-21 24 -6 19 13 25 13 35 2 17 -21 -15 -43 -73 -51 -85 -11 -126 -26 -164 -61
|
||||
-35 -31 -80 -95 -67 -95 3 0 29 12 57 26 52 26 168 45 168 28 0 -12 -92 -91
|
||||
-123 -105 -20 -9 -34 -8 -71 6 -52 20 -104 14 -130 -14 -17 -19 -17 -20 6 -23
|
||||
12 -2 24 -11 26 -20 4 -22 -37 -23 -189 -7 -99 11 -104 10 -151 -12 -26 -13
|
||||
-48 -27 -48 -30 0 -3 25 -16 55 -28 30 -13 55 -26 55 -31 0 -8 -73 -20 -119
|
||||
-20 -44 0 -99 20 -130 46 -37 31 -87 46 -124 38 l-27 -6 22 -18 c16 -13 20
|
||||
-22 12 -29 -18 -19 -125 42 -174 100 -45 53 -95 82 -165 95 l-40 7 52 -41 c96
|
||||
-76 114 -128 31 -86 -105 52 -120 69 -147 172 -9 31 -21 48 -44 62 -41 25 -61
|
||||
26 -49 2 5 -9 7 -23 5 -29 -10 -27 -32 -11 -57 39 -21 43 -26 66 -26 132 0 71
|
||||
-4 87 -29 131 -32 55 -80 106 -67 71 21 -58 18 -206 -4 -206 -5 0 -30 43 -56
|
||||
95 l-48 96 17 47 c21 57 21 89 2 122 -14 25 -14 25 -15 3 0 -14 -6 -23 -16
|
||||
-23 -14 0 -16 8 -10 53 4 28 18 70 31 92 14 22 31 55 40 73 18 37 32 152 19
|
||||
152 -5 0 -17 -21 -28 -46 -18 -45 -61 -104 -75 -104 -4 0 -4 32 0 72 6 55 16
|
||||
83 40 122 24 37 34 66 37 108 6 75 -1 91 -22 51 -16 -32 -48 -44 -61 -23 -8
|
||||
12 22 69 40 76 19 8 41 43 65 105 l21 52 -29 -29 c-16 -16 -48 -40 -71 -54
|
||||
-23 -14 -56 -45 -72 -69 -28 -43 -29 -47 -29 -175 0 -186 -12 -220 -43 -122
|
||||
-10 29 -15 62 -11 72 4 14 -2 11 -20 -9 -31 -33 -33 -65 -7 -115 21 -40 38
|
||||
-112 27 -112 -16 0 -58 35 -71 59 l-14 26 -1 -37 c0 -24 13 -63 36 -108 39
|
||||
-76 51 -155 24 -155 -8 0 -20 16 -27 35 l-11 35 -7 -29 c-11 -40 11 -127 43
|
||||
-175 15 -22 56 -72 90 -111 70 -78 66 -86 -28 -51 -71 27 -60 7 30 -54 71 -48
|
||||
106 -93 86 -112 -4 -4 -46 12 -93 37 -110 58 -109 58 -87 20 29 -49 70 -81
|
||||
188 -145 114 -63 148 -94 126 -120 -13 -16 -52 -6 -82 22 -28 25 -21 0 11 -44
|
||||
45 -63 100 -83 259 -99 154 -15 221 -29 272 -54 l35 -18 -35 -6 c-56 -10 -164
|
||||
-26 -175 -26 -5 -1 15 -10 45 -21 62 -23 124 -27 255 -15 72 6 94 5 109 -7 28
|
||||
-21 11 -37 -42 -38 l-47 -1 40 -19 c63 -31 153 -22 244 25 80 41 166 66 224
|
||||
66 l38 0 -26 -35 c-15 -19 -48 -48 -73 -66 l-47 -31 72 4 c71 4 72 4 130 59
|
||||
66 64 104 86 160 95 27 4 42 2 50 -8 16 -19 4 -34 -43 -58 l-39 -19 54 -1 c71
|
||||
0 129 28 181 88 22 26 68 80 102 120 35 41 74 89 87 108 13 19 26 33 28 31 2
|
||||
-2 -3 -46 -12 -98 -20 -128 -20 -128 26 -73 52 63 64 91 83 196 18 108 39 150
|
||||
69 146 23 -3 27 -20 14 -70 -11 -42 18 -25 49 28 34 58 41 139 20 222 -21 84
|
||||
-21 83 19 172 19 43 38 99 41 125 l7 46 38 -68 39 -68 0 88 c0 80 -3 93 -35
|
||||
157 -41 80 -47 140 -15 140 11 0 26 -12 34 -27 15 -27 15 -27 16 35 0 54 -4
|
||||
70 -29 105 -16 23 -49 57 -74 76 -56 42 -111 153 -122 248 l-7 63 58 -26 c32
|
||||
-14 71 -35 86 -45 40 -29 35 -10 -18 60 -34 45 -65 72 -117 103 -118 69 -153
|
||||
102 -141 132 9 22 25 20 68 -9 42 -28 43 -26 14 26 -36 65 -136 119 -217 119
|
||||
-48 0 -233 88 -211 100 8 4 44 11 80 14 129 14 130 15 58 47 -62 29 -64 29
|
||||
-198 23 -121 -6 -138 -5 -153 11 -25 24 -6 43 46 47 l42 3 -35 20 c-25 13 -53
|
||||
19 -98 19 -54 0 -74 -6 -143 -42 -76 -39 -139 -54 -139 -33 0 6 45 32 100 60
|
||||
l99 50 -55 7 c-76 9 -111 2 -214 -46 -99 -46 -155 -51 -155 -15 0 14 12 24 44
|
||||
36 40 14 42 16 20 22 -37 10 -80 8 -120 -7z"/>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 4.7 KiB |
@ -59,7 +59,7 @@
|
||||
const [minDate, maxDate] = text.split(" - ");
|
||||
const buildDate = asOfStartOfDay(new Date(value));
|
||||
|
||||
return (buildDate >= asOfStartOfDay(new Date(minDate))) && (buildDate <= asOfStartOfDay(new Date(maxDate)));
|
||||
return (buildDate >= new Date(minDate)) && (buildDate <= new Date(maxDate));
|
||||
}
|
||||
|
||||
function filterList(index, value, field, data) {
|
||||
|
@ -1,6 +1,6 @@
|
||||
# AUTOMATICALLY GENERATED by `shtab`
|
||||
|
||||
_shtab_ahriman_subparsers=('aur-search' 'search' 'help' 'help-commands-unsafe' 'help-updates' 'help-version' 'version' 'package-add' 'add' 'package-update' 'package-remove' 'remove' 'package-status' 'status' 'package-status-remove' 'package-status-update' 'status-update' 'patch-add' 'patch-list' 'patch-remove' 'patch-set-add' 'repo-backup' 'repo-check' 'check' 'repo-create-keyring' 'repo-create-mirrorlist' 'repo-daemon' 'daemon' 'repo-rebuild' 'rebuild' 'repo-remove-unknown' 'remove-unknown' 'repo-report' 'report' 'repo-restore' 'repo-sign' 'sign' 'repo-status-update' 'repo-sync' 'sync' 'repo-tree' 'repo-triggers' 'repo-update' 'update' 'service-clean' 'clean' 'repo-clean' 'service-config' 'config' 'repo-config' 'service-config-validate' 'config-validate' 'repo-config-validate' 'service-key-import' 'key-import' 'service-setup' 'init' 'repo-init' 'repo-setup' 'setup' 'service-shell' 'shell' 'service-tree-migrate' 'user-add' 'user-list' 'user-remove' 'web')
|
||||
_shtab_ahriman_subparsers=('aur-search' 'search' 'help' 'help-commands-unsafe' 'help-updates' 'help-version' 'version' 'package-add' 'add' 'package-update' 'package-remove' 'remove' 'package-status' 'status' 'package-status-remove' 'package-status-update' 'status-update' 'patch-add' 'patch-list' 'patch-remove' 'patch-set-add' 'repo-backup' 'repo-check' 'check' 'repo-create-keyring' 'repo-create-mirrorlist' 'repo-daemon' 'daemon' 'repo-rebuild' 'rebuild' 'repo-remove-unknown' 'remove-unknown' 'repo-report' 'report' 'repo-restore' 'repo-sign' 'sign' 'repo-status-update' 'repo-sync' 'sync' 'repo-tree' 'repo-triggers' 'repo-update' 'update' 'service-clean' 'clean' 'repo-clean' 'service-config' 'config' 'repo-config' 'service-config-validate' 'config-validate' 'repo-config-validate' 'service-key-import' 'key-import' 'service-repositories' 'service-setup' 'init' 'repo-init' 'repo-setup' 'setup' 'service-shell' 'shell' 'service-tree-migrate' 'user-add' 'user-list' 'user-remove' 'web')
|
||||
|
||||
_shtab_ahriman_option_strings=('-h' '--help' '-a' '--architecture' '-c' '--configuration' '--force' '-l' '--lock' '--log-handler' '-q' '--quiet' '--report' '--no-report' '-r' '--repository' '--unsafe' '--wait-timeout' '-V' '--version')
|
||||
_shtab_ahriman_aur_search_option_strings=('-h' '--help' '-e' '--exit-code' '--info' '--no-info' '--sort-by')
|
||||
@ -50,14 +50,15 @@ _shtab_ahriman_update_option_strings=('-h' '--help' '--aur' '--no-aur' '--depend
|
||||
_shtab_ahriman_service_clean_option_strings=('-h' '--help' '--cache' '--no-cache' '--chroot' '--no-chroot' '--manual' '--no-manual' '--packages' '--no-packages' '--pacman' '--no-pacman')
|
||||
_shtab_ahriman_clean_option_strings=('-h' '--help' '--cache' '--no-cache' '--chroot' '--no-chroot' '--manual' '--no-manual' '--packages' '--no-packages' '--pacman' '--no-pacman')
|
||||
_shtab_ahriman_repo_clean_option_strings=('-h' '--help' '--cache' '--no-cache' '--chroot' '--no-chroot' '--manual' '--no-manual' '--packages' '--no-packages' '--pacman' '--no-pacman')
|
||||
_shtab_ahriman_service_config_option_strings=('-h' '--help' '--secure' '--no-secure')
|
||||
_shtab_ahriman_config_option_strings=('-h' '--help' '--secure' '--no-secure')
|
||||
_shtab_ahriman_repo_config_option_strings=('-h' '--help' '--secure' '--no-secure')
|
||||
_shtab_ahriman_service_config_option_strings=('-h' '--help' '--info' '--no-info' '--secure' '--no-secure')
|
||||
_shtab_ahriman_config_option_strings=('-h' '--help' '--info' '--no-info' '--secure' '--no-secure')
|
||||
_shtab_ahriman_repo_config_option_strings=('-h' '--help' '--info' '--no-info' '--secure' '--no-secure')
|
||||
_shtab_ahriman_service_config_validate_option_strings=('-h' '--help' '-e' '--exit-code')
|
||||
_shtab_ahriman_config_validate_option_strings=('-h' '--help' '-e' '--exit-code')
|
||||
_shtab_ahriman_repo_config_validate_option_strings=('-h' '--help' '-e' '--exit-code')
|
||||
_shtab_ahriman_service_key_import_option_strings=('-h' '--help' '--key-server')
|
||||
_shtab_ahriman_key_import_option_strings=('-h' '--help' '--key-server')
|
||||
_shtab_ahriman_service_repositories_option_strings=('-h' '--help' '--id-only' '--no-id-only')
|
||||
_shtab_ahriman_service_setup_option_strings=('-h' '--help' '--build-as-user' '--from-configuration' '--generate-salt' '--no-generate-salt' '--makeflags-jobs' '--no-makeflags-jobs' '--mirror' '--multilib' '--no-multilib' '--packager' '--server' '--sign-key' '--sign-target' '--web-port' '--web-unix-socket')
|
||||
_shtab_ahriman_init_option_strings=('-h' '--help' '--build-as-user' '--from-configuration' '--generate-salt' '--no-generate-salt' '--makeflags-jobs' '--no-makeflags-jobs' '--mirror' '--multilib' '--no-multilib' '--packager' '--server' '--sign-key' '--sign-target' '--web-port' '--web-unix-socket')
|
||||
_shtab_ahriman_repo_init_option_strings=('-h' '--help' '--build-as-user' '--from-configuration' '--generate-salt' '--no-generate-salt' '--makeflags-jobs' '--no-makeflags-jobs' '--mirror' '--multilib' '--no-multilib' '--packager' '--server' '--sign-key' '--sign-target' '--web-port' '--web-unix-socket')
|
||||
@ -73,7 +74,7 @@ _shtab_ahriman_web_option_strings=('-h' '--help')
|
||||
|
||||
|
||||
|
||||
_shtab_ahriman_pos_0_choices=('aur-search' 'search' 'help' 'help-commands-unsafe' 'help-updates' 'help-version' 'version' 'package-add' 'add' 'package-update' 'package-remove' 'remove' 'package-status' 'status' 'package-status-remove' 'package-status-update' 'status-update' 'patch-add' 'patch-list' 'patch-remove' 'patch-set-add' 'repo-backup' 'repo-check' 'check' 'repo-create-keyring' 'repo-create-mirrorlist' 'repo-daemon' 'daemon' 'repo-rebuild' 'rebuild' 'repo-remove-unknown' 'remove-unknown' 'repo-report' 'report' 'repo-restore' 'repo-sign' 'sign' 'repo-status-update' 'repo-sync' 'sync' 'repo-tree' 'repo-triggers' 'repo-update' 'update' 'service-clean' 'clean' 'repo-clean' 'service-config' 'config' 'repo-config' 'service-config-validate' 'config-validate' 'repo-config-validate' 'service-key-import' 'key-import' 'service-setup' 'init' 'repo-init' 'repo-setup' 'setup' 'service-shell' 'shell' 'service-tree-migrate' 'user-add' 'user-list' 'user-remove' 'web')
|
||||
_shtab_ahriman_pos_0_choices=('aur-search' 'search' 'help' 'help-commands-unsafe' 'help-updates' 'help-version' 'version' 'package-add' 'add' 'package-update' 'package-remove' 'remove' 'package-status' 'status' 'package-status-remove' 'package-status-update' 'status-update' 'patch-add' 'patch-list' 'patch-remove' 'patch-set-add' 'repo-backup' 'repo-check' 'check' 'repo-create-keyring' 'repo-create-mirrorlist' 'repo-daemon' 'daemon' 'repo-rebuild' 'rebuild' 'repo-remove-unknown' 'remove-unknown' 'repo-report' 'report' 'repo-restore' 'repo-sign' 'sign' 'repo-status-update' 'repo-sync' 'sync' 'repo-tree' 'repo-triggers' 'repo-update' 'update' 'service-clean' 'clean' 'repo-clean' 'service-config' 'config' 'repo-config' 'service-config-validate' 'config-validate' 'repo-config-validate' 'service-key-import' 'key-import' 'service-repositories' 'service-setup' 'init' 'repo-init' 'repo-setup' 'setup' 'service-shell' 'shell' 'service-tree-migrate' 'user-add' 'user-list' 'user-remove' 'web')
|
||||
_shtab_ahriman___log_handler_choices=('console' 'syslog' 'journald')
|
||||
_shtab_ahriman_aur_search___sort_by_choices=('description' 'first_submitted' 'id' 'last_modified' 'maintainer' 'name' 'num_votes' 'out_of_date' 'package_base' 'package_base_id' 'popularity' 'repository' 'submitter' 'url' 'url_path' 'version')
|
||||
_shtab_ahriman_search___sort_by_choices=('description' 'first_submitted' 'id' 'last_modified' 'maintainer' 'name' 'num_votes' 'out_of_date' 'package_base' 'package_base_id' 'popularity' 'repository' 'submitter' 'url' 'url_path' 'version')
|
||||
@ -400,14 +401,20 @@ _shtab_ahriman_repo_clean___pacman_nargs=0
|
||||
_shtab_ahriman_repo_clean___no_pacman_nargs=0
|
||||
_shtab_ahriman_service_config__h_nargs=0
|
||||
_shtab_ahriman_service_config___help_nargs=0
|
||||
_shtab_ahriman_service_config___info_nargs=0
|
||||
_shtab_ahriman_service_config___no_info_nargs=0
|
||||
_shtab_ahriman_service_config___secure_nargs=0
|
||||
_shtab_ahriman_service_config___no_secure_nargs=0
|
||||
_shtab_ahriman_config__h_nargs=0
|
||||
_shtab_ahriman_config___help_nargs=0
|
||||
_shtab_ahriman_config___info_nargs=0
|
||||
_shtab_ahriman_config___no_info_nargs=0
|
||||
_shtab_ahriman_config___secure_nargs=0
|
||||
_shtab_ahriman_config___no_secure_nargs=0
|
||||
_shtab_ahriman_repo_config__h_nargs=0
|
||||
_shtab_ahriman_repo_config___help_nargs=0
|
||||
_shtab_ahriman_repo_config___info_nargs=0
|
||||
_shtab_ahriman_repo_config___no_info_nargs=0
|
||||
_shtab_ahriman_repo_config___secure_nargs=0
|
||||
_shtab_ahriman_repo_config___no_secure_nargs=0
|
||||
_shtab_ahriman_service_config_validate__h_nargs=0
|
||||
@ -426,6 +433,10 @@ _shtab_ahriman_service_key_import__h_nargs=0
|
||||
_shtab_ahriman_service_key_import___help_nargs=0
|
||||
_shtab_ahriman_key_import__h_nargs=0
|
||||
_shtab_ahriman_key_import___help_nargs=0
|
||||
_shtab_ahriman_service_repositories__h_nargs=0
|
||||
_shtab_ahriman_service_repositories___help_nargs=0
|
||||
_shtab_ahriman_service_repositories___id_only_nargs=0
|
||||
_shtab_ahriman_service_repositories___no_id_only_nargs=0
|
||||
_shtab_ahriman_service_setup__h_nargs=0
|
||||
_shtab_ahriman_service_setup___help_nargs=0
|
||||
_shtab_ahriman_service_setup___generate_salt_nargs=0
|
||||
|
@ -1,16 +1,16 @@
|
||||
.TH AHRIMAN "1" "2023\-09\-02" "ahriman" "Generated Python Manual"
|
||||
.TH AHRIMAN "1" "2023\-10\-09" "ahriman" "Generated Python Manual"
|
||||
.SH NAME
|
||||
ahriman
|
||||
.SH SYNOPSIS
|
||||
.B ahriman
|
||||
[-h] [-a ARCHITECTURE] [-c CONFIGURATION] [--force] [-l LOCK] [--log-handler {console,syslog,journald}] [-q] [--report | --no-report] [-r REPOSITORY] [--unsafe] [--wait-timeout WAIT_TIMEOUT] [-V] {aur-search,search,help,help-commands-unsafe,help-updates,help-version,version,package-add,add,package-update,package-remove,remove,package-status,status,package-status-remove,package-status-update,status-update,patch-add,patch-list,patch-remove,patch-set-add,repo-backup,repo-check,check,repo-create-keyring,repo-create-mirrorlist,repo-daemon,daemon,repo-rebuild,rebuild,repo-remove-unknown,remove-unknown,repo-report,report,repo-restore,repo-sign,sign,repo-status-update,repo-sync,sync,repo-tree,repo-triggers,repo-update,update,service-clean,clean,repo-clean,service-config,config,repo-config,service-config-validate,config-validate,repo-config-validate,service-key-import,key-import,service-setup,init,repo-init,repo-setup,setup,service-shell,shell,service-tree-migrate,user-add,user-list,user-remove,web} ...
|
||||
[-h] [-a ARCHITECTURE] [-c CONFIGURATION] [--force] [-l LOCK] [--log-handler {console,syslog,journald}] [-q] [--report | --no-report] [-r REPOSITORY] [--unsafe] [--wait-timeout WAIT_TIMEOUT] [-V] {aur-search,search,help,help-commands-unsafe,help-updates,help-version,version,package-add,add,package-update,package-remove,remove,package-status,status,package-status-remove,package-status-update,status-update,patch-add,patch-list,patch-remove,patch-set-add,repo-backup,repo-check,check,repo-create-keyring,repo-create-mirrorlist,repo-daemon,daemon,repo-rebuild,rebuild,repo-remove-unknown,remove-unknown,repo-report,report,repo-restore,repo-sign,sign,repo-status-update,repo-sync,sync,repo-tree,repo-triggers,repo-update,update,service-clean,clean,repo-clean,service-config,config,repo-config,service-config-validate,config-validate,repo-config-validate,service-key-import,key-import,service-repositories,service-setup,init,repo-init,repo-setup,setup,service-shell,shell,service-tree-migrate,user-add,user-list,user-remove,web} ...
|
||||
.SH DESCRIPTION
|
||||
ArcH linux ReposItory MANager
|
||||
|
||||
.SH OPTIONS
|
||||
.TP
|
||||
\fB\-a\fR \fI\,ARCHITECTURE\/\fR, \fB\-\-architecture\fR \fI\,ARCHITECTURE\/\fR
|
||||
target architectures. For several subcommands it can be used multiple times
|
||||
filter by target architecture
|
||||
|
||||
.TP
|
||||
\fB\-c\fR \fI\,CONFIGURATION\/\fR, \fB\-\-configuration\fR \fI\,CONFIGURATION\/\fR
|
||||
@ -38,7 +38,7 @@ force enable or disable reporting to web service
|
||||
|
||||
.TP
|
||||
\fB\-r\fR \fI\,REPOSITORY\/\fR, \fB\-\-repository\fR \fI\,REPOSITORY\/\fR
|
||||
target repository. For several subcommands it can be used multiple times
|
||||
filter by target repository
|
||||
|
||||
.TP
|
||||
\fB\-\-unsafe\fR
|
||||
@ -155,6 +155,9 @@ validate system configuration
|
||||
\fBahriman\fR \fI\,service\-key\-import\/\fR
|
||||
import PGP key
|
||||
.TP
|
||||
\fBahriman\fR \fI\,service\-repositories\/\fR
|
||||
show repositories
|
||||
.TP
|
||||
\fBahriman\fR \fI\,service\-setup\/\fR
|
||||
initial service configuration
|
||||
.TP
|
||||
@ -664,11 +667,23 @@ clear directory with built packages
|
||||
clear directory with pacman local database cache
|
||||
|
||||
.SH COMMAND \fI\,'ahriman service\-config'\/\fR
|
||||
usage: ahriman service\-config [\-h] [\-\-secure | \-\-no\-secure]
|
||||
usage: ahriman service\-config [\-h] [\-\-info | \-\-no\-info] [\-\-secure | \-\-no\-secure] [section] [key]
|
||||
|
||||
dump configuration for the specified architecture
|
||||
|
||||
.TP
|
||||
\fBsection\fR
|
||||
filter settings by section
|
||||
|
||||
.TP
|
||||
\fBkey\fR
|
||||
filter settings by key
|
||||
|
||||
.SH OPTIONS \fI\,'ahriman service\-config'\/\fR
|
||||
.TP
|
||||
\fB\-\-info\fR, \fB\-\-no\-info\fR
|
||||
show additional information, e.g. configuration files
|
||||
|
||||
.TP
|
||||
\fB\-\-secure\fR, \fB\-\-no\-secure\fR
|
||||
hide passwords and secrets from output
|
||||
@ -697,6 +712,16 @@ PGP key to import from public server
|
||||
\fB\-\-key\-server\fR \fI\,KEY_SERVER\/\fR
|
||||
key server for key import
|
||||
|
||||
.SH COMMAND \fI\,'ahriman service\-repositories'\/\fR
|
||||
usage: ahriman service\-repositories [\-h] [\-\-id\-only | \-\-no\-id\-only]
|
||||
|
||||
list all available repositories
|
||||
|
||||
.SH OPTIONS \fI\,'ahriman service\-repositories'\/\fR
|
||||
.TP
|
||||
\fB\-\-id\-only\fR, \fB\-\-no\-id\-only\fR
|
||||
show machine readable identifier instead
|
||||
|
||||
.SH COMMAND \fI\,'ahriman service\-setup'\/\fR
|
||||
usage: ahriman service\-setup [\-h] [\-\-build\-as\-user BUILD_AS_USER] [\-\-from\-configuration FROM_CONFIGURATION]
|
||||
[\-\-generate\-salt | \-\-no\-generate\-salt] [\-\-makeflags\-jobs | \-\-no\-makeflags\-jobs]
|
||||
|
@ -57,6 +57,7 @@ _shtab_ahriman_commands() {
|
||||
"service-config:dump configuration for the specified architecture"
|
||||
"service-config-validate:validate configuration and print found errors"
|
||||
"service-key-import:import PGP key from public sources to the repository user"
|
||||
"service-repositories:list all available repositories"
|
||||
"service-setup:create initial service configuration, requires root"
|
||||
"service-shell:drop into python shell"
|
||||
"service-tree-migrate:migrate repository tree between versions"
|
||||
@ -78,14 +79,14 @@ _shtab_ahriman_commands() {
|
||||
|
||||
_shtab_ahriman_options=(
|
||||
"(- : *)"{-h,--help}"[show this help message and exit]"
|
||||
"*"{-a,--architecture}"[target architectures. For several subcommands it can be used multiple times (default\: None)]:architecture:"
|
||||
{-a,--architecture}"[filter by target architecture (default\: None)]:architecture:"
|
||||
{-c,--configuration}"[configuration path (default\: \/etc\/ahriman.ini)]:configuration:"
|
||||
"--force[force run, remove file lock (default\: False)]"
|
||||
{-l,--lock}"[lock file (default\: \/tmp\/ahriman.lock)]:lock:"
|
||||
"--log-handler[explicit log handler specification. If none set, the handler will be guessed from environment (default\: None)]:log_handler:(console syslog journald)"
|
||||
{-q,--quiet}"[force disable any logging (default\: False)]"
|
||||
{--report,--no-report}"[force enable or disable reporting to web service (default\: True)]:report:"
|
||||
"*"{-r,--repository}"[target repository. For several subcommands it can be used multiple times (default\: None)]:repository:"
|
||||
{-r,--repository}"[filter by target repository (default\: None)]:repository:"
|
||||
"--unsafe[allow to run ahriman as non-ahriman user. Some actions might be unavailable (default\: False)]"
|
||||
"--wait-timeout[wait for lock to be free. Negative value will lead to immediate application run even if there is lock file. In case of zero value, the application will wait infinitely (default\: -1)]:wait_timeout:"
|
||||
"(- : *)"{-V,--version}"[show program\'s version number and exit]"
|
||||
@ -130,7 +131,10 @@ _shtab_ahriman_clean_options=(
|
||||
|
||||
_shtab_ahriman_config_options=(
|
||||
"(- : *)"{-h,--help}"[show this help message and exit]"
|
||||
{--info,--no-info}"[show additional information, e.g. configuration files (default\: True)]:info:"
|
||||
{--secure,--no-secure}"[hide passwords and secrets from output (default\: True)]:secure:"
|
||||
":filter settings by section (default\: None):"
|
||||
":filter settings by key (default\: None):"
|
||||
)
|
||||
|
||||
_shtab_ahriman_config_validate_options=(
|
||||
@ -310,7 +314,10 @@ _shtab_ahriman_repo_clean_options=(
|
||||
|
||||
_shtab_ahriman_repo_config_options=(
|
||||
"(- : *)"{-h,--help}"[show this help message and exit]"
|
||||
{--info,--no-info}"[show additional information, e.g. configuration files (default\: True)]:info:"
|
||||
{--secure,--no-secure}"[hide passwords and secrets from output (default\: True)]:secure:"
|
||||
":filter settings by section (default\: None):"
|
||||
":filter settings by key (default\: None):"
|
||||
)
|
||||
|
||||
_shtab_ahriman_repo_config_validate_options=(
|
||||
@ -457,7 +464,10 @@ _shtab_ahriman_service_clean_options=(
|
||||
|
||||
_shtab_ahriman_service_config_options=(
|
||||
"(- : *)"{-h,--help}"[show this help message and exit]"
|
||||
{--info,--no-info}"[show additional information, e.g. configuration files (default\: True)]:info:"
|
||||
{--secure,--no-secure}"[hide passwords and secrets from output (default\: True)]:secure:"
|
||||
":filter settings by section (default\: None):"
|
||||
":filter settings by key (default\: None):"
|
||||
)
|
||||
|
||||
_shtab_ahriman_service_config_validate_options=(
|
||||
@ -471,6 +481,11 @@ _shtab_ahriman_service_key_import_options=(
|
||||
":PGP key to import from public server:"
|
||||
)
|
||||
|
||||
_shtab_ahriman_service_repositories_options=(
|
||||
"(- : *)"{-h,--help}"[show this help message and exit]"
|
||||
{--id-only,--no-id-only}"[show machine readable identifier instead (default\: False)]:id_only:"
|
||||
)
|
||||
|
||||
_shtab_ahriman_service_setup_options=(
|
||||
"(- : *)"{-h,--help}"[show this help message and exit]"
|
||||
"--build-as-user[force makepkg user to the specific one (default\: None)]:build_as_user:"
|
||||
@ -652,6 +667,7 @@ _shtab_ahriman() {
|
||||
service-config) _arguments -C -s $_shtab_ahriman_service_config_options ;;
|
||||
service-config-validate) _arguments -C -s $_shtab_ahriman_service_config_validate_options ;;
|
||||
service-key-import) _arguments -C -s $_shtab_ahriman_service_key_import_options ;;
|
||||
service-repositories) _arguments -C -s $_shtab_ahriman_service_repositories_options ;;
|
||||
service-setup) _arguments -C -s $_shtab_ahriman_service_setup_options ;;
|
||||
service-shell) _arguments -C -s $_shtab_ahriman_service_shell_options ;;
|
||||
service-tree-migrate) _arguments -C -s $_shtab_ahriman_service_tree_migrate_options ;;
|
||||
|
@ -69,8 +69,7 @@ def _parser() -> argparse.ArgumentParser:
|
||||
parser = argparse.ArgumentParser(prog="ahriman", description="ArcH linux ReposItory MANager",
|
||||
epilog="Argument list can also be read from file by using @ prefix.",
|
||||
fromfile_prefix_chars="@", formatter_class=_formatter)
|
||||
parser.add_argument("-a", "--architecture", help="target architectures. For several subcommands it can be used "
|
||||
"multiple times", action="append")
|
||||
parser.add_argument("-a", "--architecture", help="filter by target architecture")
|
||||
parser.add_argument("-c", "--configuration", help="configuration path", type=Path,
|
||||
default=Path("/etc") / "ahriman.ini")
|
||||
parser.add_argument("--force", help="force run, remove file lock", action="store_true")
|
||||
@ -82,8 +81,7 @@ def _parser() -> argparse.ArgumentParser:
|
||||
parser.add_argument("-q", "--quiet", help="force disable any logging", action="store_true")
|
||||
parser.add_argument("--report", help="force enable or disable reporting to web service",
|
||||
action=argparse.BooleanOptionalAction, default=True)
|
||||
parser.add_argument("-r", "--repository", help="target repository. For several subcommands it can be used "
|
||||
"multiple times", action="append")
|
||||
parser.add_argument("-r", "--repository", help="filter by target repository")
|
||||
# special secret argument for systemd unit. The issue is that systemd doesn't allow multiple arguments to template
|
||||
# name. This parameter accepts [[arch]-repo] in order to keep backward compatibility
|
||||
parser.add_argument("--repository-id", help=argparse.SUPPRESS)
|
||||
@ -130,6 +128,7 @@ def _parser() -> argparse.ArgumentParser:
|
||||
_set_service_config_parser(subparsers)
|
||||
_set_service_config_validate_parser(subparsers)
|
||||
_set_service_key_import_parser(subparsers)
|
||||
_set_service_repositories(subparsers)
|
||||
_set_service_setup_parser(subparsers)
|
||||
_set_service_shell_parser(subparsers)
|
||||
_set_service_tree_migrate_parser(subparsers)
|
||||
@ -161,8 +160,8 @@ def _set_aur_search_parser(root: SubParserAction) -> argparse.ArgumentParser:
|
||||
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(handlers.Search.SORT_FIELDS))
|
||||
parser.set_defaults(handler=handlers.Search, architecture=[""], lock=None, quiet=True, report=False,
|
||||
repository=[""], unsafe=True)
|
||||
parser.set_defaults(handler=handlers.Search, architecture="", lock=None, quiet=True, report=False,
|
||||
repository="", unsafe=True)
|
||||
return parser
|
||||
|
||||
|
||||
@ -180,7 +179,7 @@ def _set_help_parser(root: SubParserAction) -> argparse.ArgumentParser:
|
||||
description="show help message for application or command and exit",
|
||||
formatter_class=_formatter)
|
||||
parser.add_argument("command", help="show help message for specific command", nargs="?")
|
||||
parser.set_defaults(handler=handlers.Help, architecture=[""], lock=None, quiet=True, report=False, repository=[""],
|
||||
parser.set_defaults(handler=handlers.Help, architecture="", lock=None, quiet=True, report=False, repository="",
|
||||
unsafe=True, parser=_parser)
|
||||
return parser
|
||||
|
||||
@ -199,8 +198,8 @@ def _set_help_commands_unsafe_parser(root: SubParserAction) -> argparse.Argument
|
||||
description="list unsafe commands as defined in default args", formatter_class=_formatter)
|
||||
parser.add_argument("command", 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(handler=handlers.UnsafeCommands, architecture=[""], lock=None, quiet=True, report=False,
|
||||
repository=[""], unsafe=True, parser=_parser)
|
||||
parser.set_defaults(handler=handlers.UnsafeCommands, architecture="", lock=None, quiet=True, report=False,
|
||||
repository="", unsafe=True, parser=_parser)
|
||||
return parser
|
||||
|
||||
|
||||
@ -218,8 +217,8 @@ def _set_help_updates_parser(root: SubParserAction) -> argparse.ArgumentParser:
|
||||
description="request AUR for current version and compare with current service version",
|
||||
formatter_class=_formatter)
|
||||
parser.add_argument("-e", "--exit-code", help="return non-zero exit code if updates available", action="store_true")
|
||||
parser.set_defaults(handler=handlers.ServiceUpdates, architecture=[""], lock=None, quiet=True, report=False,
|
||||
repository=[""], unsafe=True)
|
||||
parser.set_defaults(handler=handlers.ServiceUpdates, architecture="", lock=None, quiet=True, report=False,
|
||||
repository="", unsafe=True)
|
||||
return parser
|
||||
|
||||
|
||||
@ -235,8 +234,8 @@ def _set_help_version_parser(root: SubParserAction) -> argparse.ArgumentParser:
|
||||
"""
|
||||
parser = root.add_parser("help-version", aliases=["version"], help="application version",
|
||||
description="print application and its dependencies versions", formatter_class=_formatter)
|
||||
parser.set_defaults(handler=handlers.Versions, architecture=[""], lock=None, quiet=True, report=False,
|
||||
repository=[""], unsafe=True)
|
||||
parser.set_defaults(handler=handlers.Versions, architecture="", lock=None, quiet=True, report=False,
|
||||
repository="", unsafe=True)
|
||||
return parser
|
||||
|
||||
|
||||
@ -387,8 +386,8 @@ def _set_patch_add_parser(root: SubParserAction) -> argparse.ArgumentParser:
|
||||
"it must end with ()")
|
||||
parser.add_argument("patch", help="path to file which contains function or variable value. If not set, "
|
||||
"the value will be read from stdin", type=Path, nargs="?")
|
||||
parser.set_defaults(handler=handlers.Patch, action=Action.Update, architecture=[""], lock=None, report=False,
|
||||
repository=[""])
|
||||
parser.set_defaults(handler=handlers.Patch, action=Action.Update, architecture="", lock=None, report=False,
|
||||
repository="")
|
||||
return parser
|
||||
|
||||
|
||||
@ -408,8 +407,8 @@ def _set_patch_list_parser(root: SubParserAction) -> argparse.ArgumentParser:
|
||||
parser.add_argument("-e", "--exit-code", help="return non-zero exit status if result is empty", action="store_true")
|
||||
parser.add_argument("-v", "--variable", help="if set, show only patches for specified PKGBUILD variables",
|
||||
action="append")
|
||||
parser.set_defaults(handler=handlers.Patch, action=Action.List, architecture=[""], lock=None, report=False,
|
||||
repository=[""], unsafe=True)
|
||||
parser.set_defaults(handler=handlers.Patch, action=Action.List, architecture="", lock=None, report=False,
|
||||
repository="", unsafe=True)
|
||||
return parser
|
||||
|
||||
|
||||
@ -430,8 +429,8 @@ def _set_patch_remove_parser(root: SubParserAction) -> argparse.ArgumentParser:
|
||||
"to remove only specified PKGBUILD variables. In case if not set, "
|
||||
"it will remove all patches related to the package",
|
||||
action="append")
|
||||
parser.set_defaults(handler=handlers.Patch, action=Action.Remove, architecture=[""], lock=None, report=False,
|
||||
repository=[""])
|
||||
parser.set_defaults(handler=handlers.Patch, action=Action.Remove, architecture="", lock=None, report=False,
|
||||
repository="")
|
||||
return parser
|
||||
|
||||
|
||||
@ -455,8 +454,8 @@ def _set_patch_set_add_parser(root: SubParserAction) -> argparse.ArgumentParser:
|
||||
parser.add_argument("package", help="path to directory with changed files for patch addition/update", type=Path)
|
||||
parser.add_argument("-t", "--track", help="files which has to be tracked", action="append",
|
||||
default=["*.diff", "*.patch"])
|
||||
parser.set_defaults(handler=handlers.Patch, action=Action.Update, architecture=[""], lock=None, report=False,
|
||||
repository=[""], variable=None)
|
||||
parser.set_defaults(handler=handlers.Patch, action=Action.Update, architecture="", lock=None, report=False,
|
||||
repository="", variable=None)
|
||||
return parser
|
||||
|
||||
|
||||
@ -473,7 +472,7 @@ def _set_repo_backup_parser(root: SubParserAction) -> argparse.ArgumentParser:
|
||||
parser = root.add_parser("repo-backup", help="backup repository data",
|
||||
description="backup repository settings and database", formatter_class=_formatter)
|
||||
parser.add_argument("path", help="path of the output archive", type=Path)
|
||||
parser.set_defaults(handler=handlers.Backup, architecture=[""], lock=None, report=False, repository=[""],
|
||||
parser.set_defaults(handler=handlers.Backup, architecture="", lock=None, report=False, repository="",
|
||||
unsafe=True)
|
||||
return parser
|
||||
|
||||
@ -653,7 +652,7 @@ def _set_repo_restore_parser(root: SubParserAction) -> argparse.ArgumentParser:
|
||||
description="restore settings and database", formatter_class=_formatter)
|
||||
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(handler=handlers.Restore, architecture=[""], lock=None, report=False, repository=[""],
|
||||
parser.set_defaults(handler=handlers.Restore, architecture="", lock=None, report=False, repository="",
|
||||
unsafe=True)
|
||||
return parser
|
||||
|
||||
@ -830,6 +829,10 @@ def _set_service_config_parser(root: SubParserAction) -> argparse.ArgumentParser
|
||||
parser = root.add_parser("service-config", aliases=["config", "repo-config"], help="dump configuration",
|
||||
description="dump configuration for the specified architecture",
|
||||
formatter_class=_formatter)
|
||||
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(handler=handlers.Dump, lock=None, quiet=True, report=False, unsafe=True)
|
||||
@ -875,7 +878,27 @@ def _set_service_key_import_parser(root: SubParserAction) -> argparse.ArgumentPa
|
||||
formatter_class=_formatter)
|
||||
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(handler=handlers.KeyImport, architecture=[""], lock=None, report=False, repository=[""])
|
||||
parser.set_defaults(handler=handlers.KeyImport, architecture="", lock=None, report=False, repository="")
|
||||
return parser
|
||||
|
||||
|
||||
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",
|
||||
formatter_class=_formatter)
|
||||
parser.add_argument("--id-only", help="show machine readable identifier instead",
|
||||
action=argparse.BooleanOptionalAction, default=False)
|
||||
parser.set_defaults(handler=handlers.Repositories, architecture="", lock=None, report=False, repository="",
|
||||
unsafe=True)
|
||||
return parser
|
||||
|
||||
|
||||
@ -971,8 +994,8 @@ def _set_user_add_parser(root: SubParserAction) -> argparse.ArgumentParser:
|
||||
"which is in particular must be used for OAuth2 authorization type.")
|
||||
parser.add_argument("-R", "--role", help="user access level",
|
||||
type=UserAccess, choices=enum_values(UserAccess), default=UserAccess.Read)
|
||||
parser.set_defaults(handler=handlers.Users, action=Action.Update, architecture=[""], lock=None, quiet=True,
|
||||
report=False, repository=[""])
|
||||
parser.set_defaults(handler=handlers.Users, action=Action.Update, architecture="", lock=None, quiet=True,
|
||||
report=False, repository="")
|
||||
return parser
|
||||
|
||||
|
||||
@ -992,8 +1015,8 @@ def _set_user_list_parser(root: SubParserAction) -> argparse.ArgumentParser:
|
||||
parser.add_argument("username", help="filter users by username", nargs="?")
|
||||
parser.add_argument("-e", "--exit-code", help="return non-zero exit status if result is empty", action="store_true")
|
||||
parser.add_argument("-R", "--role", help="filter users by role", type=UserAccess, choices=enum_values(UserAccess))
|
||||
parser.set_defaults(handler=handlers.Users, action=Action.List, architecture=[""], lock=None, quiet=True,
|
||||
report=False, repository=[""], unsafe=True)
|
||||
parser.set_defaults(handler=handlers.Users, action=Action.List, architecture="", lock=None, quiet=True,
|
||||
report=False, repository="", unsafe=True)
|
||||
return parser
|
||||
|
||||
|
||||
@ -1011,8 +1034,8 @@ def _set_user_remove_parser(root: SubParserAction) -> argparse.ArgumentParser:
|
||||
description="remove user from the user mapping and update the configuration",
|
||||
formatter_class=_formatter)
|
||||
parser.add_argument("username", help="username for web service")
|
||||
parser.set_defaults(handler=handlers.Users, action=Action.Remove, architecture=[""], lock=None, quiet=True,
|
||||
report=False, repository=[""])
|
||||
parser.set_defaults(handler=handlers.Users, action=Action.Remove, architecture="", lock=None, quiet=True,
|
||||
report=False, repository="")
|
||||
return parser
|
||||
|
||||
|
||||
@ -1027,8 +1050,8 @@ def _set_web_parser(root: SubParserAction) -> argparse.ArgumentParser:
|
||||
argparse.ArgumentParser: created argument parser
|
||||
"""
|
||||
parser = root.add_parser("web", help="web server", description="start web server", formatter_class=_formatter)
|
||||
parser.set_defaults(handler=handlers.Web, lock=Path(tempfile.gettempdir()) / "ahriman-web.lock", report=False,
|
||||
parser=_parser)
|
||||
parser.set_defaults(handler=handlers.Web, architecture="", lock=Path(tempfile.gettempdir()) / "ahriman-web.lock",
|
||||
report=False, repository="", parser=_parser)
|
||||
return parser
|
||||
|
||||
|
||||
|
@ -146,7 +146,7 @@ class Application(ApplicationPackages, ApplicationRepository):
|
||||
# there is local cache, load package from it
|
||||
package = Package.from_build(source_dir, self.repository.architecture, username)
|
||||
else:
|
||||
package = Package.from_aur(package_name, self.repository.pacman, username)
|
||||
package = Package.from_aur(package_name, username)
|
||||
with_dependencies[package.base] = package
|
||||
|
||||
# register package in local database
|
||||
|
@ -63,7 +63,7 @@ class ApplicationPackages(ApplicationProperties):
|
||||
source(str): package base name
|
||||
username(str | None): optional override of username for build process
|
||||
"""
|
||||
package = Package.from_aur(source, self.repository.pacman, username)
|
||||
package = Package.from_aur(source, username)
|
||||
self.database.build_queue_insert(package)
|
||||
self.database.remote_update(package)
|
||||
|
||||
|
@ -104,7 +104,7 @@ class ApplicationRepository(ApplicationProperties):
|
||||
packages: list[str] = []
|
||||
for single in probe.packages:
|
||||
try:
|
||||
_ = Package.from_aur(single, self.repository.pacman, None)
|
||||
_ = Package.from_aur(single, None)
|
||||
except Exception:
|
||||
packages.append(single)
|
||||
return packages
|
||||
|
@ -30,6 +30,7 @@ from ahriman.application.handlers.patch import Patch
|
||||
from ahriman.application.handlers.rebuild import Rebuild
|
||||
from ahriman.application.handlers.remove import Remove
|
||||
from ahriman.application.handlers.remove_unknown import RemoveUnknown
|
||||
from ahriman.application.handlers.repositories import Repositories
|
||||
from ahriman.application.handlers.restore import Restore
|
||||
from ahriman.application.handlers.search import Search
|
||||
from ahriman.application.handlers.service_updates import ServiceUpdates
|
||||
|
@ -44,12 +44,18 @@ class Dump(Handler):
|
||||
configuration(Configuration): configuration instance
|
||||
report(bool): force enable or disable reporting
|
||||
"""
|
||||
root, _ = configuration.check_loaded()
|
||||
ConfigurationPathsPrinter(root, configuration.includes)(verbose=True, separator=" = ")
|
||||
if args.info:
|
||||
root, _ = configuration.check_loaded()
|
||||
ConfigurationPathsPrinter(root, configuration.includes)(verbose=True, separator=" = ")
|
||||
|
||||
# empty line
|
||||
StringPrinter("")(verbose=False)
|
||||
|
||||
dump = configuration.dump()
|
||||
for section, values in sorted(dump.items()):
|
||||
ConfigurationPrinter(section, values)(verbose=not args.secure, separator=" = ")
|
||||
match (args.section, args.key):
|
||||
case None, None: # full configuration
|
||||
dump = configuration.dump()
|
||||
for section, values in sorted(dump.items()):
|
||||
ConfigurationPrinter(section, values)(verbose=not args.secure, separator=" = ")
|
||||
case section, None: # section only
|
||||
values = dict(configuration.items(section)) if configuration.has_section(section) else {}
|
||||
ConfigurationPrinter(section, values)(verbose=not args.secure, separator=" = ")
|
||||
case section, key: # key only
|
||||
value = configuration.get(section, key, fallback="")
|
||||
StringPrinter(value)(verbose=False)
|
||||
|
@ -20,6 +20,7 @@
|
||||
import argparse
|
||||
import logging
|
||||
|
||||
from collections.abc import Iterable
|
||||
from multiprocessing import Pool
|
||||
|
||||
from ahriman.application.lock import Lock
|
||||
@ -64,7 +65,7 @@ class Handler:
|
||||
configuration = Configuration.from_path(args.configuration, repository_id)
|
||||
|
||||
log_handler = LogLoader.handler(args.log_handler)
|
||||
LogLoader.load(configuration, log_handler, quiet=args.quiet, report=args.report)
|
||||
LogLoader.load(repository_id, configuration, log_handler, quiet=args.quiet, report=args.report)
|
||||
|
||||
with Lock(args, repository_id, configuration):
|
||||
cls.run(args, repository_id, configuration, report=args.report)
|
||||
@ -105,59 +106,6 @@ class Handler:
|
||||
|
||||
return 0 if all(result) else 1
|
||||
|
||||
@classmethod
|
||||
def repositories_extract(cls, args: argparse.Namespace) -> list[RepositoryId]:
|
||||
"""
|
||||
get known architectures
|
||||
|
||||
Args:
|
||||
args(argparse.Namespace): command line args
|
||||
|
||||
Returns:
|
||||
list[RepositoryId]: list of repository names and architectures for which tree is created
|
||||
|
||||
Raises:
|
||||
MissingArchitectureError: if no architecture set and automatic detection is not allowed or failed
|
||||
"""
|
||||
configuration = Configuration()
|
||||
configuration.load(args.configuration)
|
||||
# pylint, wtf???
|
||||
root = configuration.getpath("repository", "root") # pylint: disable=assignment-from-no-return
|
||||
|
||||
# preparse systemd repository-id argument
|
||||
# we are using unescaped values, so / is not allowed here, because it is impossible to separate if from dashes
|
||||
if args.repository_id is not None:
|
||||
# repository parts is optional for backward compatibility
|
||||
architecture, *repository_parts = args.repository_id.split("/")
|
||||
args.architecture = [architecture]
|
||||
if repository_parts:
|
||||
args.repository = ["-".join(repository_parts)] # replace slash with dash
|
||||
|
||||
# extract repository names first
|
||||
names = args.repository
|
||||
if names is None: # try to read file system first
|
||||
names = RepositoryPaths.known_repositories(root)
|
||||
if not names: # try to read configuration now
|
||||
names = [configuration.get("repository", "name")]
|
||||
|
||||
# extract architecture names
|
||||
if (architectures := args.architecture) is not None:
|
||||
repositories = set(
|
||||
RepositoryId(architecture, name)
|
||||
for name in names
|
||||
for architecture in architectures
|
||||
)
|
||||
else: # try to read from file system
|
||||
repositories = set(
|
||||
RepositoryId(architecture, name)
|
||||
for name in names
|
||||
for architecture in RepositoryPaths.known_architectures(root, name)
|
||||
)
|
||||
|
||||
if not repositories:
|
||||
raise MissingArchitectureError(args.command)
|
||||
return sorted(repositories)
|
||||
|
||||
@classmethod
|
||||
def run(cls, args: argparse.Namespace, repository_id: RepositoryId, configuration: Configuration, *,
|
||||
report: bool) -> None:
|
||||
@ -189,3 +137,57 @@ class Handler:
|
||||
"""
|
||||
if enabled and predicate:
|
||||
raise ExitCode
|
||||
|
||||
@staticmethod
|
||||
def repositories_extract(args: argparse.Namespace) -> list[RepositoryId]:
|
||||
"""
|
||||
get known architectures
|
||||
|
||||
Args:
|
||||
args(argparse.Namespace): command line args
|
||||
|
||||
Returns:
|
||||
list[RepositoryId]: list of repository names and architectures for which tree is created
|
||||
|
||||
Raises:
|
||||
MissingArchitectureError: if no architecture set and automatic detection is not allowed or failed
|
||||
"""
|
||||
configuration = Configuration()
|
||||
configuration.load(args.configuration)
|
||||
# pylint, wtf???
|
||||
root = configuration.getpath("repository", "root") # pylint: disable=assignment-from-no-return
|
||||
|
||||
# preparse systemd repository-id argument
|
||||
# we are using unescaped values, so / is not allowed here, because it is impossible to separate if from dashes
|
||||
if args.repository_id is not None:
|
||||
separator = "/" if "/" in args.repository_id else "-" # systemd and non-systemd identifiers
|
||||
# repository parts is optional for backward compatibility
|
||||
architecture, *repository_parts = args.repository_id.split(separator)
|
||||
args.architecture = architecture
|
||||
if repository_parts:
|
||||
args.repository = "-".join(repository_parts) # replace slash with dash
|
||||
|
||||
# extract repository names first
|
||||
if (from_args := args.repository) is not None:
|
||||
repositories: Iterable[str] = [from_args]
|
||||
elif (from_filesystem := RepositoryPaths.known_repositories(root)):
|
||||
repositories = from_filesystem
|
||||
else: # try to read configuration now
|
||||
repositories = [configuration.get("repository", "name")]
|
||||
|
||||
# extract architecture names
|
||||
if (architecture := args.architecture) is not None:
|
||||
parsed = set(
|
||||
RepositoryId(architecture, repository)
|
||||
for repository in repositories
|
||||
)
|
||||
else: # try to read from file system
|
||||
parsed = set(
|
||||
RepositoryId(architecture, repository)
|
||||
for repository in repositories
|
||||
for architecture in RepositoryPaths.known_architectures(root, repository)
|
||||
)
|
||||
|
||||
if not parsed:
|
||||
raise MissingArchitectureError(args.command)
|
||||
return sorted(parsed)
|
||||
|
54
src/ahriman/application/handlers/repositories.py
Normal file
54
src/ahriman/application/handlers/repositories.py
Normal file
@ -0,0 +1,54 @@
|
||||
#
|
||||
# Copyright (c) 2021-2023 ahriman team.
|
||||
#
|
||||
# This file is part of ahriman
|
||||
# (see https://github.com/arcan1s/ahriman).
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
import argparse
|
||||
|
||||
from ahriman.application.handlers import Handler
|
||||
from ahriman.core.configuration import Configuration
|
||||
from ahriman.core.formatters import RepositoryPrinter
|
||||
from ahriman.models.repository_id import RepositoryId
|
||||
|
||||
|
||||
class Repositories(Handler):
|
||||
"""
|
||||
repositories listing handler
|
||||
"""
|
||||
|
||||
ALLOW_MULTI_ARCHITECTURE_RUN = False # system-wide action
|
||||
|
||||
@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
|
||||
"""
|
||||
dummy_args = argparse.Namespace(
|
||||
architecture=None,
|
||||
configuration=args.configuration,
|
||||
repository=None,
|
||||
repository_id=None,
|
||||
)
|
||||
for repository in cls.repositories_extract(dummy_args):
|
||||
RepositoryPrinter(repository)(verbose=not args.id_only)
|
@ -22,7 +22,6 @@ import argparse
|
||||
from dataclasses import fields
|
||||
from collections.abc import Callable, Iterable
|
||||
|
||||
from ahriman.application.application import Application
|
||||
from ahriman.application.handlers import Handler
|
||||
from ahriman.core.alpm.remote import AUR, Official
|
||||
from ahriman.core.configuration import Configuration
|
||||
@ -59,10 +58,8 @@ class Search(Handler):
|
||||
configuration(Configuration): configuration instance
|
||||
report(bool): force enable or disable reporting
|
||||
"""
|
||||
application = Application(repository_id, configuration, report=report)
|
||||
|
||||
official_packages_list = Official.multisearch(*args.search, pacman=application.repository.pacman)
|
||||
aur_packages_list = AUR.multisearch(*args.search, pacman=application.repository.pacman)
|
||||
official_packages_list = Official.multisearch(*args.search)
|
||||
aur_packages_list = AUR.multisearch(*args.search)
|
||||
Search.check_if_empty(args.exit_code, not official_packages_list and not aur_packages_list)
|
||||
|
||||
for packages_list in (official_packages_list, aur_packages_list):
|
||||
|
@ -20,7 +20,6 @@
|
||||
import argparse
|
||||
|
||||
from ahriman import __version__
|
||||
from ahriman.application.application import Application
|
||||
from ahriman.application.handlers import Handler
|
||||
from ahriman.core.configuration import Configuration
|
||||
from ahriman.core.formatters import UpdatePrinter
|
||||
@ -47,9 +46,7 @@ class ServiceUpdates(Handler):
|
||||
configuration(Configuration): configuration instance
|
||||
report(bool): force enable or disable reporting
|
||||
"""
|
||||
application = Application(repository_id, configuration, report=report)
|
||||
|
||||
remote = Package.from_aur("ahriman", application.repository.pacman, None)
|
||||
remote = Package.from_aur("ahriman", None)
|
||||
_, release = remote.version.rsplit("-", 1) # we don't store pkgrel locally, so we just append it
|
||||
local_version = f"{__version__}-{release}"
|
||||
|
||||
|
@ -126,16 +126,16 @@ class Setup(Handler):
|
||||
configuration.set_option(section, "target", " ".join([target.name.lower() for target in sign_targets]))
|
||||
configuration.set_option(section, "key", args.sign_key)
|
||||
|
||||
section = Configuration.section_name("web", repository_id.name, repository_id.architecture)
|
||||
if args.web_port is not None:
|
||||
configuration.set_option(section, "port", str(args.web_port))
|
||||
configuration.set_option("web", "port", str(args.web_port))
|
||||
if args.web_unix_socket is not None:
|
||||
configuration.set_option(section, "unix_socket", str(args.web_unix_socket))
|
||||
configuration.set_option("web", "unix_socket", str(args.web_unix_socket))
|
||||
|
||||
if args.generate_salt:
|
||||
configuration.set_option("auth", "salt", User.generate_password(20))
|
||||
|
||||
target = root.include / "00-setup-overrides.ini"
|
||||
(root.include / "00-setup-overrides.ini").unlink(missing_ok=True) # remove old-style configuration
|
||||
target = root.include / f"00-setup-overrides-{repository_id.id}.ini"
|
||||
with target.open("w") as ahriman_configuration:
|
||||
configuration.write(ahriman_configuration)
|
||||
|
||||
|
@ -45,14 +45,18 @@ class StatusUpdate(Handler):
|
||||
configuration(Configuration): configuration instance
|
||||
report(bool): force enable or disable reporting
|
||||
"""
|
||||
# we are using reporter here
|
||||
client = Application(repository_id, configuration, report=True).repository.reporter
|
||||
application = Application(repository_id, configuration, report=True)
|
||||
client = application.repository.reporter
|
||||
|
||||
match args.action:
|
||||
case Action.Update if args.package:
|
||||
# update packages statuses
|
||||
for package in args.package:
|
||||
client.package_update(package, args.status)
|
||||
packages = application.repository.packages()
|
||||
for base in args.package:
|
||||
if (local := next((package for package in packages if package.base == base), None)) is not None:
|
||||
client.package_add(local, args.status)
|
||||
else:
|
||||
client.package_update(base, args.status)
|
||||
case Action.Update:
|
||||
# update service status
|
||||
client.status_update(args.status)
|
||||
|
@ -32,7 +32,7 @@ class Web(Handler):
|
||||
web server handler
|
||||
"""
|
||||
|
||||
ALLOW_MULTI_ARCHITECTURE_RUN = False # required to be able to spawn external processes
|
||||
ALLOW_MULTI_ARCHITECTURE_RUN = False # system-wide action
|
||||
|
||||
@classmethod
|
||||
def run(cls, args: argparse.Namespace, repository_id: RepositoryId, configuration: Configuration, *,
|
||||
@ -47,13 +47,20 @@ class Web(Handler):
|
||||
report(bool): force enable or disable reporting
|
||||
"""
|
||||
# we are using local import for optional dependencies
|
||||
from ahriman.web.web import run_server, setup_service
|
||||
from ahriman.web.web import run_server, setup_server
|
||||
|
||||
spawner_args = Web.extract_arguments(args, repository_id, configuration)
|
||||
spawner = Spawn(args.parser(), repository_id, list(spawner_args))
|
||||
spawner_args = Web.extract_arguments(args, configuration)
|
||||
spawner = Spawn(args.parser(), list(spawner_args))
|
||||
spawner.start()
|
||||
|
||||
application = setup_service(repository_id, configuration, spawner)
|
||||
dummy_args = argparse.Namespace(
|
||||
architecture=None,
|
||||
configuration=args.configuration,
|
||||
repository=None,
|
||||
repository_id=None,
|
||||
)
|
||||
repositories = cls.repositories_extract(dummy_args)
|
||||
application = setup_server(configuration, spawner, repositories)
|
||||
run_server(application)
|
||||
|
||||
# terminate spawn process at the last
|
||||
@ -61,22 +68,17 @@ class Web(Handler):
|
||||
spawner.join()
|
||||
|
||||
@staticmethod
|
||||
def extract_arguments(args: argparse.Namespace, repository_id: RepositoryId,
|
||||
configuration: Configuration) -> Generator[str, None, None]:
|
||||
def extract_arguments(args: argparse.Namespace, configuration: Configuration) -> Generator[str, None, None]:
|
||||
"""
|
||||
extract list of arguments used for current command, except for command specific ones
|
||||
|
||||
Args:
|
||||
args(argparse.Namespace): command line args
|
||||
repository_id(RepositoryId): repository unique identifier
|
||||
configuration(Configuration): configuration instance
|
||||
|
||||
Returns:
|
||||
Generator[str, None, None]: command line arguments which were used for this specific command
|
||||
"""
|
||||
# read architecture from the same argument list
|
||||
yield from ["--architecture", repository_id.architecture]
|
||||
yield from ["--repository", repository_id.name]
|
||||
# read configuration path from current settings
|
||||
if (configuration_path := configuration.path) is not None:
|
||||
yield from ["--configuration", str(configuration_path)]
|
||||
|
@ -70,16 +70,15 @@ class Lock(LazyLogging):
|
||||
repository_id(RepositoryId): repository unique identifier
|
||||
configuration(Configuration): configuration instance
|
||||
"""
|
||||
lock_suffix = f"{repository_id.name}_{repository_id.architecture}"
|
||||
self.path: Path | None = \
|
||||
args.lock.with_stem(f"{args.lock.stem}_{lock_suffix}") if args.lock is not None else None
|
||||
args.lock.with_stem(f"{args.lock.stem}_{repository_id.id}") if args.lock is not None else None
|
||||
|
||||
self.force: bool = args.force
|
||||
self.unsafe: bool = args.unsafe
|
||||
self.wait_timeout: int = args.wait_timeout
|
||||
|
||||
self.paths = configuration.repository_paths
|
||||
self.reporter = Client.load(configuration, report=args.report)
|
||||
self.reporter = Client.load(repository_id, configuration, report=args.report)
|
||||
|
||||
def check_version(self) -> None:
|
||||
"""
|
||||
|
@ -113,13 +113,13 @@ class AUR(Remote):
|
||||
response = self.make_request("GET", self.DEFAULT_RPC_URL, params=query)
|
||||
return self.parse_response(response.json())
|
||||
|
||||
def package_info(self, package_name: str, *, pacman: Pacman) -> AURPackage:
|
||||
def package_info(self, package_name: str, *, pacman: Pacman | None) -> AURPackage:
|
||||
"""
|
||||
get package info by its name
|
||||
|
||||
Args:
|
||||
package_name(str): package name to search
|
||||
pacman(Pacman): alpm wrapper instance
|
||||
pacman(Pacman | None): alpm wrapper instance, required for official repositories search
|
||||
|
||||
Returns:
|
||||
AURPackage: package which match the package name
|
||||
@ -133,13 +133,13 @@ class AUR(Remote):
|
||||
except StopIteration:
|
||||
raise UnknownPackageError(package_name) from None
|
||||
|
||||
def package_search(self, *keywords: str, pacman: Pacman) -> list[AURPackage]:
|
||||
def package_search(self, *keywords: str, pacman: Pacman | None) -> list[AURPackage]:
|
||||
"""
|
||||
search package in AUR web
|
||||
|
||||
Args:
|
||||
*keywords(str): keywords to search
|
||||
pacman(Pacman): alpm wrapper instance
|
||||
pacman(Pacman | None): alpm wrapper instance, required for official repositories search
|
||||
|
||||
Returns:
|
||||
list[AURPackage]: list of packages which match the criteria
|
||||
|
@ -107,13 +107,13 @@ class Official(Remote):
|
||||
response = self.make_request("GET", self.DEFAULT_RPC_URL, params=query)
|
||||
return self.parse_response(response.json())
|
||||
|
||||
def package_info(self, package_name: str, *, pacman: Pacman) -> AURPackage:
|
||||
def package_info(self, package_name: str, *, pacman: Pacman | None) -> AURPackage:
|
||||
"""
|
||||
get package info by its name
|
||||
|
||||
Args:
|
||||
package_name(str): package name to search
|
||||
pacman(Pacman): alpm wrapper instance
|
||||
pacman(Pacman | None): alpm wrapper instance, required for official repositories search
|
||||
|
||||
Returns:
|
||||
AURPackage: package which match the package name
|
||||
@ -127,13 +127,13 @@ class Official(Remote):
|
||||
except StopIteration:
|
||||
raise UnknownPackageError(package_name) from None
|
||||
|
||||
def package_search(self, *keywords: str, pacman: Pacman) -> list[AURPackage]:
|
||||
def package_search(self, *keywords: str, pacman: Pacman | None) -> list[AURPackage]:
|
||||
"""
|
||||
search package in AUR web
|
||||
|
||||
Args:
|
||||
*keywords(str): keywords to search
|
||||
pacman(Pacman): alpm wrapper instance
|
||||
pacman(Pacman | None): alpm wrapper instance, required for official repositories search
|
||||
|
||||
Returns:
|
||||
list[AURPackage]: list of packages which match the criteria
|
||||
|
@ -38,13 +38,13 @@ class OfficialSyncdb(Official):
|
||||
Still we leave search function based on the official repositories RPC.
|
||||
"""
|
||||
|
||||
def package_info(self, package_name: str, *, pacman: Pacman) -> AURPackage:
|
||||
def package_info(self, package_name: str, *, pacman: Pacman | None) -> AURPackage:
|
||||
"""
|
||||
get package info by its name
|
||||
|
||||
Args:
|
||||
package_name(str): package name to search
|
||||
pacman(Pacman): alpm wrapper instance
|
||||
pacman(Pacman | None): alpm wrapper instance, required for official repositories search
|
||||
|
||||
Returns:
|
||||
AURPackage: package which match the package name
|
||||
@ -52,6 +52,9 @@ class OfficialSyncdb(Official):
|
||||
Raises:
|
||||
UnknownPackageError: package doesn't exist
|
||||
"""
|
||||
if pacman is None:
|
||||
raise UnknownPackageError(package_name)
|
||||
|
||||
try:
|
||||
return next(AURPackage.from_pacman(package) for package in pacman.package_get(package_name))
|
||||
except StopIteration:
|
||||
|
@ -40,13 +40,14 @@ class Remote(SyncHttpClient):
|
||||
"""
|
||||
|
||||
@classmethod
|
||||
def info(cls, package_name: str, *, pacman: Pacman) -> AURPackage:
|
||||
def info(cls, package_name: str, *, pacman: Pacman | None = None) -> AURPackage:
|
||||
"""
|
||||
get package info by its name
|
||||
|
||||
Args:
|
||||
package_name(str): package name to search
|
||||
pacman(Pacman): alpm wrapper instance
|
||||
pacman(Pacman | None, optional): alpm wrapper instance, required for official repositories search
|
||||
(Default value = None)
|
||||
|
||||
Returns:
|
||||
AURPackage: package which match the package name
|
||||
@ -54,14 +55,15 @@ class Remote(SyncHttpClient):
|
||||
return cls().package_info(package_name, pacman=pacman)
|
||||
|
||||
@classmethod
|
||||
def multisearch(cls, *keywords: str, pacman: Pacman) -> list[AURPackage]:
|
||||
def multisearch(cls, *keywords: str, pacman: Pacman | None = None) -> list[AURPackage]:
|
||||
"""
|
||||
search in remote repository by using API with multiple words. This method is required in order to handle
|
||||
https://bugs.archlinux.org/task/49133. In addition, short words will be dropped
|
||||
|
||||
Args:
|
||||
*keywords(str): search terms, e.g. "ahriman", "is", "cool"
|
||||
pacman(Pacman): alpm wrapper instance
|
||||
pacman(Pacman | None, optional): alpm wrapper instance, required for official repositories search
|
||||
(Default value = None)
|
||||
|
||||
Returns:
|
||||
list[AURPackage]: list of packages each of them matches all search terms
|
||||
@ -111,26 +113,27 @@ class Remote(SyncHttpClient):
|
||||
raise NotImplementedError
|
||||
|
||||
@classmethod
|
||||
def search(cls, *keywords: str, pacman: Pacman) -> list[AURPackage]:
|
||||
def search(cls, *keywords: str, pacman: Pacman | None = None) -> list[AURPackage]:
|
||||
"""
|
||||
search package in AUR web
|
||||
|
||||
Args:
|
||||
*keywords(str): search terms, e.g. "ahriman", "is", "cool"
|
||||
pacman(Pacman): alpm wrapper instance
|
||||
pacman(Pacman | None, optional): alpm wrapper instance, required for official repositories search
|
||||
(Default value = None)
|
||||
|
||||
Returns:
|
||||
list[AURPackage]: list of packages which match the criteria
|
||||
"""
|
||||
return cls().package_search(*keywords, pacman=pacman)
|
||||
|
||||
def package_info(self, package_name: str, *, pacman: Pacman) -> AURPackage:
|
||||
def package_info(self, package_name: str, *, pacman: Pacman | None) -> AURPackage:
|
||||
"""
|
||||
get package info by its name
|
||||
|
||||
Args:
|
||||
package_name(str): package name to search
|
||||
pacman(Pacman): alpm wrapper instance
|
||||
pacman(Pacman | None): alpm wrapper instance, required for official repositories search
|
||||
|
||||
Returns:
|
||||
AURPackage: package which match the package name
|
||||
@ -140,13 +143,13 @@ class Remote(SyncHttpClient):
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
def package_search(self, *keywords: str, pacman: Pacman) -> list[AURPackage]:
|
||||
def package_search(self, *keywords: str, pacman: Pacman | None) -> list[AURPackage]:
|
||||
"""
|
||||
search package in AUR web
|
||||
|
||||
Args:
|
||||
*keywords(str): keywords to search
|
||||
pacman(Pacman): alpm wrapper instance
|
||||
pacman(Pacman | None): alpm wrapper instance, required for official repositories search
|
||||
|
||||
Returns:
|
||||
list[AURPackage]: list of packages which match the criteria
|
||||
|
@ -63,7 +63,7 @@ class Configuration(configparser.RawConfigParser):
|
||||
>>> path, repository_id = configuration.check_loaded()
|
||||
"""
|
||||
|
||||
ARCHITECTURE_SPECIFIC_SECTIONS = ["alpm", "build", "sign", "web"]
|
||||
ARCHITECTURE_SPECIFIC_SECTIONS = ["alpm", "build", "sign"]
|
||||
SYSTEM_CONFIGURATION_PATH = Path(sys.prefix) / "share" / "ahriman" / "settings" / "ahriman.ini"
|
||||
converters: dict[str, Callable[[str], Any]] # typing guard
|
||||
|
||||
@ -232,7 +232,7 @@ class Configuration(configparser.RawConfigParser):
|
||||
dict[str, dict[str, str]]: configuration dump for specific architecture
|
||||
"""
|
||||
return {
|
||||
section: dict(self[section])
|
||||
section: dict(self.items(section))
|
||||
for section in self.sections()
|
||||
}
|
||||
|
||||
@ -282,20 +282,27 @@ class Configuration(configparser.RawConfigParser):
|
||||
if not path.is_file(): # fallback to the system file
|
||||
path = self.SYSTEM_CONFIGURATION_PATH
|
||||
self.path = path
|
||||
self.read(self.path)
|
||||
self.load_includes()
|
||||
|
||||
def load_includes(self) -> None:
|
||||
self.read(self.path)
|
||||
self.load_includes() # load includes
|
||||
|
||||
def load_includes(self, path: Path | None = None) -> None:
|
||||
"""
|
||||
load configuration includes
|
||||
load configuration includes from specified path
|
||||
|
||||
Args:
|
||||
path(Path | None, optional): path to directory with include files. If none set, the default path will be
|
||||
used (Default value = None)
|
||||
"""
|
||||
self.includes = [] # reset state
|
||||
|
||||
try:
|
||||
for path in sorted(self.include.glob("*.ini")):
|
||||
if path == self.logging_path:
|
||||
path = path or self.include
|
||||
for include in sorted(path.glob("*.ini")):
|
||||
if include == self.logging_path:
|
||||
continue # we don't want to load logging explicitly
|
||||
self.read(path)
|
||||
self.includes.append(path)
|
||||
self.read(include)
|
||||
self.includes.append(include)
|
||||
except (FileNotFoundError, configparser.NoOptionError, configparser.NoSectionError):
|
||||
pass
|
||||
|
||||
|
@ -42,7 +42,6 @@ CONFIGURATION_SCHEMA: ConfigurationSchema = {
|
||||
"include": {
|
||||
"type": "path",
|
||||
"coerce": "absolute_path",
|
||||
"required": True,
|
||||
"path_exists": True,
|
||||
"path_type": "dir",
|
||||
},
|
||||
|
@ -43,7 +43,7 @@ class BuildOperations(Operations):
|
||||
""",
|
||||
{
|
||||
"package_base": package_base,
|
||||
"repository": self.repository_id.id,
|
||||
"repository": self._repository_id.id,
|
||||
})
|
||||
|
||||
return self.with_connection(run, commit=True)
|
||||
@ -60,7 +60,7 @@ class BuildOperations(Operations):
|
||||
Package.from_json(row["properties"])
|
||||
for row in connection.execute(
|
||||
"""select properties from build_queue where repository = :repository""",
|
||||
{"repository": self.repository_id.id}
|
||||
{"repository": self._repository_id.id}
|
||||
)
|
||||
]
|
||||
|
||||
@ -86,7 +86,7 @@ class BuildOperations(Operations):
|
||||
{
|
||||
"package_base": package.base,
|
||||
"properties": package.view(),
|
||||
"repository": self.repository_id.id,
|
||||
"repository": self._repository_id.id,
|
||||
})
|
||||
|
||||
return self.with_connection(run, commit=True)
|
||||
|
@ -21,6 +21,7 @@ from sqlite3 import Connection
|
||||
|
||||
from ahriman.core.database.operations import Operations
|
||||
from ahriman.models.log_record_id import LogRecordId
|
||||
from ahriman.models.repository_id import RepositoryId
|
||||
|
||||
|
||||
class LogsOperations(Operations):
|
||||
@ -28,7 +29,8 @@ class LogsOperations(Operations):
|
||||
logs operations
|
||||
"""
|
||||
|
||||
def logs_get(self, package_base: str, limit: int = -1, offset: int = 0) -> list[tuple[float, str]]:
|
||||
def logs_get(self, package_base: str, limit: int = -1, offset: int = 0,
|
||||
repository_id: RepositoryId | None = None) -> list[tuple[float, str]]:
|
||||
"""
|
||||
extract logs for specified package base
|
||||
|
||||
@ -36,10 +38,13 @@ class LogsOperations(Operations):
|
||||
package_base(str): package base to extract logs
|
||||
limit(int, optional): limit records to the specified count, -1 means unlimited (Default value = -1)
|
||||
offset(int, optional): records offset (Default value = 0)
|
||||
repository_id(RepositoryId, optional): repository unique identifier override (Default value = None)
|
||||
|
||||
Return:
|
||||
list[tuple[float, str]]: sorted package log records and their timestamps
|
||||
"""
|
||||
repository_id = repository_id or self._repository_id
|
||||
|
||||
def run(connection: Connection) -> list[tuple[float, str]]:
|
||||
return [
|
||||
(row["created"], row["record"])
|
||||
@ -51,7 +56,7 @@ class LogsOperations(Operations):
|
||||
""",
|
||||
{
|
||||
"package_base": package_base,
|
||||
"repository": self.repository_id.id,
|
||||
"repository": repository_id.id,
|
||||
"limit": limit,
|
||||
"offset": offset,
|
||||
})
|
||||
@ -59,7 +64,8 @@ class LogsOperations(Operations):
|
||||
|
||||
return self.with_connection(run)
|
||||
|
||||
def logs_insert(self, log_record_id: LogRecordId, created: float, record: str) -> None:
|
||||
def logs_insert(self, log_record_id: LogRecordId, created: float, record: str,
|
||||
repository_id: RepositoryId | None = None) -> None:
|
||||
"""
|
||||
write new log record to database
|
||||
|
||||
@ -67,7 +73,10 @@ class LogsOperations(Operations):
|
||||
log_record_id(LogRecordId): current log record id
|
||||
created(float): log created timestamp from log record attribute
|
||||
record(str): log record
|
||||
repository_id(RepositoryId, optional): repository unique identifier override (Default value = None)
|
||||
"""
|
||||
repository_id = repository_id or self._repository_id
|
||||
|
||||
def run(connection: Connection) -> None:
|
||||
connection.execute(
|
||||
"""
|
||||
@ -81,21 +90,23 @@ class LogsOperations(Operations):
|
||||
"version": log_record_id.version,
|
||||
"created": created,
|
||||
"record": record,
|
||||
"repository": self.repository_id.id,
|
||||
"repository": repository_id.id,
|
||||
}
|
||||
)
|
||||
|
||||
return self.with_connection(run, commit=True)
|
||||
|
||||
def logs_remove(self, package_base: str, version: str | None) -> None:
|
||||
def logs_remove(self, package_base: str, version: str | None, repository_id: RepositoryId | None = None) -> None:
|
||||
"""
|
||||
remove log records for the specified package
|
||||
|
||||
Args:
|
||||
package_base(str): package base to remove logs
|
||||
version(str): package version. If set it will remove only logs belonging to another
|
||||
version
|
||||
version(str | None): package version. If set it will remove only logs belonging to another version
|
||||
repository_id(RepositoryId, optional): repository unique identifier override (Default value = None)
|
||||
"""
|
||||
repository_id = repository_id or self._repository_id
|
||||
|
||||
def run(connection: Connection) -> None:
|
||||
connection.execute(
|
||||
"""
|
||||
@ -107,7 +118,7 @@ class LogsOperations(Operations):
|
||||
{
|
||||
"package_base": package_base,
|
||||
"version": version,
|
||||
"repository": self.repository_id.id,
|
||||
"repository": repository_id.id,
|
||||
}
|
||||
)
|
||||
|
||||
|
@ -36,7 +36,6 @@ class Operations(LazyLogging):
|
||||
|
||||
Attributes:
|
||||
path(Path): path to the database file
|
||||
repository_id(RepositoryId): repository unique identifier to perform implicit filtering
|
||||
"""
|
||||
|
||||
def __init__(self, path: Path, repository_id: RepositoryId) -> None:
|
||||
@ -48,7 +47,7 @@ class Operations(LazyLogging):
|
||||
repository_id(RepositoryId): repository unique identifier
|
||||
"""
|
||||
self.path = path
|
||||
self.repository_id = repository_id
|
||||
self._repository_id = repository_id
|
||||
|
||||
@staticmethod
|
||||
def factory(cursor: sqlite3.Cursor, row: tuple[Any, ...]) -> dict[str, Any]:
|
||||
|
@ -25,6 +25,7 @@ from ahriman.models.build_status import BuildStatus
|
||||
from ahriman.models.package import Package
|
||||
from ahriman.models.package_description import PackageDescription
|
||||
from ahriman.models.remote_source import RemoteSource
|
||||
from ahriman.models.repository_id import RepositoryId
|
||||
|
||||
|
||||
class PackageOperations(Operations):
|
||||
@ -32,23 +33,25 @@ class PackageOperations(Operations):
|
||||
package operations
|
||||
"""
|
||||
|
||||
def _package_remove_package_base(self, connection: Connection, package_base: str) -> None:
|
||||
def _package_remove_package_base(self, connection: Connection, package_base: str,
|
||||
repository_id: RepositoryId) -> None:
|
||||
"""
|
||||
remove package base information
|
||||
|
||||
Args:
|
||||
connection(Connection): database connection
|
||||
package_base(str): package base name
|
||||
repository_id(RepositoryId): repository unique identifier
|
||||
"""
|
||||
connection.execute(
|
||||
"""delete from package_statuses where package_base = :package_base and repository = :repository""",
|
||||
{"package_base": package_base, "repository": self.repository_id.id})
|
||||
{"package_base": package_base, "repository": repository_id.id})
|
||||
connection.execute(
|
||||
"""delete from package_bases where package_base = :package_base and repository = :repository""",
|
||||
{"package_base": package_base, "repository": self.repository_id.id})
|
||||
{"package_base": package_base, "repository": repository_id.id})
|
||||
|
||||
def _package_remove_packages(self, connection: Connection, package_base: str,
|
||||
current_packages: Iterable[str]) -> None:
|
||||
current_packages: Iterable[str], repository_id: RepositoryId) -> None:
|
||||
"""
|
||||
remove packages belong to the package base
|
||||
|
||||
@ -56,6 +59,7 @@ class PackageOperations(Operations):
|
||||
connection(Connection): database connection
|
||||
package_base(str): package base name
|
||||
current_packages(Iterable[str]): current packages list which has to be left in database
|
||||
repository_id(RepositoryId): repository unique identifier
|
||||
"""
|
||||
packages = [
|
||||
package
|
||||
@ -63,20 +67,22 @@ class PackageOperations(Operations):
|
||||
"""
|
||||
select package, repository from packages
|
||||
where package_base = :package_base and repository = :repository""",
|
||||
{"package_base": package_base, "repository": self.repository_id.id})
|
||||
{"package_base": package_base, "repository": repository_id.id})
|
||||
if package["package"] not in current_packages
|
||||
]
|
||||
connection.executemany(
|
||||
"""delete from packages where package = :package and repository = :repository""",
|
||||
packages)
|
||||
|
||||
def _package_update_insert_base(self, connection: Connection, package: Package) -> None:
|
||||
def _package_update_insert_base(self, connection: Connection, package: Package,
|
||||
repository_id: RepositoryId) -> None:
|
||||
"""
|
||||
insert base package into table
|
||||
|
||||
Args:
|
||||
connection(Connection): database connection
|
||||
package(Package): package properties
|
||||
repository_id(RepositoryId): repository unique identifier
|
||||
"""
|
||||
connection.execute(
|
||||
"""
|
||||
@ -97,17 +103,19 @@ class PackageOperations(Operations):
|
||||
"web_url": package.remote.web_url,
|
||||
"source": package.remote.source.value,
|
||||
"packager": package.packager,
|
||||
"repository": self.repository_id.id,
|
||||
"repository": repository_id.id,
|
||||
}
|
||||
)
|
||||
|
||||
def _package_update_insert_packages(self, connection: Connection, package: Package) -> None:
|
||||
def _package_update_insert_packages(self, connection: Connection, package: Package,
|
||||
repository_id: RepositoryId) -> None:
|
||||
"""
|
||||
insert packages into table
|
||||
|
||||
Args:
|
||||
connection(Connection): database connection
|
||||
package(Package): package properties
|
||||
repository_id(RepositoryId): repository unique identifier
|
||||
"""
|
||||
package_list = []
|
||||
for name, description in package.packages.items():
|
||||
@ -116,7 +124,7 @@ class PackageOperations(Operations):
|
||||
package_list.append({
|
||||
"package": name,
|
||||
"package_base": package.base,
|
||||
"repository": self.repository_id.id,
|
||||
"repository": repository_id.id,
|
||||
**description.view(),
|
||||
})
|
||||
connection.executemany(
|
||||
@ -141,7 +149,8 @@ class PackageOperations(Operations):
|
||||
""",
|
||||
package_list)
|
||||
|
||||
def _package_update_insert_status(self, connection: Connection, package_base: str, status: BuildStatus) -> None:
|
||||
def _package_update_insert_status(self, connection: Connection, package_base: str, status: BuildStatus,
|
||||
repository_id: RepositoryId) -> None:
|
||||
"""
|
||||
insert base package status into table
|
||||
|
||||
@ -149,6 +158,7 @@ class PackageOperations(Operations):
|
||||
connection(Connection): database connection
|
||||
package_base(str): package base name
|
||||
status(BuildStatus): new build status
|
||||
repository_id(RepositoryId): repository unique identifier
|
||||
"""
|
||||
connection.execute(
|
||||
"""
|
||||
@ -163,15 +173,17 @@ class PackageOperations(Operations):
|
||||
"package_base": package_base,
|
||||
"status": status.status.value,
|
||||
"last_updated": status.timestamp,
|
||||
"repository": self.repository_id.id,
|
||||
"repository": repository_id.id,
|
||||
})
|
||||
|
||||
def _packages_get_select_package_bases(self, connection: Connection) -> dict[str, Package]:
|
||||
def _packages_get_select_package_bases(self, connection: Connection,
|
||||
repository_id: RepositoryId) -> dict[str, Package]:
|
||||
"""
|
||||
select package bases from the table
|
||||
|
||||
Args:
|
||||
connection(Connection): database connection
|
||||
repository_id(RepositoryId): repository unique identifier
|
||||
|
||||
Returns:
|
||||
dict[str, Package]: map of the package base to its descriptor (without packages themselves)
|
||||
@ -185,36 +197,40 @@ class PackageOperations(Operations):
|
||||
packager=row["packager"] or None,
|
||||
) for row in connection.execute(
|
||||
"""select * from package_bases where repository = :repository""",
|
||||
{"repository": self.repository_id.id}
|
||||
{"repository": repository_id.id}
|
||||
)
|
||||
}
|
||||
|
||||
def _packages_get_select_packages(self, connection: Connection, packages: dict[str, Package]) -> dict[str, Package]:
|
||||
def _packages_get_select_packages(self, connection: Connection, packages: dict[str, Package],
|
||||
repository_id: RepositoryId) -> dict[str, Package]:
|
||||
"""
|
||||
select packages from the table
|
||||
|
||||
Args:
|
||||
connection(Connection): database connection
|
||||
packages(dict[str, Package]): packages descriptor map
|
||||
repository_id(RepositoryId): repository unique identifier
|
||||
|
||||
Returns:
|
||||
dict[str, Package]: map of the package base to its descriptor including individual packages
|
||||
"""
|
||||
for row in connection.execute(
|
||||
"""select * from packages where repository = :repository""",
|
||||
{"repository": self.repository_id.id}
|
||||
{"repository": repository_id.id}
|
||||
):
|
||||
if row["package_base"] not in packages:
|
||||
continue # normally must never happen though
|
||||
packages[row["package_base"]].packages[row["package"]] = PackageDescription.from_json(row)
|
||||
return packages
|
||||
|
||||
def _packages_get_select_statuses(self, connection: Connection) -> dict[str, BuildStatus]:
|
||||
def _packages_get_select_statuses(self, connection: Connection,
|
||||
repository_id: RepositoryId) -> dict[str, BuildStatus]:
|
||||
"""
|
||||
select package build statuses from the table
|
||||
|
||||
Args:
|
||||
connection(Connection): database connection
|
||||
repository_id(RepositoryId): repository unique identifier
|
||||
|
||||
Returns:
|
||||
dict[str, BuildStatus]: map of the package base to its status
|
||||
@ -223,50 +239,62 @@ class PackageOperations(Operations):
|
||||
row["package_base"]: BuildStatus.from_json({"status": row["status"], "timestamp": row["last_updated"]})
|
||||
for row in connection.execute(
|
||||
"""select * from package_statuses where repository = :repository""",
|
||||
{"repository": self.repository_id.id}
|
||||
{"repository": repository_id.id}
|
||||
)
|
||||
}
|
||||
|
||||
def package_remove(self, package_base: str) -> None:
|
||||
def package_remove(self, package_base: str, repository_id: RepositoryId | None = None) -> None:
|
||||
"""
|
||||
remove package from database
|
||||
|
||||
Args:
|
||||
package_base(str): package base name
|
||||
repository_id(RepositoryId, optional): repository unique identifier override (Default value = None)
|
||||
"""
|
||||
repository_id = repository_id or self._repository_id
|
||||
|
||||
def run(connection: Connection) -> None:
|
||||
self._package_remove_packages(connection, package_base, [])
|
||||
self._package_remove_package_base(connection, package_base)
|
||||
self._package_remove_packages(connection, package_base, [], repository_id)
|
||||
self._package_remove_package_base(connection, package_base, repository_id)
|
||||
|
||||
return self.with_connection(run, commit=True)
|
||||
|
||||
def package_update(self, package: Package, status: BuildStatus) -> None:
|
||||
def package_update(self, package: Package, status: BuildStatus, repository_id: RepositoryId | None = None) -> None:
|
||||
"""
|
||||
update package status
|
||||
|
||||
Args:
|
||||
package(Package): package properties
|
||||
status(BuildStatus): new build status
|
||||
repository_id(RepositoryId, optional): repository unique identifier override (Default value = None)
|
||||
"""
|
||||
repository_id = repository_id or self._repository_id
|
||||
|
||||
def run(connection: Connection) -> None:
|
||||
self._package_update_insert_base(connection, package)
|
||||
self._package_update_insert_status(connection, package.base, status)
|
||||
self._package_update_insert_packages(connection, package)
|
||||
self._package_remove_packages(connection, package.base, package.packages.keys())
|
||||
self._package_update_insert_base(connection, package, repository_id)
|
||||
self._package_update_insert_status(connection, package.base, status, repository_id)
|
||||
self._package_update_insert_packages(connection, package, repository_id)
|
||||
self._package_remove_packages(connection, package.base, package.packages.keys(), repository_id)
|
||||
|
||||
return self.with_connection(run, commit=True)
|
||||
|
||||
def packages_get(self) -> list[tuple[Package, BuildStatus]]:
|
||||
def packages_get(self, repository_id: RepositoryId | None = None) -> list[tuple[Package, BuildStatus]]:
|
||||
"""
|
||||
get package list and their build statuses from database
|
||||
|
||||
Args:
|
||||
repository_id(RepositoryId, optional): repository unique identifier override (Default value = None)
|
||||
|
||||
Return:
|
||||
list[tuple[Package, BuildStatus]]: list of package properties and their statuses
|
||||
"""
|
||||
repository_id = repository_id or self._repository_id
|
||||
|
||||
def run(connection: Connection) -> Generator[tuple[Package, BuildStatus], None, None]:
|
||||
packages = self._packages_get_select_package_bases(connection)
|
||||
statuses = self._packages_get_select_statuses(connection)
|
||||
for package_base, package in self._packages_get_select_packages(connection, packages).items():
|
||||
packages = self._packages_get_select_package_bases(connection, repository_id)
|
||||
statuses = self._packages_get_select_statuses(connection, repository_id)
|
||||
per_package_base = self._packages_get_select_packages(connection, packages, repository_id)
|
||||
for package_base, package in per_package_base.items():
|
||||
yield package, statuses.get(package_base, BuildStatus())
|
||||
|
||||
return self.with_connection(lambda connection: list(run(connection)))
|
||||
@ -279,7 +307,7 @@ class PackageOperations(Operations):
|
||||
package(Package): package properties
|
||||
"""
|
||||
return self.with_connection(
|
||||
lambda connection: self._package_update_insert_base(connection, package),
|
||||
lambda connection: self._package_update_insert_base(connection, package, self._repository_id),
|
||||
commit=True)
|
||||
|
||||
def remotes_get(self) -> dict[str, RemoteSource]:
|
||||
@ -289,8 +317,10 @@ class PackageOperations(Operations):
|
||||
Returns:
|
||||
dict[str, RemoteSource]: map of package base to its remote sources
|
||||
"""
|
||||
packages = self.with_connection(self._packages_get_select_package_bases)
|
||||
def run(connection: Connection) -> dict[str, Package]:
|
||||
return self._packages_get_select_package_bases(connection, self._repository_id)
|
||||
|
||||
return {
|
||||
package_base: package.remote
|
||||
for package_base, package in packages.items()
|
||||
for package_base, package in self.with_connection(run).items()
|
||||
}
|
||||
|
@ -26,6 +26,7 @@ from ahriman.core.formatters.configuration_paths_printer import ConfigurationPat
|
||||
from ahriman.core.formatters.configuration_printer import ConfigurationPrinter
|
||||
from ahriman.core.formatters.package_printer import PackagePrinter
|
||||
from ahriman.core.formatters.patch_printer import PatchPrinter
|
||||
from ahriman.core.formatters.repository_printer import RepositoryPrinter
|
||||
from ahriman.core.formatters.status_printer import StatusPrinter
|
||||
from ahriman.core.formatters.tree_printer import TreePrinter
|
||||
from ahriman.core.formatters.update_printer import UpdatePrinter
|
||||
|
53
src/ahriman/core/formatters/repository_printer.py
Normal file
53
src/ahriman/core/formatters/repository_printer.py
Normal file
@ -0,0 +1,53 @@
|
||||
#
|
||||
# Copyright (c) 2021-2023 ahriman team.
|
||||
#
|
||||
# This file is part of ahriman
|
||||
# (see https://github.com/arcan1s/ahriman).
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
from ahriman.core.formatters import StringPrinter
|
||||
from ahriman.models.property import Property
|
||||
from ahriman.models.repository_id import RepositoryId
|
||||
|
||||
|
||||
class RepositoryPrinter(StringPrinter):
|
||||
"""
|
||||
print repository unique identifier
|
||||
|
||||
Attributes:
|
||||
repository_id(RepositoryId): repository unique identifier
|
||||
"""
|
||||
|
||||
def __init__(self, repository_id: RepositoryId) -> None:
|
||||
"""
|
||||
default constructor
|
||||
|
||||
Args:
|
||||
repository_id(RepositoryId): repository unique identifier
|
||||
"""
|
||||
StringPrinter.__init__(self, repository_id.id)
|
||||
self.repository_id = repository_id
|
||||
|
||||
def properties(self) -> list[Property]:
|
||||
"""
|
||||
convert content into printable data
|
||||
|
||||
Returns:
|
||||
list[Property]: list of content properties
|
||||
"""
|
||||
return [
|
||||
Property("Name", self.repository_id.name),
|
||||
Property("Architecture", self.repository_id.architecture),
|
||||
]
|
@ -22,6 +22,7 @@ import logging
|
||||
from typing import Self
|
||||
|
||||
from ahriman.core.configuration import Configuration
|
||||
from ahriman.models.repository_id import RepositoryId
|
||||
|
||||
|
||||
class HttpLogHandler(logging.Handler):
|
||||
@ -34,11 +35,13 @@ class HttpLogHandler(logging.Handler):
|
||||
suppress_errors(bool): suppress logging errors (e.g. if no web server available)
|
||||
"""
|
||||
|
||||
def __init__(self, configuration: Configuration, *, report: bool, suppress_errors: bool) -> None:
|
||||
def __init__(self, repository_id: RepositoryId, configuration: Configuration, *,
|
||||
report: bool, suppress_errors: bool) -> None:
|
||||
"""
|
||||
default constructor
|
||||
|
||||
Args:
|
||||
repository_id(RepositoryId): repository unique identifier
|
||||
configuration(Configuration): configuration instance
|
||||
report(bool): force enable or disable reporting
|
||||
suppress_errors(bool): suppress logging errors (e.g. if no web server available)
|
||||
@ -48,16 +51,17 @@ class HttpLogHandler(logging.Handler):
|
||||
|
||||
# client has to be imported here because of circular imports
|
||||
from ahriman.core.status.client import Client
|
||||
self.reporter = Client.load(configuration, report=report)
|
||||
self.reporter = Client.load(repository_id, configuration, report=report)
|
||||
self.suppress_errors = suppress_errors
|
||||
|
||||
@classmethod
|
||||
def load(cls, configuration: Configuration, *, report: bool) -> Self:
|
||||
def load(cls, repository_id: RepositoryId, configuration: Configuration, *, report: bool) -> Self:
|
||||
"""
|
||||
install logger. This function creates handler instance and adds it to the handler list in case if no other
|
||||
http handler found
|
||||
|
||||
Args:
|
||||
repository_id(RepositoryId): repository unique identifier
|
||||
configuration(Configuration): configuration instance
|
||||
report(bool): force enable or disable reporting
|
||||
|
||||
@ -69,7 +73,7 @@ class HttpLogHandler(logging.Handler):
|
||||
return handler # there is already registered instance
|
||||
|
||||
suppress_errors = configuration.getboolean("settings", "suppress_http_log_errors", fallback=False)
|
||||
handler = cls(configuration, report=report, suppress_errors=suppress_errors)
|
||||
handler = cls(repository_id, configuration, report=report, suppress_errors=suppress_errors)
|
||||
root.addHandler(handler)
|
||||
|
||||
return handler
|
||||
|
@ -25,6 +25,7 @@ from pathlib import Path
|
||||
from ahriman.core.configuration import Configuration
|
||||
from ahriman.core.log.http_log_handler import HttpLogHandler
|
||||
from ahriman.models.log_handler import LogHandler
|
||||
from ahriman.models.repository_id import RepositoryId
|
||||
|
||||
|
||||
class LogLoader:
|
||||
@ -68,11 +69,13 @@ class LogLoader:
|
||||
return LogHandler.Console
|
||||
|
||||
@staticmethod
|
||||
def load(configuration: Configuration, handler: LogHandler, *, quiet: bool, report: bool) -> None:
|
||||
def load(repository_id: RepositoryId, configuration: Configuration, handler: LogHandler, *,
|
||||
quiet: bool, report: bool) -> None:
|
||||
"""
|
||||
setup logging settings from configuration
|
||||
|
||||
Args:
|
||||
repository_id(RepositoryId): repository unique identifier
|
||||
configuration(Configuration): configuration instance
|
||||
handler(LogHandler): selected default log handler, which will be used if no handlers were set
|
||||
quiet(bool): force disable any log messages
|
||||
@ -97,7 +100,7 @@ class LogLoader:
|
||||
logging.basicConfig(filename=None, format=LogLoader.DEFAULT_LOG_FORMAT, level=LogLoader.DEFAULT_LOG_LEVEL)
|
||||
logging.exception("could not load logging from configuration, fallback to stderr")
|
||||
|
||||
HttpLogHandler.load(configuration, report=report)
|
||||
HttpLogHandler.load(repository_id, configuration, report=report)
|
||||
|
||||
if quiet:
|
||||
logging.disable(logging.WARNING) # only print errors here
|
||||
|
@ -51,7 +51,7 @@ class RemoteCall(Report):
|
||||
"""
|
||||
Report.__init__(self, repository_id, configuration)
|
||||
|
||||
self.client = WebClient(configuration)
|
||||
self.client = WebClient(repository_id, configuration)
|
||||
|
||||
self.update_aur = configuration.getboolean(section, "aur", fallback=False)
|
||||
self.update_local = configuration.getboolean(section, "local", fallback=False)
|
||||
@ -100,11 +100,13 @@ class RemoteCall(Report):
|
||||
Returns:
|
||||
str: remote process id
|
||||
"""
|
||||
response = self.client.make_request("POST", "/api/v1/service/update", json={
|
||||
"aur": self.update_aur,
|
||||
"local": self.update_local,
|
||||
"manual": self.update_manual,
|
||||
})
|
||||
response = self.client.make_request("POST", "/api/v1/service/update",
|
||||
params=self.repository_id.query(),
|
||||
json={
|
||||
"aur": self.update_aur,
|
||||
"local": self.update_local,
|
||||
"manual": self.update_manual,
|
||||
})
|
||||
response_json = response.json()
|
||||
|
||||
process_id: str = response_json["process_id"]
|
||||
|
@ -75,7 +75,7 @@ class RepositoryProperties(LazyLogging):
|
||||
self.pacman = Pacman(repository_id, configuration, refresh_database=refresh_pacman_database)
|
||||
self.sign = GPG(configuration)
|
||||
self.repo = Repo(self.name, self.paths, self.sign.repository_sign_args)
|
||||
self.reporter = Client.load(configuration, report=report)
|
||||
self.reporter = Client.load(repository_id, configuration, report=report)
|
||||
self.triggers = TriggerLoader.load(repository_id, configuration)
|
||||
|
||||
@property
|
||||
|
@ -61,7 +61,7 @@ class UpdateHandler(Cleaner):
|
||||
try:
|
||||
if package.remote.source == PackageSource.Repository:
|
||||
return Package.from_official(probe, self.pacman, None)
|
||||
return Package.from_aur(probe, self.pacman, None)
|
||||
return Package.from_aur(probe, None)
|
||||
except UnknownPackageError:
|
||||
continue
|
||||
raise UnknownPackageError(package.base)
|
||||
|
@ -28,6 +28,7 @@ from multiprocessing import Process, Queue
|
||||
from threading import Lock, Thread
|
||||
|
||||
from ahriman.core.log import LazyLogging
|
||||
from ahriman.models.process_status import ProcessStatus
|
||||
from ahriman.models.repository_id import RepositoryId
|
||||
|
||||
|
||||
@ -39,22 +40,18 @@ class Spawn(Thread, LazyLogging):
|
||||
Attributes:
|
||||
active(dict[str, Process]): map of active child processes required to avoid zombies
|
||||
command_arguments(list[str]): base command line arguments
|
||||
queue(Queue[tuple[str, bool, int]]): multiprocessing queue to read updates from processes
|
||||
repository_id(RepositoryId): repository unique identifier
|
||||
queue(Queue[ProcessStatus | None]): multiprocessing queue to read updates from processes
|
||||
"""
|
||||
|
||||
def __init__(self, args_parser: argparse.ArgumentParser, repository_id: RepositoryId,
|
||||
command_arguments: list[str]) -> None:
|
||||
def __init__(self, args_parser: argparse.ArgumentParser, command_arguments: list[str]) -> None:
|
||||
"""
|
||||
default constructor
|
||||
|
||||
Args:
|
||||
args_parser(argparse.ArgumentParser): command line parser for the application
|
||||
repository_id(RepositoryId): repository unique identifier
|
||||
command_arguments(list[str]): base command line arguments
|
||||
"""
|
||||
Thread.__init__(self, name="spawn")
|
||||
self.repository_id = repository_id
|
||||
|
||||
self.args_parser = args_parser
|
||||
self.command_arguments = command_arguments
|
||||
@ -62,7 +59,7 @@ class Spawn(Thread, LazyLogging):
|
||||
self.lock = Lock()
|
||||
self.active: dict[str, Process] = {}
|
||||
# stupid pylint does not know that it is possible
|
||||
self.queue: Queue[tuple[str, bool, int] | None] = Queue() # pylint: disable=unsubscriptable-object
|
||||
self.queue: Queue[ProcessStatus | None] = Queue() # pylint: disable=unsubscriptable-object
|
||||
|
||||
@staticmethod
|
||||
def boolean_action_argument(name: str, value: bool) -> str:
|
||||
@ -80,16 +77,16 @@ class Spawn(Thread, LazyLogging):
|
||||
|
||||
@staticmethod
|
||||
def process(callback: Callable[[argparse.Namespace, RepositoryId], bool], args: argparse.Namespace,
|
||||
repository_id: RepositoryId, process_id: str, queue: Queue[tuple[str, bool, int]]) -> None: # pylint: disable=unsubscriptable-object
|
||||
repository_id: RepositoryId, process_id: str, queue: Queue[ProcessStatus | None]) -> None: # pylint: disable=unsubscriptable-object
|
||||
"""
|
||||
helper to run external process
|
||||
|
||||
Args:
|
||||
callback(Callable[[argparse.Namespace, str], bool]): application run function (i.e. Handler.run method)
|
||||
callback(Callable[[argparse.Namespace, str], bool]): application run function (i.e. ``Handler.call`` method)
|
||||
args(argparse.Namespace): command line arguments
|
||||
repository_id(RepositoryId): repository unique identifier
|
||||
process_id(str): process unique identifier
|
||||
queue(Queue[tuple[str, bool, int]]): output queue
|
||||
queue(Queue[ProcessStatus | None]): output queue
|
||||
"""
|
||||
start_time = time.monotonic()
|
||||
result = callback(args, repository_id)
|
||||
@ -97,19 +94,20 @@ class Spawn(Thread, LazyLogging):
|
||||
|
||||
consumed_time = int(1000 * (stop_time - start_time))
|
||||
|
||||
queue.put((process_id, result, consumed_time))
|
||||
queue.put(ProcessStatus(process_id, result, consumed_time))
|
||||
|
||||
def _spawn_process(self, command: str, *args: str, **kwargs: str | None) -> str:
|
||||
def _spawn_process(self, repository_id: RepositoryId, command: str, *args: str, **kwargs: str | None) -> str:
|
||||
"""
|
||||
spawn external ahriman process with supplied arguments
|
||||
|
||||
Args:
|
||||
repository_id(RepositoryId): repository unique identifier
|
||||
command(str): subcommand to run
|
||||
*args(str): positional command arguments
|
||||
**kwargs(str): named command arguments
|
||||
|
||||
Returns:
|
||||
str: spawned process id
|
||||
str: spawned process identifier
|
||||
"""
|
||||
# default arguments
|
||||
arguments = self.command_arguments[:]
|
||||
@ -125,12 +123,14 @@ class Spawn(Thread, LazyLogging):
|
||||
arguments.append(value)
|
||||
|
||||
process_id = str(uuid.uuid4())
|
||||
self.logger.info("full command line arguments of %s are %s", process_id, arguments)
|
||||
self.logger.info("full command line arguments of %s are %s using repository %s",
|
||||
process_id, arguments, repository_id)
|
||||
|
||||
parsed = self.args_parser.parse_args(arguments)
|
||||
|
||||
callback = parsed.handler.call
|
||||
process = Process(target=self.process,
|
||||
args=(callback, parsed, self.repository_id, process_id, self.queue),
|
||||
args=(callback, parsed, repository_id, process_id, self.queue),
|
||||
daemon=True)
|
||||
process.start()
|
||||
|
||||
@ -160,66 +160,73 @@ class Spawn(Thread, LazyLogging):
|
||||
server(str | None): PGP key server
|
||||
|
||||
Returns:
|
||||
str: spawned process id
|
||||
str: spawned process identifier
|
||||
"""
|
||||
kwargs = {} if server is None else {"key-server": server}
|
||||
return self._spawn_process("service-key-import", key, **kwargs)
|
||||
repository_id = RepositoryId("", "")
|
||||
return self._spawn_process(repository_id, "service-key-import", key, **kwargs)
|
||||
|
||||
def packages_add(self, packages: Iterable[str], username: str | None, *, now: bool) -> str:
|
||||
def packages_add(self, repository_id: RepositoryId, packages: Iterable[str], username: str | None, *,
|
||||
now: bool) -> str:
|
||||
"""
|
||||
add packages
|
||||
|
||||
Args:
|
||||
repository_id(RepositoryId): repository unique identifier
|
||||
packages(Iterable[str]): packages list to add
|
||||
username(str | None): optional override of username for build process
|
||||
now(bool): build packages now
|
||||
|
||||
Returns:
|
||||
str: spawned process id
|
||||
str: spawned process identifier
|
||||
"""
|
||||
kwargs = {"username": username}
|
||||
if now:
|
||||
kwargs["now"] = ""
|
||||
return self._spawn_process("package-add", *packages, **kwargs)
|
||||
return self._spawn_process(repository_id, "package-add", *packages, **kwargs)
|
||||
|
||||
def packages_rebuild(self, depends_on: str, username: str | None) -> str:
|
||||
def packages_rebuild(self, repository_id: RepositoryId, depends_on: str, username: str | None) -> str:
|
||||
"""
|
||||
rebuild packages which depend on the specified package
|
||||
|
||||
Args:
|
||||
repository_id(RepositoryId): repository unique identifier
|
||||
depends_on(str): packages dependency
|
||||
username(str | None): optional override of username for build process
|
||||
|
||||
Returns:
|
||||
str: spawned process id
|
||||
str: spawned process identifier
|
||||
"""
|
||||
kwargs = {"depends-on": depends_on, "username": username}
|
||||
return self._spawn_process("repo-rebuild", **kwargs)
|
||||
return self._spawn_process(repository_id, "repo-rebuild", **kwargs)
|
||||
|
||||
def packages_remove(self, packages: Iterable[str]) -> str:
|
||||
def packages_remove(self, repository_id: RepositoryId, packages: Iterable[str]) -> str:
|
||||
"""
|
||||
remove packages
|
||||
|
||||
Args:
|
||||
repository_id(RepositoryId): repository unique identifier
|
||||
packages(Iterable[str]): packages list to remove
|
||||
|
||||
Returns:
|
||||
str: spawned process id
|
||||
str: spawned process identifier
|
||||
"""
|
||||
return self._spawn_process("package-remove", *packages)
|
||||
return self._spawn_process(repository_id, "package-remove", *packages)
|
||||
|
||||
def packages_update(self, username: str | None, *, aur: bool, local: bool, manual: bool) -> str:
|
||||
def packages_update(self, repository_id: RepositoryId, username: str | None, *,
|
||||
aur: bool, local: bool, manual: bool) -> str:
|
||||
"""
|
||||
run full repository update
|
||||
|
||||
Args:
|
||||
repository_id(RepositoryId): repository unique identifier
|
||||
username(str | None): optional override of username for build process
|
||||
aur(bool): check for aur updates
|
||||
local(bool): check for local packages updates
|
||||
manual(bool): check for manual packages
|
||||
|
||||
Returns:
|
||||
str: spawned process id
|
||||
str: spawned process identifier
|
||||
"""
|
||||
kwargs = {
|
||||
"username": username,
|
||||
@ -227,18 +234,18 @@ class Spawn(Thread, LazyLogging):
|
||||
self.boolean_action_argument("local", local): "",
|
||||
self.boolean_action_argument("manual", manual): "",
|
||||
}
|
||||
return self._spawn_process("repo-update", **kwargs)
|
||||
return self._spawn_process(repository_id, "repo-update", **kwargs)
|
||||
|
||||
def run(self) -> None:
|
||||
"""
|
||||
thread run method
|
||||
"""
|
||||
for process_id, status, consumed_time in iter(self.queue.get, None):
|
||||
self.logger.info("process %s has been terminated with status %s, consumed time %s",
|
||||
process_id, status, consumed_time / 1000)
|
||||
for terminated in iter(self.queue.get, None):
|
||||
self.logger.info("process %s has been terminated with status %s, consumed time %ss",
|
||||
terminated.process_id, terminated.status, terminated.consumed_time / 1000)
|
||||
|
||||
with self.lock:
|
||||
process = self.active.pop(process_id, None)
|
||||
process = self.active.pop(terminated.process_id, None)
|
||||
|
||||
if process is not None:
|
||||
process.join()
|
||||
|
@ -26,6 +26,7 @@ from ahriman.models.build_status import BuildStatus, BuildStatusEnum
|
||||
from ahriman.models.internal_status import InternalStatus
|
||||
from ahriman.models.log_record_id import LogRecordId
|
||||
from ahriman.models.package import Package
|
||||
from ahriman.models.repository_id import RepositoryId
|
||||
|
||||
|
||||
class Client:
|
||||
@ -34,11 +35,12 @@ class Client:
|
||||
"""
|
||||
|
||||
@staticmethod
|
||||
def load(configuration: Configuration, *, report: bool) -> Client:
|
||||
def load(repository_id: RepositoryId, configuration: Configuration, *, report: bool) -> Client:
|
||||
"""
|
||||
load client from settings
|
||||
|
||||
Args:
|
||||
repository_id(RepositoryId): repository unqiue identifier
|
||||
configuration(Configuration): configuration instance
|
||||
report(bool): force enable or disable reporting
|
||||
|
||||
@ -58,7 +60,7 @@ class Client:
|
||||
# but it will totally break used experience
|
||||
if address or (host and port) or socket:
|
||||
from ahriman.core.status.web_client import WebClient
|
||||
return WebClient(configuration)
|
||||
return WebClient(repository_id, configuration)
|
||||
return Client()
|
||||
|
||||
def package_add(self, package: Package, status: BuildStatusEnum) -> None:
|
||||
|
@ -17,11 +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/>.
|
||||
#
|
||||
from ahriman.core.configuration import Configuration
|
||||
from ahriman.core.database import SQLite
|
||||
from ahriman.core.exceptions import UnknownPackageError
|
||||
from ahriman.core.log import LazyLogging
|
||||
from ahriman.core.repository import Repository
|
||||
from ahriman.models.build_status import BuildStatus, BuildStatusEnum
|
||||
from ahriman.models.log_record_id import LogRecordId
|
||||
from ahriman.models.package import Package
|
||||
@ -36,23 +34,20 @@ class Watcher(LazyLogging):
|
||||
database(SQLite): database instance
|
||||
known(dict[str, tuple[Package, BuildStatus]]): list of known packages. For the most cases ``packages`` should
|
||||
be used instead
|
||||
repository(Repository): repository object
|
||||
repository_id(RepositoryId): repository unique identifier
|
||||
status(BuildStatus): daemon status
|
||||
"""
|
||||
|
||||
def __init__(self, repository_id: RepositoryId, configuration: Configuration, database: SQLite) -> None:
|
||||
def __init__(self, repository_id: RepositoryId, database: SQLite) -> None:
|
||||
"""
|
||||
default constructor
|
||||
|
||||
Args:
|
||||
repository_id(RepositoryId): repository unique identifier
|
||||
configuration(Configuration): configuration instance
|
||||
database(SQLite): database instance
|
||||
"""
|
||||
self.repository_id = repository_id
|
||||
self.database = database
|
||||
self.repository = Repository.load(repository_id, configuration, database, report=False)
|
||||
|
||||
self.known: dict[str, tuple[Package, BuildStatus]] = {}
|
||||
self.status = BuildStatus()
|
||||
@ -72,20 +67,12 @@ class Watcher(LazyLogging):
|
||||
|
||||
def load(self) -> None:
|
||||
"""
|
||||
load packages from local repository. In case if last status is known, it will use it
|
||||
load packages from local database
|
||||
"""
|
||||
for package in self.repository.packages():
|
||||
# get status of build or assign unknown
|
||||
if (current := self.known.get(package.base)) is not None:
|
||||
_, status = current
|
||||
else:
|
||||
status = BuildStatus()
|
||||
self.known = {} # reset state
|
||||
for package, status in self.database.packages_get(self.repository_id):
|
||||
self.known[package.base] = (package, status)
|
||||
|
||||
for package, status in self.database.packages_get():
|
||||
if package.base in self.known:
|
||||
self.known[package.base] = (package, status)
|
||||
|
||||
def logs_get(self, package_base: str, limit: int = -1, offset: int = 0) -> list[tuple[float, str]]:
|
||||
"""
|
||||
extract logs for the package base
|
||||
@ -98,7 +85,7 @@ class Watcher(LazyLogging):
|
||||
Returns:
|
||||
list[tuple[float, str]]: package logs
|
||||
"""
|
||||
return self.database.logs_get(package_base, limit, offset)
|
||||
return self.database.logs_get(package_base, limit, offset, self.repository_id)
|
||||
|
||||
def logs_remove(self, package_base: str, version: str | None) -> None:
|
||||
"""
|
||||
@ -108,7 +95,7 @@ class Watcher(LazyLogging):
|
||||
package_base(str): package base
|
||||
version(str): package versio
|
||||
"""
|
||||
self.database.logs_remove(package_base, version)
|
||||
self.database.logs_remove(package_base, version, self.repository_id)
|
||||
|
||||
def logs_update(self, log_record_id: LogRecordId, created: float, record: str) -> None:
|
||||
"""
|
||||
@ -123,7 +110,7 @@ class Watcher(LazyLogging):
|
||||
# there is new log record, so we remove old ones
|
||||
self.logs_remove(log_record_id.package_base, log_record_id.version)
|
||||
self._last_log_record_id = log_record_id
|
||||
self.database.logs_insert(log_record_id, created, record)
|
||||
self.database.logs_insert(log_record_id, created, record, self.repository_id)
|
||||
|
||||
def package_get(self, package_base: str) -> tuple[Package, BuildStatus]:
|
||||
"""
|
||||
@ -151,7 +138,7 @@ class Watcher(LazyLogging):
|
||||
package_base(str): package base
|
||||
"""
|
||||
self.known.pop(package_base, None)
|
||||
self.database.package_remove(package_base)
|
||||
self.database.package_remove(package_base, self.repository_id)
|
||||
self.logs_remove(package_base, None)
|
||||
|
||||
def package_update(self, package_base: str, status: BuildStatusEnum, package: Package | None) -> None:
|
||||
@ -173,7 +160,7 @@ class Watcher(LazyLogging):
|
||||
raise UnknownPackageError(package_base) from None
|
||||
full_status = BuildStatus(status)
|
||||
self.known[package_base] = (package, full_status)
|
||||
self.database.package_update(package, full_status)
|
||||
self.database.package_update(package, full_status, self.repository_id)
|
||||
|
||||
def status_update(self, status: BuildStatusEnum) -> None:
|
||||
"""
|
||||
|
@ -32,6 +32,7 @@ from ahriman.models.build_status import BuildStatus, BuildStatusEnum
|
||||
from ahriman.models.internal_status import InternalStatus
|
||||
from ahriman.models.log_record_id import LogRecordId
|
||||
from ahriman.models.package import Package
|
||||
from ahriman.models.repository_id import RepositoryId
|
||||
|
||||
|
||||
class WebClient(Client, SyncHttpClient):
|
||||
@ -40,19 +41,22 @@ class WebClient(Client, SyncHttpClient):
|
||||
|
||||
Attributes:
|
||||
address(str): address of the web service
|
||||
repository_id(RepositoryId): repository unique identifier
|
||||
use_unix_socket(bool): use websocket or not
|
||||
"""
|
||||
|
||||
def __init__(self, configuration: Configuration) -> None:
|
||||
def __init__(self, repository_id: RepositoryId, configuration: Configuration) -> None:
|
||||
"""
|
||||
default constructor
|
||||
|
||||
Args:
|
||||
repository_id(RepositoryId): repository unique identifier
|
||||
configuration(Configuration): configuration instance
|
||||
"""
|
||||
suppress_errors = configuration.getboolean("settings", "suppress_http_log_errors", fallback=False)
|
||||
SyncHttpClient.__init__(self, configuration, "web", suppress_errors=suppress_errors)
|
||||
|
||||
self.repository_id = repository_id
|
||||
self.address, self.use_unix_socket = self.parse_address(configuration)
|
||||
|
||||
@cached_property
|
||||
@ -184,7 +188,8 @@ class WebClient(Client, SyncHttpClient):
|
||||
"package": package.view()
|
||||
}
|
||||
with contextlib.suppress(Exception):
|
||||
self.make_request("POST", self._package_url(package.base), json=payload)
|
||||
self.make_request("POST", self._package_url(package.base),
|
||||
params=self.repository_id.query(), json=payload)
|
||||
|
||||
def package_get(self, package_base: str | None) -> list[tuple[Package, BuildStatus]]:
|
||||
"""
|
||||
@ -197,7 +202,8 @@ class WebClient(Client, SyncHttpClient):
|
||||
list[tuple[Package, BuildStatus]]: list of current package description and status if it has been found
|
||||
"""
|
||||
with contextlib.suppress(Exception):
|
||||
response = self.make_request("GET", self._package_url(package_base or ""))
|
||||
response = self.make_request("GET", self._package_url(package_base or ""),
|
||||
params=self.repository_id.query())
|
||||
response_json = response.json()
|
||||
|
||||
return [
|
||||
@ -224,7 +230,8 @@ class WebClient(Client, SyncHttpClient):
|
||||
# this is special case, because we would like to do not suppress exception here
|
||||
# in case of exception raised it will be handled by upstream HttpLogHandler
|
||||
# In the other hand, we force to suppress all http logs here to avoid cyclic reporting
|
||||
self.make_request("POST", self._logs_url(log_record_id.package_base), json=payload, suppress_errors=True)
|
||||
self.make_request("POST", self._logs_url(log_record_id.package_base),
|
||||
params=self.repository_id.query(), json=payload, suppress_errors=True)
|
||||
|
||||
def package_remove(self, package_base: str) -> None:
|
||||
"""
|
||||
@ -234,7 +241,7 @@ class WebClient(Client, SyncHttpClient):
|
||||
package_base(str): basename to remove
|
||||
"""
|
||||
with contextlib.suppress(Exception):
|
||||
self.make_request("DELETE", self._package_url(package_base))
|
||||
self.make_request("DELETE", self._package_url(package_base), params=self.repository_id.query())
|
||||
|
||||
def package_update(self, package_base: str, status: BuildStatusEnum) -> None:
|
||||
"""
|
||||
@ -246,7 +253,8 @@ class WebClient(Client, SyncHttpClient):
|
||||
"""
|
||||
payload = {"status": status.value}
|
||||
with contextlib.suppress(Exception):
|
||||
self.make_request("POST", self._package_url(package_base), json=payload)
|
||||
self.make_request("POST", self._package_url(package_base),
|
||||
params=self.repository_id.query(), json=payload)
|
||||
|
||||
def status_get(self) -> InternalStatus:
|
||||
"""
|
||||
@ -256,7 +264,7 @@ class WebClient(Client, SyncHttpClient):
|
||||
InternalStatus: current internal (web) service status
|
||||
"""
|
||||
with contextlib.suppress(Exception):
|
||||
response = self.make_request("GET", self._status_url())
|
||||
response = self.make_request("GET", self._status_url(), params=self.repository_id.query())
|
||||
response_json = response.json()
|
||||
|
||||
return InternalStatus.from_json(response_json)
|
||||
@ -272,4 +280,4 @@ class WebClient(Client, SyncHttpClient):
|
||||
"""
|
||||
payload = {"status": status.value}
|
||||
with contextlib.suppress(Exception):
|
||||
self.make_request("POST", self._status_url(), json=payload)
|
||||
self.make_request("POST", self._status_url(), params=self.repository_id.query(), json=payload)
|
||||
|
@ -51,7 +51,7 @@ class RemoteService(Upload, HttpUpload):
|
||||
"""
|
||||
Upload.__init__(self, repository_id, configuration)
|
||||
HttpUpload.__init__(self, configuration, section)
|
||||
self.client = WebClient(configuration)
|
||||
self.client = WebClient(repository_id, configuration)
|
||||
|
||||
@cached_property
|
||||
def session(self) -> requests.Session:
|
||||
@ -81,7 +81,8 @@ class RemoteService(Upload, HttpUpload):
|
||||
if signature_path is not None:
|
||||
files["signature"] = signature_path.name, signature_path.open("rb"), "application/octet-stream", {}
|
||||
|
||||
self.make_request("POST", f"{self.client.address}/api/v1/service/upload", files=files)
|
||||
self.make_request("POST", f"{self.client.address}/api/v1/service/upload",
|
||||
params=self.repository_id.query(), files=files)
|
||||
finally:
|
||||
for _, fd, _, _ in files.values():
|
||||
fd.close()
|
||||
|
@ -214,19 +214,18 @@ class Package(LazyLogging):
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def from_aur(cls, name: str, pacman: Pacman, packager: str | None = None) -> Self:
|
||||
def from_aur(cls, name: str, packager: str | None = None) -> Self:
|
||||
"""
|
||||
construct package properties from AUR page
|
||||
|
||||
Args:
|
||||
name(str): package name (either base or normal name)
|
||||
pacman(Pacman): alpm wrapper instance
|
||||
packager(str | None, optional): packager to be used for this build (Default value = None)
|
||||
|
||||
Returns:
|
||||
Self: package properties
|
||||
"""
|
||||
package = AUR.info(name, pacman=pacman)
|
||||
package = AUR.info(name)
|
||||
|
||||
remote = RemoteSource(
|
||||
source=PackageSource.AUR,
|
||||
@ -331,7 +330,7 @@ class Package(LazyLogging):
|
||||
Returns:
|
||||
Self: package properties
|
||||
"""
|
||||
package = OfficialSyncdb.info(name, pacman=pacman) if use_syncdb else Official.info(name, pacman=pacman)
|
||||
package = OfficialSyncdb.info(name, pacman=pacman) if use_syncdb else Official.info(name)
|
||||
|
||||
remote = RemoteSource(
|
||||
source=PackageSource.Repository,
|
||||
|
36
src/ahriman/models/process_status.py
Normal file
36
src/ahriman/models/process_status.py
Normal file
@ -0,0 +1,36 @@
|
||||
#
|
||||
# Copyright (c) 2021-2023 ahriman team.
|
||||
#
|
||||
# This file is part of ahriman
|
||||
# (see https://github.com/arcan1s/ahriman).
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
from dataclasses import dataclass
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class ProcessStatus:
|
||||
"""
|
||||
terminated process status descriptor
|
||||
|
||||
Attributes:
|
||||
process_id(str): unique process identifier
|
||||
status(bool): process exit code status
|
||||
consumed_time(int): consumed time in ms
|
||||
"""
|
||||
|
||||
process_id: str
|
||||
status: bool
|
||||
consumed_time: int
|
@ -54,6 +54,27 @@ class RepositoryId:
|
||||
"""
|
||||
return f"{self.architecture}-{self.name}" # basically the same as used for command line
|
||||
|
||||
def query(self) -> list[tuple[str, str]]:
|
||||
"""
|
||||
generate query parameters
|
||||
|
||||
Returns:
|
||||
list[tuple[str, str]]: json view as query parameters
|
||||
"""
|
||||
return list(self.view().items())
|
||||
|
||||
def view(self) -> dict[str, Any]:
|
||||
"""
|
||||
generate json package view
|
||||
|
||||
Returns:
|
||||
dict[str, Any]: json-friendly dictionary
|
||||
"""
|
||||
return {
|
||||
"architecture": self.architecture,
|
||||
"repository": self.name,
|
||||
}
|
||||
|
||||
def __lt__(self, other: Any) -> bool:
|
||||
"""
|
||||
comparison operator for sorting
|
||||
|
@ -38,6 +38,7 @@ from ahriman.web.schemas.pgp_key_schema import PGPKeySchema
|
||||
from ahriman.web.schemas.process_id_schema import ProcessIdSchema
|
||||
from ahriman.web.schemas.process_schema import ProcessSchema
|
||||
from ahriman.web.schemas.remote_schema import RemoteSchema
|
||||
from ahriman.web.schemas.repository_id_schema import RepositoryIdSchema
|
||||
from ahriman.web.schemas.search_schema import SearchSchema
|
||||
from ahriman.web.schemas.status_schema import StatusSchema
|
||||
from ahriman.web.schemas.update_flags_schema import UpdateFlagsSchema
|
||||
|
@ -17,29 +17,22 @@
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
from marshmallow import Schema, fields
|
||||
from marshmallow import fields
|
||||
|
||||
from ahriman import __version__
|
||||
from ahriman.web.schemas.counters_schema import CountersSchema
|
||||
from ahriman.web.schemas.repository_id_schema import RepositoryIdSchema
|
||||
from ahriman.web.schemas.status_schema import StatusSchema
|
||||
|
||||
|
||||
class InternalStatusSchema(Schema):
|
||||
class InternalStatusSchema(RepositoryIdSchema):
|
||||
"""
|
||||
response service status schema
|
||||
"""
|
||||
|
||||
architecture = fields.String(required=True, metadata={
|
||||
"description": "Repository architecture",
|
||||
"example": "x86_64",
|
||||
})
|
||||
packages = fields.Nested(CountersSchema(), required=True, metadata={
|
||||
"description": "Repository package counters",
|
||||
})
|
||||
repository = fields.String(required=True, metadata={
|
||||
"description": "Repository name",
|
||||
"example": "repo-clone",
|
||||
})
|
||||
status = fields.Nested(StatusSchema(), required=True, metadata={
|
||||
"description": "Repository status as stored by web service",
|
||||
})
|
||||
|
@ -17,12 +17,13 @@
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
from marshmallow import Schema, fields
|
||||
from marshmallow import fields
|
||||
|
||||
from ahriman import __version__
|
||||
from ahriman.web.schemas.repository_id_schema import RepositoryIdSchema
|
||||
|
||||
|
||||
class LogSchema(Schema):
|
||||
class LogSchema(RepositoryIdSchema):
|
||||
"""
|
||||
request package log schema
|
||||
"""
|
||||
|
@ -21,6 +21,7 @@ from marshmallow import Schema, fields
|
||||
|
||||
from ahriman.models.build_status import BuildStatusEnum
|
||||
from ahriman.web.schemas.package_schema import PackageSchema
|
||||
from ahriman.web.schemas.repository_id_schema import RepositoryIdSchema
|
||||
from ahriman.web.schemas.status_schema import StatusSchema
|
||||
|
||||
|
||||
@ -35,6 +36,9 @@ class PackageStatusSimplifiedSchema(Schema):
|
||||
status = fields.Enum(BuildStatusEnum, by_value=True, required=True, metadata={
|
||||
"description": "Current status",
|
||||
})
|
||||
repository = fields.Nested(RepositoryIdSchema(), required=True, metadata={
|
||||
"description": "Repository identifier",
|
||||
})
|
||||
|
||||
|
||||
class PackageStatusSchema(Schema):
|
||||
@ -48,3 +52,6 @@ class PackageStatusSchema(Schema):
|
||||
status = fields.Nested(StatusSchema(), required=True, metadata={
|
||||
"description": "Last package status",
|
||||
})
|
||||
repository = fields.Nested(RepositoryIdSchema(), required=True, metadata={
|
||||
"description": "Repository identifier",
|
||||
})
|
||||
|
@ -17,10 +17,12 @@
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
from marshmallow import Schema, fields
|
||||
from marshmallow import fields
|
||||
|
||||
from ahriman.web.schemas.repository_id_schema import RepositoryIdSchema
|
||||
|
||||
|
||||
class PaginationSchema(Schema):
|
||||
class PaginationSchema(RepositoryIdSchema):
|
||||
"""
|
||||
request pagination schema
|
||||
"""
|
||||
|
35
src/ahriman/web/schemas/repository_id_schema.py
Normal file
35
src/ahriman/web/schemas/repository_id_schema.py
Normal file
@ -0,0 +1,35 @@
|
||||
#
|
||||
# Copyright (c) 2021-2023 ahriman team.
|
||||
#
|
||||
# This file is part of ahriman
|
||||
# (see https://github.com/arcan1s/ahriman).
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
from marshmallow import Schema, fields
|
||||
|
||||
|
||||
class RepositoryIdSchema(Schema):
|
||||
"""
|
||||
request and response repository unique identifier schema
|
||||
"""
|
||||
|
||||
architecture = fields.String(metadata={
|
||||
"description": "Repository architecture",
|
||||
"example": "x86_64",
|
||||
})
|
||||
repository = fields.String(metadata={
|
||||
"description": "Repository name",
|
||||
"example": "aur-clone",
|
||||
})
|
@ -18,14 +18,16 @@
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
from aiohttp_cors import CorsViewMixin # type: ignore[import-untyped]
|
||||
from aiohttp.web import HTTPBadRequest, Request, StreamResponse, View
|
||||
from aiohttp.web import HTTPBadRequest, HTTPNotFound, Request, StreamResponse, View
|
||||
from collections.abc import Awaitable, Callable
|
||||
from typing import Any, TypeVar
|
||||
|
||||
from ahriman.core.auth import Auth
|
||||
from ahriman.core.configuration import Configuration
|
||||
from ahriman.core.sign.gpg import GPG
|
||||
from ahriman.core.spawn import Spawn
|
||||
from ahriman.core.status.watcher import Watcher
|
||||
from ahriman.models.repository_id import RepositoryId
|
||||
from ahriman.models.user_access import UserAccess
|
||||
|
||||
|
||||
@ -56,15 +58,25 @@ class BaseView(View, CorsViewMixin):
|
||||
return configuration
|
||||
|
||||
@property
|
||||
def service(self) -> Watcher:
|
||||
def services(self) -> dict[RepositoryId, Watcher]:
|
||||
"""
|
||||
get status watcher instance
|
||||
get all loaded watchers
|
||||
|
||||
Returns:
|
||||
Watcher: build status watcher instance
|
||||
dict[RepositoryId, Watcher]: map of loaded watchers per known repository
|
||||
"""
|
||||
watcher: Watcher = self.request.app["watcher"]
|
||||
return watcher
|
||||
watchers: dict[RepositoryId, Watcher] = self.request.app["watcher"]
|
||||
return watchers
|
||||
|
||||
@property
|
||||
def sign(self) -> GPG:
|
||||
"""
|
||||
get GPG control instance
|
||||
|
||||
Returns:
|
||||
GPG: GPG wrapper instance
|
||||
"""
|
||||
return GPG(self.configuration)
|
||||
|
||||
@property
|
||||
def spawner(self) -> Spawn:
|
||||
@ -197,8 +209,8 @@ class BaseView(View, CorsViewMixin):
|
||||
HTTPBadRequest: if supplied parameters are invalid
|
||||
"""
|
||||
try:
|
||||
limit = int(self.request.query.getone("limit", default=-1))
|
||||
offset = int(self.request.query.getone("offset", default=0))
|
||||
limit = int(self.request.query.get("limit", default=-1))
|
||||
offset = int(self.request.query.get("offset", default=0))
|
||||
except Exception as ex:
|
||||
raise HTTPBadRequest(reason=str(ex))
|
||||
|
||||
@ -210,6 +222,40 @@ class BaseView(View, CorsViewMixin):
|
||||
|
||||
return limit, offset
|
||||
|
||||
def repository_id(self) -> RepositoryId:
|
||||
"""
|
||||
extract repository from request
|
||||
|
||||
Returns:
|
||||
RepositoryIde: repository if possible to construct and first one otherwise
|
||||
"""
|
||||
architecture = self.request.query.get("architecture")
|
||||
name = self.request.query.get("repository")
|
||||
|
||||
if architecture and name:
|
||||
return RepositoryId(architecture, name)
|
||||
return next(iter(sorted(self.services.keys())))
|
||||
|
||||
def service(self, repository_id: RepositoryId | None = None) -> Watcher:
|
||||
"""
|
||||
get status watcher instance
|
||||
|
||||
Args:
|
||||
repository_id(RepositoryId | None, optional): repository unique identifier (Default value = None)
|
||||
|
||||
Returns:
|
||||
Watcher: build status watcher instance. If no repository provided, it will return the first one
|
||||
|
||||
Raises:
|
||||
HTTPNotFound: if no repository found
|
||||
"""
|
||||
if repository_id is None:
|
||||
repository_id = self.repository_id()
|
||||
try:
|
||||
return self.services[repository_id]
|
||||
except KeyError:
|
||||
raise HTTPNotFound(reason=f"Repository {repository_id.id} is unknown")
|
||||
|
||||
async def username(self) -> str | None:
|
||||
"""
|
||||
extract username from request if any
|
||||
|
@ -37,7 +37,10 @@ class IndexView(BaseView):
|
||||
* enabled - whether authorization is enabled by configuration or not, boolean, required
|
||||
* username - authenticated username if any, string, null means not authenticated
|
||||
* index_url - url to the repository index, string, optional
|
||||
* repository - repository name, string, required
|
||||
* repositories - list of repositories unique identifiers, required
|
||||
* id - unique repository identifier, string, required
|
||||
* repository - repository name, string, required
|
||||
* architecture - repository architecture, string, required
|
||||
|
||||
Attributes:
|
||||
GET_PERMISSION(UserAccess): (class attribute) get permissions of self
|
||||
@ -64,5 +67,11 @@ class IndexView(BaseView):
|
||||
return {
|
||||
"auth": auth,
|
||||
"index_url": self.configuration.get("web", "index_url", fallback=None),
|
||||
"repository": self.service.repository.name,
|
||||
"repositories": [
|
||||
{
|
||||
"id": repository.id,
|
||||
**repository.view(),
|
||||
}
|
||||
for repository in sorted(self.services)
|
||||
]
|
||||
}
|
||||
|
@ -22,7 +22,7 @@ import aiohttp_apispec # type: ignore[import-untyped]
|
||||
from aiohttp.web import HTTPBadRequest, Response, json_response
|
||||
|
||||
from ahriman.models.user_access import UserAccess
|
||||
from ahriman.web.schemas import AuthSchema, ErrorSchema, PackageNamesSchema, ProcessIdSchema
|
||||
from ahriman.web.schemas import AuthSchema, ErrorSchema, PackageNamesSchema, ProcessIdSchema, RepositoryIdSchema
|
||||
from ahriman.web.views.base import BaseView
|
||||
|
||||
|
||||
@ -46,11 +46,13 @@ class AddView(BaseView):
|
||||
400: {"description": "Bad data is supplied", "schema": ErrorSchema},
|
||||
401: {"description": "Authorization required", "schema": ErrorSchema},
|
||||
403: {"description": "Access is forbidden", "schema": ErrorSchema},
|
||||
404: {"description": "Repository is unknown", "schema": ErrorSchema},
|
||||
500: {"description": "Internal server error", "schema": ErrorSchema},
|
||||
},
|
||||
security=[{"token": [POST_PERMISSION]}],
|
||||
)
|
||||
@aiohttp_apispec.cookies_schema(AuthSchema)
|
||||
@aiohttp_apispec.querystring_schema(RepositoryIdSchema)
|
||||
@aiohttp_apispec.json_schema(PackageNamesSchema)
|
||||
async def post(self) -> Response:
|
||||
"""
|
||||
@ -68,7 +70,8 @@ class AddView(BaseView):
|
||||
except Exception as ex:
|
||||
raise HTTPBadRequest(reason=str(ex))
|
||||
|
||||
repository_id = self.repository_id()
|
||||
username = await self.username()
|
||||
process_id = self.spawner.packages_add(packages, username, now=True)
|
||||
process_id = self.spawner.packages_add(repository_id, packages, username, now=True)
|
||||
|
||||
return json_response({"process_id": process_id})
|
||||
|
@ -48,7 +48,7 @@ class PGPView(BaseView):
|
||||
400: {"description": "Bad data is supplied", "schema": ErrorSchema},
|
||||
401: {"description": "Authorization required", "schema": ErrorSchema},
|
||||
403: {"description": "Access is forbidden", "schema": ErrorSchema},
|
||||
404: {"description": "Package base is unknown", "schema": ErrorSchema},
|
||||
404: {"description": "PGP key is unknown", "schema": ErrorSchema},
|
||||
500: {"description": "Internal server error", "schema": ErrorSchema},
|
||||
},
|
||||
security=[{"token": [GET_PERMISSION]}],
|
||||
@ -67,15 +67,15 @@ class PGPView(BaseView):
|
||||
HTTPNotFound: if key wasn't found or service was unable to fetch it
|
||||
"""
|
||||
try:
|
||||
key = self.get_non_empty(self.request.query.getone, "key")
|
||||
server = self.get_non_empty(self.request.query.getone, "server")
|
||||
key = self.get_non_empty(self.request.query.get, "key")
|
||||
server = self.get_non_empty(self.request.query.get, "server")
|
||||
except Exception as ex:
|
||||
raise HTTPBadRequest(reason=str(ex))
|
||||
|
||||
try:
|
||||
key = self.service.repository.sign.key_download(server, key)
|
||||
key = self.sign.key_download(server, key)
|
||||
except Exception:
|
||||
raise HTTPNotFound
|
||||
raise HTTPNotFound(reason=f"Key {key} is unknown")
|
||||
|
||||
return json_response({"key": key})
|
||||
|
||||
|
@ -45,7 +45,7 @@ class ProcessView(BaseView):
|
||||
200: {"description": "Success response", "schema": ProcessSchema},
|
||||
401: {"description": "Authorization required", "schema": ErrorSchema},
|
||||
403: {"description": "Access is forbidden", "schema": ErrorSchema},
|
||||
404: {"description": "Not found", "schema": ErrorSchema},
|
||||
404: {"description": "Process is unknown", "schema": ErrorSchema},
|
||||
500: {"description": "Internal server error", "schema": ErrorSchema},
|
||||
},
|
||||
security=[{"token": [GET_PERMISSION]}],
|
||||
|
@ -22,7 +22,7 @@ import aiohttp_apispec # type: ignore[import-untyped]
|
||||
from aiohttp.web import HTTPBadRequest, Response, json_response
|
||||
|
||||
from ahriman.models.user_access import UserAccess
|
||||
from ahriman.web.schemas import AuthSchema, ErrorSchema, PackageNamesSchema, ProcessIdSchema
|
||||
from ahriman.web.schemas import AuthSchema, ErrorSchema, PackageNamesSchema, ProcessIdSchema, RepositoryIdSchema
|
||||
from ahriman.web.views.base import BaseView
|
||||
|
||||
|
||||
@ -46,11 +46,13 @@ class RebuildView(BaseView):
|
||||
400: {"description": "Bad data is supplied", "schema": ErrorSchema},
|
||||
401: {"description": "Authorization required", "schema": ErrorSchema},
|
||||
403: {"description": "Access is forbidden", "schema": ErrorSchema},
|
||||
404: {"description": "Repository is unknown", "schema": ErrorSchema},
|
||||
500: {"description": "Internal server error", "schema": ErrorSchema},
|
||||
},
|
||||
security=[{"token": [POST_PERMISSION]}],
|
||||
)
|
||||
@aiohttp_apispec.cookies_schema(AuthSchema)
|
||||
@aiohttp_apispec.querystring_schema(RepositoryIdSchema)
|
||||
@aiohttp_apispec.json_schema(PackageNamesSchema)
|
||||
async def post(self) -> Response:
|
||||
"""
|
||||
@ -69,7 +71,8 @@ class RebuildView(BaseView):
|
||||
except Exception as ex:
|
||||
raise HTTPBadRequest(reason=str(ex))
|
||||
|
||||
repository_id = self.repository_id()
|
||||
username = await self.username()
|
||||
process_id = self.spawner.packages_rebuild(depends_on, username)
|
||||
process_id = self.spawner.packages_rebuild(repository_id, depends_on, username)
|
||||
|
||||
return json_response({"process_id": process_id})
|
||||
|
@ -22,7 +22,7 @@ import aiohttp_apispec # type: ignore[import-untyped]
|
||||
from aiohttp.web import HTTPBadRequest, Response, json_response
|
||||
|
||||
from ahriman.models.user_access import UserAccess
|
||||
from ahriman.web.schemas import AuthSchema, ErrorSchema, PackageNamesSchema, ProcessIdSchema
|
||||
from ahriman.web.schemas import AuthSchema, ErrorSchema, PackageNamesSchema, ProcessIdSchema, RepositoryIdSchema
|
||||
from ahriman.web.views.base import BaseView
|
||||
|
||||
|
||||
@ -46,11 +46,13 @@ class RemoveView(BaseView):
|
||||
400: {"description": "Bad data is supplied", "schema": ErrorSchema},
|
||||
401: {"description": "Authorization required", "schema": ErrorSchema},
|
||||
403: {"description": "Access is forbidden", "schema": ErrorSchema},
|
||||
404: {"description": "Repository is unknown", "schema": ErrorSchema},
|
||||
500: {"description": "Internal server error", "schema": ErrorSchema},
|
||||
},
|
||||
security=[{"token": [POST_PERMISSION]}],
|
||||
)
|
||||
@aiohttp_apispec.cookies_schema(AuthSchema)
|
||||
@aiohttp_apispec.querystring_schema(RepositoryIdSchema)
|
||||
@aiohttp_apispec.json_schema(PackageNamesSchema)
|
||||
async def post(self) -> Response:
|
||||
"""
|
||||
@ -68,6 +70,7 @@ class RemoveView(BaseView):
|
||||
except Exception as ex:
|
||||
raise HTTPBadRequest(reason=str(ex))
|
||||
|
||||
process_id = self.spawner.packages_remove(packages)
|
||||
repository_id = self.repository_id()
|
||||
process_id = self.spawner.packages_remove(repository_id, packages)
|
||||
|
||||
return json_response({"process_id": process_id})
|
||||
|
@ -22,7 +22,7 @@ import aiohttp_apispec # type: ignore[import-untyped]
|
||||
from aiohttp.web import HTTPBadRequest, Response, json_response
|
||||
|
||||
from ahriman.models.user_access import UserAccess
|
||||
from ahriman.web.schemas import AuthSchema, ErrorSchema, PackageNamesSchema, ProcessIdSchema
|
||||
from ahriman.web.schemas import AuthSchema, ErrorSchema, PackageNamesSchema, ProcessIdSchema, RepositoryIdSchema
|
||||
from ahriman.web.views.base import BaseView
|
||||
|
||||
|
||||
@ -46,11 +46,13 @@ class RequestView(BaseView):
|
||||
400: {"description": "Bad data is supplied", "schema": ErrorSchema},
|
||||
401: {"description": "Authorization required", "schema": ErrorSchema},
|
||||
403: {"description": "Access is forbidden", "schema": ErrorSchema},
|
||||
404: {"description": "Repository is unknown", "schema": ErrorSchema},
|
||||
500: {"description": "Internal server error", "schema": ErrorSchema},
|
||||
},
|
||||
security=[{"token": [POST_PERMISSION]}],
|
||||
)
|
||||
@aiohttp_apispec.cookies_schema(AuthSchema)
|
||||
@aiohttp_apispec.querystring_schema(RepositoryIdSchema)
|
||||
@aiohttp_apispec.json_schema(PackageNamesSchema)
|
||||
async def post(self) -> Response:
|
||||
"""
|
||||
@ -69,6 +71,7 @@ class RequestView(BaseView):
|
||||
raise HTTPBadRequest(reason=str(ex))
|
||||
|
||||
username = await self.username()
|
||||
process_id = self.spawner.packages_add(packages, username, now=False)
|
||||
repository_id = self.repository_id()
|
||||
process_id = self.spawner.packages_add(repository_id, packages, username, now=False)
|
||||
|
||||
return json_response({"process_id": process_id})
|
||||
|
@ -69,7 +69,7 @@ class SearchView(BaseView):
|
||||
"""
|
||||
try:
|
||||
search: list[str] = self.get_non_empty(lambda key: self.request.query.getall(key, default=[]), "for")
|
||||
packages = AUR.multisearch(*search, pacman=self.service.repository.pacman)
|
||||
packages = AUR.multisearch(*search)
|
||||
except Exception as ex:
|
||||
raise HTTPBadRequest(reason=str(ex))
|
||||
|
||||
|
@ -22,7 +22,7 @@ import aiohttp_apispec # type: ignore[import-untyped]
|
||||
from aiohttp.web import HTTPBadRequest, Response, json_response
|
||||
|
||||
from ahriman.models.user_access import UserAccess
|
||||
from ahriman.web.schemas import AuthSchema, ErrorSchema, ProcessIdSchema, UpdateFlagsSchema
|
||||
from ahriman.web.schemas import AuthSchema, ErrorSchema, ProcessIdSchema, UpdateFlagsSchema, RepositoryIdSchema
|
||||
from ahriman.web.views.base import BaseView
|
||||
|
||||
|
||||
@ -46,11 +46,13 @@ class UpdateView(BaseView):
|
||||
400: {"description": "Bad data is supplied", "schema": ErrorSchema},
|
||||
401: {"description": "Authorization required", "schema": ErrorSchema},
|
||||
403: {"description": "Access is forbidden", "schema": ErrorSchema},
|
||||
404: {"description": "Repository is unknown", "schema": ErrorSchema},
|
||||
500: {"description": "Internal server error", "schema": ErrorSchema},
|
||||
},
|
||||
security=[{"token": [POST_PERMISSION]}],
|
||||
)
|
||||
@aiohttp_apispec.cookies_schema(AuthSchema)
|
||||
@aiohttp_apispec.querystring_schema(RepositoryIdSchema)
|
||||
@aiohttp_apispec.json_schema(UpdateFlagsSchema)
|
||||
async def post(self) -> Response:
|
||||
"""
|
||||
@ -67,8 +69,10 @@ class UpdateView(BaseView):
|
||||
except Exception as ex:
|
||||
raise HTTPBadRequest(reason=str(ex))
|
||||
|
||||
repository_id = self.repository_id()
|
||||
username = await self.username()
|
||||
process_id = self.spawner.packages_update(
|
||||
repository_id,
|
||||
username,
|
||||
aur=data.get("aur", True),
|
||||
local=data.get("local", True),
|
||||
|
@ -25,8 +25,9 @@ from aiohttp.web import HTTPBadRequest, HTTPCreated, HTTPNotFound
|
||||
from pathlib import Path
|
||||
from tempfile import NamedTemporaryFile
|
||||
|
||||
from ahriman.models.repository_paths import RepositoryPaths
|
||||
from ahriman.models.user_access import UserAccess
|
||||
from ahriman.web.schemas import AuthSchema, ErrorSchema, FileSchema
|
||||
from ahriman.web.schemas import AuthSchema, ErrorSchema, FileSchema, RepositoryIdSchema
|
||||
from ahriman.web.views.base import BaseView
|
||||
|
||||
|
||||
@ -100,12 +101,13 @@ class UploadView(BaseView):
|
||||
400: {"description": "Bad data is supplied", "schema": ErrorSchema},
|
||||
401: {"description": "Authorization required", "schema": ErrorSchema},
|
||||
403: {"description": "Access is forbidden", "schema": ErrorSchema},
|
||||
404: {"description": "Not found", "schema": ErrorSchema},
|
||||
404: {"description": "Repository is unknown or endpoint is disabled", "schema": ErrorSchema},
|
||||
500: {"description": "Internal server error", "schema": ErrorSchema},
|
||||
},
|
||||
security=[{"token": [POST_PERMISSION]}],
|
||||
)
|
||||
@aiohttp_apispec.cookies_schema(AuthSchema)
|
||||
@aiohttp_apispec.querystring_schema(RepositoryIdSchema)
|
||||
@aiohttp_apispec.form_schema(FileSchema)
|
||||
async def post(self) -> None:
|
||||
"""
|
||||
@ -125,7 +127,10 @@ class UploadView(BaseView):
|
||||
raise HTTPBadRequest(reason=str(ex))
|
||||
|
||||
max_body_size = self.configuration.getint("web", "max_body_size", fallback=None)
|
||||
target = self.configuration.repository_paths.packages
|
||||
|
||||
paths_root = self.configuration.repository_paths.root
|
||||
repository_id = self.repository_id()
|
||||
target = RepositoryPaths(paths_root, repository_id).packages
|
||||
|
||||
files = []
|
||||
while (part := await reader.next()) is not None:
|
||||
|
@ -25,7 +25,7 @@ from ahriman.core.exceptions import UnknownPackageError
|
||||
from ahriman.core.util import pretty_datetime
|
||||
from ahriman.models.log_record_id import LogRecordId
|
||||
from ahriman.models.user_access import UserAccess
|
||||
from ahriman.web.schemas import AuthSchema, ErrorSchema, LogSchema, LogsSchema, PackageNameSchema
|
||||
from ahriman.web.schemas import AuthSchema, ErrorSchema, LogSchema, LogsSchema, PackageNameSchema, RepositoryIdSchema
|
||||
from ahriman.web.views.base import BaseView
|
||||
|
||||
|
||||
@ -51,12 +51,14 @@ class LogsView(BaseView):
|
||||
204: {"description": "Success response"},
|
||||
401: {"description": "Authorization required", "schema": ErrorSchema},
|
||||
403: {"description": "Access is forbidden", "schema": ErrorSchema},
|
||||
404: {"description": "Repository is unknown", "schema": ErrorSchema},
|
||||
500: {"description": "Internal server error", "schema": ErrorSchema},
|
||||
},
|
||||
security=[{"token": [DELETE_PERMISSION]}],
|
||||
)
|
||||
@aiohttp_apispec.cookies_schema(AuthSchema)
|
||||
@aiohttp_apispec.match_info_schema(PackageNameSchema)
|
||||
@aiohttp_apispec.querystring_schema(RepositoryIdSchema)
|
||||
async def delete(self) -> None:
|
||||
"""
|
||||
delete package logs
|
||||
@ -65,7 +67,7 @@ class LogsView(BaseView):
|
||||
HTTPNoContent: on success response
|
||||
"""
|
||||
package_base = self.request.match_info["package"]
|
||||
self.service.logs_remove(package_base, None)
|
||||
self.service().logs_remove(package_base, None)
|
||||
|
||||
raise HTTPNoContent
|
||||
|
||||
@ -77,13 +79,14 @@ class LogsView(BaseView):
|
||||
200: {"description": "Success response", "schema": LogsSchema},
|
||||
401: {"description": "Authorization required", "schema": ErrorSchema},
|
||||
403: {"description": "Access is forbidden", "schema": ErrorSchema},
|
||||
404: {"description": "Package base is unknown", "schema": ErrorSchema},
|
||||
404: {"description": "Package base and/or repository are unknown", "schema": ErrorSchema},
|
||||
500: {"description": "Internal server error", "schema": ErrorSchema},
|
||||
},
|
||||
security=[{"token": [GET_PERMISSION]}],
|
||||
)
|
||||
@aiohttp_apispec.cookies_schema(AuthSchema)
|
||||
@aiohttp_apispec.match_info_schema(PackageNameSchema)
|
||||
@aiohttp_apispec.querystring_schema(RepositoryIdSchema)
|
||||
async def get(self) -> Response:
|
||||
"""
|
||||
get last package logs
|
||||
@ -97,10 +100,10 @@ class LogsView(BaseView):
|
||||
package_base = self.request.match_info["package"]
|
||||
|
||||
try:
|
||||
_, status = self.service.package_get(package_base)
|
||||
_, status = self.service().package_get(package_base)
|
||||
except UnknownPackageError:
|
||||
raise HTTPNotFound
|
||||
logs = self.service.logs_get(package_base)
|
||||
raise HTTPNotFound(reason=f"Package {package_base} is unknown")
|
||||
logs = self.service().logs_get(package_base)
|
||||
|
||||
response = {
|
||||
"package_base": package_base,
|
||||
@ -118,6 +121,7 @@ class LogsView(BaseView):
|
||||
400: {"description": "Bad data is supplied", "schema": ErrorSchema},
|
||||
401: {"description": "Authorization required", "schema": ErrorSchema},
|
||||
403: {"description": "Access is forbidden", "schema": ErrorSchema},
|
||||
404: {"description": "Repository is unknown", "schema": ErrorSchema},
|
||||
500: {"description": "Internal server error", "schema": ErrorSchema},
|
||||
},
|
||||
security=[{"token": [POST_PERMISSION]}],
|
||||
@ -143,6 +147,6 @@ class LogsView(BaseView):
|
||||
except Exception as ex:
|
||||
raise HTTPBadRequest(reason=str(ex))
|
||||
|
||||
self.service.logs_update(LogRecordId(package_base, version), created, record)
|
||||
self.service().logs_update(LogRecordId(package_base, version), created, record)
|
||||
|
||||
raise HTTPNoContent
|
||||
|
@ -25,7 +25,8 @@ from ahriman.core.exceptions import UnknownPackageError
|
||||
from ahriman.models.build_status import BuildStatusEnum
|
||||
from ahriman.models.package import Package
|
||||
from ahriman.models.user_access import UserAccess
|
||||
from ahriman.web.schemas import AuthSchema, ErrorSchema, PackageNameSchema, PackageStatusSchema, PackageStatusSimplifiedSchema
|
||||
from ahriman.web.schemas import AuthSchema, ErrorSchema, PackageNameSchema, PackageStatusSchema, \
|
||||
PackageStatusSimplifiedSchema, RepositoryIdSchema
|
||||
from ahriman.web.views.base import BaseView
|
||||
|
||||
|
||||
@ -51,12 +52,14 @@ class PackageView(BaseView):
|
||||
204: {"description": "Success response"},
|
||||
401: {"description": "Authorization required", "schema": ErrorSchema},
|
||||
403: {"description": "Access is forbidden", "schema": ErrorSchema},
|
||||
404: {"description": "Repository is unknown", "schema": ErrorSchema},
|
||||
500: {"description": "Internal server error", "schema": ErrorSchema},
|
||||
},
|
||||
security=[{"token": [DELETE_PERMISSION]}],
|
||||
)
|
||||
@aiohttp_apispec.cookies_schema(AuthSchema)
|
||||
@aiohttp_apispec.match_info_schema(PackageNameSchema)
|
||||
@aiohttp_apispec.querystring_schema(RepositoryIdSchema)
|
||||
async def delete(self) -> None:
|
||||
"""
|
||||
delete package base from status page
|
||||
@ -65,7 +68,7 @@ class PackageView(BaseView):
|
||||
HTTPNoContent: on success response
|
||||
"""
|
||||
package_base = self.request.match_info["package"]
|
||||
self.service.package_remove(package_base)
|
||||
self.service().package_remove(package_base)
|
||||
|
||||
raise HTTPNoContent
|
||||
|
||||
@ -77,13 +80,14 @@ class PackageView(BaseView):
|
||||
200: {"description": "Success response", "schema": PackageStatusSchema(many=True)},
|
||||
401: {"description": "Authorization required", "schema": ErrorSchema},
|
||||
403: {"description": "Access is forbidden", "schema": ErrorSchema},
|
||||
404: {"description": "Package base is unknown", "schema": ErrorSchema},
|
||||
404: {"description": "Package base and/or repository are unknown", "schema": ErrorSchema},
|
||||
500: {"description": "Internal server error", "schema": ErrorSchema},
|
||||
},
|
||||
security=[{"token": [GET_PERMISSION]}],
|
||||
)
|
||||
@aiohttp_apispec.cookies_schema(AuthSchema)
|
||||
@aiohttp_apispec.match_info_schema(PackageNameSchema)
|
||||
@aiohttp_apispec.querystring_schema(RepositoryIdSchema)
|
||||
async def get(self) -> Response:
|
||||
"""
|
||||
get current package base status
|
||||
@ -95,16 +99,18 @@ class PackageView(BaseView):
|
||||
HTTPNotFound: if no package was found
|
||||
"""
|
||||
package_base = self.request.match_info["package"]
|
||||
repository_id = self.repository_id()
|
||||
|
||||
try:
|
||||
package, status = self.service.package_get(package_base)
|
||||
package, status = self.service(repository_id).package_get(package_base)
|
||||
except UnknownPackageError:
|
||||
raise HTTPNotFound
|
||||
raise HTTPNotFound(reason=f"Package {package_base} is unknown")
|
||||
|
||||
response = [
|
||||
{
|
||||
"package": package.view(),
|
||||
"status": status.view()
|
||||
"status": status.view(),
|
||||
"repository": repository_id.view(),
|
||||
}
|
||||
]
|
||||
return json_response(response)
|
||||
@ -118,12 +124,14 @@ class PackageView(BaseView):
|
||||
400: {"description": "Bad data is supplied", "schema": ErrorSchema},
|
||||
401: {"description": "Authorization required", "schema": ErrorSchema},
|
||||
403: {"description": "Access is forbidden", "schema": ErrorSchema},
|
||||
404: {"description": "Repository is unknown", "schema": ErrorSchema},
|
||||
500: {"description": "Internal server error", "schema": ErrorSchema},
|
||||
},
|
||||
security=[{"token": [POST_PERMISSION]}],
|
||||
)
|
||||
@aiohttp_apispec.cookies_schema(AuthSchema)
|
||||
@aiohttp_apispec.match_info_schema(PackageNameSchema)
|
||||
@aiohttp_apispec.querystring_schema(RepositoryIdSchema)
|
||||
@aiohttp_apispec.json_schema(PackageStatusSimplifiedSchema)
|
||||
async def post(self) -> None:
|
||||
"""
|
||||
@ -143,7 +151,7 @@ class PackageView(BaseView):
|
||||
raise HTTPBadRequest(reason=str(ex))
|
||||
|
||||
try:
|
||||
self.service.package_update(package_base, status, package)
|
||||
self.service().package_update(package_base, status, package)
|
||||
except UnknownPackageError:
|
||||
raise HTTPBadRequest(reason=f"Package {package_base} is unknown, but no package body set")
|
||||
|
||||
|
@ -26,7 +26,7 @@ from aiohttp.web import HTTPNoContent, Response, json_response
|
||||
from ahriman.models.build_status import BuildStatus
|
||||
from ahriman.models.package import Package
|
||||
from ahriman.models.user_access import UserAccess
|
||||
from ahriman.web.schemas import AuthSchema, ErrorSchema, PackageStatusSchema, PaginationSchema
|
||||
from ahriman.web.schemas import AuthSchema, ErrorSchema, PackageStatusSchema, PaginationSchema, RepositoryIdSchema
|
||||
from ahriman.web.views.base import BaseView
|
||||
|
||||
|
||||
@ -52,6 +52,7 @@ class PackagesView(BaseView):
|
||||
400: {"description": "Bad data is supplied", "schema": ErrorSchema},
|
||||
401: {"description": "Authorization required", "schema": ErrorSchema},
|
||||
403: {"description": "Access is forbidden", "schema": ErrorSchema},
|
||||
404: {"description": "Repository is unknown", "schema": ErrorSchema},
|
||||
500: {"description": "Internal server error", "schema": ErrorSchema},
|
||||
},
|
||||
security=[{"token": [GET_PERMISSION]}],
|
||||
@ -68,12 +69,16 @@ class PackagesView(BaseView):
|
||||
limit, offset = self.page()
|
||||
stop = offset + limit if limit >= 0 else None
|
||||
|
||||
comparator: Callable[[tuple[Package, BuildStatus]], str] = lambda pair: pair[0].base
|
||||
repository_id = self.repository_id()
|
||||
packages = self.service(repository_id).packages
|
||||
|
||||
comparator: Callable[[tuple[Package, BuildStatus]], str] = lambda items: items[0].base
|
||||
response = [
|
||||
{
|
||||
"package": package.view(),
|
||||
"status": status.view()
|
||||
} for package, status in itertools.islice(sorted(self.service.packages, key=comparator), offset, stop)
|
||||
"status": status.view(),
|
||||
"repository": repository_id.view(),
|
||||
} for package, status in itertools.islice(sorted(packages, key=comparator), offset, stop)
|
||||
]
|
||||
|
||||
return json_response(response)
|
||||
@ -86,11 +91,13 @@ class PackagesView(BaseView):
|
||||
204: {"description": "Success response"},
|
||||
401: {"description": "Authorization required", "schema": ErrorSchema},
|
||||
403: {"description": "Access is forbidden", "schema": ErrorSchema},
|
||||
404: {"description": "Repository is unknown", "schema": ErrorSchema},
|
||||
500: {"description": "Internal server error", "schema": ErrorSchema},
|
||||
},
|
||||
security=[{"token": [POST_PERMISSION]}],
|
||||
)
|
||||
@aiohttp_apispec.cookies_schema(AuthSchema)
|
||||
@aiohttp_apispec.querystring_schema(RepositoryIdSchema)
|
||||
async def post(self) -> None:
|
||||
"""
|
||||
reload all packages from repository
|
||||
@ -98,6 +105,6 @@ class PackagesView(BaseView):
|
||||
Raises:
|
||||
HTTPNoContent: on success response
|
||||
"""
|
||||
self.service.load()
|
||||
self.service().load()
|
||||
|
||||
raise HTTPNoContent
|
||||
|
65
src/ahriman/web/views/v1/status/repositories.py
Normal file
65
src/ahriman/web/views/v1/status/repositories.py
Normal file
@ -0,0 +1,65 @@
|
||||
#
|
||||
# Copyright (c) 2021-2023 ahriman team.
|
||||
#
|
||||
# This file is part of ahriman
|
||||
# (see https://github.com/arcan1s/ahriman).
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
import aiohttp_apispec # type: ignore[import-untyped]
|
||||
|
||||
from aiohttp.web import Response, json_response
|
||||
|
||||
from ahriman.models.user_access import UserAccess
|
||||
from ahriman.web.schemas import AuthSchema, ErrorSchema, RepositoryIdSchema
|
||||
from ahriman.web.views.base import BaseView
|
||||
|
||||
|
||||
class RepositoriesView(BaseView):
|
||||
"""
|
||||
repositories view
|
||||
|
||||
Attributes:
|
||||
GET_PERMISSION(UserAccess): (class attribute) get permissions of self
|
||||
"""
|
||||
|
||||
GET_PERMISSION = UserAccess.Read
|
||||
ROUTES = ["/api/v1/repositories"]
|
||||
|
||||
@aiohttp_apispec.docs(
|
||||
tags=["Status"],
|
||||
summary="Available repositories",
|
||||
description="List available repositories",
|
||||
responses={
|
||||
200: {"description": "Success response", "schema": RepositoryIdSchema(many=True)},
|
||||
401: {"description": "Authorization required", "schema": ErrorSchema},
|
||||
403: {"description": "Access is forbidden", "schema": ErrorSchema},
|
||||
500: {"description": "Internal server error", "schema": ErrorSchema},
|
||||
},
|
||||
security=[{"token": [GET_PERMISSION]}],
|
||||
)
|
||||
@aiohttp_apispec.cookies_schema(AuthSchema)
|
||||
async def get(self) -> Response:
|
||||
"""
|
||||
get list of available repositories
|
||||
|
||||
Returns:
|
||||
Response: 200 with service status object
|
||||
"""
|
||||
repositories = [
|
||||
repository_id.view()
|
||||
for repository_id in sorted(self.services)
|
||||
]
|
||||
|
||||
return json_response(repositories)
|
@ -26,7 +26,7 @@ from ahriman.models.build_status import BuildStatusEnum
|
||||
from ahriman.models.counters import Counters
|
||||
from ahriman.models.internal_status import InternalStatus
|
||||
from ahriman.models.user_access import UserAccess
|
||||
from ahriman.web.schemas import AuthSchema, ErrorSchema, InternalStatusSchema, StatusSchema
|
||||
from ahriman.web.schemas import AuthSchema, ErrorSchema, InternalStatusSchema, StatusSchema, RepositoryIdSchema
|
||||
from ahriman.web.views.base import BaseView
|
||||
|
||||
|
||||
@ -51,6 +51,7 @@ class StatusView(BaseView):
|
||||
200: {"description": "Success response", "schema": InternalStatusSchema},
|
||||
401: {"description": "Authorization required", "schema": ErrorSchema},
|
||||
403: {"description": "Access is forbidden", "schema": ErrorSchema},
|
||||
404: {"description": "Repository is unknown", "schema": ErrorSchema},
|
||||
500: {"description": "Internal server error", "schema": ErrorSchema},
|
||||
},
|
||||
security=[{"token": [GET_PERMISSION]}],
|
||||
@ -63,12 +64,13 @@ class StatusView(BaseView):
|
||||
Returns:
|
||||
Response: 200 with service status object
|
||||
"""
|
||||
counters = Counters.from_packages(self.service.packages)
|
||||
repository_id = self.repository_id()
|
||||
counters = Counters.from_packages(self.service(repository_id).packages)
|
||||
status = InternalStatus(
|
||||
status=self.service.status,
|
||||
architecture=self.service.repository_id.architecture,
|
||||
status=self.service(repository_id).status,
|
||||
architecture=repository_id.architecture,
|
||||
packages=counters,
|
||||
repository=self.service.repository_id.name,
|
||||
repository=repository_id.name,
|
||||
version=__version__,
|
||||
)
|
||||
|
||||
@ -83,11 +85,13 @@ class StatusView(BaseView):
|
||||
400: {"description": "Bad data is supplied", "schema": ErrorSchema},
|
||||
401: {"description": "Authorization required", "schema": ErrorSchema},
|
||||
403: {"description": "Access is forbidden", "schema": ErrorSchema},
|
||||
404: {"description": "Repository is unknown", "schema": ErrorSchema},
|
||||
500: {"description": "Internal server error", "schema": ErrorSchema},
|
||||
},
|
||||
security=[{"token": [POST_PERMISSION]}],
|
||||
)
|
||||
@aiohttp_apispec.cookies_schema(AuthSchema)
|
||||
@aiohttp_apispec.querystring_schema(RepositoryIdSchema)
|
||||
@aiohttp_apispec.json_schema(StatusSchema)
|
||||
async def post(self) -> None:
|
||||
"""
|
||||
@ -103,6 +107,6 @@ class StatusView(BaseView):
|
||||
except Exception as ex:
|
||||
raise HTTPBadRequest(reason=str(ex))
|
||||
|
||||
self.service.status_update(status)
|
||||
self.service().status_update(status)
|
||||
|
||||
raise HTTPNoContent
|
||||
|
@ -75,7 +75,7 @@ class LoginView(BaseView):
|
||||
if not isinstance(oauth_provider, OAuth): # there is actually property, but mypy does not like it anyway
|
||||
raise HTTPMethodNotAllowed(self.request.method, ["POST"])
|
||||
|
||||
code = self.request.query.getone("code", default=None)
|
||||
code = self.request.query.get("code")
|
||||
if not code:
|
||||
raise HTTPFound(oauth_provider.get_oauth_url())
|
||||
|
||||
|
@ -23,8 +23,7 @@ from aiohttp.web import HTTPNotFound, Response, json_response
|
||||
|
||||
from ahriman.core.exceptions import UnknownPackageError
|
||||
from ahriman.models.user_access import UserAccess
|
||||
from ahriman.web.schemas import AuthSchema, ErrorSchema, PackageNameSchema, PaginationSchema
|
||||
from ahriman.web.schemas.logs_schema import LogsSchemaV2
|
||||
from ahriman.web.schemas import AuthSchema, ErrorSchema, LogsSchemaV2, PackageNameSchema, PaginationSchema
|
||||
from ahriman.web.views.base import BaseView
|
||||
|
||||
|
||||
@ -48,7 +47,7 @@ class LogsView(BaseView):
|
||||
400: {"description": "Bad data is supplied", "schema": ErrorSchema},
|
||||
401: {"description": "Authorization required", "schema": ErrorSchema},
|
||||
403: {"description": "Access is forbidden", "schema": ErrorSchema},
|
||||
404: {"description": "Package base is unknown", "schema": ErrorSchema},
|
||||
404: {"description": "Package base and/or repository are unknown", "schema": ErrorSchema},
|
||||
500: {"description": "Internal server error", "schema": ErrorSchema},
|
||||
},
|
||||
security=[{"token": [GET_PERMISSION]}],
|
||||
@ -70,10 +69,10 @@ class LogsView(BaseView):
|
||||
limit, offset = self.page()
|
||||
|
||||
try:
|
||||
_, status = self.service.package_get(package_base)
|
||||
_, status = self.service().package_get(package_base)
|
||||
except UnknownPackageError:
|
||||
raise HTTPNotFound
|
||||
logs = self.service.logs_get(package_base, limit, offset)
|
||||
raise HTTPNotFound(reason=f"Package {package_base} is unknown")
|
||||
logs = self.service().logs_get(package_base, limit, offset)
|
||||
|
||||
response = {
|
||||
"package_base": package_base,
|
||||
|
@ -38,7 +38,7 @@ from ahriman.web.middlewares.exception_handler import exception_handler
|
||||
from ahriman.web.routes import setup_routes
|
||||
|
||||
|
||||
__all__ = ["run_server", "setup_service"]
|
||||
__all__ = ["run_server", "setup_server"]
|
||||
|
||||
|
||||
def _create_socket(configuration: Configuration, application: Application) -> socket.socket | None:
|
||||
@ -95,8 +95,10 @@ async def _on_startup(application: Application) -> None:
|
||||
InitializeError: in case if matched could not be loaded
|
||||
"""
|
||||
application.logger.info("server started")
|
||||
|
||||
try:
|
||||
application["watcher"].load()
|
||||
for watcher in application["watcher"].values():
|
||||
watcher.load()
|
||||
except Exception:
|
||||
message = "could not load packages"
|
||||
application.logger.exception(message)
|
||||
@ -121,17 +123,20 @@ def run_server(application: Application) -> None:
|
||||
access_log=logging.getLogger("http"), access_log_class=FilteredAccessLogger)
|
||||
|
||||
|
||||
def setup_service(repository_id: RepositoryId, configuration: Configuration, spawner: Spawn) -> Application:
|
||||
def setup_server(configuration: Configuration, spawner: Spawn, repositories: list[RepositoryId]) -> Application:
|
||||
"""
|
||||
create web application
|
||||
|
||||
Args:
|
||||
repository_id(RepositoryId): repository unique identifier
|
||||
configuration(Configuration): configuration instance
|
||||
spawner(Spawn): spawner thread
|
||||
repositories(list[RepositoryId]): list of known repositories
|
||||
|
||||
Returns:
|
||||
Application: web application instance
|
||||
|
||||
Raises:
|
||||
InitializeError: if no repositories set
|
||||
"""
|
||||
application = Application(logger=logging.getLogger(__name__))
|
||||
application.on_shutdown.append(_on_shutdown)
|
||||
@ -153,11 +158,15 @@ def setup_service(repository_id: RepositoryId, configuration: Configuration, spa
|
||||
application.logger.info("setup configuration")
|
||||
application["configuration"] = configuration
|
||||
|
||||
application.logger.info("setup database and perform migrations")
|
||||
database = application["database"] = SQLite.load(configuration)
|
||||
|
||||
application.logger.info("setup watcher")
|
||||
application["watcher"] = Watcher(repository_id, configuration, database)
|
||||
application.logger.info("setup watchers")
|
||||
if not repositories:
|
||||
raise InitializeError("No repositories configured, exiting")
|
||||
database = SQLite.load(configuration)
|
||||
watchers: dict[RepositoryId, Watcher] = {}
|
||||
for repository_id in repositories:
|
||||
application.logger.info("load repository %s", repository_id)
|
||||
watchers[repository_id] = Watcher(repository_id, database)
|
||||
application["watcher"] = watchers
|
||||
|
||||
application.logger.info("setup process spawner")
|
||||
application["spawn"] = spawner
|
||||
|
@ -99,8 +99,8 @@ def test_with_dependencies(application: Application, package_ahriman: Package, p
|
||||
result = application.with_dependencies([package_ahriman], process_dependencies=True)
|
||||
assert {package.base: package for package in result} == packages
|
||||
package_aur_mock.assert_has_calls([
|
||||
MockCall(package_python_schedule.base, application.repository.pacman, package_ahriman.packager),
|
||||
MockCall("python-installer", application.repository.pacman, package_ahriman.packager),
|
||||
MockCall(package_python_schedule.base, package_ahriman.packager),
|
||||
MockCall("python-installer", package_ahriman.packager),
|
||||
], any_order=True)
|
||||
package_local_mock.assert_has_calls([
|
||||
MockCall(application.repository.paths.cache_for("python"), "x86_64", package_ahriman.packager),
|
||||
|
@ -30,7 +30,12 @@ def test_call(args: argparse.Namespace, configuration: Configuration, mocker: Mo
|
||||
assert Handler.call(args, repository_id)
|
||||
configuration_mock.assert_called_once_with(args.configuration, repository_id)
|
||||
log_handler_mock.assert_called_once_with(args.log_handler)
|
||||
log_load_mock.assert_called_once_with(configuration, args.log_handler, quiet=args.quiet, report=args.report)
|
||||
log_load_mock.assert_called_once_with(
|
||||
repository_id,
|
||||
configuration,
|
||||
args.log_handler,
|
||||
quiet=args.quiet,
|
||||
report=args.report)
|
||||
enter_mock.assert_called_once_with()
|
||||
exit_mock.assert_called_once_with(None, None, None)
|
||||
|
||||
@ -115,13 +120,24 @@ def test_run(args: argparse.Namespace, configuration: Configuration) -> None:
|
||||
Handler.run(args, repository_id, configuration, report=True)
|
||||
|
||||
|
||||
def test_check_if_empty() -> None:
|
||||
"""
|
||||
must raise exception in case if predicate is True and enabled
|
||||
"""
|
||||
Handler.check_if_empty(False, False)
|
||||
Handler.check_if_empty(True, False)
|
||||
Handler.check_if_empty(False, True)
|
||||
with pytest.raises(ExitCode):
|
||||
Handler.check_if_empty(True, True)
|
||||
|
||||
|
||||
def test_repositories_extract(args: argparse.Namespace, configuration: Configuration, mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must generate list of available repositories based on flags
|
||||
"""
|
||||
args.architecture = ["arch"]
|
||||
args.architecture = "arch"
|
||||
args.configuration = configuration.path
|
||||
args.repository = ["repo"]
|
||||
args.repository = "repo"
|
||||
known_architectures_mock = mocker.patch("ahriman.models.repository_paths.RepositoryPaths.known_architectures")
|
||||
known_repositories_mock = mocker.patch("ahriman.models.repository_paths.RepositoryPaths.known_repositories")
|
||||
|
||||
@ -135,7 +151,7 @@ def test_repositories_extract_repository(args: argparse.Namespace, configuration
|
||||
"""
|
||||
must generate list of available repositories based on flags and tree
|
||||
"""
|
||||
args.architecture = ["arch"]
|
||||
args.architecture = "arch"
|
||||
args.configuration = configuration.path
|
||||
known_architectures_mock = mocker.patch("ahriman.models.repository_paths.RepositoryPaths.known_architectures")
|
||||
known_repositories_mock = mocker.patch("ahriman.models.repository_paths.RepositoryPaths.known_repositories",
|
||||
@ -151,7 +167,7 @@ def test_repositories_extract_repository_legacy(args: argparse.Namespace, config
|
||||
"""
|
||||
must generate list of available repositories based on flags and tree
|
||||
"""
|
||||
args.architecture = ["arch"]
|
||||
args.architecture = "arch"
|
||||
args.configuration = configuration.path
|
||||
known_architectures_mock = mocker.patch("ahriman.models.repository_paths.RepositoryPaths.known_architectures")
|
||||
known_repositories_mock = mocker.patch("ahriman.models.repository_paths.RepositoryPaths.known_repositories",
|
||||
@ -168,7 +184,7 @@ def test_repositories_extract_architecture(args: argparse.Namespace, configurati
|
||||
must read repository name from config
|
||||
"""
|
||||
args.configuration = configuration.path
|
||||
args.repository = ["repo"]
|
||||
args.repository = "repo"
|
||||
known_architectures_mock = mocker.patch("ahriman.models.repository_paths.RepositoryPaths.known_architectures",
|
||||
return_value={"arch"})
|
||||
known_repositories_mock = mocker.patch("ahriman.models.repository_paths.RepositoryPaths.known_repositories")
|
||||
@ -207,6 +223,21 @@ def test_repositories_extract_systemd(args: argparse.Namespace, configuration: C
|
||||
known_repositories_mock.assert_not_called()
|
||||
|
||||
|
||||
def test_repositories_extract_systemd_with_dash(args: argparse.Namespace, configuration: Configuration,
|
||||
mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must extract repository list by using dash separated identifier
|
||||
"""
|
||||
args.configuration = configuration.path
|
||||
args.repository_id = "i686-some-repo-name"
|
||||
known_architectures_mock = mocker.patch("ahriman.models.repository_paths.RepositoryPaths.known_architectures")
|
||||
known_repositories_mock = mocker.patch("ahriman.models.repository_paths.RepositoryPaths.known_repositories")
|
||||
|
||||
assert Handler.repositories_extract(args) == [RepositoryId("i686", "some-repo-name")]
|
||||
known_architectures_mock.assert_not_called()
|
||||
known_repositories_mock.assert_not_called()
|
||||
|
||||
|
||||
def test_repositories_extract_systemd_legacy(args: argparse.Namespace, configuration: Configuration,
|
||||
mocker: MockerFixture) -> None:
|
||||
"""
|
||||
@ -221,14 +252,3 @@ def test_repositories_extract_systemd_legacy(args: argparse.Namespace, configura
|
||||
assert Handler.repositories_extract(args) == [RepositoryId("i686", "aur-clone")]
|
||||
known_architectures_mock.assert_not_called()
|
||||
known_repositories_mock.assert_called_once_with(configuration.repository_paths.root)
|
||||
|
||||
|
||||
def test_check_if_empty() -> None:
|
||||
"""
|
||||
must raise exception in case if predicate is True and enabled
|
||||
"""
|
||||
Handler.check_if_empty(False, False)
|
||||
Handler.check_if_empty(True, False)
|
||||
Handler.check_if_empty(False, True)
|
||||
with pytest.raises(ExitCode):
|
||||
Handler.check_if_empty(True, True)
|
||||
|
@ -1,4 +1,5 @@
|
||||
import argparse
|
||||
import pytest
|
||||
|
||||
from pytest_mock import MockerFixture
|
||||
|
||||
@ -16,6 +17,9 @@ def _default_args(args: argparse.Namespace) -> argparse.Namespace:
|
||||
Returns:
|
||||
argparse.Namespace: generated arguments for these test cases
|
||||
"""
|
||||
args.info = False
|
||||
args.key = None
|
||||
args.section = None
|
||||
args.secure = True
|
||||
return args
|
||||
|
||||
@ -35,6 +39,48 @@ def test_run(args: argparse.Namespace, configuration: Configuration, mocker: Moc
|
||||
print_mock.assert_called()
|
||||
|
||||
|
||||
def test_run_info(args: argparse.Namespace, configuration: Configuration, mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must run command with info
|
||||
"""
|
||||
args = _default_args(args)
|
||||
args.info = True
|
||||
print_mock = mocker.patch("ahriman.core.formatters.Printer.print")
|
||||
|
||||
_, repository_id = configuration.check_loaded()
|
||||
Dump.run(args, repository_id, configuration, report=False)
|
||||
print_mock.assert_called()
|
||||
|
||||
|
||||
def test_run_section(args: argparse.Namespace, configuration: Configuration, mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must run command with filter by section
|
||||
"""
|
||||
args = _default_args(args)
|
||||
args.section = "settings"
|
||||
print_mock = mocker.patch("ahriman.core.formatters.Printer.print")
|
||||
|
||||
_, repository_id = configuration.check_loaded()
|
||||
Dump.run(args, repository_id, configuration, report=False)
|
||||
print_mock.assert_called_once_with(verbose=False, log_fn=pytest.helpers.anyvar(int), separator=" = ")
|
||||
|
||||
|
||||
def test_run_section_key(args: argparse.Namespace, configuration: Configuration, mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must run command with filter by section and key
|
||||
"""
|
||||
args = _default_args(args)
|
||||
args.section = "settings"
|
||||
args.key = "include"
|
||||
print_mock = mocker.patch("ahriman.core.formatters.Printer.print")
|
||||
application_mock = mocker.patch("ahriman.core.configuration.Configuration.dump")
|
||||
|
||||
_, repository_id = configuration.check_loaded()
|
||||
Dump.run(args, repository_id, configuration, report=False)
|
||||
application_mock.assert_not_called()
|
||||
print_mock.assert_called_once_with(verbose=False, log_fn=pytest.helpers.anyvar(int), separator=": ")
|
||||
|
||||
|
||||
def test_disallow_multi_architecture_run() -> None:
|
||||
"""
|
||||
must not allow multi architecture run
|
||||
|
@ -0,0 +1,37 @@
|
||||
import argparse
|
||||
import pytest
|
||||
|
||||
from pytest_mock import MockerFixture
|
||||
|
||||
from ahriman.application.handlers import Repositories
|
||||
from ahriman.core.configuration import Configuration
|
||||
|
||||
|
||||
def _default_args(args: argparse.Namespace) -> argparse.Namespace:
|
||||
"""
|
||||
default arguments for these test cases
|
||||
|
||||
Args:
|
||||
args(argparse.Namespace): command line arguments fixture
|
||||
|
||||
Returns:
|
||||
argparse.Namespace: generated arguments for these test cases
|
||||
"""
|
||||
args.configuration = None # doesn't matter actually
|
||||
args.id_only = False
|
||||
return args
|
||||
|
||||
|
||||
def test_run(args: argparse.Namespace, configuration: Configuration, mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must run command
|
||||
"""
|
||||
args = _default_args(args)
|
||||
print_mock = mocker.patch("ahriman.core.formatters.Printer.print")
|
||||
_, repository_id = configuration.check_loaded()
|
||||
application_mock = mocker.patch("ahriman.application.handlers.Handler.repositories_extract",
|
||||
return_value=[repository_id])
|
||||
|
||||
Repositories.run(args, repository_id, configuration, report=False)
|
||||
application_mock.assert_called_once_with(pytest.helpers.anyvar(int))
|
||||
print_mock.assert_called_once_with(verbose=not args.id_only, log_fn=pytest.helpers.anyvar(int), separator=": ")
|
@ -44,8 +44,8 @@ def test_run(args: argparse.Namespace, configuration: Configuration, repository:
|
||||
|
||||
_, repository_id = configuration.check_loaded()
|
||||
Search.run(args, repository_id, configuration, report=False)
|
||||
aur_search_mock.assert_called_once_with("ahriman", pacman=pytest.helpers.anyvar(int))
|
||||
official_search_mock.assert_called_once_with("ahriman", pacman=pytest.helpers.anyvar(int))
|
||||
aur_search_mock.assert_called_once_with("ahriman")
|
||||
official_search_mock.assert_called_once_with("ahriman")
|
||||
check_mock.assert_called_once_with(False, False)
|
||||
print_mock.assert_has_calls([
|
||||
MockCall(verbose=False, log_fn=pytest.helpers.anyvar(int), separator=": "),
|
||||
|
@ -38,7 +38,7 @@ def test_run(args: argparse.Namespace, configuration: Configuration, repository:
|
||||
|
||||
_, repository_id = configuration.check_loaded()
|
||||
ServiceUpdates.run(args, repository_id, configuration, report=False)
|
||||
package_mock.assert_called_once_with(package_ahriman.base, repository.pacman, None)
|
||||
package_mock.assert_called_once_with(package_ahriman.base, None)
|
||||
application_mock.assert_called_once_with(verbose=True, log_fn=pytest.helpers.anyvar(int), separator=" -> ")
|
||||
check_mock.assert_called_once_with(args.exit_code, True)
|
||||
|
||||
|
@ -25,7 +25,7 @@ def _default_args(args: argparse.Namespace) -> argparse.Namespace:
|
||||
Returns:
|
||||
argparse.Namespace: generated arguments for these test cases
|
||||
"""
|
||||
args.architecture = ["x86_64"]
|
||||
args.architecture = "x86_64"
|
||||
args.build_as_user = "ahriman"
|
||||
args.from_configuration = Path("/usr/share/devtools/pacman.conf.d/extra.conf")
|
||||
args.generate_salt = True
|
||||
@ -33,7 +33,7 @@ def _default_args(args: argparse.Namespace) -> argparse.Namespace:
|
||||
args.mirror = "mirror"
|
||||
args.multilib = True
|
||||
args.packager = "John Doe <john@doe.com>"
|
||||
args.repository = ["aur-clone"]
|
||||
args.repository = "aur-clone"
|
||||
args.server = None
|
||||
args.sign_key = "key"
|
||||
args.sign_target = [SignSettings.Packages]
|
||||
@ -127,6 +127,7 @@ def test_configuration_create_ahriman(args: argparse.Namespace, configuration: C
|
||||
mocker.patch("pathlib.Path.open")
|
||||
set_option_mock = mocker.patch("ahriman.core.configuration.Configuration.set_option")
|
||||
write_mock = mocker.patch("ahriman.core.configuration.Configuration.write")
|
||||
remove_mock = mocker.patch("pathlib.Path.unlink", autospec=True)
|
||||
_, repository_id = configuration.check_loaded()
|
||||
command = Setup.build_command(repository_paths.root, repository_id)
|
||||
|
||||
@ -143,13 +144,12 @@ def test_configuration_create_ahriman(args: argparse.Namespace, configuration: C
|
||||
" ".join([target.name.lower() for target in args.sign_target])),
|
||||
MockCall(Configuration.section_name("sign", repository_id.name, repository_id.architecture), "key",
|
||||
args.sign_key),
|
||||
MockCall(Configuration.section_name("web", repository_id.name, repository_id.architecture), "port",
|
||||
str(args.web_port)),
|
||||
MockCall(Configuration.section_name("web", repository_id.name, repository_id.architecture), "unix_socket",
|
||||
str(args.web_unix_socket)),
|
||||
MockCall("web", "port", str(args.web_port)),
|
||||
MockCall("web", "unix_socket", str(args.web_unix_socket)),
|
||||
MockCall("auth", "salt", pytest.helpers.anyvar(str, strict=True)),
|
||||
])
|
||||
write_mock.assert_called_once_with(pytest.helpers.anyvar(int))
|
||||
remove_mock.assert_called_once_with(configuration.include / "00-setup-overrides.ini", missing_ok=True)
|
||||
|
||||
|
||||
def test_configuration_create_ahriman_no_multilib(args: argparse.Namespace, configuration: Configuration,
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user