mirror of
https://github.com/arcan1s/ahriman.git
synced 2025-09-09 10:19:55 +00:00
Compare commits
1 Commits
master
...
808c4e5b1f
Author | SHA1 | Date | |
---|---|---|---|
808c4e5b1f |
@ -100,14 +100,6 @@ ahriman.application.handlers.rebuild module
|
|||||||
:no-undoc-members:
|
:no-undoc-members:
|
||||||
:show-inheritance:
|
:show-inheritance:
|
||||||
|
|
||||||
ahriman.application.handlers.reload module
|
|
||||||
------------------------------------------
|
|
||||||
|
|
||||||
.. automodule:: ahriman.application.handlers.reload
|
|
||||||
:members:
|
|
||||||
:no-undoc-members:
|
|
||||||
:show-inheritance:
|
|
||||||
|
|
||||||
ahriman.application.handlers.remove module
|
ahriman.application.handlers.remove module
|
||||||
------------------------------------------
|
------------------------------------------
|
||||||
|
|
||||||
|
@ -1,21 +0,0 @@
|
|||||||
ahriman.core.housekeeping package
|
|
||||||
=================================
|
|
||||||
|
|
||||||
Submodules
|
|
||||||
----------
|
|
||||||
|
|
||||||
ahriman.core.housekeeping.logs\_rotation\_trigger module
|
|
||||||
--------------------------------------------------------
|
|
||||||
|
|
||||||
.. automodule:: ahriman.core.housekeeping.logs_rotation_trigger
|
|
||||||
:members:
|
|
||||||
:no-undoc-members:
|
|
||||||
:show-inheritance:
|
|
||||||
|
|
||||||
Module contents
|
|
||||||
---------------
|
|
||||||
|
|
||||||
.. automodule:: ahriman.core.housekeeping
|
|
||||||
:members:
|
|
||||||
:no-undoc-members:
|
|
||||||
:show-inheritance:
|
|
@ -15,7 +15,6 @@ Subpackages
|
|||||||
ahriman.core.distributed
|
ahriman.core.distributed
|
||||||
ahriman.core.formatters
|
ahriman.core.formatters
|
||||||
ahriman.core.gitremote
|
ahriman.core.gitremote
|
||||||
ahriman.core.housekeeping
|
|
||||||
ahriman.core.http
|
ahriman.core.http
|
||||||
ahriman.core.log
|
ahriman.core.log
|
||||||
ahriman.core.report
|
ahriman.core.report
|
||||||
|
@ -44,14 +44,6 @@ ahriman.web.schemas.changes\_schema module
|
|||||||
:no-undoc-members:
|
:no-undoc-members:
|
||||||
:show-inheritance:
|
:show-inheritance:
|
||||||
|
|
||||||
ahriman.web.schemas.configuration\_schema module
|
|
||||||
------------------------------------------------
|
|
||||||
|
|
||||||
.. automodule:: ahriman.web.schemas.configuration_schema
|
|
||||||
:members:
|
|
||||||
:no-undoc-members:
|
|
||||||
:show-inheritance:
|
|
||||||
|
|
||||||
ahriman.web.schemas.counters\_schema module
|
ahriman.web.schemas.counters\_schema module
|
||||||
-------------------------------------------
|
-------------------------------------------
|
||||||
|
|
||||||
|
@ -12,14 +12,6 @@ ahriman.web.views.v1.service.add module
|
|||||||
:no-undoc-members:
|
:no-undoc-members:
|
||||||
:show-inheritance:
|
:show-inheritance:
|
||||||
|
|
||||||
ahriman.web.views.v1.service.config module
|
|
||||||
------------------------------------------
|
|
||||||
|
|
||||||
.. automodule:: ahriman.web.views.v1.service.config
|
|
||||||
:members:
|
|
||||||
:no-undoc-members:
|
|
||||||
:show-inheritance:
|
|
||||||
|
|
||||||
ahriman.web.views.v1.service.logs module
|
ahriman.web.views.v1.service.logs module
|
||||||
----------------------------------------
|
----------------------------------------
|
||||||
|
|
||||||
|
@ -40,7 +40,6 @@ This package contains everything required for the most of application actions an
|
|||||||
* ``ahriman.core.distributed`` package with triggers and helpers for distributed build system.
|
* ``ahriman.core.distributed`` package with triggers and helpers for distributed build system.
|
||||||
* ``ahriman.core.formatters`` package provides ``Printer`` sub-classes for printing data (e.g. package properties) to stdout which are used by some handlers.
|
* ``ahriman.core.formatters`` package provides ``Printer`` sub-classes for printing data (e.g. package properties) to stdout which are used by some handlers.
|
||||||
* ``ahriman.core.gitremote`` is a package with remote PKGBUILD triggers. Should not be called directly.
|
* ``ahriman.core.gitremote`` is a package with remote PKGBUILD triggers. Should not be called directly.
|
||||||
* ``ahriman.core.housekeeping`` package provides few triggers for removing old data.
|
|
||||||
* ``ahriman.core.http`` package provides HTTP clients which can be used later by other classes.
|
* ``ahriman.core.http`` package provides HTTP clients which can be used later by other classes.
|
||||||
* ``ahriman.core.log`` is a log utils package. It includes logger loader class, custom HTTP based logger and some wrappers.
|
* ``ahriman.core.log`` is a log utils package. It includes logger loader class, custom HTTP based logger and some wrappers.
|
||||||
* ``ahriman.core.report`` is a package with reporting triggers. Should not be called directly.
|
* ``ahriman.core.report`` is a package with reporting triggers. Should not be called directly.
|
||||||
|
@ -65,8 +65,6 @@ will try to read value from ``SECRET`` environment variable. In case if the requ
|
|||||||
|
|
||||||
will eventually lead ``key`` option in section ``section1`` to be set to the value of ``HOME`` environment variable (if available).
|
will eventually lead ``key`` option in section ``section1`` to be set to the value of ``HOME`` environment variable (if available).
|
||||||
|
|
||||||
Moreover, configuration can be read from environment variables directly by following the same naming convention, e.g. in the example above, one can have environment variable named ``section1:key`` (e.g. ``section1:key=$HOME``) and it will be substituted to the configuration with the highest priority.
|
|
||||||
|
|
||||||
There is also additional subcommand which will allow to validate configuration and print found errors. In order to do so, run ``service-config-validate`` subcommand, e.g.:
|
There is also additional subcommand which will allow to validate configuration and print found errors. In order to do so, run ``service-config-validate`` subcommand, e.g.:
|
||||||
|
|
||||||
.. code-block:: shell
|
.. code-block:: shell
|
||||||
@ -83,6 +81,7 @@ Base configuration settings.
|
|||||||
* ``apply_migrations`` - perform database migrations on the 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 manually.
|
* ``apply_migrations`` - perform database migrations on the 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 manually.
|
||||||
* ``database`` - path to the application SQLite database, string, required.
|
* ``database`` - path to the application SQLite database, string, required.
|
||||||
* ``include`` - path to directory with configuration files overrides, string, optional. Files will be read in alphabetical order.
|
* ``include`` - path to directory with configuration files overrides, string, optional. Files will be read in alphabetical order.
|
||||||
|
* ``keep_last_logs`` - amount of build logs to be kept for each package, integer, optional ,default ``0``. Logs will be cleared at the end of each process.
|
||||||
* ``logging`` - path to logging configuration, string, required. Check ``logging.ini`` for reference.
|
* ``logging`` - path to logging configuration, string, required. Check ``logging.ini`` for reference.
|
||||||
|
|
||||||
``alpm:*`` groups
|
``alpm:*`` groups
|
||||||
@ -139,8 +138,6 @@ Build related configuration. Group name can refer to architecture, e.g. ``build:
|
|||||||
|
|
||||||
Base repository settings.
|
Base repository settings.
|
||||||
|
|
||||||
* ``architecture`` - repository architecture, string. This field is read-only and generated automatically from run options if possible.
|
|
||||||
* ``name`` - repository name, string. This field is read-only and generated automatically from run options if possible.
|
|
||||||
* ``root`` - root path for application, string, required.
|
* ``root`` - root path for application, string, required.
|
||||||
|
|
||||||
``sign:*`` groups
|
``sign:*`` groups
|
||||||
@ -183,7 +180,7 @@ Web server settings. This feature requires ``aiohttp`` libraries to be installed
|
|||||||
* ``wait_timeout`` - wait timeout in seconds, maximum amount of time to be waited before lock will be free, integer, optional.
|
* ``wait_timeout`` - wait timeout in seconds, maximum amount of time to be waited before lock will be free, integer, optional.
|
||||||
|
|
||||||
``keyring`` group
|
``keyring`` group
|
||||||
-----------------
|
--------------------
|
||||||
|
|
||||||
Keyring package generator plugin.
|
Keyring package generator plugin.
|
||||||
|
|
||||||
@ -201,13 +198,6 @@ Keyring generator plugin
|
|||||||
* ``revoked`` - list of revoked packagers keys, space separated list of strings, optional.
|
* ``revoked`` - list of revoked packagers keys, space separated list of strings, optional.
|
||||||
* ``trusted`` - list of master keys, space separated list of strings, optional, if not set, the ``key`` option from ``sign`` group will be used.
|
* ``trusted`` - list of master keys, space separated list of strings, optional, if not set, the ``key`` option from ``sign`` group will be used.
|
||||||
|
|
||||||
``housekeeping`` group
|
|
||||||
----------------------
|
|
||||||
|
|
||||||
This section describes settings for the ``ahriman.core.housekeeping.LogsRotationTrigger`` plugin.
|
|
||||||
|
|
||||||
* ``keep_last_logs`` - amount of build logs to be kept for each package, integer, optional ,default ``0``. Logs will be cleared at the end of each process.
|
|
||||||
|
|
||||||
``mirrorlist`` group
|
``mirrorlist`` group
|
||||||
--------------------
|
--------------------
|
||||||
|
|
||||||
|
@ -40,7 +40,6 @@ package_ahriman-core() {
|
|||||||
'rsync: sync by using rsync')
|
'rsync: sync by using rsync')
|
||||||
install="$pkgbase.install"
|
install="$pkgbase.install"
|
||||||
backup=('etc/ahriman.ini'
|
backup=('etc/ahriman.ini'
|
||||||
'etc/ahriman.ini.d/00-housekeeping.ini'
|
|
||||||
'etc/ahriman.ini.d/logging.ini')
|
'etc/ahriman.ini.d/logging.ini')
|
||||||
|
|
||||||
cd "$pkgbase-$pkgver"
|
cd "$pkgbase-$pkgver"
|
||||||
@ -50,7 +49,6 @@ package_ahriman-core() {
|
|||||||
|
|
||||||
# keep usr/share configs as reference and copy them to /etc
|
# keep usr/share configs as reference and copy them to /etc
|
||||||
install -Dm644 "$pkgdir/usr/share/$pkgbase/settings/ahriman.ini" "$pkgdir/etc/ahriman.ini"
|
install -Dm644 "$pkgdir/usr/share/$pkgbase/settings/ahriman.ini" "$pkgdir/etc/ahriman.ini"
|
||||||
install -Dm644 "$pkgdir/usr/share/$pkgbase/settings/ahriman.ini.d/00-housekeeping.ini" "$pkgdir/etc/ahriman.ini.d/00-housekeeping.ini"
|
|
||||||
install -Dm644 "$pkgdir/usr/share/$pkgbase/settings/ahriman.ini.d/logging.ini" "$pkgdir/etc/ahriman.ini.d/logging.ini"
|
install -Dm644 "$pkgdir/usr/share/$pkgbase/settings/ahriman.ini.d/logging.ini" "$pkgdir/etc/ahriman.ini.d/logging.ini"
|
||||||
|
|
||||||
install -Dm644 "$srcdir/$pkgbase.sysusers" "$pkgdir/usr/lib/sysusers.d/$pkgbase.conf"
|
install -Dm644 "$srcdir/$pkgbase.sysusers" "$pkgdir/usr/lib/sysusers.d/$pkgbase.conf"
|
||||||
|
@ -5,7 +5,6 @@ After=network.target
|
|||||||
[Service]
|
[Service]
|
||||||
Type=simple
|
Type=simple
|
||||||
ExecStart=/usr/bin/ahriman web
|
ExecStart=/usr/bin/ahriman web
|
||||||
ExecReload=/usr/bin/ahriman web-reload
|
|
||||||
User=ahriman
|
User=ahriman
|
||||||
Group=ahriman
|
Group=ahriman
|
||||||
|
|
||||||
|
@ -7,6 +7,8 @@ logging = ahriman.ini.d/logging.ini
|
|||||||
;apply_migrations = yes
|
;apply_migrations = yes
|
||||||
; Path to the application SQLite database.
|
; Path to the application SQLite database.
|
||||||
database = ${repository:root}/ahriman.db
|
database = ${repository:root}/ahriman.db
|
||||||
|
; Keep last build logs for each package
|
||||||
|
keep_last_logs = 5
|
||||||
|
|
||||||
[alpm]
|
[alpm]
|
||||||
; Path to pacman system database cache.
|
; Path to pacman system database cache.
|
||||||
@ -43,11 +45,9 @@ triggers[] = ahriman.core.gitremote.RemotePullTrigger
|
|||||||
triggers[] = ahriman.core.report.ReportTrigger
|
triggers[] = ahriman.core.report.ReportTrigger
|
||||||
triggers[] = ahriman.core.upload.UploadTrigger
|
triggers[] = ahriman.core.upload.UploadTrigger
|
||||||
triggers[] = ahriman.core.gitremote.RemotePushTrigger
|
triggers[] = ahriman.core.gitremote.RemotePushTrigger
|
||||||
triggers[] = ahriman.core.housekeeping.LogsRotationTrigger
|
|
||||||
; List of well-known triggers. Used only for configuration purposes.
|
; List of well-known triggers. Used only for configuration purposes.
|
||||||
triggers_known[] = ahriman.core.gitremote.RemotePullTrigger
|
triggers_known[] = ahriman.core.gitremote.RemotePullTrigger
|
||||||
triggers_known[] = ahriman.core.gitremote.RemotePushTrigger
|
triggers_known[] = ahriman.core.gitremote.RemotePushTrigger
|
||||||
triggers_known[] = ahriman.core.housekeeping.LogsRotationTrigger
|
|
||||||
triggers_known[] = ahriman.core.report.ReportTrigger
|
triggers_known[] = ahriman.core.report.ReportTrigger
|
||||||
triggers_known[] = ahriman.core.upload.UploadTrigger
|
triggers_known[] = ahriman.core.upload.UploadTrigger
|
||||||
; Maximal age in seconds of the VCS packages before their version will be updated with its remote source.
|
; Maximal age in seconds of the VCS packages before their version will be updated with its remote source.
|
||||||
|
@ -1,3 +0,0 @@
|
|||||||
[logs-rotation]
|
|
||||||
; Keep last build logs for each package
|
|
||||||
keep_last_logs = 5
|
|
@ -99,9 +99,6 @@
|
|||||||
|
|
||||||
<table id="packages"
|
<table id="packages"
|
||||||
data-classes="table table-hover"
|
data-classes="table table-hover"
|
||||||
data-cookie="true"
|
|
||||||
data-cookie-id-table="ahriman-packages"
|
|
||||||
data-cookie-storage="localStorage"
|
|
||||||
data-export-options='{"fileName": "packages"}'
|
data-export-options='{"fileName": "packages"}'
|
||||||
data-filter-control="true"
|
data-filter-control="true"
|
||||||
data-filter-control-visible="false"
|
data-filter-control-visible="false"
|
||||||
@ -120,8 +117,7 @@
|
|||||||
data-sortable="true"
|
data-sortable="true"
|
||||||
data-sort-name="base"
|
data-sort-name="base"
|
||||||
data-sort-order="asc"
|
data-sort-order="asc"
|
||||||
data-toolbar="#toolbar"
|
data-toolbar="#toolbar">
|
||||||
data-unique-id="id">
|
|
||||||
<thead class="table-primary">
|
<thead class="table-primary">
|
||||||
<tr>
|
<tr>
|
||||||
<th data-checkbox="true"></th>
|
<th data-checkbox="true"></th>
|
||||||
|
@ -3,9 +3,7 @@
|
|||||||
|
|
||||||
function createAlert(title, message, clz, action, id) {
|
function createAlert(title, message, clz, action, id) {
|
||||||
id ??= md5(title + message); // MD5 id from the content
|
id ??= md5(title + message); // MD5 id from the content
|
||||||
if (alertPlaceholder.querySelector(`#alert-${id}`)) {
|
if (alertPlaceholder.querySelector(`#alert-${id}`)) return; // check if there are duplicates
|
||||||
return; // check if there are duplicates
|
|
||||||
}
|
|
||||||
|
|
||||||
const wrapper = document.createElement("div");
|
const wrapper = document.createElement("div");
|
||||||
wrapper.id = `alert-${id}`;
|
wrapper.id = `alert-${id}`;
|
||||||
|
@ -51,87 +51,6 @@
|
|||||||
const dashboardPackagesCountChartCanvas = document.getElementById("dashboard-packages-count-chart");
|
const dashboardPackagesCountChartCanvas = document.getElementById("dashboard-packages-count-chart");
|
||||||
let dashboardPackagesCountChart = null;
|
let dashboardPackagesCountChart = null;
|
||||||
|
|
||||||
function statusLoad() {
|
|
||||||
const badgeClass = status => {
|
|
||||||
if (status === "pending") return "btn-outline-warning";
|
|
||||||
if (status === "building") return "btn-outline-warning";
|
|
||||||
if (status === "failed") return "btn-outline-danger";
|
|
||||||
if (status === "success") return "btn-outline-success";
|
|
||||||
return "btn-outline-secondary";
|
|
||||||
};
|
|
||||||
|
|
||||||
makeRequest(
|
|
||||||
"/api/v1/status",
|
|
||||||
{
|
|
||||||
query: {
|
|
||||||
architecture: repository.architecture,
|
|
||||||
repository: repository.repository,
|
|
||||||
},
|
|
||||||
convert: response => response.json(),
|
|
||||||
},
|
|
||||||
data => {
|
|
||||||
versionBadge.innerHTML = `<i class="bi bi-github"></i> ahriman ${safe(data.version)}`;
|
|
||||||
|
|
||||||
dashboardButton.classList.remove(...dashboardButton.classList);
|
|
||||||
dashboardButton.classList.add("btn");
|
|
||||||
dashboardButton.classList.add(badgeClass(data.status.status));
|
|
||||||
|
|
||||||
dashboardModalHeader.classList.remove(...dashboardModalHeader.classList);
|
|
||||||
dashboardModalHeader.classList.add("modal-header");
|
|
||||||
headerClass(data.status.status).forEach(clz => dashboardModalHeader.classList.add(clz));
|
|
||||||
|
|
||||||
dashboardName.textContent = data.repository;
|
|
||||||
dashboardArchitecture.textContent = data.architecture;
|
|
||||||
dashboardStatus.textContent = data.status.status;
|
|
||||||
dashboardStatusTimestamp.textContent = new Date(1000 * data.status.timestamp).toISOStringShort();
|
|
||||||
|
|
||||||
if (dashboardPackagesStatusesChart) {
|
|
||||||
const labels = [
|
|
||||||
"unknown",
|
|
||||||
"pending",
|
|
||||||
"building",
|
|
||||||
"failed",
|
|
||||||
"success",
|
|
||||||
];
|
|
||||||
dashboardPackagesStatusesChart.config.data = {
|
|
||||||
labels: labels,
|
|
||||||
datasets: [{
|
|
||||||
label: "packages in status",
|
|
||||||
data: labels.map(label => data.packages[label]),
|
|
||||||
backgroundColor: [
|
|
||||||
"rgb(55, 58, 60)",
|
|
||||||
"rgb(255, 117, 24)",
|
|
||||||
"rgb(255, 117, 24)",
|
|
||||||
"rgb(255, 0, 57)",
|
|
||||||
"rgb(63, 182, 24)", // copy-paste from current style
|
|
||||||
],
|
|
||||||
}],
|
|
||||||
};
|
|
||||||
dashboardPackagesStatusesChart.update();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (dashboardPackagesCountChart) {
|
|
||||||
dashboardPackagesCountChart.config.data = {
|
|
||||||
labels: ["packages"],
|
|
||||||
datasets: [
|
|
||||||
{
|
|
||||||
label: "archives",
|
|
||||||
data: [data.stats.packages],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: "bases",
|
|
||||||
data: [data.stats.bases],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
};
|
|
||||||
dashboardPackagesCountChart.update();
|
|
||||||
}
|
|
||||||
|
|
||||||
dashboardCanvas.hidden = data.status.total > 0;
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
ready(_ => {
|
ready(_ => {
|
||||||
dashboardPackagesStatusesChart = new Chart(dashboardPackagesStatusesChartCanvas, {
|
dashboardPackagesStatusesChart = new Chart(dashboardPackagesStatusesChartCanvas, {
|
||||||
type: "pie",
|
type: "pie",
|
||||||
|
@ -148,19 +148,8 @@
|
|||||||
|
|
||||||
packageAddInput.addEventListener("keyup", _ => {
|
packageAddInput.addEventListener("keyup", _ => {
|
||||||
clearTimeout(packageAddInput.requestTimeout);
|
clearTimeout(packageAddInput.requestTimeout);
|
||||||
|
|
||||||
// do not update datalist if search string didn't change yet
|
|
||||||
const value = packageAddInput.value;
|
|
||||||
const previousValue = packageAddInput.dataset.previousValue;
|
|
||||||
if (value === previousValue) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// store current search string in attributes
|
|
||||||
packageAddInput.dataset.previousValue = value;
|
|
||||||
|
|
||||||
// perform data list update
|
|
||||||
packageAddInput.requestTimeout = setTimeout(_ => {
|
packageAddInput.requestTimeout = setTimeout(_ => {
|
||||||
|
const value = packageAddInput.value;
|
||||||
|
|
||||||
if (value.length >= 3) {
|
if (value.length >= 3) {
|
||||||
makeRequest(
|
makeRequest(
|
||||||
|
@ -80,7 +80,8 @@
|
|||||||
data-classes="table table-hover"
|
data-classes="table table-hover"
|
||||||
data-sortable="true"
|
data-sortable="true"
|
||||||
data-sort-name="timestamp"
|
data-sort-name="timestamp"
|
||||||
data-sort-order="desc">
|
data-sort-order="desc"
|
||||||
|
data-toggle="table">
|
||||||
<thead class="table-primary">
|
<thead class="table-primary">
|
||||||
<tr>
|
<tr>
|
||||||
<th data-align="right" data-field="timestamp">date</th>
|
<th data-align="right" data-field="timestamp">date</th>
|
||||||
@ -97,7 +98,7 @@
|
|||||||
<input id="package-info-refresh-input" type="checkbox" class="form-check-input" value="" checked>
|
<input id="package-info-refresh-input" type="checkbox" class="form-check-input" value="" checked>
|
||||||
<label for="package-info-refresh-input" class="form-check-label">update pacman databases</label>
|
<label for="package-info-refresh-input" class="form-check-label">update pacman databases</label>
|
||||||
|
|
||||||
<button id="package-info-update-button" type="submit" class="btn btn-success" onclick="packageInfoUpdate()"><i class="bi bi-play"></i><span class="d-none d-sm-inline"> update</span></button>
|
<button id="package-info-update-button" type="submit" class="btn btn-success" onclick="packageInfoUpdate()" data-bs-dismiss="modal"><i class="bi bi-play"></i><span class="d-none d-sm-inline"> update</span></button>
|
||||||
<button id="package-info-remove-button" type="submit" class="btn btn-danger" onclick="packageInfoRemove()" data-bs-dismiss="modal"><i class="bi bi-trash"></i><span class="d-none d-sm-inline"> remove</span></button>
|
<button id="package-info-remove-button" type="submit" class="btn btn-danger" onclick="packageInfoRemove()" data-bs-dismiss="modal"><i class="bi bi-trash"></i><span class="d-none d-sm-inline"> remove</span></button>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if autorefresh_intervals %}
|
{% if autorefresh_intervals %}
|
||||||
@ -315,69 +316,6 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
function loadLogs(packageBase, onFailure) {
|
function loadLogs(packageBase, onFailure) {
|
||||||
const sortFn = (left, right) => left.process_id.localeCompare(right.process_id) || left.version.localeCompare(right.version);
|
|
||||||
const compareFn = (left, right) => left.process_id === right.process_id && left.version === right.version;
|
|
||||||
|
|
||||||
makeRequest(
|
|
||||||
`/api/v2/packages/${packageBase}/logs`,
|
|
||||||
{
|
|
||||||
query: {
|
|
||||||
architecture: repository.architecture,
|
|
||||||
head: true,
|
|
||||||
repository: repository.repository,
|
|
||||||
},
|
|
||||||
convert: response => response.json(),
|
|
||||||
},
|
|
||||||
data => {
|
|
||||||
const currentVersions = Array.from(packageInfoLogsVersions.children)
|
|
||||||
.map(el => {
|
|
||||||
return {
|
|
||||||
process_id: el.dataset.processId,
|
|
||||||
version: el.dataset.version,
|
|
||||||
};
|
|
||||||
})
|
|
||||||
.sort(sortFn);
|
|
||||||
const newVersions = data
|
|
||||||
.map(el => {
|
|
||||||
return {
|
|
||||||
process_id: el.process_id,
|
|
||||||
version: el.version,
|
|
||||||
};
|
|
||||||
})
|
|
||||||
.sort(sortFn);
|
|
||||||
|
|
||||||
if (currentVersions.equals(newVersions, compareFn))
|
|
||||||
loadLogsActive(packageBase);
|
|
||||||
else
|
|
||||||
loadLogsAll(packageBase, onFailure);
|
|
||||||
},
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
function loadLogsActive(packageBase) {
|
|
||||||
const activeLogSelector = packageInfoLogsVersions.querySelector(".active");
|
|
||||||
|
|
||||||
if (activeLogSelector) {
|
|
||||||
makeRequest(
|
|
||||||
`/api/v2/packages/${packageBase}/logs`,
|
|
||||||
{
|
|
||||||
query: {
|
|
||||||
architecture: repository.architecture,
|
|
||||||
repository: repository.repository,
|
|
||||||
version: activeLogSelector.dataset.version,
|
|
||||||
process_id: activeLogSelector.dataset.processId,
|
|
||||||
},
|
|
||||||
convert: response => response.json(),
|
|
||||||
},
|
|
||||||
data => {
|
|
||||||
activeLogSelector.dataset.logs = convertLogs(data);
|
|
||||||
activeLogSelector.click();
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function loadLogsAll(packageBase, onFailure) {
|
|
||||||
makeRequest(
|
makeRequest(
|
||||||
`/api/v2/packages/${packageBase}/logs`,
|
`/api/v2/packages/${packageBase}/logs`,
|
||||||
{
|
{
|
||||||
@ -503,6 +441,29 @@
|
|||||||
packagesAdd(packageBase, [], repository, {refresh: packageInfoRefreshInput.checked});
|
packagesAdd(packageBase, [], repository, {refresh: packageInfoRefreshInput.checked});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function reloadActiveLogs(packageBase) {
|
||||||
|
const activeLogSelector = packageInfoLogsVersions.querySelector(".active");
|
||||||
|
|
||||||
|
if (activeLogSelector) {
|
||||||
|
makeRequest(
|
||||||
|
`/api/v2/packages/${packageBase}/logs`,
|
||||||
|
{
|
||||||
|
query: {
|
||||||
|
architecture: repository.architecture,
|
||||||
|
repository: repository.repository,
|
||||||
|
version: activeLogSelector.dataset.version,
|
||||||
|
process_id: activeLogSelector.dataset.processId,
|
||||||
|
},
|
||||||
|
convert: response => response.json(),
|
||||||
|
},
|
||||||
|
data => {
|
||||||
|
activeLogSelector.dataset.logs = convertLogs(data);
|
||||||
|
activeLogSelector.click();
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function showPackageInfo(packageBase) {
|
function showPackageInfo(packageBase) {
|
||||||
const isPackageBaseSet = packageBase !== undefined;
|
const isPackageBaseSet = packageBase !== undefined;
|
||||||
if (isPackageBaseSet) {
|
if (isPackageBaseSet) {
|
||||||
@ -542,14 +503,12 @@
|
|||||||
const packageBase = packageInfoModal.dataset.package;
|
const packageBase = packageInfoModal.dataset.package;
|
||||||
// we only poll status and logs here
|
// we only poll status and logs here
|
||||||
loadPackage(packageBase);
|
loadPackage(packageBase);
|
||||||
loadLogs(packageBase);
|
reloadActiveLogs(packageBase);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
ready(_ => {
|
ready(_ => {
|
||||||
packageInfoEventsTable.bootstrapTable({});
|
|
||||||
|
|
||||||
packageInfoEventsUpdateChart = new Chart(packageInfoEventsUpdateChartCanvas, {
|
packageInfoEventsUpdateChart = new Chart(packageInfoEventsUpdateChartCanvas, {
|
||||||
type: "line",
|
type: "line",
|
||||||
data: {},
|
data: {},
|
||||||
@ -581,6 +540,16 @@
|
|||||||
packageInfoAutoReloadTask = null; // not really required (?) but lets clear everything
|
packageInfoAutoReloadTask = null; // not really required (?) but lets clear everything
|
||||||
});
|
});
|
||||||
|
|
||||||
restoreAutoReloadSettings(packageInfoAutoReloadButton, packageInfoAutoReloadInput);
|
// set refresh interval
|
||||||
|
for (let refreshEnabled = Cookies.get(`ahriman-${packageInfoAutoReloadButton.id}-refresh-enabled`);
|
||||||
|
!!refreshEnabled;
|
||||||
|
refreshEnabled = undefined) {
|
||||||
|
packageInfoAutoReloadButton.checked = refreshEnabled === "true";
|
||||||
|
}
|
||||||
|
for (let refreshInterval = Cookies.get(`ahriman-${packageInfoAutoReloadButton.id}-refresh-interval`);
|
||||||
|
!!refreshInterval;
|
||||||
|
refreshInterval = undefined) {
|
||||||
|
toggleActiveElement(packageInfoAutoReloadInput, "interval", refreshInterval);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
@ -59,41 +59,6 @@
|
|||||||
return table.bootstrapTable("getSelections").map(row => row.id);
|
return table.bootstrapTable("getSelections").map(row => row.id);
|
||||||
}
|
}
|
||||||
|
|
||||||
function packagesLoad(onFailure) {
|
|
||||||
makeRequest(
|
|
||||||
"/api/v1/packages",
|
|
||||||
{
|
|
||||||
query: {
|
|
||||||
architecture: repository.architecture,
|
|
||||||
repository: repository.repository,
|
|
||||||
},
|
|
||||||
convert: response => response.json(),
|
|
||||||
},
|
|
||||||
data => {
|
|
||||||
const payload = data
|
|
||||||
.map(description => {
|
|
||||||
const package_base = description.package.base;
|
|
||||||
const web_url = description.package.remote.web_url;
|
|
||||||
return {
|
|
||||||
id: package_base,
|
|
||||||
base: web_url ? safeLink(web_url, package_base, package_base).outerHTML : safe(package_base),
|
|
||||||
version: safe(description.package.version),
|
|
||||||
packager: description.package.packager ? safe(description.package.packager) : "",
|
|
||||||
packages: listToTable(Object.keys(description.package.packages)),
|
|
||||||
groups: listToTable(extractListProperties(description.package, "groups")),
|
|
||||||
licenses: listToTable(extractListProperties(description.package, "licenses")),
|
|
||||||
timestamp: new Date(1000 * description.status.timestamp).toISOStringShort(),
|
|
||||||
status: description.status.status,
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
updateTable(table, payload);
|
|
||||||
table.bootstrapTable("hideLoading");
|
|
||||||
},
|
|
||||||
onFailure,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function packagesRemove(packages) {
|
function packagesRemove(packages) {
|
||||||
packages = packages ?? getSelection();
|
packages = packages ?? getSelection();
|
||||||
const onSuccess = update => `Packages ${update} have been removed`;
|
const onSuccess = update => `Packages ${update} have been removed`;
|
||||||
@ -125,9 +90,51 @@
|
|||||||
doPackageAction("/api/v1/service/update", [], repository, onSuccess, onFailure, parameters);
|
doPackageAction("/api/v1/service/update", [], repository, onSuccess, onFailure, parameters);
|
||||||
}
|
}
|
||||||
|
|
||||||
function reload() {
|
function reload(silent) {
|
||||||
|
if (!silent) {
|
||||||
table.bootstrapTable("showLoading");
|
table.bootstrapTable("showLoading");
|
||||||
const onFailure = error => {
|
}
|
||||||
|
|
||||||
|
const badgeClass = status => {
|
||||||
|
if (status === "pending") return "btn-outline-warning";
|
||||||
|
if (status === "building") return "btn-outline-warning";
|
||||||
|
if (status === "failed") return "btn-outline-danger";
|
||||||
|
if (status === "success") return "btn-outline-success";
|
||||||
|
return "btn-outline-secondary";
|
||||||
|
};
|
||||||
|
|
||||||
|
makeRequest(
|
||||||
|
"/api/v1/packages",
|
||||||
|
{
|
||||||
|
query: {
|
||||||
|
architecture: repository.architecture,
|
||||||
|
repository: repository.repository,
|
||||||
|
},
|
||||||
|
convert: response => response.json(),
|
||||||
|
},
|
||||||
|
data => {
|
||||||
|
const payload = data.map(description => {
|
||||||
|
const package_base = description.package.base;
|
||||||
|
const web_url = description.package.remote.web_url;
|
||||||
|
return {
|
||||||
|
id: package_base,
|
||||||
|
base: web_url ? safeLink(web_url, package_base, package_base).outerHTML : safe(package_base),
|
||||||
|
version: safe(description.package.version),
|
||||||
|
packager: description.package.packager ? safe(description.package.packager) : "",
|
||||||
|
packages: listToTable(Object.keys(description.package.packages)),
|
||||||
|
groups: listToTable(extractListProperties(description.package, "groups")),
|
||||||
|
licenses: listToTable(extractListProperties(description.package, "licenses")),
|
||||||
|
timestamp: new Date(1000 * description.status.timestamp).toISOStringShort(),
|
||||||
|
status: description.status.status,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
table.bootstrapTable("load", payload);
|
||||||
|
table.bootstrapTable("uncheckAll");
|
||||||
|
table.bootstrapTable("hideLoading");
|
||||||
|
},
|
||||||
|
error => {
|
||||||
|
if (!silent) {
|
||||||
if ((error.status === 401) || (error.status === 403)) {
|
if ((error.status === 401) || (error.status === 403)) {
|
||||||
// authorization error
|
// authorization error
|
||||||
const text = "In order to see statuses you must login first.";
|
const text = "In order to see statuses you must login first.";
|
||||||
@ -139,10 +146,80 @@
|
|||||||
const message = details => `Could not load list of packages: ${details}`;
|
const message = details => `Could not load list of packages: ${details}`;
|
||||||
showFailure("Load failure", message, error);
|
showFailure("Load failure", message, error);
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
packagesLoad(onFailure);
|
makeRequest(
|
||||||
statusLoad();
|
"/api/v1/status",
|
||||||
|
{
|
||||||
|
query: {
|
||||||
|
architecture: repository.architecture,
|
||||||
|
repository: repository.repository,
|
||||||
|
},
|
||||||
|
convert: response => response.json(),
|
||||||
|
},
|
||||||
|
data => {
|
||||||
|
versionBadge.innerHTML = `<i class="bi bi-github"></i> ahriman ${safe(data.version)}`;
|
||||||
|
|
||||||
|
dashboardButton.classList.remove(...dashboardButton.classList);
|
||||||
|
dashboardButton.classList.add("btn");
|
||||||
|
dashboardButton.classList.add(badgeClass(data.status.status));
|
||||||
|
|
||||||
|
dashboardModalHeader.classList.remove(...dashboardModalHeader.classList);
|
||||||
|
dashboardModalHeader.classList.add("modal-header");
|
||||||
|
headerClass(data.status.status).forEach(clz => dashboardModalHeader.classList.add(clz));
|
||||||
|
|
||||||
|
dashboardName.textContent = data.repository;
|
||||||
|
dashboardArchitecture.textContent = data.architecture;
|
||||||
|
dashboardStatus.textContent = data.status.status;
|
||||||
|
dashboardStatusTimestamp.textContent = new Date(1000 * data.status.timestamp).toISOStringShort();
|
||||||
|
|
||||||
|
if (dashboardPackagesStatusesChart) {
|
||||||
|
const labels = [
|
||||||
|
"unknown",
|
||||||
|
"pending",
|
||||||
|
"building",
|
||||||
|
"failed",
|
||||||
|
"success",
|
||||||
|
];
|
||||||
|
dashboardPackagesStatusesChart.config.data = {
|
||||||
|
labels: labels,
|
||||||
|
datasets: [{
|
||||||
|
label: "packages in status",
|
||||||
|
data: labels.map(label => data.packages[label]),
|
||||||
|
backgroundColor: [
|
||||||
|
"rgb(55, 58, 60)",
|
||||||
|
"rgb(255, 117, 24)",
|
||||||
|
"rgb(255, 117, 24)",
|
||||||
|
"rgb(255, 0, 57)",
|
||||||
|
"rgb(63, 182, 24)", // copy-paste from current style
|
||||||
|
],
|
||||||
|
}],
|
||||||
|
};
|
||||||
|
dashboardPackagesStatusesChart.update();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (dashboardPackagesCountChart) {
|
||||||
|
dashboardPackagesCountChart.config.data = {
|
||||||
|
labels: ["packages"],
|
||||||
|
datasets: [
|
||||||
|
{
|
||||||
|
label: "archives",
|
||||||
|
data: [data.stats.packages],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "bases",
|
||||||
|
data: [data.stats.bases],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
dashboardPackagesCountChart.update();
|
||||||
|
}
|
||||||
|
|
||||||
|
dashboardCanvas.hidden = data.status.total > 0;
|
||||||
|
},
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function selectRepository() {
|
function selectRepository() {
|
||||||
@ -164,10 +241,10 @@
|
|||||||
function toggleTableAutoReload(interval) {
|
function toggleTableAutoReload(interval) {
|
||||||
clearInterval(tableAutoReloadTask);
|
clearInterval(tableAutoReloadTask);
|
||||||
tableAutoReloadTask = toggleAutoReload(tableAutoReloadButton, interval, tableAutoReloadInput, _ => {
|
tableAutoReloadTask = toggleAutoReload(tableAutoReloadButton, interval, tableAutoReloadInput, _ => {
|
||||||
if (!hasActiveModal() &&
|
if ((getSelection().length === 0) &&
|
||||||
!hasActiveDropdown()) {
|
(table.bootstrapTable("getOptions").pageNumber === 1) &&
|
||||||
packagesLoad();
|
(!dashboardModal.classList.contains("show"))) {
|
||||||
statusLoad();
|
reload(true);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -233,11 +310,22 @@
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
onPageChange: (_, size) => { Cookies.set(`ahriman-${table.attr("id")}-page-size`, size); },
|
||||||
onUncheck: onCheckFunction,
|
onUncheck: onCheckFunction,
|
||||||
onUncheckAll: onCheckFunction,
|
onUncheckAll: onCheckFunction,
|
||||||
});
|
});
|
||||||
|
|
||||||
restoreAutoReloadSettings(tableAutoReloadButton, tableAutoReloadInput);
|
// set refresh interval
|
||||||
|
for (let refreshEnabled = Cookies.get(`ahriman-${tableAutoReloadButton.id}-refresh-enabled`);
|
||||||
|
!!refreshEnabled;
|
||||||
|
refreshEnabled = undefined) {
|
||||||
|
tableAutoReloadButton.checked = refreshEnabled === "true";
|
||||||
|
}
|
||||||
|
for (let refreshInterval = Cookies.get(`ahriman-${tableAutoReloadButton.id}-refresh-interval`);
|
||||||
|
!!refreshInterval;
|
||||||
|
refreshInterval = undefined) {
|
||||||
|
toggleActiveElement(tableAutoReloadInput, "interval", refreshInterval);
|
||||||
|
}
|
||||||
|
|
||||||
selectRepository();
|
selectRepository();
|
||||||
{% if autorefresh_intervals %}
|
{% if autorefresh_intervals %}
|
||||||
|
@ -53,7 +53,8 @@ SigLevel = Database{% if has_repo_signed %}Required{% else %}Never{% endif %} Pa
|
|||||||
data-show-search-clear-button="true"
|
data-show-search-clear-button="true"
|
||||||
data-sortable="true"
|
data-sortable="true"
|
||||||
data-sort-name="base"
|
data-sort-name="base"
|
||||||
data-sort-order="asc">
|
data-sort-order="asc"
|
||||||
|
data-toggle="table">
|
||||||
<thead class="table-primary">
|
<thead class="table-primary">
|
||||||
<tr>
|
<tr>
|
||||||
<th data-sortable="true" data-switchable="false" data-field="name" data-filter-control="input" data-filter-control-placeholder="(any package)">package</th>
|
<th data-sortable="true" data-switchable="false" data-field="name" data-filter-control="input" data-filter-control-placeholder="(any package)">package</th>
|
||||||
@ -127,8 +128,7 @@ SigLevel = Database{% if has_repo_signed %}Required{% else %}Never{% endif %} Pa
|
|||||||
}
|
}
|
||||||
|
|
||||||
ready(_ => {
|
ready(_ => {
|
||||||
table.bootstrapTable({
|
table.on("created-controls.bs.table", _ => {
|
||||||
onCreatedControls: _ => {
|
|
||||||
new easepick.create({
|
new easepick.create({
|
||||||
element: document.querySelector(".bootstrap-table-filter-control-timestamp"),
|
element: document.querySelector(".bootstrap-table-filter-control-timestamp"),
|
||||||
css: [
|
css: [
|
||||||
@ -158,7 +158,6 @@ SigLevel = Database{% if has_repo_signed %}Required{% else %}Never{% endif %} Pa
|
|||||||
};
|
};
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
},
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
@ -1,24 +1,25 @@
|
|||||||
<script src="https://cdn.jsdelivr.net/npm/jquery@3.7.1/dist/jquery.min.js" crossorigin="anonymous" type="application/javascript"></script>
|
<script src="https://cdn.jsdelivr.net/npm/jquery@3.7.1/dist/jquery.min.js" crossorigin="anonymous" type="application/javascript"></script>
|
||||||
<script src="https://cdn.jsdelivr.net/npm/js-md5@0.8.3/src/md5.min.js" crossorigin="anonymous" type="application/javascript"></script>
|
<script src="https://cdn.jsdelivr.net/npm/js-md5@0.8.3/src/md5.min.js" crossorigin="anonymous" type="application/javascript"></script>
|
||||||
|
|
||||||
<script src="https://cdn.jsdelivr.net/npm/tableexport.jquery.plugin@1.33.0/tableExport.min.js" crossorigin="anonymous" type="application/javascript"></script>
|
<script src="https://cdn.jsdelivr.net/npm/tableexport.jquery.plugin@1.30.0/tableExport.min.js" crossorigin="anonymous" type="application/javascript"></script>
|
||||||
|
|
||||||
<script src="https://cdn.jsdelivr.net/npm/jquery-resizable-columns@0.2.3/dist/jquery.resizableColumns.min.js" crossorigin="anonymous" type="application/javascript"></script>
|
<script src="https://cdn.jsdelivr.net/npm/jquery-resizable-columns@0.2.3/dist/jquery.resizableColumns.min.js" crossorigin="anonymous" type="application/javascript"></script>
|
||||||
|
|
||||||
<script src="https://cdn.jsdelivr.net/npm/@popperjs/core@2.11.8/dist/umd/popper.min.js" crossorigin="anonymous" type="application/javascript"></script>
|
<script src="https://cdn.jsdelivr.net/npm/@popperjs/core@2.11.8/dist/umd/popper.min.js" crossorigin="anonymous" type="application/javascript"></script>
|
||||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.7/dist/js/bootstrap.min.js" crossorigin="anonymous" type="application/javascript"></script>
|
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.min.js" crossorigin="anonymous" type="application/javascript"></script>
|
||||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap-table@1.24.1/dist/bootstrap-table.min.js" crossorigin="anonymous" type="application/javascript"></script>
|
<script src="https://cdn.jsdelivr.net/npm/bootstrap-table@1.23.2/dist/bootstrap-table.min.js" crossorigin="anonymous" type="application/javascript"></script>
|
||||||
|
|
||||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap-table@1.24.1/dist/extensions/export/bootstrap-table-export.min.js" crossorigin="anonymous" type="application/javascript"></script>
|
<script src="https://cdn.jsdelivr.net/npm/bootstrap-table@1.23.2/dist/extensions/export/bootstrap-table-export.min.js" crossorigin="anonymous" type="application/javascript"></script>
|
||||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap-table@1.24.1/dist/extensions/resizable/bootstrap-table-resizable.js" crossorigin="anonymous" type="application/javascript"></script>
|
<script src="https://cdn.jsdelivr.net/npm/bootstrap-table@1.23.2/dist/extensions/resizable/bootstrap-table-resizable.js" crossorigin="anonymous" type="application/javascript"></script>
|
||||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap-table@1.24.1/dist/extensions/filter-control/bootstrap-table-filter-control.js" crossorigin="anonymous" type="application/javascript"></script>
|
<script src="https://cdn.jsdelivr.net/npm/bootstrap-table@1.23.2/dist/extensions/filter-control/bootstrap-table-filter-control.js" crossorigin="anonymous" type="application/javascript"></script>
|
||||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap-table@1.24.1/dist/extensions/cookie/bootstrap-table-cookie.min.js" crossorigin="anonymous" type="application/javascript"></script>
|
|
||||||
|
|
||||||
<script src="https://cdn.jsdelivr.net/npm/@easepick/bundle@1.2.1/dist/index.umd.min.js" crossorigin="anonymous" type="application/javascript"></script>
|
<script src="https://cdn.jsdelivr.net/npm/@easepick/bundle@1.2.1/dist/index.umd.min.js" crossorigin="anonymous" type="application/javascript"></script>
|
||||||
|
|
||||||
<script src="https://cdn.jsdelivr.net/gh/highlightjs/cdn-release@11.11.1/build/highlight.min.js" crossorigin="anonymous" type="application/javascript"></script>
|
<script src="https://cdn.jsdelivr.net/gh/highlightjs/cdn-release@11.10.0/build/highlight.min.js" crossorigin="anonymous" type="application/javascript"></script>
|
||||||
|
|
||||||
<script src="https://cdn.jsdelivr.net/npm/chart.js@4.5.0/dist/chart.umd.min.js" crossorigin="anonymous" type="application/javascript"></script>
|
<script src="https://cdn.jsdelivr.net/npm/chart.js@4.4.4/dist/chart.umd.min.js" crossorigin="anonymous" type="application/javascript"></script>
|
||||||
|
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/js-cookie@3.0.5/dist/js.cookie.min.js" crossorigin="anonymous" type="application/javascript"></script>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
async function copyToClipboard(text, button) {
|
async function copyToClipboard(text, button) {
|
||||||
@ -63,16 +64,6 @@
|
|||||||
return !document.getSelection().isCollapsed; // not sure if it is a valid way, but I guess so
|
return !document.getSelection().isCollapsed; // not sure if it is a valid way, but I guess so
|
||||||
}
|
}
|
||||||
|
|
||||||
function hasActiveDropdown() {
|
|
||||||
return Array.from(document.querySelectorAll(".dropdown-menu"))
|
|
||||||
.some(el => el.classList.contains("show"));
|
|
||||||
}
|
|
||||||
|
|
||||||
function hasActiveModal() {
|
|
||||||
return Array.from(document.querySelectorAll(".modal"))
|
|
||||||
.some(el => el.classList.contains("show"));
|
|
||||||
}
|
|
||||||
|
|
||||||
function headerClass(status) {
|
function headerClass(status) {
|
||||||
if (status === "pending") return ["bg-warning"];
|
if (status === "pending") return ["bg-warning"];
|
||||||
if (status === "building") return ["bg-warning"];
|
if (status === "building") return ["bg-warning"];
|
||||||
@ -121,12 +112,6 @@
|
|||||||
.catch(error => onFailure && onFailure(error));
|
.catch(error => onFailure && onFailure(error));
|
||||||
}
|
}
|
||||||
|
|
||||||
function readOptional(extractor, callback) {
|
|
||||||
for (let value = extractor(); !!value; value = null) {
|
|
||||||
callback(value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function ready(fn) {
|
function ready(fn) {
|
||||||
if (document.readyState === "complete" || document.readyState === "interactive") {
|
if (document.readyState === "complete" || document.readyState === "interactive") {
|
||||||
setTimeout(fn, 1);
|
setTimeout(fn, 1);
|
||||||
@ -135,11 +120,6 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function restoreAutoReloadSettings(toggle, intervalSelector) {
|
|
||||||
readOptional(() => localStorage.getItem(`ahriman-${toggle.id}-refresh-enabled`), value => toggle.checked = value === "true");
|
|
||||||
readOptional(() => localStorage.getItem(`ahriman-${toggle.id}-refresh-interval`), value => toggleActiveElement(intervalSelector, "interval", value));
|
|
||||||
}
|
|
||||||
|
|
||||||
function safe(string) {
|
function safe(string) {
|
||||||
return String(string)
|
return String(string)
|
||||||
.replace(/&/g, "&")
|
.replace(/&/g, "&")
|
||||||
@ -159,18 +139,6 @@
|
|||||||
return element;
|
return element;
|
||||||
}
|
}
|
||||||
|
|
||||||
function toggleActiveElement(selector, dataType, value) {
|
|
||||||
const targetElement = selector.querySelector(`a[data-${dataType}="${value}"]`);
|
|
||||||
if (targetElement?.classList?.contains("active")) {
|
|
||||||
return; // element is already active, skip processing
|
|
||||||
}
|
|
||||||
|
|
||||||
Array.from(selector.children).forEach(il => {
|
|
||||||
Array.from(il.children).forEach(el => el.classList.remove("active"));
|
|
||||||
});
|
|
||||||
targetElement?.classList?.add("active");
|
|
||||||
}
|
|
||||||
|
|
||||||
function toggleAutoReload(toggle, interval, intervalSelector, callback) {
|
function toggleAutoReload(toggle, interval, intervalSelector, callback) {
|
||||||
if (interval) {
|
if (interval) {
|
||||||
toggle.checked = true; // toggle reload
|
toggle.checked = true; // toggle reload
|
||||||
@ -190,52 +158,16 @@
|
|||||||
toggle.checked = false; // no active interval found, disable toggle
|
toggle.checked = false; // no active interval found, disable toggle
|
||||||
}
|
}
|
||||||
|
|
||||||
localStorage.setItem(`ahriman-${toggle.id}-refresh-enabled`, toggle.checked);
|
Cookies.set(`ahriman-${toggle.id}-refresh-enabled`, toggle.checked);
|
||||||
localStorage.setItem(`ahriman-${toggle.id}-refresh-interval`, interval);
|
Cookies.set(`ahriman-${toggle.id}-refresh-interval`, interval);
|
||||||
return intervalId;
|
return intervalId;
|
||||||
}
|
}
|
||||||
|
|
||||||
function updateTable(table, rows) {
|
function toggleActiveElement(selector, dataType, value) {
|
||||||
// instead of using load method here, we just update rows manually to avoid table reinitialization
|
Array.from(selector.children).forEach(il => {
|
||||||
const currentData = table.bootstrapTable("getData").reduce((accumulator, row) => {
|
Array.from(il.children).forEach(el => el.classList.remove("active"));
|
||||||
accumulator[row.id] = row["0"];
|
|
||||||
return accumulator;
|
|
||||||
}, {});
|
|
||||||
// insert or update rows
|
|
||||||
rows.forEach(row => {
|
|
||||||
if (Object.hasOwn(currentData, row.id)) {
|
|
||||||
row["0"] = currentData[row.id]; // copy checkbox state
|
|
||||||
table.bootstrapTable("updateByUniqueId", {
|
|
||||||
id: row.id,
|
|
||||||
row: row,
|
|
||||||
replace: true,
|
|
||||||
});
|
});
|
||||||
} else {
|
selector.querySelector(`a[data-${dataType}="${value}"]`)?.classList?.add("active");
|
||||||
table.bootstrapTable("insertRow", {index: 0, row: row});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
// remove old rows
|
|
||||||
const newData = rows.map(value => value.id);
|
|
||||||
Object.keys(currentData).forEach(id => {
|
|
||||||
if (!newData.includes(id)) {
|
|
||||||
table.bootstrapTable("removeByUniqueId", id);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
Array.prototype.equals = function (right, comparator) {
|
|
||||||
let index = this.length;
|
|
||||||
if (index !== right.length) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
while (index--) {
|
|
||||||
if (!comparator(this[index], right[index])) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Date.prototype.toISOStringShort = function() {
|
Date.prototype.toISOStringShort = function() {
|
||||||
|
@ -1,15 +1,15 @@
|
|||||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.7/dist/css/bootstrap.min.css" crossorigin="anonymous" type="text/css">
|
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" crossorigin="anonymous" type="text/css">
|
||||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.13.1/font/bootstrap-icons.css" crossorigin="anonymous" type="text/css">
|
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.1/font/bootstrap-icons.css" crossorigin="anonymous" type="text/css">
|
||||||
|
|
||||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-table@1.24.1/dist/bootstrap-table.min.css" crossorigin="anonymous" type="text/css">
|
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-table@1.23.2/dist/bootstrap-table.min.css" crossorigin="anonymous" type="text/css">
|
||||||
|
|
||||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/jquery-resizable-columns@0.2.3/dist/jquery.resizableColumns.css" crossorigin="anonymous" type="text/css">
|
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/jquery-resizable-columns@0.2.3/dist/jquery.resizableColumns.css" crossorigin="anonymous" type="text/css">
|
||||||
|
|
||||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-table@1.24.1/dist/extensions/filter-control/bootstrap-table-filter-control.css" crossorigin="anonymous" type="text/css">
|
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-table@1.23.2/dist/extensions/filter-control/bootstrap-table-filter-control.css" crossorigin="anonymous" type="text/css">
|
||||||
|
|
||||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootswatch@5.3.7/dist/cosmo/bootstrap.min.css" crossorigin="anonymous" type="text/css">
|
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootswatch@5.3.3/dist/cosmo/bootstrap.min.css" crossorigin="anonymous" type="text/css">
|
||||||
|
|
||||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/highlightjs/cdn-release@11.11.1/build/styles/github.min.css" crossorigin="anonymous" type="text/css">
|
<link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/highlightjs/cdn-release@11.10.0/build/styles/github.min.css" crossorigin="anonymous" type="text/css">
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
.pre-scrollable {
|
.pre-scrollable {
|
||||||
|
@ -8,7 +8,7 @@ services:
|
|||||||
AHRIMAN_OUTPUT: console
|
AHRIMAN_OUTPUT: console
|
||||||
AHRIMAN_PASSWORD: ${AHRIMAN_PASSWORD}
|
AHRIMAN_PASSWORD: ${AHRIMAN_PASSWORD}
|
||||||
AHRIMAN_PORT: 8080
|
AHRIMAN_PORT: 8080
|
||||||
AHRIMAN_POSTSETUP_COMMAND: (cat /run/secrets/password; echo; cat /run/secrets/password) | sudo -u ahriman ahriman user-add demo -R full
|
AHRIMAN_PRESETUP_COMMAND: (cat /run/secrets/password; echo; cat /run/secrets/password) | sudo -u ahriman ahriman user-add demo -R full
|
||||||
AHRIMAN_REPOSITORY: ahriman-demo
|
AHRIMAN_REPOSITORY: ahriman-demo
|
||||||
AHRIMAN_UNIX_SOCKET: /var/lib/ahriman/ahriman/ahriman.sock
|
AHRIMAN_UNIX_SOCKET: /var/lib/ahriman/ahriman/ahriman.sock
|
||||||
|
|
||||||
|
@ -8,7 +8,7 @@ services:
|
|||||||
AHRIMAN_OUTPUT: console
|
AHRIMAN_OUTPUT: console
|
||||||
AHRIMAN_PASSWORD: ${AHRIMAN_PASSWORD}
|
AHRIMAN_PASSWORD: ${AHRIMAN_PASSWORD}
|
||||||
AHRIMAN_PORT: 8080
|
AHRIMAN_PORT: 8080
|
||||||
AHRIMAN_POSTSETUP_COMMAND: (cat /run/secrets/password; echo; cat /run/secrets/password) | sudo -u ahriman ahriman user-add demo -R full
|
AHRIMAN_PRESETUP_COMMAND: (cat /run/secrets/password; echo; cat /run/secrets/password) | sudo -u ahriman ahriman user-add demo -R full
|
||||||
AHRIMAN_REPOSITORY: ahriman-demo
|
AHRIMAN_REPOSITORY: ahriman-demo
|
||||||
AHRIMAN_UNIX_SOCKET: /var/lib/ahriman/ahriman/ahriman.sock
|
AHRIMAN_UNIX_SOCKET: /var/lib/ahriman/ahriman/ahriman.sock
|
||||||
|
|
||||||
|
@ -8,7 +8,7 @@ services:
|
|||||||
AHRIMAN_OUTPUT: console
|
AHRIMAN_OUTPUT: console
|
||||||
AHRIMAN_PASSWORD: ${AHRIMAN_PASSWORD}
|
AHRIMAN_PASSWORD: ${AHRIMAN_PASSWORD}
|
||||||
AHRIMAN_PORT: 8080
|
AHRIMAN_PORT: 8080
|
||||||
AHRIMAN_POSTSETUP_COMMAND: (cat /run/secrets/password; echo; cat /run/secrets/password) | sudo -u ahriman ahriman user-add demo -R full
|
AHRIMAN_PRESETUP_COMMAND: (cat /run/secrets/password; echo; cat /run/secrets/password) | sudo -u ahriman ahriman user-add demo -R full
|
||||||
AHRIMAN_REPOSITORY: ahriman-demo
|
AHRIMAN_REPOSITORY: ahriman-demo
|
||||||
AHRIMAN_UNIX_SOCKET: /var/lib/ahriman/ahriman/ahriman.sock
|
AHRIMAN_UNIX_SOCKET: /var/lib/ahriman/ahriman/ahriman.sock
|
||||||
|
|
||||||
@ -62,7 +62,7 @@ services:
|
|||||||
AHRIMAN_OUTPUT: console
|
AHRIMAN_OUTPUT: console
|
||||||
AHRIMAN_PASSWORD: ${AHRIMAN_PASSWORD}
|
AHRIMAN_PASSWORD: ${AHRIMAN_PASSWORD}
|
||||||
AHRIMAN_PORT: 8080
|
AHRIMAN_PORT: 8080
|
||||||
AHRIMAN_POSTSETUP_COMMAND: (cat /run/secrets/password; echo; cat /run/secrets/password) | sudo -u ahriman ahriman user-add demo -R full
|
AHRIMAN_PRESETUP_COMMAND: (cat /run/secrets/password; echo; cat /run/secrets/password) | sudo -u ahriman ahriman user-add demo -R full
|
||||||
AHRIMAN_REPOSITORY: ahriman-demo
|
AHRIMAN_REPOSITORY: ahriman-demo
|
||||||
AHRIMAN_REPOSITORY_SERVER: http://frontend/repo/$$repo/$$arch
|
AHRIMAN_REPOSITORY_SERVER: http://frontend/repo/$$repo/$$arch
|
||||||
|
|
||||||
|
@ -12,7 +12,7 @@ services:
|
|||||||
AHRIMAN_PACMAN_MIRROR: https://de.mirror.archlinux32.org/$$arch/$$repo
|
AHRIMAN_PACMAN_MIRROR: https://de.mirror.archlinux32.org/$$arch/$$repo
|
||||||
AHRIMAN_PASSWORD: ${AHRIMAN_PASSWORD}
|
AHRIMAN_PASSWORD: ${AHRIMAN_PASSWORD}
|
||||||
AHRIMAN_PORT: 8080
|
AHRIMAN_PORT: 8080
|
||||||
AHRIMAN_POSTSETUP_COMMAND: (cat /run/secrets/password; echo; cat /run/secrets/password) | sudo -u ahriman ahriman user-add demo -R full
|
AHRIMAN_PRESETUP_COMMAND: (cat /run/secrets/password; echo; cat /run/secrets/password) | sudo -u ahriman ahriman user-add demo -R full
|
||||||
AHRIMAN_REPOSITORY: ahriman-demo
|
AHRIMAN_REPOSITORY: ahriman-demo
|
||||||
AHRIMAN_UNIX_SOCKET: /var/lib/ahriman/ahriman/ahriman.sock
|
AHRIMAN_UNIX_SOCKET: /var/lib/ahriman/ahriman/ahriman.sock
|
||||||
|
|
||||||
|
@ -8,8 +8,8 @@ services:
|
|||||||
AHRIMAN_OUTPUT: console
|
AHRIMAN_OUTPUT: console
|
||||||
AHRIMAN_PASSWORD: ${AHRIMAN_PASSWORD}
|
AHRIMAN_PASSWORD: ${AHRIMAN_PASSWORD}
|
||||||
AHRIMAN_PORT: 8080
|
AHRIMAN_PORT: 8080
|
||||||
AHRIMAN_POSTSETUP_COMMAND: (cat /run/secrets/password; echo; cat /run/secrets/password) | sudo -u ahriman ahriman user-add demo -R full
|
AHRIMAN_POSTSETUP_COMMAND: ahriman --architecture x86_64 --repository another-demo service-setup --build-as-user ahriman --packager 'ahriman bot <ahriman@example.com>'
|
||||||
AHRIMAN_PRESETUP_COMMAND: ahriman --architecture x86_64 --repository another-demo service-setup --build-as-user ahriman --packager 'ahriman bot <ahriman@example.com>'
|
AHRIMAN_PRESETUP_COMMAND: (cat /run/secrets/password; echo; cat /run/secrets/password) | sudo -u ahriman ahriman user-add demo -R full
|
||||||
AHRIMAN_REPOSITORY: ahriman-demo
|
AHRIMAN_REPOSITORY: ahriman-demo
|
||||||
AHRIMAN_UNIX_SOCKET: /var/lib/ahriman/ahriman/ahriman.sock
|
AHRIMAN_UNIX_SOCKET: /var/lib/ahriman/ahriman/ahriman.sock
|
||||||
|
|
||||||
|
@ -9,7 +9,7 @@ services:
|
|||||||
AHRIMAN_OAUTH_CLIENT_SECRET: ${AHRIMAN_OAUTH_CLIENT_SECRET}
|
AHRIMAN_OAUTH_CLIENT_SECRET: ${AHRIMAN_OAUTH_CLIENT_SECRET}
|
||||||
AHRIMAN_OUTPUT: console
|
AHRIMAN_OUTPUT: console
|
||||||
AHRIMAN_PORT: 8080
|
AHRIMAN_PORT: 8080
|
||||||
AHRIMAN_POSTSETUP_COMMAND: sudo -u ahriman ahriman user-add ${AHRIMAN_OAUTH_USER} -R full -p ""
|
AHRIMAN_PRESETUP_COMMAND: sudo -u ahriman ahriman user-add ${AHRIMAN_OAUTH_USER} -R full -p ""
|
||||||
AHRIMAN_REPOSITORY: ahriman-demo
|
AHRIMAN_REPOSITORY: ahriman-demo
|
||||||
AHRIMAN_UNIX_SOCKET: /var/lib/ahriman/ahriman/ahriman.sock
|
AHRIMAN_UNIX_SOCKET: /var/lib/ahriman/ahriman/ahriman.sock
|
||||||
|
|
||||||
|
@ -6,7 +6,7 @@ services:
|
|||||||
environment:
|
environment:
|
||||||
AHRIMAN_DEBUG: yes
|
AHRIMAN_DEBUG: yes
|
||||||
AHRIMAN_OUTPUT: console
|
AHRIMAN_OUTPUT: console
|
||||||
AHRIMAN_POSTSETUP_COMMAND: sudo -u ahriman gpg --import /run/secrets/key
|
AHRIMAN_PRESETUP_COMMAND: sudo -u ahriman gpg --import /run/secrets/key
|
||||||
AHRIMAN_REPOSITORY: ahriman-demo
|
AHRIMAN_REPOSITORY: ahriman-demo
|
||||||
|
|
||||||
configs:
|
configs:
|
||||||
|
@ -8,7 +8,7 @@ services:
|
|||||||
AHRIMAN_OUTPUT: console
|
AHRIMAN_OUTPUT: console
|
||||||
AHRIMAN_PASSWORD: ${AHRIMAN_PASSWORD}
|
AHRIMAN_PASSWORD: ${AHRIMAN_PASSWORD}
|
||||||
AHRIMAN_PORT: 8080
|
AHRIMAN_PORT: 8080
|
||||||
AHRIMAN_POSTSETUP_COMMAND: (cat /run/secrets/password; echo; cat /run/secrets/password) | sudo -u ahriman ahriman user-add demo -R full
|
AHRIMAN_PRESETUP_COMMAND: (cat /run/secrets/password; echo; cat /run/secrets/password) | sudo -u ahriman ahriman user-add demo -R full
|
||||||
AHRIMAN_REPOSITORY: ahriman-demo
|
AHRIMAN_REPOSITORY: ahriman-demo
|
||||||
AHRIMAN_UNIX_SOCKET: /var/lib/ahriman/ahriman/ahriman.sock
|
AHRIMAN_UNIX_SOCKET: /var/lib/ahriman/ahriman/ahriman.sock
|
||||||
|
|
||||||
|
@ -1,70 +0,0 @@
|
|||||||
#
|
|
||||||
# Copyright (c) 2021-2025 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.application import Application
|
|
||||||
from ahriman.application.handlers.handler import Handler, SubParserAction
|
|
||||||
from ahriman.core.configuration import Configuration
|
|
||||||
from ahriman.models.repository_id import RepositoryId
|
|
||||||
|
|
||||||
|
|
||||||
class Reload(Handler):
|
|
||||||
"""
|
|
||||||
web server reload 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
|
|
||||||
"""
|
|
||||||
application = Application(repository_id, configuration, report=True)
|
|
||||||
client = application.repository.reporter
|
|
||||||
client.configuration_reload()
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def _set_web_reload_parser(root: SubParserAction) -> argparse.ArgumentParser:
|
|
||||||
"""
|
|
||||||
add parser for web reload subcommand
|
|
||||||
|
|
||||||
Args:
|
|
||||||
root(SubParserAction): subparsers for the commands
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
argparse.ArgumentParser: created argument parser
|
|
||||||
"""
|
|
||||||
parser = root.add_parser("web-reload", help="reload configuration",
|
|
||||||
description="reload web server configuration",
|
|
||||||
epilog="This method forces the web server to reload its configuration. "
|
|
||||||
"Note, however, that this method does not apply all configuration changes "
|
|
||||||
"(like ports, authentication, etc)")
|
|
||||||
parser.set_defaults(architecture="", lock=None, quiet=True, report=False, repository="", unsafe=True)
|
|
||||||
return parser
|
|
||||||
|
|
||||||
arguments = [_set_web_reload_parser]
|
|
@ -28,7 +28,6 @@ from ahriman.core.alpm.remote import AUR, Official
|
|||||||
from ahriman.core.configuration import Configuration
|
from ahriman.core.configuration import Configuration
|
||||||
from ahriman.core.exceptions import OptionError
|
from ahriman.core.exceptions import OptionError
|
||||||
from ahriman.core.formatters import AurPrinter
|
from ahriman.core.formatters import AurPrinter
|
||||||
from ahriman.core.types import Comparable
|
|
||||||
from ahriman.models.aur_package import AURPackage
|
from ahriman.models.aur_package import AURPackage
|
||||||
from ahriman.models.repository_id import RepositoryId
|
from ahriman.models.repository_id import RepositoryId
|
||||||
|
|
||||||
@ -116,7 +115,7 @@ class Search(Handler):
|
|||||||
raise OptionError(sort_by)
|
raise OptionError(sort_by)
|
||||||
# always sort by package name at the last
|
# always sort by package name at the last
|
||||||
# well technically it is not a string, but we can deal with it
|
# well technically it is not a string, but we can deal with it
|
||||||
comparator: Callable[[AURPackage], Comparable] = \
|
comparator: Callable[[AURPackage], tuple[str, str]] =\
|
||||||
lambda package: (getattr(package, sort_by), package.name)
|
lambda package: (getattr(package, sort_by), package.name)
|
||||||
return sorted(packages, key=comparator)
|
return sorted(packages, key=comparator)
|
||||||
|
|
||||||
|
@ -72,7 +72,6 @@ class Setup(Handler):
|
|||||||
|
|
||||||
application = Application(repository_id, configuration, report=report)
|
application = Application(repository_id, configuration, report=report)
|
||||||
|
|
||||||
with application.repository.paths.preserve_owner():
|
|
||||||
Setup.configuration_create_makepkg(args.packager, args.makeflags_jobs, application.repository.paths)
|
Setup.configuration_create_makepkg(args.packager, args.makeflags_jobs, application.repository.paths)
|
||||||
Setup.executable_create(application.repository.paths, repository_id)
|
Setup.executable_create(application.repository.paths, repository_id)
|
||||||
repository_server = f"file://{application.repository.paths.repository}" if args.server is None else args.server
|
repository_server = f"file://{application.repository.paths.repository}" if args.server is None else args.server
|
||||||
@ -281,5 +280,6 @@ class Setup(Handler):
|
|||||||
command = Setup.build_command(paths.root, repository_id)
|
command = Setup.build_command(paths.root, repository_id)
|
||||||
command.unlink(missing_ok=True)
|
command.unlink(missing_ok=True)
|
||||||
command.symlink_to(Setup.ARCHBUILD_COMMAND_PATH)
|
command.symlink_to(Setup.ARCHBUILD_COMMAND_PATH)
|
||||||
|
paths.chown(command) # we would like to keep owner inside ahriman's home
|
||||||
|
|
||||||
arguments = [_set_service_setup_parser]
|
arguments = [_set_service_setup_parser]
|
||||||
|
@ -25,7 +25,6 @@ from ahriman.application.application import Application
|
|||||||
from ahriman.application.handlers.handler import Handler, SubParserAction
|
from ahriman.application.handlers.handler import Handler, SubParserAction
|
||||||
from ahriman.core.configuration import Configuration
|
from ahriman.core.configuration import Configuration
|
||||||
from ahriman.core.formatters import PackagePrinter, StatusPrinter
|
from ahriman.core.formatters import PackagePrinter, StatusPrinter
|
||||||
from ahriman.core.types import Comparable
|
|
||||||
from ahriman.core.utils import enum_values
|
from ahriman.core.utils import enum_values
|
||||||
from ahriman.models.build_status import BuildStatus, BuildStatusEnum
|
from ahriman.models.build_status import BuildStatus, BuildStatusEnum
|
||||||
from ahriman.models.package import Package
|
from ahriman.models.package import Package
|
||||||
@ -65,7 +64,7 @@ class Status(Handler):
|
|||||||
|
|
||||||
Status.check_status(args.exit_code, packages)
|
Status.check_status(args.exit_code, packages)
|
||||||
|
|
||||||
comparator: Callable[[tuple[Package, BuildStatus]], Comparable] = lambda item: item[0].base
|
comparator: Callable[[tuple[Package, BuildStatus]], str] = lambda item: item[0].base
|
||||||
filter_fn: Callable[[tuple[Package, BuildStatus]], bool] =\
|
filter_fn: Callable[[tuple[Package, BuildStatus]], bool] =\
|
||||||
lambda item: args.status is None or item[1].status == args.status
|
lambda item: args.status is None or item[1].status == args.status
|
||||||
for package, package_status in sorted(filter(filter_fn, packages), key=comparator):
|
for package, package_status in sorted(filter(filter_fn, packages), key=comparator):
|
||||||
|
@ -52,7 +52,7 @@ class Validate(Handler):
|
|||||||
"""
|
"""
|
||||||
from ahriman.core.configuration.validator import Validator
|
from ahriman.core.configuration.validator import Validator
|
||||||
|
|
||||||
schema = Validate.schema(configuration)
|
schema = Validate.schema(repository_id, configuration)
|
||||||
validator = Validator(configuration=configuration, schema=schema)
|
validator = Validator(configuration=configuration, schema=schema)
|
||||||
|
|
||||||
if validator.validate(configuration.dump()):
|
if validator.validate(configuration.dump()):
|
||||||
@ -83,11 +83,12 @@ class Validate(Handler):
|
|||||||
return parser
|
return parser
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def schema(configuration: Configuration) -> ConfigurationSchema:
|
def schema(repository_id: RepositoryId, configuration: Configuration) -> ConfigurationSchema:
|
||||||
"""
|
"""
|
||||||
get schema with triggers
|
get schema with triggers
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
|
repository_id(RepositoryId): repository unique identifier
|
||||||
configuration(Configuration): configuration instance
|
configuration(Configuration): configuration instance
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
@ -106,12 +107,12 @@ class Validate(Handler):
|
|||||||
continue
|
continue
|
||||||
|
|
||||||
# default settings if any
|
# default settings if any
|
||||||
for schema_name, schema in trigger_class.configuration_schema(None).items():
|
for schema_name, schema in trigger_class.configuration_schema(repository_id, None).items():
|
||||||
erased = Validate.schema_erase_required(copy.deepcopy(schema))
|
erased = Validate.schema_erase_required(copy.deepcopy(schema))
|
||||||
root[schema_name] = Validate.schema_merge(root.get(schema_name, {}), erased)
|
root[schema_name] = Validate.schema_merge(root.get(schema_name, {}), erased)
|
||||||
|
|
||||||
# settings according to enabled triggers
|
# settings according to enabled triggers
|
||||||
for schema_name, schema in trigger_class.configuration_schema(configuration).items():
|
for schema_name, schema in trigger_class.configuration_schema(repository_id, configuration).items():
|
||||||
root[schema_name] = Validate.schema_merge(root.get(schema_name, {}), copy.deepcopy(schema))
|
root[schema_name] = Validate.schema_merge(root.get(schema_name, {}), copy.deepcopy(schema))
|
||||||
|
|
||||||
return root
|
return root
|
||||||
|
@ -130,8 +130,8 @@ class Pacman(LazyLogging):
|
|||||||
return # database for some reason deos not exist
|
return # database for some reason deos not exist
|
||||||
|
|
||||||
self.logger.info("copy pacman database %s from operating system root to ahriman's home %s", src, dst)
|
self.logger.info("copy pacman database %s from operating system root to ahriman's home %s", src, dst)
|
||||||
with self.repository_paths.preserve_owner(dst.parent):
|
|
||||||
shutil.copy(src, dst)
|
shutil.copy(src, dst)
|
||||||
|
self.repository_paths.chown(dst)
|
||||||
|
|
||||||
def database_init(self, handle: Handle, repository: str, architecture: str) -> DB:
|
def database_init(self, handle: Handle, repository: str, architecture: str) -> DB:
|
||||||
"""
|
"""
|
||||||
@ -267,8 +267,7 @@ class Pacman(LazyLogging):
|
|||||||
Package: list of packages which were returned by the query
|
Package: list of packages which were returned by the query
|
||||||
"""
|
"""
|
||||||
def is_package_provided(package: Package) -> bool:
|
def is_package_provided(package: Package) -> bool:
|
||||||
provides = [trim_package(name) for name in package.provides]
|
return package_name in package.provides
|
||||||
return package_name in provides
|
|
||||||
|
|
||||||
for database in self.handle.get_syncdbs():
|
for database in self.handle.get_syncdbs():
|
||||||
yield from filter(is_package_provided, database.search(package_name))
|
yield from filter(is_package_provided, database.search(package_name))
|
||||||
|
@ -146,7 +146,7 @@ class AUR(Remote):
|
|||||||
# search api provides reduced models
|
# search api provides reduced models
|
||||||
for stub in self.package_search(package_name, pacman=pacman, search_by="provides")
|
for stub in self.package_search(package_name, pacman=pacman, search_by="provides")
|
||||||
# verity that found package actually provides it
|
# verity that found package actually provides it
|
||||||
if package_name in (package := self.package_info(stub.name, pacman=pacman)).provides
|
if package_name in (package := self.package_info(stub.package_base, pacman=pacman)).provides
|
||||||
]
|
]
|
||||||
|
|
||||||
def package_search(self, *keywords: str, pacman: Pacman | None, search_by: str | None) -> list[AURPackage]:
|
def package_search(self, *keywords: str, pacman: Pacman | None, search_by: str | None) -> list[AURPackage]:
|
||||||
|
@ -19,7 +19,6 @@
|
|||||||
#
|
#
|
||||||
# pylint: disable=too-many-public-methods
|
# pylint: disable=too-many-public-methods
|
||||||
import configparser
|
import configparser
|
||||||
import os
|
|
||||||
import shlex
|
import shlex
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
@ -43,6 +42,7 @@ class Configuration(configparser.RawConfigParser):
|
|||||||
SYSTEM_CONFIGURATION_PATH(Path): (class attribute) default system configuration path distributed by package
|
SYSTEM_CONFIGURATION_PATH(Path): (class attribute) default system configuration path distributed by package
|
||||||
includes(list[Path]): list of includes which were read
|
includes(list[Path]): list of includes which were read
|
||||||
path(Path | None): path to root configuration file
|
path(Path | None): path to root configuration file
|
||||||
|
repository_id(RepositoryId | None): repository unique identifier
|
||||||
|
|
||||||
Examples:
|
Examples:
|
||||||
Configuration class provides additional method in order to handle application configuration. Since this class is
|
Configuration class provides additional method in order to handle application configuration. Since this class is
|
||||||
@ -93,7 +93,7 @@ class Configuration(configparser.RawConfigParser):
|
|||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
self._repository_id: RepositoryId | None = None
|
self.repository_id: RepositoryId | None = None
|
||||||
self.path: Path | None = None
|
self.path: Path | None = None
|
||||||
self.includes: list[Path] = []
|
self.includes: list[Path] = []
|
||||||
|
|
||||||
@ -128,32 +128,6 @@ class Configuration(configparser.RawConfigParser):
|
|||||||
"""
|
"""
|
||||||
return self.getpath("settings", "logging")
|
return self.getpath("settings", "logging")
|
||||||
|
|
||||||
@property
|
|
||||||
def repository_id(self) -> RepositoryId | None:
|
|
||||||
"""
|
|
||||||
repository identifier
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
RepositoryId: repository unique identifier
|
|
||||||
"""
|
|
||||||
return self._repository_id
|
|
||||||
|
|
||||||
@repository_id.setter
|
|
||||||
def repository_id(self, repository_id: RepositoryId | None) -> None:
|
|
||||||
"""
|
|
||||||
setter for repository identifier
|
|
||||||
|
|
||||||
Args:
|
|
||||||
repository_id(RepositoryId | None): repository unique identifier
|
|
||||||
"""
|
|
||||||
self._repository_id = repository_id
|
|
||||||
if repository_id is None or repository_id.is_empty:
|
|
||||||
self.remove_option("repository", "name")
|
|
||||||
self.remove_option("repository", "architecture")
|
|
||||||
else:
|
|
||||||
self.set_option("repository", "name", repository_id.name)
|
|
||||||
self.set_option("repository", "architecture", repository_id.architecture)
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def repository_name(self) -> str:
|
def repository_name(self) -> str:
|
||||||
"""
|
"""
|
||||||
@ -190,7 +164,6 @@ class Configuration(configparser.RawConfigParser):
|
|||||||
"""
|
"""
|
||||||
configuration = cls()
|
configuration = cls()
|
||||||
configuration.load(path)
|
configuration.load(path)
|
||||||
configuration.load_environment()
|
|
||||||
configuration.merge_sections(repository_id)
|
configuration.merge_sections(repository_id)
|
||||||
return configuration
|
return configuration
|
||||||
|
|
||||||
@ -315,16 +288,6 @@ class Configuration(configparser.RawConfigParser):
|
|||||||
self.read(self.path)
|
self.read(self.path)
|
||||||
self.load_includes() # load includes
|
self.load_includes() # load includes
|
||||||
|
|
||||||
def load_environment(self) -> None:
|
|
||||||
"""
|
|
||||||
load environment variables into configuration
|
|
||||||
"""
|
|
||||||
for name, value in os.environ.items():
|
|
||||||
if ":" not in name:
|
|
||||||
continue
|
|
||||||
section, key = name.rsplit(":", maxsplit=1)
|
|
||||||
self.set_option(section, key, value)
|
|
||||||
|
|
||||||
def load_includes(self, path: Path | None = None) -> None:
|
def load_includes(self, path: Path | None = None) -> None:
|
||||||
"""
|
"""
|
||||||
load configuration includes from specified path
|
load configuration includes from specified path
|
||||||
@ -393,16 +356,11 @@ class Configuration(configparser.RawConfigParser):
|
|||||||
"""
|
"""
|
||||||
reload configuration if possible or raise exception otherwise
|
reload configuration if possible or raise exception otherwise
|
||||||
"""
|
"""
|
||||||
# get current properties and validate input
|
|
||||||
path, repository_id = self.check_loaded()
|
path, repository_id = self.check_loaded()
|
||||||
|
for section in self.sections(): # clear current content
|
||||||
# clear current content
|
|
||||||
for section in self.sections():
|
|
||||||
self.remove_section(section)
|
self.remove_section(section)
|
||||||
|
self.load(path)
|
||||||
# create another instance and copy values from there
|
self.merge_sections(repository_id)
|
||||||
instance = self.from_path(path, repository_id)
|
|
||||||
self.copy_from(instance)
|
|
||||||
|
|
||||||
def set_option(self, section: str, option: str, value: str) -> None:
|
def set_option(self, section: str, option: str, value: str) -> None:
|
||||||
"""
|
"""
|
||||||
|
@ -45,6 +45,11 @@ CONFIGURATION_SCHEMA: ConfigurationSchema = {
|
|||||||
"path_exists": True,
|
"path_exists": True,
|
||||||
"path_type": "dir",
|
"path_type": "dir",
|
||||||
},
|
},
|
||||||
|
"keep_last_logs": {
|
||||||
|
"type": "integer",
|
||||||
|
"coerce": "integer",
|
||||||
|
"min": 0,
|
||||||
|
},
|
||||||
"logging": {
|
"logging": {
|
||||||
"type": "path",
|
"type": "path",
|
||||||
"coerce": "absolute_path",
|
"coerce": "absolute_path",
|
||||||
@ -249,10 +254,6 @@ CONFIGURATION_SCHEMA: ConfigurationSchema = {
|
|||||||
"repository": {
|
"repository": {
|
||||||
"type": "dict",
|
"type": "dict",
|
||||||
"schema": {
|
"schema": {
|
||||||
"architecture": {
|
|
||||||
"type": "string",
|
|
||||||
"empty": False,
|
|
||||||
},
|
|
||||||
"name": {
|
"name": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"empty": False,
|
"empty": False,
|
||||||
|
@ -94,13 +94,9 @@ class SQLite(
|
|||||||
sqlite3.register_adapter(list, json.dumps)
|
sqlite3.register_adapter(list, json.dumps)
|
||||||
sqlite3.register_converter("json", json.loads)
|
sqlite3.register_converter("json", json.loads)
|
||||||
|
|
||||||
if not self._configuration.getboolean("settings", "apply_migrations", fallback=True):
|
if self._configuration.getboolean("settings", "apply_migrations", fallback=True):
|
||||||
return
|
|
||||||
if self._repository_id.is_empty:
|
|
||||||
return # do not perform migration on empty repository identifier (e.g. multirepo command)
|
|
||||||
|
|
||||||
with self._repository_paths.preserve_owner():
|
|
||||||
self.with_connection(lambda connection: Migrations.migrate(connection, self._configuration))
|
self.with_connection(lambda connection: Migrations.migrate(connection, self._configuration))
|
||||||
|
self._repository_paths.chown(self.path)
|
||||||
|
|
||||||
def package_clear(self, package_base: str, repository_id: RepositoryId | None = None) -> None:
|
def package_clear(self, package_base: str, repository_id: RepositoryId | None = None) -> None:
|
||||||
"""
|
"""
|
||||||
|
@ -1,20 +0,0 @@
|
|||||||
#
|
|
||||||
# Copyright (c) 2021-2025 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.housekeeping.logs_rotation_trigger import LogsRotationTrigger
|
|
@ -1,87 +0,0 @@
|
|||||||
#
|
|
||||||
# Copyright (c) 2021-2025 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 import context
|
|
||||||
from ahriman.core.configuration import Configuration
|
|
||||||
from ahriman.core.status import Client
|
|
||||||
from ahriman.core.triggers import Trigger
|
|
||||||
from ahriman.models.package import Package
|
|
||||||
from ahriman.models.repository_id import RepositoryId
|
|
||||||
from ahriman.models.result import Result
|
|
||||||
|
|
||||||
|
|
||||||
class LogsRotationTrigger(Trigger):
|
|
||||||
"""
|
|
||||||
rotate logs after build processes
|
|
||||||
|
|
||||||
Attributes:
|
|
||||||
keep_last_records(int): number of last records to keep
|
|
||||||
"""
|
|
||||||
|
|
||||||
CONFIGURATION_SCHEMA = {
|
|
||||||
"logs-rotation": {
|
|
||||||
"type": "dict",
|
|
||||||
"schema": {
|
|
||||||
"keep_last_logs": {
|
|
||||||
"type": "integer",
|
|
||||||
"required": True,
|
|
||||||
"coerce": "integer",
|
|
||||||
"min": 0,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
def __init__(self, repository_id: RepositoryId, configuration: Configuration) -> None:
|
|
||||||
"""
|
|
||||||
Args:
|
|
||||||
repository_id(RepositoryId): repository unique identifier
|
|
||||||
configuration(Configuration): configuration instance
|
|
||||||
"""
|
|
||||||
Trigger.__init__(self, repository_id, configuration)
|
|
||||||
|
|
||||||
section = next(iter(self.configuration_sections(configuration)))
|
|
||||||
self.keep_last_records = configuration.getint( # read old-style first and then fallback to new style
|
|
||||||
"settings", "keep_last_logs",
|
|
||||||
fallback=configuration.getint(section, "keep_last_logs"))
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def configuration_sections(cls, configuration: Configuration) -> list[str]:
|
|
||||||
"""
|
|
||||||
extract configuration sections from configuration
|
|
||||||
|
|
||||||
Args:
|
|
||||||
configuration(Configuration): configuration instance
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
list[str]: read configuration sections belong to this trigger
|
|
||||||
"""
|
|
||||||
return list(cls.CONFIGURATION_SCHEMA.keys())
|
|
||||||
|
|
||||||
def on_result(self, result: Result, packages: list[Package]) -> None:
|
|
||||||
"""
|
|
||||||
run trigger
|
|
||||||
|
|
||||||
Args:
|
|
||||||
result(Result): build result
|
|
||||||
packages(list[Package]): list of all available packages
|
|
||||||
"""
|
|
||||||
ctx = context.get()
|
|
||||||
reporter = ctx.get(Client)
|
|
||||||
reporter.logs_rotate(self.keep_last_records)
|
|
@ -17,6 +17,7 @@
|
|||||||
# You should have received a copy of the GNU General Public License
|
# You should have received a copy of the GNU General Public License
|
||||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
#
|
#
|
||||||
|
import atexit
|
||||||
import logging
|
import logging
|
||||||
import uuid
|
import uuid
|
||||||
|
|
||||||
@ -36,6 +37,7 @@ class HttpLogHandler(logging.Handler):
|
|||||||
method
|
method
|
||||||
|
|
||||||
Attributes:
|
Attributes:
|
||||||
|
keep_last_records(int): number of last records to keep
|
||||||
reporter(Client): build status reporter instance
|
reporter(Client): build status reporter instance
|
||||||
suppress_errors(bool): suppress logging errors (e.g. if no web server available)
|
suppress_errors(bool): suppress logging errors (e.g. if no web server available)
|
||||||
"""
|
"""
|
||||||
@ -54,6 +56,7 @@ class HttpLogHandler(logging.Handler):
|
|||||||
|
|
||||||
self.reporter = Client.load(repository_id, configuration, report=report)
|
self.reporter = Client.load(repository_id, configuration, report=report)
|
||||||
self.suppress_errors = suppress_errors
|
self.suppress_errors = suppress_errors
|
||||||
|
self.keep_last_records = configuration.getint("settings", "keep_last_logs", fallback=0)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def load(cls, repository_id: RepositoryId, configuration: Configuration, *, report: bool) -> Self:
|
def load(cls, repository_id: RepositoryId, configuration: Configuration, *, report: bool) -> Self:
|
||||||
@ -80,6 +83,7 @@ class HttpLogHandler(logging.Handler):
|
|||||||
root.addHandler(handler)
|
root.addHandler(handler)
|
||||||
|
|
||||||
LogRecordId.DEFAULT_PROCESS_ID = str(uuid.uuid4()) # assign default process identifier for log records
|
LogRecordId.DEFAULT_PROCESS_ID = str(uuid.uuid4()) # assign default process identifier for log records
|
||||||
|
atexit.register(handler.rotate)
|
||||||
|
|
||||||
return handler
|
return handler
|
||||||
|
|
||||||
@ -100,3 +104,9 @@ class HttpLogHandler(logging.Handler):
|
|||||||
if self.suppress_errors:
|
if self.suppress_errors:
|
||||||
return
|
return
|
||||||
self.handleError(record)
|
self.handleError(record)
|
||||||
|
|
||||||
|
def rotate(self) -> None:
|
||||||
|
"""
|
||||||
|
rotate log records, removing older ones
|
||||||
|
"""
|
||||||
|
self.reporter.logs_rotate(self.keep_last_records)
|
||||||
|
@ -26,7 +26,6 @@ from typing import Any
|
|||||||
|
|
||||||
from ahriman.core.configuration import Configuration
|
from ahriman.core.configuration import Configuration
|
||||||
from ahriman.core.sign.gpg import GPG
|
from ahriman.core.sign.gpg import GPG
|
||||||
from ahriman.core.types import Comparable
|
|
||||||
from ahriman.core.utils import pretty_datetime, pretty_size, utcnow
|
from ahriman.core.utils import pretty_datetime, pretty_size, utcnow
|
||||||
from ahriman.models.repository_id import RepositoryId
|
from ahriman.models.repository_id import RepositoryId
|
||||||
from ahriman.models.result import Result
|
from ahriman.models.result import Result
|
||||||
@ -112,7 +111,7 @@ class JinjaTemplate:
|
|||||||
Returns:
|
Returns:
|
||||||
list[dict[str, str]]: sorted content according to comparator defined
|
list[dict[str, str]]: sorted content according to comparator defined
|
||||||
"""
|
"""
|
||||||
comparator: Callable[[dict[str, str]], Comparable] = lambda item: item["filename"]
|
comparator: Callable[[dict[str, str]], str] = lambda item: item["filename"]
|
||||||
return sorted(content, key=comparator)
|
return sorted(content, key=comparator)
|
||||||
|
|
||||||
def make_html(self, result: Result, template_name: Path | str) -> str:
|
def make_html(self, result: Result, template_name: Path | str) -> str:
|
||||||
|
@ -28,7 +28,6 @@ from ahriman.core.configuration import Configuration
|
|||||||
from ahriman.core.report.jinja_template import JinjaTemplate
|
from ahriman.core.report.jinja_template import JinjaTemplate
|
||||||
from ahriman.core.report.report import Report
|
from ahriman.core.report.report import Report
|
||||||
from ahriman.core.status import Client
|
from ahriman.core.status import Client
|
||||||
from ahriman.core.types import Comparable
|
|
||||||
from ahriman.models.event import EventType
|
from ahriman.models.event import EventType
|
||||||
from ahriman.models.package import Package
|
from ahriman.models.package import Package
|
||||||
from ahriman.models.repository_id import RepositoryId
|
from ahriman.models.repository_id import RepositoryId
|
||||||
@ -87,7 +86,7 @@ class RSS(Report, JinjaTemplate):
|
|||||||
Returns:
|
Returns:
|
||||||
list[dict[str, str]]: sorted content according to comparator defined
|
list[dict[str, str]]: sorted content according to comparator defined
|
||||||
"""
|
"""
|
||||||
comparator: Callable[[dict[str, str]], Comparable] = \
|
comparator: Callable[[dict[str, str]], datetime.datetime] = \
|
||||||
lambda item: parsedate_to_datetime(item["build_date"])
|
lambda item: parsedate_to_datetime(item["build_date"])
|
||||||
return sorted(content, key=comparator, reverse=True)
|
return sorted(content, key=comparator, reverse=True)
|
||||||
|
|
||||||
|
@ -81,11 +81,6 @@ class Client:
|
|||||||
|
|
||||||
return make_local_client()
|
return make_local_client()
|
||||||
|
|
||||||
def configuration_reload(self) -> None:
|
|
||||||
"""
|
|
||||||
reload configuration
|
|
||||||
"""
|
|
||||||
|
|
||||||
def event_add(self, event: Event) -> None:
|
def event_add(self, event: Event) -> None:
|
||||||
"""
|
"""
|
||||||
create new event
|
create new event
|
||||||
|
@ -17,7 +17,6 @@
|
|||||||
# You should have received a copy of the GNU General Public License
|
# You should have received a copy of the GNU General Public License
|
||||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
#
|
#
|
||||||
# pylint: disable=too-many-public-methods
|
|
||||||
import contextlib
|
import contextlib
|
||||||
|
|
||||||
from urllib.parse import quote_plus as url_encode
|
from urllib.parse import quote_plus as url_encode
|
||||||
@ -166,13 +165,6 @@ class WebClient(Client, SyncAhrimanClient):
|
|||||||
"""
|
"""
|
||||||
return f"{self.address}/api/v1/status"
|
return f"{self.address}/api/v1/status"
|
||||||
|
|
||||||
def configuration_reload(self) -> None:
|
|
||||||
"""
|
|
||||||
reload configuration
|
|
||||||
"""
|
|
||||||
with contextlib.suppress(Exception):
|
|
||||||
self.make_request("POST", f"{self.address}/api/v1/service/config")
|
|
||||||
|
|
||||||
def event_add(self, event: Event) -> None:
|
def event_add(self, event: Event) -> None:
|
||||||
"""
|
"""
|
||||||
create new event
|
create new event
|
||||||
|
@ -80,7 +80,8 @@ class Trigger(LazyLogging):
|
|||||||
return self.repository_id.architecture
|
return self.repository_id.architecture
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def configuration_schema(cls, configuration: Configuration | None) -> ConfigurationSchema:
|
def configuration_schema(cls, repository_id: RepositoryId,
|
||||||
|
configuration: Configuration | None) -> ConfigurationSchema:
|
||||||
"""
|
"""
|
||||||
configuration schema based on supplied service configuration
|
configuration schema based on supplied service configuration
|
||||||
|
|
||||||
@ -88,6 +89,7 @@ class Trigger(LazyLogging):
|
|||||||
Schema must be in cerberus format, for details and examples you can check built-in triggers.
|
Schema must be in cerberus format, for details and examples you can check built-in triggers.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
|
repository_id(str): repository unique identifier
|
||||||
configuration(Configuration | None): configuration instance. If set to None, the default schema
|
configuration(Configuration | None): configuration instance. If set to None, the default schema
|
||||||
should be returned
|
should be returned
|
||||||
|
|
||||||
@ -99,12 +101,10 @@ class Trigger(LazyLogging):
|
|||||||
|
|
||||||
result: ConfigurationSchema = {}
|
result: ConfigurationSchema = {}
|
||||||
for target in cls.configuration_sections(configuration):
|
for target in cls.configuration_sections(configuration):
|
||||||
for section in configuration.sections():
|
if not configuration.has_section(target):
|
||||||
if not (section == target or section.startswith(f"{target}:")):
|
|
||||||
# either repository specific or exact name
|
|
||||||
continue
|
continue
|
||||||
schema_name = configuration.get(section, "type", fallback=section)
|
section, schema_name = configuration.gettype(
|
||||||
|
target, repository_id, fallback=cls.CONFIGURATION_SCHEMA_FALLBACK)
|
||||||
if schema_name not in cls.CONFIGURATION_SCHEMA:
|
if schema_name not in cls.CONFIGURATION_SCHEMA:
|
||||||
continue
|
continue
|
||||||
result[section] = cls.CONFIGURATION_SCHEMA[schema_name]
|
result[section] = cls.CONFIGURATION_SCHEMA[schema_name]
|
||||||
|
@ -17,15 +17,7 @@
|
|||||||
# You should have received a copy of the GNU General Public License
|
# You should have received a copy of the GNU General Public License
|
||||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
#
|
#
|
||||||
from typing import Any, Protocol
|
from typing import Protocol
|
||||||
|
|
||||||
|
|
||||||
class Comparable(Protocol):
|
|
||||||
"""
|
|
||||||
class which supports :func:`__lt__` operation`
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __lt__(self, other: Any) -> bool: ...
|
|
||||||
|
|
||||||
|
|
||||||
class HasBool(Protocol):
|
class HasBool(Protocol):
|
||||||
|
@ -25,7 +25,7 @@ from dataclasses import dataclass, field, fields
|
|||||||
from pyalpm import Package # type: ignore[import-not-found]
|
from pyalpm import Package # type: ignore[import-not-found]
|
||||||
from typing import Any, Self
|
from typing import Any, Self
|
||||||
|
|
||||||
from ahriman.core.utils import filter_json, full_version, trim_package
|
from ahriman.core.utils import filter_json, full_version
|
||||||
|
|
||||||
|
|
||||||
@dataclass(frozen=True, kw_only=True)
|
@dataclass(frozen=True, kw_only=True)
|
||||||
@ -103,17 +103,6 @@ class AURPackage:
|
|||||||
keywords: list[str] = field(default_factory=list)
|
keywords: list[str] = field(default_factory=list)
|
||||||
groups: list[str] = field(default_factory=list)
|
groups: list[str] = field(default_factory=list)
|
||||||
|
|
||||||
def __post_init__(self) -> None:
|
|
||||||
"""
|
|
||||||
update packages lists accordingly
|
|
||||||
"""
|
|
||||||
object.__setattr__(self, "depends", [trim_package(package) for package in self.depends])
|
|
||||||
object.__setattr__(self, "make_depends", [trim_package(package) for package in self.make_depends])
|
|
||||||
object.__setattr__(self, "opt_depends", [trim_package(package) for package in self.opt_depends])
|
|
||||||
object.__setattr__(self, "check_depends", [trim_package(package) for package in self.check_depends])
|
|
||||||
object.__setattr__(self, "conflicts", [trim_package(package) for package in self.conflicts])
|
|
||||||
object.__setattr__(self, "provides", [trim_package(package) for package in self.provides])
|
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_json(cls, dump: dict[str, Any]) -> Self:
|
def from_json(cls, dump: dict[str, Any]) -> Self:
|
||||||
"""
|
"""
|
||||||
|
@ -83,13 +83,12 @@ class PackageDescription:
|
|||||||
|
|
||||||
def __post_init__(self) -> None:
|
def __post_init__(self) -> None:
|
||||||
"""
|
"""
|
||||||
update packages lists accordingly
|
update dependencies list accordingly
|
||||||
"""
|
"""
|
||||||
self.depends = [trim_package(package) for package in self.depends]
|
self.depends = [trim_package(package) for package in self.depends]
|
||||||
self.make_depends = [trim_package(package) for package in self.make_depends]
|
|
||||||
self.opt_depends = [trim_package(package) for package in self.opt_depends]
|
self.opt_depends = [trim_package(package) for package in self.opt_depends]
|
||||||
|
self.make_depends = [trim_package(package) for package in self.make_depends]
|
||||||
self.check_depends = [trim_package(package) for package in self.check_depends]
|
self.check_depends = [trim_package(package) for package in self.check_depends]
|
||||||
self.provides = [trim_package(package) for package in self.provides]
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def filepath(self) -> Path | None:
|
def filepath(self) -> Path | None:
|
||||||
|
@ -17,7 +17,6 @@
|
|||||||
# You should have received a copy of the GNU General Public License
|
# You should have received a copy of the GNU General Public License
|
||||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
#
|
#
|
||||||
import contextlib
|
|
||||||
import os
|
import os
|
||||||
import shutil
|
import shutil
|
||||||
|
|
||||||
@ -222,14 +221,22 @@ class RepositoryPaths(LazyLogging):
|
|||||||
stat = path.stat()
|
stat = path.stat()
|
||||||
return stat.st_uid, stat.st_gid
|
return stat.st_uid, stat.st_gid
|
||||||
|
|
||||||
def _chown(self, path: Path) -> None:
|
def cache_for(self, package_base: str) -> Path:
|
||||||
|
"""
|
||||||
|
get path to cached PKGBUILD and package sources for the package base
|
||||||
|
|
||||||
|
Args:
|
||||||
|
package_base(str): package base name
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Path: full path to directory for specified package base cache
|
||||||
|
"""
|
||||||
|
return self.cache / package_base
|
||||||
|
|
||||||
|
def chown(self, path: Path) -> None:
|
||||||
"""
|
"""
|
||||||
set owner of path recursively (from root) to root owner
|
set owner of path recursively (from root) to root owner
|
||||||
|
|
||||||
Notes:
|
|
||||||
More likely you don't want to call this method explicitly, consider using :func:`preserve_owner`
|
|
||||||
as context manager instead
|
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
path(Path): path to be chown
|
path(Path): path to be chown
|
||||||
|
|
||||||
@ -249,56 +256,6 @@ class RepositoryPaths(LazyLogging):
|
|||||||
set_owner(path)
|
set_owner(path)
|
||||||
path = path.parent
|
path = path.parent
|
||||||
|
|
||||||
def cache_for(self, package_base: str) -> Path:
|
|
||||||
"""
|
|
||||||
get path to cached PKGBUILD and package sources for the package base
|
|
||||||
|
|
||||||
Args:
|
|
||||||
package_base(str): package base name
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
Path: full path to directory for specified package base cache
|
|
||||||
"""
|
|
||||||
return self.cache / package_base
|
|
||||||
|
|
||||||
@contextlib.contextmanager
|
|
||||||
def preserve_owner(self, path: Path | None = None) -> Generator[None, None, None]:
|
|
||||||
"""
|
|
||||||
perform any action preserving owner for any newly created file or directory
|
|
||||||
|
|
||||||
Args:
|
|
||||||
path(Path | None, optional): use this path as root instead of repository root (Default value = None)
|
|
||||||
|
|
||||||
Examples:
|
|
||||||
This method is designed to use as context manager when you are going to perform operations which might
|
|
||||||
change filesystem, especially if you are doing it under unsafe flag, e.g.::
|
|
||||||
|
|
||||||
>>> with paths.preserve_owner():
|
|
||||||
>>> paths.tree_create()
|
|
||||||
|
|
||||||
Note, however, that this method doesn't handle any exceptions and will eventually interrupt
|
|
||||||
if there will be any.
|
|
||||||
"""
|
|
||||||
path = path or self.root
|
|
||||||
|
|
||||||
def walk(root: Path) -> Generator[Path, None, None]:
|
|
||||||
# basically walk, but skipping some content
|
|
||||||
for child in root.iterdir():
|
|
||||||
yield child
|
|
||||||
if child in (self.chroot.parent,):
|
|
||||||
yield from child.iterdir() # we only yield top-level in chroot directory
|
|
||||||
elif child.is_dir():
|
|
||||||
yield from walk(child)
|
|
||||||
|
|
||||||
# get current filesystem and run action
|
|
||||||
previous_snapshot = set(walk(path))
|
|
||||||
yield
|
|
||||||
|
|
||||||
# get newly created files and directories and chown them
|
|
||||||
new_entries = set(walk(path)).difference(previous_snapshot)
|
|
||||||
for entry in new_entries:
|
|
||||||
self._chown(entry)
|
|
||||||
|
|
||||||
def tree_clear(self, package_base: str) -> None:
|
def tree_clear(self, package_base: str) -> None:
|
||||||
"""
|
"""
|
||||||
clear package specific files
|
clear package specific files
|
||||||
@ -317,8 +274,6 @@ class RepositoryPaths(LazyLogging):
|
|||||||
"""
|
"""
|
||||||
if self.repository_id.is_empty:
|
if self.repository_id.is_empty:
|
||||||
return # do not even try to create tree in case if no repository id set
|
return # do not even try to create tree in case if no repository id set
|
||||||
|
|
||||||
with self.preserve_owner():
|
|
||||||
for directory in (
|
for directory in (
|
||||||
self.cache,
|
self.cache,
|
||||||
self.chroot,
|
self.chroot,
|
||||||
@ -327,3 +282,4 @@ class RepositoryPaths(LazyLogging):
|
|||||||
self.repository,
|
self.repository,
|
||||||
):
|
):
|
||||||
directory.mkdir(mode=0o755, parents=True, exist_ok=True)
|
directory.mkdir(mode=0o755, parents=True, exist_ok=True)
|
||||||
|
self.chown(directory)
|
||||||
|
@ -22,7 +22,6 @@ from ahriman.web.schemas.aur_package_schema import AURPackageSchema
|
|||||||
from ahriman.web.schemas.auth_schema import AuthSchema
|
from ahriman.web.schemas.auth_schema import AuthSchema
|
||||||
from ahriman.web.schemas.build_options_schema import BuildOptionsSchema
|
from ahriman.web.schemas.build_options_schema import BuildOptionsSchema
|
||||||
from ahriman.web.schemas.changes_schema import ChangesSchema
|
from ahriman.web.schemas.changes_schema import ChangesSchema
|
||||||
from ahriman.web.schemas.configuration_schema import ConfigurationSchema
|
|
||||||
from ahriman.web.schemas.counters_schema import CountersSchema
|
from ahriman.web.schemas.counters_schema import CountersSchema
|
||||||
from ahriman.web.schemas.dependencies_schema import DependenciesSchema
|
from ahriman.web.schemas.dependencies_schema import DependenciesSchema
|
||||||
from ahriman.web.schemas.error_schema import ErrorSchema
|
from ahriman.web.schemas.error_schema import ErrorSchema
|
||||||
|
@ -25,11 +25,11 @@ class AURPackageSchema(Schema):
|
|||||||
response AUR package schema
|
response AUR package schema
|
||||||
"""
|
"""
|
||||||
|
|
||||||
description = fields.String(required=True, metadata={
|
|
||||||
"description": "Package description",
|
|
||||||
"example": "ArcH linux ReposItory MANager",
|
|
||||||
})
|
|
||||||
package = fields.String(required=True, metadata={
|
package = fields.String(required=True, metadata={
|
||||||
"description": "Package base",
|
"description": "Package base",
|
||||||
"example": "ahriman",
|
"example": "ahriman",
|
||||||
})
|
})
|
||||||
|
description = fields.String(required=True, metadata={
|
||||||
|
"description": "Package description",
|
||||||
|
"example": "ArcH linux ReposItory MANager",
|
||||||
|
})
|
||||||
|
@ -25,10 +25,10 @@ class ChangesSchema(Schema):
|
|||||||
response package changes schema
|
response package changes schema
|
||||||
"""
|
"""
|
||||||
|
|
||||||
changes = fields.String(metadata={
|
|
||||||
"description": "Package changes in patch format",
|
|
||||||
})
|
|
||||||
last_commit_sha = fields.String(metadata={
|
last_commit_sha = fields.String(metadata={
|
||||||
"description": "Last recorded commit hash",
|
"description": "Last recorded commit hash",
|
||||||
"example": "f1875edca1eb8fc0e55c41d1cae5fa05b6b7c6",
|
"example": "f1875edca1eb8fc0e55c41d1cae5fa05b6b7c6",
|
||||||
})
|
})
|
||||||
|
changes = fields.String(metadata={
|
||||||
|
"description": "Package changes in patch format",
|
||||||
|
})
|
||||||
|
@ -1,39 +0,0 @@
|
|||||||
#
|
|
||||||
# Copyright (c) 2021-2025 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.web.apispec import Schema, fields
|
|
||||||
|
|
||||||
|
|
||||||
class ConfigurationSchema(Schema):
|
|
||||||
"""
|
|
||||||
response configuration schema
|
|
||||||
"""
|
|
||||||
|
|
||||||
key = fields.String(required=True, metadata={
|
|
||||||
"description": "Configuration key",
|
|
||||||
"example": "host",
|
|
||||||
})
|
|
||||||
section = fields.String(required=True, metadata={
|
|
||||||
"description": "Configuration section",
|
|
||||||
"example": "web",
|
|
||||||
})
|
|
||||||
value = fields.String(required=True, metadata={
|
|
||||||
"description": "Configuration value",
|
|
||||||
"example": "127.0.0.1",
|
|
||||||
})
|
|
@ -25,6 +25,18 @@ class CountersSchema(Schema):
|
|||||||
response package counters schema
|
response package counters schema
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
total = fields.Integer(required=True, metadata={
|
||||||
|
"description": "Total amount of packages",
|
||||||
|
"example": 6,
|
||||||
|
})
|
||||||
|
_unknown = fields.Integer(data_key="unknown", required=True, metadata={
|
||||||
|
"description": "Amount of packages in unknown state",
|
||||||
|
"example": 0,
|
||||||
|
})
|
||||||
|
pending = fields.Integer(required=True, metadata={
|
||||||
|
"description": "Amount of packages in pending state",
|
||||||
|
"example": 2,
|
||||||
|
})
|
||||||
building = fields.Integer(required=True, metadata={
|
building = fields.Integer(required=True, metadata={
|
||||||
"description": "Amount of packages in building state",
|
"description": "Amount of packages in building state",
|
||||||
"example": 1,
|
"example": 1,
|
||||||
@ -33,19 +45,7 @@ class CountersSchema(Schema):
|
|||||||
"description": "Amount of packages in failed state",
|
"description": "Amount of packages in failed state",
|
||||||
"example": 1,
|
"example": 1,
|
||||||
})
|
})
|
||||||
pending = fields.Integer(required=True, metadata={
|
|
||||||
"description": "Amount of packages in pending state",
|
|
||||||
"example": 2,
|
|
||||||
})
|
|
||||||
success = fields.Integer(required=True, metadata={
|
success = fields.Integer(required=True, metadata={
|
||||||
"description": "Amount of packages in success state",
|
"description": "Amount of packages in success state",
|
||||||
"example": 3,
|
"example": 3,
|
||||||
})
|
})
|
||||||
total = fields.Integer(required=True, metadata={
|
|
||||||
"description": "Total amount of packages",
|
|
||||||
"example": 6,
|
|
||||||
})
|
|
||||||
unknown_ = fields.Integer(data_key="unknown", required=True, metadata={
|
|
||||||
"description": "Amount of packages in unknown state",
|
|
||||||
"example": 0,
|
|
||||||
})
|
|
||||||
|
@ -30,17 +30,17 @@ class EventSchema(Schema):
|
|||||||
"description": "Event creation timestamp",
|
"description": "Event creation timestamp",
|
||||||
"example": 1680537091,
|
"example": 1680537091,
|
||||||
})
|
})
|
||||||
data = fields.Dict(keys=fields.String(), metadata={
|
|
||||||
"description": "Event metadata if available",
|
|
||||||
})
|
|
||||||
event = fields.String(required=True, metadata={
|
event = fields.String(required=True, metadata={
|
||||||
"description": "Event type",
|
"description": "Event type",
|
||||||
"example": EventType.PackageUpdated,
|
"example": EventType.PackageUpdated,
|
||||||
})
|
})
|
||||||
message = fields.String(metadata={
|
|
||||||
"description": "Event message if available",
|
|
||||||
})
|
|
||||||
object_id = fields.String(required=True, metadata={
|
object_id = fields.String(required=True, metadata={
|
||||||
"description": "Event object identifier",
|
"description": "Event object identifier",
|
||||||
"example": "ahriman",
|
"example": "ahriman",
|
||||||
})
|
})
|
||||||
|
message = fields.String(metadata={
|
||||||
|
"description": "Event message if available",
|
||||||
|
})
|
||||||
|
data = fields.Dict(keys=fields.String(), metadata={
|
||||||
|
"description": "Event metadata if available",
|
||||||
|
})
|
||||||
|
@ -31,14 +31,14 @@ class EventSearchSchema(PaginationSchema):
|
|||||||
"description": "Event type",
|
"description": "Event type",
|
||||||
"example": EventType.PackageUpdated,
|
"example": EventType.PackageUpdated,
|
||||||
})
|
})
|
||||||
from_date = fields.Integer(metadata={
|
|
||||||
"description": "Minimal creation timestamp, inclusive",
|
|
||||||
"example": 1680537091,
|
|
||||||
})
|
|
||||||
object_id = fields.String(metadata={
|
object_id = fields.String(metadata={
|
||||||
"description": "Event object identifier",
|
"description": "Event object identifier",
|
||||||
"example": "ahriman",
|
"example": "ahriman",
|
||||||
})
|
})
|
||||||
|
from_date = fields.Integer(metadata={
|
||||||
|
"description": "Minimal creation timestamp, inclusive",
|
||||||
|
"example": 1680537091,
|
||||||
|
})
|
||||||
to_date = fields.Integer(metadata={
|
to_date = fields.Integer(metadata={
|
||||||
"description": "Maximal creation timestamp, exclusive",
|
"description": "Maximal creation timestamp, exclusive",
|
||||||
"example": 1680537091,
|
"example": 1680537091,
|
||||||
|
@ -33,10 +33,10 @@ class LogSchema(Schema):
|
|||||||
message = fields.String(required=True, metadata={
|
message = fields.String(required=True, metadata={
|
||||||
"description": "Log message",
|
"description": "Log message",
|
||||||
})
|
})
|
||||||
process_id = fields.String(metadata={
|
|
||||||
"description": "Process unique identifier",
|
|
||||||
})
|
|
||||||
version = fields.String(required=True, metadata={
|
version = fields.String(required=True, metadata={
|
||||||
"description": "Package version to tag",
|
"description": "Package version to tag",
|
||||||
"example": __version__,
|
"example": __version__,
|
||||||
})
|
})
|
||||||
|
process_id = fields.String(metadata={
|
||||||
|
"description": "Process unique identifier",
|
||||||
|
})
|
||||||
|
@ -25,11 +25,11 @@ class LoginSchema(Schema):
|
|||||||
request login schema
|
request login schema
|
||||||
"""
|
"""
|
||||||
|
|
||||||
password = fields.String(required=True, metadata={
|
|
||||||
"description": "Login password",
|
|
||||||
"example": "pa55w0rd",
|
|
||||||
})
|
|
||||||
username = fields.String(required=True, metadata={
|
username = fields.String(required=True, metadata={
|
||||||
"description": "Login username",
|
"description": "Login username",
|
||||||
"example": "user",
|
"example": "user",
|
||||||
})
|
})
|
||||||
|
password = fields.String(required=True, metadata={
|
||||||
|
"description": "Login password",
|
||||||
|
"example": "pa55w0rd",
|
||||||
|
})
|
||||||
|
@ -27,13 +27,10 @@ class LogsSearchSchema(PaginationSchema):
|
|||||||
request log search schema
|
request log search schema
|
||||||
"""
|
"""
|
||||||
|
|
||||||
head = fields.Boolean(metadata={
|
|
||||||
"description": "Return versions only without fetching logs themselves",
|
|
||||||
})
|
|
||||||
process_id = fields.String(metadata={
|
|
||||||
"description": "Process unique identifier to search",
|
|
||||||
})
|
|
||||||
version = fields.String(metadata={
|
version = fields.String(metadata={
|
||||||
"description": "Package version to search",
|
"description": "Package version to search",
|
||||||
"example": __version__,
|
"example": __version__,
|
||||||
})
|
})
|
||||||
|
process_id = fields.String(metadata={
|
||||||
|
"description": "Process unique identifier to search",
|
||||||
|
})
|
||||||
|
@ -37,14 +37,22 @@ class PackagePropertiesSchema(Schema):
|
|||||||
"description": "Package build timestamp",
|
"description": "Package build timestamp",
|
||||||
"example": 1680537091,
|
"example": 1680537091,
|
||||||
})
|
})
|
||||||
check_depends = fields.List(fields.String(), metadata={
|
|
||||||
"description": "Package test dependencies list",
|
|
||||||
"example": ["python-pytest"],
|
|
||||||
})
|
|
||||||
depends = fields.List(fields.String(), metadata={
|
depends = fields.List(fields.String(), metadata={
|
||||||
"description": "Package dependencies list",
|
"description": "Package dependencies list",
|
||||||
"example": ["devtools"],
|
"example": ["devtools"],
|
||||||
})
|
})
|
||||||
|
make_depends = fields.List(fields.String(), metadata={
|
||||||
|
"description": "Package make dependencies list",
|
||||||
|
"example": ["python-build"],
|
||||||
|
})
|
||||||
|
opt_depends = fields.List(fields.String(), metadata={
|
||||||
|
"description": "Package optional dependencies list",
|
||||||
|
"example": ["python-aiohttp"],
|
||||||
|
})
|
||||||
|
check_depends = fields.List(fields.String(), metadata={
|
||||||
|
"description": "Package test dependencies list",
|
||||||
|
"example": ["python-pytest"],
|
||||||
|
})
|
||||||
description = fields.String(metadata={
|
description = fields.String(metadata={
|
||||||
"description": "Package description",
|
"description": "Package description",
|
||||||
"example": "ArcH linux ReposItory MANager",
|
"example": "ArcH linux ReposItory MANager",
|
||||||
@ -65,14 +73,6 @@ class PackagePropertiesSchema(Schema):
|
|||||||
"description": "Package licenses",
|
"description": "Package licenses",
|
||||||
"example": ["GPL3"],
|
"example": ["GPL3"],
|
||||||
})
|
})
|
||||||
make_depends = fields.List(fields.String(), metadata={
|
|
||||||
"description": "Package make dependencies list",
|
|
||||||
"example": ["python-build"],
|
|
||||||
})
|
|
||||||
opt_depends = fields.List(fields.String(), metadata={
|
|
||||||
"description": "Package optional dependencies list",
|
|
||||||
"example": ["python-aiohttp"],
|
|
||||||
})
|
|
||||||
provides = fields.List(fields.String(), metadata={
|
provides = fields.List(fields.String(), metadata={
|
||||||
"description": "Package provides list",
|
"description": "Package provides list",
|
||||||
"example": ["ahriman-git"],
|
"example": ["ahriman-git"],
|
||||||
|
@ -32,18 +32,18 @@ class PackageSchema(Schema):
|
|||||||
"description": "Package base",
|
"description": "Package base",
|
||||||
"example": "ahriman",
|
"example": "ahriman",
|
||||||
})
|
})
|
||||||
packager = fields.String(metadata={
|
version = fields.String(required=True, metadata={
|
||||||
"description": "packager for the last success package build",
|
"description": "Package version",
|
||||||
"example": "ahriman bot <ahriman@example.com>",
|
"example": __version__,
|
||||||
|
})
|
||||||
|
remote = fields.Nested(RemoteSchema(), required=True, metadata={
|
||||||
|
"description": "Package remote properties",
|
||||||
})
|
})
|
||||||
packages = fields.Dict(
|
packages = fields.Dict(
|
||||||
keys=fields.String(), values=fields.Nested(PackagePropertiesSchema()), required=True, metadata={
|
keys=fields.String(), values=fields.Nested(PackagePropertiesSchema()), required=True, metadata={
|
||||||
"description": "Packages which belong to this base",
|
"description": "Packages which belong to this base",
|
||||||
})
|
})
|
||||||
remote = fields.Nested(RemoteSchema(), required=True, metadata={
|
packager = fields.String(metadata={
|
||||||
"description": "Package remote properties",
|
"description": "packager for the last success package build",
|
||||||
})
|
"example": "ahriman bot <ahriman@example.com>",
|
||||||
version = fields.String(required=True, metadata={
|
|
||||||
"description": "Package version",
|
|
||||||
"example": __version__,
|
|
||||||
})
|
})
|
||||||
|
@ -25,19 +25,19 @@ class RepositoryStatsSchema(Schema):
|
|||||||
response repository stats schema
|
response repository stats schema
|
||||||
"""
|
"""
|
||||||
|
|
||||||
archive_size = fields.Int(metadata={
|
|
||||||
"description": "Total archive size of the packages in bytes",
|
|
||||||
"example": 42000,
|
|
||||||
})
|
|
||||||
bases = fields.Int(metadata={
|
bases = fields.Int(metadata={
|
||||||
"description": "Amount of unique packages bases",
|
"description": "Amount of unique packages bases",
|
||||||
"example": 2,
|
"example": 2,
|
||||||
})
|
})
|
||||||
installed_size = fields.Int(metadata={
|
|
||||||
"description": "Total installed size of the packages in bytes",
|
|
||||||
"example": 42000000,
|
|
||||||
})
|
|
||||||
packages = fields.Int(metadata={
|
packages = fields.Int(metadata={
|
||||||
"description": "Amount of unique packages",
|
"description": "Amount of unique packages",
|
||||||
"example": 4,
|
"example": 4,
|
||||||
})
|
})
|
||||||
|
archive_size = fields.Int(metadata={
|
||||||
|
"description": "Total archive size of the packages in bytes",
|
||||||
|
"example": 42000,
|
||||||
|
})
|
||||||
|
installed_size = fields.Int(metadata={
|
||||||
|
"description": "Total installed size of the packages in bytes",
|
||||||
|
"example": 42000000,
|
||||||
|
})
|
||||||
|
@ -25,7 +25,7 @@ class SearchSchema(Schema):
|
|||||||
request package search schema
|
request package search schema
|
||||||
"""
|
"""
|
||||||
|
|
||||||
for_ = fields.List(fields.String(), data_key="for", required=True, metadata={
|
_for = fields.List(fields.String(), data_key="for", required=True, metadata={
|
||||||
"description": "Keyword for search",
|
"description": "Keyword for search",
|
||||||
"example": ["ahriman"],
|
"example": ["ahriman"],
|
||||||
})
|
})
|
||||||
|
@ -21,7 +21,6 @@ from aiohttp.web import HTTPBadRequest, HTTPNoContent, Response, json_response
|
|||||||
from collections.abc import Callable
|
from collections.abc import Callable
|
||||||
from typing import ClassVar
|
from typing import ClassVar
|
||||||
|
|
||||||
from ahriman.core.types import Comparable
|
|
||||||
from ahriman.models.user_access import UserAccess
|
from ahriman.models.user_access import UserAccess
|
||||||
from ahriman.models.worker import Worker
|
from ahriman.models.worker import Worker
|
||||||
from ahriman.web.apispec.decorators import apidocs
|
from ahriman.web.apispec.decorators import apidocs
|
||||||
@ -75,7 +74,7 @@ class WorkersView(BaseView):
|
|||||||
"""
|
"""
|
||||||
workers = self.workers.workers
|
workers = self.workers.workers
|
||||||
|
|
||||||
comparator: Callable[[Worker], Comparable] = lambda item: item.identifier
|
comparator: Callable[[Worker], str] = lambda item: item.identifier
|
||||||
response = [worker.view() for worker in sorted(workers, key=comparator)]
|
response = [worker.view() for worker in sorted(workers, key=comparator)]
|
||||||
|
|
||||||
return json_response(response)
|
return json_response(response)
|
||||||
|
@ -23,7 +23,6 @@ from aiohttp.web import HTTPNoContent, Response, json_response
|
|||||||
from collections.abc import Callable
|
from collections.abc import Callable
|
||||||
from typing import ClassVar
|
from typing import ClassVar
|
||||||
|
|
||||||
from ahriman.core.types import Comparable
|
|
||||||
from ahriman.models.build_status import BuildStatus
|
from ahriman.models.build_status import BuildStatus
|
||||||
from ahriman.models.package import Package
|
from ahriman.models.package import Package
|
||||||
from ahriman.models.user_access import UserAccess
|
from ahriman.models.user_access import UserAccess
|
||||||
@ -69,7 +68,7 @@ class PackagesView(StatusViewGuard, BaseView):
|
|||||||
repository_id = self.repository_id()
|
repository_id = self.repository_id()
|
||||||
packages = self.service(repository_id).packages
|
packages = self.service(repository_id).packages
|
||||||
|
|
||||||
comparator: Callable[[tuple[Package, BuildStatus]], Comparable] = lambda items: items[0].base
|
comparator: Callable[[tuple[Package, BuildStatus]], str] = lambda items: items[0].base
|
||||||
response = [
|
response = [
|
||||||
{
|
{
|
||||||
"package": package.view(),
|
"package": package.view(),
|
||||||
|
@ -1,84 +0,0 @@
|
|||||||
#
|
|
||||||
# Copyright (c) 2021-2025 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 aiohttp.web import HTTPNoContent, Response, json_response
|
|
||||||
from typing import ClassVar
|
|
||||||
|
|
||||||
from ahriman.core.formatters import ConfigurationPrinter
|
|
||||||
from ahriman.models.user_access import UserAccess
|
|
||||||
from ahriman.web.apispec.decorators import apidocs
|
|
||||||
from ahriman.web.schemas import ConfigurationSchema
|
|
||||||
from ahriman.web.views.base import BaseView
|
|
||||||
|
|
||||||
|
|
||||||
class ConfigView(BaseView):
|
|
||||||
"""
|
|
||||||
configuration control view
|
|
||||||
|
|
||||||
Attributes:
|
|
||||||
GET_PERMISSION(UserAccess): (class attribute) get permissions of self
|
|
||||||
POST_PERMISSION(UserAccess): (class attribute) post permissions of self
|
|
||||||
"""
|
|
||||||
|
|
||||||
GET_PERMISSION = POST_PERMISSION = UserAccess.Full # type: ClassVar[UserAccess]
|
|
||||||
ROUTES = ["/api/v1/service/config"]
|
|
||||||
|
|
||||||
@apidocs(
|
|
||||||
tags=["Actions"],
|
|
||||||
summary="Get configuration",
|
|
||||||
description="Get current web service configuration as nested dictionary",
|
|
||||||
permission=GET_PERMISSION,
|
|
||||||
schema=ConfigurationSchema(many=True),
|
|
||||||
)
|
|
||||||
async def get(self) -> Response:
|
|
||||||
"""
|
|
||||||
get current web service configuration
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
Response: current web service configuration as nested dictionary
|
|
||||||
"""
|
|
||||||
dump = self.configuration.dump()
|
|
||||||
|
|
||||||
response = [
|
|
||||||
{
|
|
||||||
"section": section,
|
|
||||||
"key": key,
|
|
||||||
"value": value,
|
|
||||||
} for section, values in dump.items()
|
|
||||||
for key, value in values.items()
|
|
||||||
if key not in ConfigurationPrinter.HIDE_KEYS
|
|
||||||
]
|
|
||||||
return json_response(response)
|
|
||||||
|
|
||||||
@apidocs(
|
|
||||||
tags=["Actions"],
|
|
||||||
summary="Reload configuration",
|
|
||||||
description="Reload configuration from current files",
|
|
||||||
permission=POST_PERMISSION,
|
|
||||||
)
|
|
||||||
async def post(self) -> None:
|
|
||||||
"""
|
|
||||||
reload web service configuration
|
|
||||||
|
|
||||||
Raises:
|
|
||||||
HTTPNoContent: on success response
|
|
||||||
"""
|
|
||||||
self.configuration.reload()
|
|
||||||
|
|
||||||
raise HTTPNoContent
|
|
@ -22,7 +22,6 @@ from collections.abc import Callable
|
|||||||
from typing import ClassVar
|
from typing import ClassVar
|
||||||
|
|
||||||
from ahriman.core.alpm.remote import AUR
|
from ahriman.core.alpm.remote import AUR
|
||||||
from ahriman.core.types import Comparable
|
|
||||||
from ahriman.models.aur_package import AURPackage
|
from ahriman.models.aur_package import AURPackage
|
||||||
from ahriman.models.user_access import UserAccess
|
from ahriman.models.user_access import UserAccess
|
||||||
from ahriman.web.apispec.decorators import apidocs
|
from ahriman.web.apispec.decorators import apidocs
|
||||||
@ -71,12 +70,7 @@ class SearchView(BaseView):
|
|||||||
if not packages:
|
if not packages:
|
||||||
raise HTTPNotFound(reason=f"No packages found for terms: {search}")
|
raise HTTPNotFound(reason=f"No packages found for terms: {search}")
|
||||||
|
|
||||||
comparator: Callable[[AURPackage], Comparable] = \
|
comparator: Callable[[AURPackage], str] = lambda item: item.package_base
|
||||||
lambda item: (
|
|
||||||
item.package_base not in search, # inverted because False < True
|
|
||||||
not any(item.package_base.startswith(term) for term in search), # same as above
|
|
||||||
item.package_base,
|
|
||||||
)
|
|
||||||
response = [
|
response = [
|
||||||
{
|
{
|
||||||
"package": package.package_base,
|
"package": package.package_base,
|
||||||
|
@ -17,10 +17,7 @@
|
|||||||
# You should have received a copy of the GNU General Public License
|
# You should have received a copy of the GNU General Public License
|
||||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
#
|
#
|
||||||
import itertools
|
|
||||||
|
|
||||||
from aiohttp.web import Response, json_response
|
from aiohttp.web import Response, json_response
|
||||||
from dataclasses import replace
|
|
||||||
from typing import ClassVar
|
from typing import ClassVar
|
||||||
|
|
||||||
from ahriman.models.user_access import UserAccess
|
from ahriman.models.user_access import UserAccess
|
||||||
@ -31,8 +28,7 @@ from ahriman.web.views.status_view_guard import StatusViewGuard
|
|||||||
|
|
||||||
|
|
||||||
class LogsView(StatusViewGuard, BaseView):
|
class LogsView(StatusViewGuard, BaseView):
|
||||||
""" else:
|
"""
|
||||||
|
|
||||||
package logs web view
|
package logs web view
|
||||||
|
|
||||||
Attributes:
|
Attributes:
|
||||||
@ -70,14 +66,5 @@ class LogsView(StatusViewGuard, BaseView):
|
|||||||
|
|
||||||
logs = self.service(package_base=package_base).package_logs_get(package_base, version, process, limit, offset)
|
logs = self.service(package_base=package_base).package_logs_get(package_base, version, process, limit, offset)
|
||||||
|
|
||||||
head = self.request.query.get("head", "false")
|
|
||||||
# pylint: disable=protected-access
|
|
||||||
if self.configuration._convert_to_boolean(head): # type: ignore[attr-defined]
|
|
||||||
# logs should be sorted already
|
|
||||||
logs = [
|
|
||||||
replace(next(log_records), message="") # remove messages
|
|
||||||
for _, log_records in itertools.groupby(logs, lambda log_record: log_record.log_record_id)
|
|
||||||
]
|
|
||||||
|
|
||||||
response = [log_record.view() for log_record in logs]
|
response = [log_record.view() for log_record in logs]
|
||||||
return json_response(response)
|
return json_response(response)
|
||||||
|
@ -166,16 +166,11 @@ def setup_server(configuration: Configuration, spawner: Spawn, repositories: lis
|
|||||||
# package cache
|
# package cache
|
||||||
if not repositories:
|
if not repositories:
|
||||||
raise InitializeError("No repositories configured, exiting")
|
raise InitializeError("No repositories configured, exiting")
|
||||||
|
database = SQLite.load(configuration)
|
||||||
watchers: dict[RepositoryId, Watcher] = {}
|
watchers: dict[RepositoryId, Watcher] = {}
|
||||||
configuration_path, _ = configuration.check_loaded()
|
|
||||||
for repository_id in repositories:
|
for repository_id in repositories:
|
||||||
application.logger.info("load repository %s", repository_id)
|
application.logger.info("load repository %s", repository_id)
|
||||||
# load settings explicitly for architecture if any
|
client = Client.load(repository_id, configuration, database, report=False) # explicitly load local client
|
||||||
repository_configuration = Configuration.from_path(configuration_path, repository_id)
|
|
||||||
# load database instance, because it holds identifier
|
|
||||||
database = SQLite.load(repository_configuration)
|
|
||||||
# explicitly load local client
|
|
||||||
client = Client.load(repository_id, repository_configuration, database, report=False)
|
|
||||||
watchers[repository_id] = Watcher(client)
|
watchers[repository_id] = Watcher(client)
|
||||||
application[WatcherKey] = watchers
|
application[WatcherKey] = watchers
|
||||||
# workers cache
|
# workers cache
|
||||||
@ -184,7 +179,6 @@ def setup_server(configuration: Configuration, spawner: Spawn, repositories: lis
|
|||||||
application[SpawnKey] = spawner
|
application[SpawnKey] = spawner
|
||||||
|
|
||||||
application.logger.info("setup authorization")
|
application.logger.info("setup authorization")
|
||||||
database = SQLite.load(configuration)
|
|
||||||
validator = application[AuthKey] = Auth.load(configuration, database)
|
validator = application[AuthKey] = Auth.load(configuration, database)
|
||||||
if validator.enabled:
|
if validator.enabled:
|
||||||
from ahriman.web.middlewares.auth_handler import setup_auth
|
from ahriman.web.middlewares.auth_handler import setup_auth
|
||||||
|
@ -141,7 +141,7 @@ def test_add_remote_missing(application_packages: ApplicationPackages, mocker: M
|
|||||||
"""
|
"""
|
||||||
must raise UnknownPackageError if remote package wasn't found
|
must raise UnknownPackageError if remote package wasn't found
|
||||||
"""
|
"""
|
||||||
mocker.patch("requests.get", side_effect=Exception)
|
mocker.patch("requests.get", side_effect=Exception())
|
||||||
with pytest.raises(UnknownPackageError):
|
with pytest.raises(UnknownPackageError):
|
||||||
application_packages._add_remote("url")
|
application_packages._add_remote("url")
|
||||||
|
|
||||||
|
@ -135,7 +135,7 @@ def test_unknown_no_aur(application_repository: ApplicationRepository, package_a
|
|||||||
must return empty list in case if there is locally stored PKGBUILD
|
must return empty list in case if there is locally stored PKGBUILD
|
||||||
"""
|
"""
|
||||||
mocker.patch("ahriman.core.repository.repository.Repository.packages", return_value=[package_ahriman])
|
mocker.patch("ahriman.core.repository.repository.Repository.packages", return_value=[package_ahriman])
|
||||||
mocker.patch("ahriman.models.package.Package.from_aur", side_effect=Exception)
|
mocker.patch("ahriman.models.package.Package.from_aur", side_effect=Exception())
|
||||||
mocker.patch("ahriman.models.package.Package.from_build", return_value=package_ahriman)
|
mocker.patch("ahriman.models.package.Package.from_build", return_value=package_ahriman)
|
||||||
mocker.patch("pathlib.Path.is_dir", return_value=True)
|
mocker.patch("pathlib.Path.is_dir", return_value=True)
|
||||||
mocker.patch("ahriman.core.build_tools.sources.Sources.has_remotes", return_value=False)
|
mocker.patch("ahriman.core.build_tools.sources.Sources.has_remotes", return_value=False)
|
||||||
@ -149,7 +149,7 @@ def test_unknown_no_aur_no_local(application_repository: ApplicationRepository,
|
|||||||
must return list of packages missing in aur and in local storage
|
must return list of packages missing in aur and in local storage
|
||||||
"""
|
"""
|
||||||
mocker.patch("ahriman.core.repository.repository.Repository.packages", return_value=[package_ahriman])
|
mocker.patch("ahriman.core.repository.repository.Repository.packages", return_value=[package_ahriman])
|
||||||
mocker.patch("ahriman.models.package.Package.from_aur", side_effect=Exception)
|
mocker.patch("ahriman.models.package.Package.from_aur", side_effect=Exception())
|
||||||
mocker.patch("pathlib.Path.is_dir", return_value=False)
|
mocker.patch("pathlib.Path.is_dir", return_value=False)
|
||||||
|
|
||||||
packages = application_repository.unknown()
|
packages = application_repository.unknown()
|
||||||
|
@ -46,7 +46,7 @@ def test_call_exception(args: argparse.Namespace, configuration: Configuration,
|
|||||||
"""
|
"""
|
||||||
args.configuration = Path("")
|
args.configuration = Path("")
|
||||||
args.quiet = False
|
args.quiet = False
|
||||||
mocker.patch("ahriman.core.configuration.Configuration.from_path", side_effect=Exception)
|
mocker.patch("ahriman.core.configuration.Configuration.from_path", side_effect=Exception())
|
||||||
logging_mock = mocker.patch("logging.Logger.exception")
|
logging_mock = mocker.patch("logging.Logger.exception")
|
||||||
|
|
||||||
_, repository_id = configuration.check_loaded()
|
_, repository_id = configuration.check_loaded()
|
||||||
@ -60,7 +60,7 @@ def test_call_exit_code(args: argparse.Namespace, configuration: Configuration,
|
|||||||
"""
|
"""
|
||||||
args.configuration = Path("")
|
args.configuration = Path("")
|
||||||
args.quiet = False
|
args.quiet = False
|
||||||
mocker.patch("ahriman.core.configuration.Configuration.from_path", side_effect=ExitCode)
|
mocker.patch("ahriman.core.configuration.Configuration.from_path", side_effect=ExitCode())
|
||||||
logging_mock = mocker.patch("logging.Logger.exception")
|
logging_mock = mocker.patch("logging.Logger.exception")
|
||||||
|
|
||||||
_, repository_id = configuration.check_loaded()
|
_, repository_id = configuration.check_loaded()
|
||||||
|
@ -1,27 +0,0 @@
|
|||||||
import argparse
|
|
||||||
|
|
||||||
from pytest_mock import MockerFixture
|
|
||||||
|
|
||||||
from ahriman.application.handlers.reload import Reload
|
|
||||||
from ahriman.core.configuration import Configuration
|
|
||||||
from ahriman.core.repository import Repository
|
|
||||||
|
|
||||||
|
|
||||||
def test_run(args: argparse.Namespace, configuration: Configuration, repository: Repository,
|
|
||||||
mocker: MockerFixture) -> None:
|
|
||||||
"""
|
|
||||||
must run command
|
|
||||||
"""
|
|
||||||
mocker.patch("ahriman.core.repository.Repository.load", return_value=repository)
|
|
||||||
run_mock = mocker.patch("ahriman.core.status.local_client.LocalClient.configuration_reload")
|
|
||||||
|
|
||||||
_, repository_id = configuration.check_loaded()
|
|
||||||
Reload.run(args, repository_id, configuration, report=False)
|
|
||||||
run_mock.assert_called_once_with()
|
|
||||||
|
|
||||||
|
|
||||||
def test_disallow_multi_architecture_run() -> None:
|
|
||||||
"""
|
|
||||||
must not allow multi architecture run
|
|
||||||
"""
|
|
||||||
assert not Reload.ALLOW_MULTI_ARCHITECTURE_RUN
|
|
@ -58,11 +58,9 @@ def test_run(args: argparse.Namespace, configuration: Configuration, repository:
|
|||||||
sudo_configuration_mock = mocker.patch("ahriman.application.handlers.setup.Setup.configuration_create_sudo")
|
sudo_configuration_mock = mocker.patch("ahriman.application.handlers.setup.Setup.configuration_create_sudo")
|
||||||
executable_mock = mocker.patch("ahriman.application.handlers.setup.Setup.executable_create")
|
executable_mock = mocker.patch("ahriman.application.handlers.setup.Setup.executable_create")
|
||||||
init_mock = mocker.patch("ahriman.core.alpm.repo.Repo.init")
|
init_mock = mocker.patch("ahriman.core.alpm.repo.Repo.init")
|
||||||
owner_guard_mock = mocker.patch("ahriman.models.repository_paths.RepositoryPaths.preserve_owner")
|
|
||||||
|
|
||||||
_, repository_id = configuration.check_loaded()
|
_, repository_id = configuration.check_loaded()
|
||||||
Setup.run(args, repository_id, configuration, report=False)
|
Setup.run(args, repository_id, configuration, report=False)
|
||||||
owner_guard_mock.assert_called_once_with()
|
|
||||||
ahriman_configuration_mock.assert_called_once_with(args, repository_id, configuration)
|
ahriman_configuration_mock.assert_called_once_with(args, repository_id, configuration)
|
||||||
devtools_configuration_mock.assert_called_once_with(
|
devtools_configuration_mock.assert_called_once_with(
|
||||||
repository_id, args.from_configuration, args.mirror, args.multilib, f"file://{repository_paths.repository}")
|
repository_id, args.from_configuration, args.mirror, args.multilib, f"file://{repository_paths.repository}")
|
||||||
@ -270,11 +268,13 @@ def test_executable_create(configuration: Configuration, repository_paths: Repos
|
|||||||
"""
|
"""
|
||||||
must create executable
|
must create executable
|
||||||
"""
|
"""
|
||||||
|
chown_mock = mocker.patch("ahriman.models.repository_paths.RepositoryPaths.chown")
|
||||||
symlink_mock = mocker.patch("pathlib.Path.symlink_to")
|
symlink_mock = mocker.patch("pathlib.Path.symlink_to")
|
||||||
unlink_mock = mocker.patch("pathlib.Path.unlink")
|
unlink_mock = mocker.patch("pathlib.Path.unlink")
|
||||||
|
|
||||||
_, repository_id = configuration.check_loaded()
|
_, repository_id = configuration.check_loaded()
|
||||||
Setup.executable_create(repository_paths, repository_id)
|
Setup.executable_create(repository_paths, repository_id)
|
||||||
|
chown_mock.assert_called_once_with(Setup.build_command(repository_paths.root, repository_id))
|
||||||
symlink_mock.assert_called_once_with(Setup.ARCHBUILD_COMMAND_PATH)
|
symlink_mock.assert_called_once_with(Setup.ARCHBUILD_COMMAND_PATH)
|
||||||
unlink_mock.assert_called_once_with(missing_ok=True)
|
unlink_mock.assert_called_once_with(missing_ok=True)
|
||||||
|
|
||||||
|
@ -2,7 +2,6 @@ import argparse
|
|||||||
import json
|
import json
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from pathlib import Path
|
|
||||||
from pytest_mock import MockerFixture
|
from pytest_mock import MockerFixture
|
||||||
|
|
||||||
from ahriman.application.handlers.validate import Validate
|
from ahriman.application.handlers.validate import Validate
|
||||||
@ -54,50 +53,12 @@ def test_run_skip(args: argparse.Namespace, configuration: Configuration, mocker
|
|||||||
print_mock.assert_not_called()
|
print_mock.assert_not_called()
|
||||||
|
|
||||||
|
|
||||||
def test_run_default(args: argparse.Namespace, configuration: Configuration) -> None:
|
|
||||||
"""
|
|
||||||
must run on default configuration without errors
|
|
||||||
"""
|
|
||||||
args.exit_code = True
|
|
||||||
_, repository_id = configuration.check_loaded()
|
|
||||||
|
|
||||||
default = Configuration.from_path(Configuration.SYSTEM_CONFIGURATION_PATH, repository_id)
|
|
||||||
# copy autogenerated values
|
|
||||||
for section, key in (("build", "build_command"), ("repository", "root")):
|
|
||||||
value = configuration.get(section, key)
|
|
||||||
default.set_option(section, key, value)
|
|
||||||
|
|
||||||
Validate.run(args, repository_id, default, report=False)
|
|
||||||
|
|
||||||
|
|
||||||
def test_run_repo_specific_triggers(args: argparse.Namespace, configuration: Configuration,
|
|
||||||
resource_path_root: Path) -> None:
|
|
||||||
"""
|
|
||||||
must correctly insert repo specific triggers
|
|
||||||
"""
|
|
||||||
args.exit_code = True
|
|
||||||
_, repository_id = configuration.check_loaded()
|
|
||||||
|
|
||||||
# remove unused sections
|
|
||||||
for section in ("customs3", "github:x86_64", "logs-rotation", "mirrorlist"):
|
|
||||||
configuration.remove_section(section)
|
|
||||||
|
|
||||||
configuration.set_option("report", "target", "test")
|
|
||||||
for section in ("test", "test:i686", "test:another-repo:x86_64"):
|
|
||||||
configuration.set_option(section, "type", "html")
|
|
||||||
configuration.set_option(section, "link_path", "http://link_path")
|
|
||||||
configuration.set_option(section, "path", "path")
|
|
||||||
configuration.set_option(section, "template", "template")
|
|
||||||
configuration.set_option(section, "templates", str(resource_path_root))
|
|
||||||
|
|
||||||
Validate.run(args, repository_id, configuration, report=False)
|
|
||||||
|
|
||||||
|
|
||||||
def test_schema(configuration: Configuration) -> None:
|
def test_schema(configuration: Configuration) -> None:
|
||||||
"""
|
"""
|
||||||
must generate full schema correctly
|
must generate full schema correctly
|
||||||
"""
|
"""
|
||||||
schema = Validate.schema(configuration)
|
_, repository_id = configuration.check_loaded()
|
||||||
|
schema = Validate.schema(repository_id, configuration)
|
||||||
|
|
||||||
# defaults
|
# defaults
|
||||||
assert schema.pop("console")
|
assert schema.pop("console")
|
||||||
@ -130,7 +91,9 @@ def test_schema_invalid_trigger(configuration: Configuration) -> None:
|
|||||||
"""
|
"""
|
||||||
configuration.set_option("build", "triggers", "some.invalid.trigger.path.Trigger")
|
configuration.set_option("build", "triggers", "some.invalid.trigger.path.Trigger")
|
||||||
configuration.remove_option("build", "triggers_known")
|
configuration.remove_option("build", "triggers_known")
|
||||||
assert Validate.schema(configuration) == CONFIGURATION_SCHEMA
|
_, repository_id = configuration.check_loaded()
|
||||||
|
|
||||||
|
assert Validate.schema(repository_id, configuration) == CONFIGURATION_SCHEMA
|
||||||
|
|
||||||
|
|
||||||
def test_schema_erase_required() -> None:
|
def test_schema_erase_required() -> None:
|
||||||
|
@ -1563,35 +1563,6 @@ def test_subparsers_web_option_repository(parser: argparse.ArgumentParser) -> No
|
|||||||
assert args.repository == ""
|
assert args.repository == ""
|
||||||
|
|
||||||
|
|
||||||
def test_subparsers_web_reload(parser: argparse.ArgumentParser) -> None:
|
|
||||||
"""
|
|
||||||
web-reload command must imply architecture, lock, quiet, report, repository and unsafe
|
|
||||||
"""
|
|
||||||
args = parser.parse_args(["web-reload"])
|
|
||||||
assert args.architecture == ""
|
|
||||||
assert args.lock is None
|
|
||||||
assert args.quiet
|
|
||||||
assert not args.report
|
|
||||||
assert args.repository == ""
|
|
||||||
assert args.unsafe
|
|
||||||
|
|
||||||
|
|
||||||
def test_subparsers_web_reload_option_architecture(parser: argparse.ArgumentParser) -> None:
|
|
||||||
"""
|
|
||||||
web-reload command must correctly parse architecture list
|
|
||||||
"""
|
|
||||||
args = parser.parse_args(["-a", "x86_64", "web-reload"])
|
|
||||||
assert args.architecture == ""
|
|
||||||
|
|
||||||
|
|
||||||
def test_subparsers_web_reload_option_repository(parser: argparse.ArgumentParser) -> None:
|
|
||||||
"""
|
|
||||||
web-reload command must correctly parse repository list
|
|
||||||
"""
|
|
||||||
args = parser.parse_args(["-r", "repo", "web-reload"])
|
|
||||||
assert args.repository == ""
|
|
||||||
|
|
||||||
|
|
||||||
def test_run(args: argparse.Namespace, configuration: Configuration, mocker: MockerFixture) -> None:
|
def test_run(args: argparse.Namespace, configuration: Configuration, mocker: MockerFixture) -> None:
|
||||||
"""
|
"""
|
||||||
application must be run
|
application must be run
|
||||||
|
@ -230,7 +230,7 @@ def test_clear_close_exception(lock: Lock) -> None:
|
|||||||
must suppress IO exception on file closure
|
must suppress IO exception on file closure
|
||||||
"""
|
"""
|
||||||
close_mock = lock._pid_file = MagicMock()
|
close_mock = lock._pid_file = MagicMock()
|
||||||
close_mock.close.side_effect = IOError
|
close_mock.close.side_effect = IOError()
|
||||||
lock.clear()
|
lock.clear()
|
||||||
|
|
||||||
|
|
||||||
|
@ -108,7 +108,7 @@ def test_aur_request_failed(aur: AUR, mocker: MockerFixture) -> None:
|
|||||||
"""
|
"""
|
||||||
must reraise generic exception
|
must reraise generic exception
|
||||||
"""
|
"""
|
||||||
mocker.patch("requests.Session.request", side_effect=Exception)
|
mocker.patch("requests.Session.request", side_effect=Exception())
|
||||||
with pytest.raises(Exception):
|
with pytest.raises(Exception):
|
||||||
aur.aur_request("info", "ahriman")
|
aur.aur_request("info", "ahriman")
|
||||||
|
|
||||||
@ -116,7 +116,7 @@ def test_aur_request_failed(aur: AUR, mocker: MockerFixture) -> None:
|
|||||||
def test_aur_request_failed_http_error(aur: AUR, mocker: MockerFixture) -> None:
|
def test_aur_request_failed_http_error(aur: AUR, mocker: MockerFixture) -> None:
|
||||||
""" must reraise http exception
|
""" must reraise http exception
|
||||||
"""
|
"""
|
||||||
mocker.patch("requests.Session.request", side_effect=requests.HTTPError)
|
mocker.patch("requests.Session.request", side_effect=requests.HTTPError())
|
||||||
with pytest.raises(requests.HTTPError):
|
with pytest.raises(requests.HTTPError):
|
||||||
aur.aur_request("info", "ahriman")
|
aur.aur_request("info", "ahriman")
|
||||||
|
|
||||||
|
@ -80,7 +80,7 @@ def test_arch_request_failed(official: Official, mocker: MockerFixture) -> None:
|
|||||||
"""
|
"""
|
||||||
must reraise generic exception
|
must reraise generic exception
|
||||||
"""
|
"""
|
||||||
mocker.patch("requests.Session.request", side_effect=Exception)
|
mocker.patch("requests.Session.request", side_effect=Exception())
|
||||||
with pytest.raises(Exception):
|
with pytest.raises(Exception):
|
||||||
official.arch_request("akonadi", by="q")
|
official.arch_request("akonadi", by="q")
|
||||||
|
|
||||||
@ -89,7 +89,7 @@ def test_arch_request_failed_http_error(official: Official, mocker: MockerFixtur
|
|||||||
"""
|
"""
|
||||||
must reraise http exception
|
must reraise http exception
|
||||||
"""
|
"""
|
||||||
mocker.patch("requests.Session.request", side_effect=requests.HTTPError)
|
mocker.patch("requests.Session.request", side_effect=requests.HTTPError())
|
||||||
with pytest.raises(requests.HTTPError):
|
with pytest.raises(requests.HTTPError):
|
||||||
official.arch_request("akonadi", by="q")
|
official.arch_request("akonadi", by="q")
|
||||||
|
|
||||||
|
@ -62,12 +62,12 @@ def test_database_copy(pacman: Pacman, mocker: MockerFixture) -> None:
|
|||||||
mocker.patch("pathlib.Path.is_file", autospec=True, side_effect=lambda p: p.is_relative_to(path))
|
mocker.patch("pathlib.Path.is_file", autospec=True, side_effect=lambda p: p.is_relative_to(path))
|
||||||
mkdir_mock = mocker.patch("pathlib.Path.mkdir")
|
mkdir_mock = mocker.patch("pathlib.Path.mkdir")
|
||||||
copy_mock = mocker.patch("shutil.copy")
|
copy_mock = mocker.patch("shutil.copy")
|
||||||
owner_guard_mock = mocker.patch("ahriman.models.repository_paths.RepositoryPaths.preserve_owner")
|
chown_mock = mocker.patch("ahriman.models.repository_paths.RepositoryPaths.chown")
|
||||||
|
|
||||||
pacman.database_copy(pacman.handle, database, path, use_ahriman_cache=True)
|
pacman.database_copy(pacman.handle, database, path, use_ahriman_cache=True)
|
||||||
mkdir_mock.assert_called_once_with(mode=0o755, exist_ok=True)
|
mkdir_mock.assert_called_once_with(mode=0o755, exist_ok=True)
|
||||||
copy_mock.assert_called_once_with(path / "sync" / "core.db", dst_path)
|
copy_mock.assert_called_once_with(path / "sync" / "core.db", dst_path)
|
||||||
owner_guard_mock.assert_called_once_with(dst_path.parent)
|
chown_mock.assert_called_once_with(dst_path)
|
||||||
|
|
||||||
|
|
||||||
def test_database_copy_skip(pacman: Pacman, mocker: MockerFixture) -> None:
|
def test_database_copy_skip(pacman: Pacman, mocker: MockerFixture) -> None:
|
||||||
@ -242,7 +242,7 @@ def test_files_no_entry(pacman: Pacman, pyalpm_package_ahriman: pyalpm.Package,
|
|||||||
pacman.handle = handle_mock
|
pacman.handle = handle_mock
|
||||||
|
|
||||||
tar_mock = MagicMock()
|
tar_mock = MagicMock()
|
||||||
tar_mock.extractfile.side_effect = KeyError
|
tar_mock.extractfile.side_effect = KeyError()
|
||||||
|
|
||||||
open_mock = MagicMock()
|
open_mock = MagicMock()
|
||||||
open_mock.__enter__.return_value = tar_mock
|
open_mock.__enter__.return_value = tar_mock
|
||||||
@ -289,4 +289,3 @@ def test_package_provided_by(pacman: Pacman) -> None:
|
|||||||
must search through the provides lists
|
must search through the provides lists
|
||||||
"""
|
"""
|
||||||
assert list(pacman.provided_by("sh"))
|
assert list(pacman.provided_by("sh"))
|
||||||
assert list(pacman.provided_by("libacl.so")) # case with exact version
|
|
||||||
|
@ -131,7 +131,7 @@ def test_sync_exception(pacman_database: PacmanDatabase, mocker: MockerFixture)
|
|||||||
"""
|
"""
|
||||||
must suppress all exceptions on failure
|
must suppress all exceptions on failure
|
||||||
"""
|
"""
|
||||||
mocker.patch("ahriman.core.alpm.pacman_database.PacmanDatabase.sync_packages", side_effect=Exception)
|
mocker.patch("ahriman.core.alpm.pacman_database.PacmanDatabase.sync_packages", side_effect=Exception())
|
||||||
pacman_database.sync(force=True)
|
pacman_database.sync(force=True)
|
||||||
|
|
||||||
|
|
||||||
|
@ -52,7 +52,7 @@ def test_repo_remove_fail_no_file(repo: Repo, mocker: MockerFixture) -> None:
|
|||||||
must fail on missing file
|
must fail on missing file
|
||||||
"""
|
"""
|
||||||
mocker.patch("pathlib.Path.glob", return_value=[Path("package.pkg.tar.xz")])
|
mocker.patch("pathlib.Path.glob", return_value=[Path("package.pkg.tar.xz")])
|
||||||
mocker.patch("pathlib.Path.unlink", side_effect=FileNotFoundError)
|
mocker.patch("pathlib.Path.unlink", side_effect=FileNotFoundError())
|
||||||
|
|
||||||
with pytest.raises(FileNotFoundError):
|
with pytest.raises(FileNotFoundError):
|
||||||
repo.remove("package", Path("package.pkg.tar.xz"))
|
repo.remove("package", Path("package.pkg.tar.xz"))
|
||||||
|
@ -90,7 +90,7 @@ async def test_get_oauth_username_exception_1(oauth: OAuth, mocker: MockerFixtur
|
|||||||
"""
|
"""
|
||||||
must return None in case of OAuth request error (get_access_token)
|
must return None in case of OAuth request error (get_access_token)
|
||||||
"""
|
"""
|
||||||
mocker.patch("aioauth_client.GoogleClient.get_access_token", side_effect=Exception)
|
mocker.patch("aioauth_client.GoogleClient.get_access_token", side_effect=Exception())
|
||||||
user_info_mock = mocker.patch("aioauth_client.GoogleClient.user_info")
|
user_info_mock = mocker.patch("aioauth_client.GoogleClient.user_info")
|
||||||
|
|
||||||
email = await oauth.get_oauth_username("code")
|
email = await oauth.get_oauth_username("code")
|
||||||
@ -103,7 +103,7 @@ async def test_get_oauth_username_exception_2(oauth: OAuth, mocker: MockerFixtur
|
|||||||
must return None in case of OAuth request error (user_info)
|
must return None in case of OAuth request error (user_info)
|
||||||
"""
|
"""
|
||||||
mocker.patch("aioauth_client.GoogleClient.get_access_token", return_value=("token", ""))
|
mocker.patch("aioauth_client.GoogleClient.get_access_token", return_value=("token", ""))
|
||||||
mocker.patch("aioauth_client.GoogleClient.user_info", side_effect=Exception)
|
mocker.patch("aioauth_client.GoogleClient.user_info", side_effect=Exception())
|
||||||
|
|
||||||
email = await oauth.get_oauth_username("code")
|
email = await oauth.get_oauth_username("code")
|
||||||
assert email is None
|
assert email is None
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
import configparser
|
import configparser
|
||||||
import pytest
|
|
||||||
import os
|
|
||||||
|
|
||||||
from io import StringIO
|
from io import StringIO
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from pytest_mock import MockerFixture
|
from pytest_mock import MockerFixture
|
||||||
from unittest.mock import call as MockCall
|
from unittest.mock import call as MockCall
|
||||||
@ -20,40 +20,6 @@ def test_architecture(configuration: Configuration) -> None:
|
|||||||
assert configuration.architecture == "x86_64"
|
assert configuration.architecture == "x86_64"
|
||||||
|
|
||||||
|
|
||||||
def test_repository_id(configuration: Configuration, repository_id: RepositoryId) -> None:
|
|
||||||
"""
|
|
||||||
must return repository identifier
|
|
||||||
"""
|
|
||||||
assert configuration.repository_id == repository_id
|
|
||||||
assert configuration.get("repository", "name") == repository_id.name
|
|
||||||
assert configuration.get("repository", "architecture") == repository_id.architecture
|
|
||||||
|
|
||||||
|
|
||||||
def test_repository_id_erase(configuration: Configuration) -> None:
|
|
||||||
"""
|
|
||||||
must remove repository identifier properties if empty identifier supplied
|
|
||||||
"""
|
|
||||||
configuration.repository_id = None
|
|
||||||
assert configuration.get("repository", "name", fallback=None) is None
|
|
||||||
assert configuration.get("repository", "architecture", fallback=None) is None
|
|
||||||
|
|
||||||
configuration.repository_id = RepositoryId("", "")
|
|
||||||
assert configuration.get("repository", "name", fallback=None) is None
|
|
||||||
assert configuration.get("repository", "architecture", fallback=None) is None
|
|
||||||
|
|
||||||
|
|
||||||
def test_repository_id_update(configuration: Configuration, repository_id: RepositoryId) -> None:
|
|
||||||
"""
|
|
||||||
must update repository identifier and related configuration options
|
|
||||||
"""
|
|
||||||
repository_id = RepositoryId("i686", repository_id.name)
|
|
||||||
|
|
||||||
configuration.repository_id = repository_id
|
|
||||||
assert configuration.repository_id == repository_id
|
|
||||||
assert configuration.get("repository", "name") == repository_id.name
|
|
||||||
assert configuration.get("repository", "architecture") == repository_id.architecture
|
|
||||||
|
|
||||||
|
|
||||||
def test_repository_name(configuration: Configuration) -> None:
|
def test_repository_name(configuration: Configuration) -> None:
|
||||||
"""
|
"""
|
||||||
must return valid repository name
|
must return valid repository name
|
||||||
@ -76,16 +42,12 @@ def test_from_path(repository_id: RepositoryId, mocker: MockerFixture) -> None:
|
|||||||
mocker.patch("ahriman.core.configuration.Configuration.get", return_value="ahriman.ini.d")
|
mocker.patch("ahriman.core.configuration.Configuration.get", return_value="ahriman.ini.d")
|
||||||
read_mock = mocker.patch("ahriman.core.configuration.Configuration.read")
|
read_mock = mocker.patch("ahriman.core.configuration.Configuration.read")
|
||||||
load_includes_mock = mocker.patch("ahriman.core.configuration.Configuration.load_includes")
|
load_includes_mock = mocker.patch("ahriman.core.configuration.Configuration.load_includes")
|
||||||
merge_mock = mocker.patch("ahriman.core.configuration.Configuration.merge_sections")
|
|
||||||
environment_mock = mocker.patch("ahriman.core.configuration.Configuration.load_environment")
|
|
||||||
path = Path("path")
|
path = Path("path")
|
||||||
|
|
||||||
configuration = Configuration.from_path(path, repository_id)
|
configuration = Configuration.from_path(path, repository_id)
|
||||||
assert configuration.path == path
|
assert configuration.path == path
|
||||||
read_mock.assert_called_once_with(path)
|
read_mock.assert_called_once_with(path)
|
||||||
load_includes_mock.assert_called_once_with()
|
load_includes_mock.assert_called_once_with()
|
||||||
merge_mock.assert_called_once_with(repository_id)
|
|
||||||
environment_mock.assert_called_once_with()
|
|
||||||
|
|
||||||
|
|
||||||
def test_from_path_file_missing(repository_id: RepositoryId, mocker: MockerFixture) -> None:
|
def test_from_path_file_missing(repository_id: RepositoryId, mocker: MockerFixture) -> None:
|
||||||
@ -362,18 +324,6 @@ def test_gettype_from_section_no_section(configuration: Configuration) -> None:
|
|||||||
configuration.gettype("rsync:x86_64", configuration.repository_id)
|
configuration.gettype("rsync:x86_64", configuration.repository_id)
|
||||||
|
|
||||||
|
|
||||||
def test_load_environment(configuration: Configuration) -> None:
|
|
||||||
"""
|
|
||||||
must load environment variables
|
|
||||||
"""
|
|
||||||
os.environ["section:key"] = "value1"
|
|
||||||
os.environ["section:identifier:key"] = "value2"
|
|
||||||
|
|
||||||
configuration.load_environment()
|
|
||||||
assert configuration.get("section", "key") == "value1"
|
|
||||||
assert configuration.get("section:identifier", "key") == "value2"
|
|
||||||
|
|
||||||
|
|
||||||
def test_load_includes(mocker: MockerFixture) -> None:
|
def test_load_includes(mocker: MockerFixture) -> None:
|
||||||
"""
|
"""
|
||||||
must load includes
|
must load includes
|
||||||
@ -494,12 +444,10 @@ def test_reload(configuration: Configuration, mocker: MockerFixture) -> None:
|
|||||||
"""
|
"""
|
||||||
load_mock = mocker.patch("ahriman.core.configuration.Configuration.load")
|
load_mock = mocker.patch("ahriman.core.configuration.Configuration.load")
|
||||||
merge_mock = mocker.patch("ahriman.core.configuration.Configuration.merge_sections")
|
merge_mock = mocker.patch("ahriman.core.configuration.Configuration.merge_sections")
|
||||||
environment_mock = mocker.patch("ahriman.core.configuration.Configuration.load_environment")
|
|
||||||
|
|
||||||
configuration.reload()
|
configuration.reload()
|
||||||
load_mock.assert_called_once_with(configuration.path)
|
load_mock.assert_called_once_with(configuration.path)
|
||||||
merge_mock.assert_called_once_with(configuration.repository_id)
|
merge_mock.assert_called_once_with(configuration.repository_id)
|
||||||
environment_mock.assert_called_once_with()
|
|
||||||
|
|
||||||
|
|
||||||
def test_reload_clear(configuration: Configuration, mocker: MockerFixture) -> None:
|
def test_reload_clear(configuration: Configuration, mocker: MockerFixture) -> None:
|
||||||
|
@ -42,7 +42,7 @@ def test_apply_migration_exception(migrations: Migrations, mocker: MockerFixture
|
|||||||
must roll back and close cursor on exception during migration
|
must roll back and close cursor on exception during migration
|
||||||
"""
|
"""
|
||||||
cursor = MagicMock()
|
cursor = MagicMock()
|
||||||
mocker.patch("logging.Logger.info", side_effect=Exception)
|
mocker.patch("logging.Logger.info", side_effect=Exception())
|
||||||
migrations.connection.cursor.return_value = cursor
|
migrations.connection.cursor.return_value = cursor
|
||||||
|
|
||||||
with pytest.raises(Exception):
|
with pytest.raises(Exception):
|
||||||
@ -59,7 +59,7 @@ def test_apply_migration_sql_exception(migrations: Migrations) -> None:
|
|||||||
must close cursor on general migration error
|
must close cursor on general migration error
|
||||||
"""
|
"""
|
||||||
cursor = MagicMock()
|
cursor = MagicMock()
|
||||||
cursor.execute.side_effect = Exception
|
cursor.execute.side_effect = Exception()
|
||||||
migrations.connection.cursor.return_value = cursor
|
migrations.connection.cursor.return_value = cursor
|
||||||
|
|
||||||
with pytest.raises(Exception):
|
with pytest.raises(Exception):
|
||||||
|
@ -36,17 +36,6 @@ def test_init_skip_migration(database: SQLite, mocker: MockerFixture) -> None:
|
|||||||
migrate_schema_mock.assert_not_called()
|
migrate_schema_mock.assert_not_called()
|
||||||
|
|
||||||
|
|
||||||
def test_init_skip_empty_repository(database: SQLite, mocker: MockerFixture) -> None:
|
|
||||||
"""
|
|
||||||
must skip migrations if repository identifier is not set
|
|
||||||
"""
|
|
||||||
database._repository_id = RepositoryId("", "")
|
|
||||||
migrate_schema_mock = mocker.patch("ahriman.core.database.migrations.Migrations.migrate")
|
|
||||||
|
|
||||||
database.init()
|
|
||||||
migrate_schema_mock.assert_not_called()
|
|
||||||
|
|
||||||
|
|
||||||
def test_package_clear(database: SQLite, repository_id: RepositoryId, mocker: MockerFixture) -> None:
|
def test_package_clear(database: SQLite, repository_id: RepositoryId, mocker: MockerFixture) -> None:
|
||||||
"""
|
"""
|
||||||
must clear package data
|
must clear package data
|
||||||
|
@ -37,7 +37,7 @@ def test_register_failed(distributed_system: DistributedSystem, mocker: MockerFi
|
|||||||
"""
|
"""
|
||||||
must suppress any exception happened during worker registration
|
must suppress any exception happened during worker registration
|
||||||
"""
|
"""
|
||||||
mocker.patch("requests.Session.request", side_effect=Exception)
|
mocker.patch("requests.Session.request", side_effect=Exception())
|
||||||
distributed_system.register()
|
distributed_system.register()
|
||||||
|
|
||||||
|
|
||||||
@ -45,7 +45,7 @@ def test_register_failed_http_error(distributed_system: DistributedSystem, mocke
|
|||||||
"""
|
"""
|
||||||
must suppress HTTP exception happened during worker registration
|
must suppress HTTP exception happened during worker registration
|
||||||
"""
|
"""
|
||||||
mocker.patch("requests.Session.request", side_effect=requests.HTTPError)
|
mocker.patch("requests.Session.request", side_effect=requests.HTTPError())
|
||||||
distributed_system.register()
|
distributed_system.register()
|
||||||
|
|
||||||
|
|
||||||
@ -70,7 +70,7 @@ def test_workers_failed(distributed_system: DistributedSystem, mocker: MockerFix
|
|||||||
"""
|
"""
|
||||||
must suppress any exception happened during worker extraction
|
must suppress any exception happened during worker extraction
|
||||||
"""
|
"""
|
||||||
mocker.patch("requests.Session.request", side_effect=Exception)
|
mocker.patch("requests.Session.request", side_effect=Exception())
|
||||||
distributed_system.workers()
|
distributed_system.workers()
|
||||||
|
|
||||||
|
|
||||||
@ -78,5 +78,5 @@ def test_workers_failed_http_error(distributed_system: DistributedSystem, mocker
|
|||||||
"""
|
"""
|
||||||
must suppress HTTP exception happened during worker extraction
|
must suppress HTTP exception happened during worker extraction
|
||||||
"""
|
"""
|
||||||
mocker.patch("requests.Session.request", side_effect=requests.HTTPError)
|
mocker.patch("requests.Session.request", side_effect=requests.HTTPError())
|
||||||
distributed_system.workers()
|
distributed_system.workers()
|
||||||
|
@ -85,7 +85,7 @@ def test_run_failed(configuration: Configuration, mocker: MockerFixture) -> None
|
|||||||
"""
|
"""
|
||||||
must reraise exception on error occurred
|
must reraise exception on error occurred
|
||||||
"""
|
"""
|
||||||
mocker.patch("ahriman.core.gitremote.remote_pull.RemotePull.repo_clone", side_effect=Exception)
|
mocker.patch("ahriman.core.gitremote.remote_pull.RemotePull.repo_clone", side_effect=Exception())
|
||||||
_, repository_id = configuration.check_loaded()
|
_, repository_id = configuration.check_loaded()
|
||||||
runner = RemotePull(repository_id, configuration, "gitremote")
|
runner = RemotePull(repository_id, configuration, "gitremote")
|
||||||
|
|
||||||
|
@ -82,7 +82,7 @@ def test_run_failed(local_client: Client, configuration: Configuration, result:
|
|||||||
"""
|
"""
|
||||||
must reraise exception on error occurred
|
must reraise exception on error occurred
|
||||||
"""
|
"""
|
||||||
mocker.patch("ahriman.core.build_tools.sources.Sources.fetch", side_effect=Exception)
|
mocker.patch("ahriman.core.build_tools.sources.Sources.fetch", side_effect=Exception())
|
||||||
runner = RemotePush(local_client, configuration, "gitremote")
|
runner = RemotePush(local_client, configuration, "gitremote")
|
||||||
|
|
||||||
with pytest.raises(GitRemoteError):
|
with pytest.raises(GitRemoteError):
|
||||||
|
@ -1,19 +0,0 @@
|
|||||||
import pytest
|
|
||||||
|
|
||||||
from ahriman.core.configuration import Configuration
|
|
||||||
from ahriman.core.housekeeping import LogsRotationTrigger
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
|
||||||
def logs_rotation_trigger(configuration: Configuration) -> LogsRotationTrigger:
|
|
||||||
"""
|
|
||||||
logs roration trigger fixture
|
|
||||||
|
|
||||||
Args:
|
|
||||||
configuration(Configuration): configuration fixture
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
LogsRotationTrigger: logs rotation trigger test instance
|
|
||||||
"""
|
|
||||||
_, repository_id = configuration.check_loaded()
|
|
||||||
return LogsRotationTrigger(repository_id, configuration)
|
|
@ -1,26 +0,0 @@
|
|||||||
from pytest_mock import MockerFixture
|
|
||||||
from unittest.mock import MagicMock
|
|
||||||
|
|
||||||
from ahriman.core.configuration import Configuration
|
|
||||||
from ahriman.core.housekeeping import LogsRotationTrigger
|
|
||||||
from ahriman.core.status import Client
|
|
||||||
from ahriman.models.result import Result
|
|
||||||
|
|
||||||
|
|
||||||
def test_configuration_sections(configuration: Configuration) -> None:
|
|
||||||
"""
|
|
||||||
must correctly parse target list
|
|
||||||
"""
|
|
||||||
assert LogsRotationTrigger.configuration_sections(configuration) == ["logs-rotation"]
|
|
||||||
|
|
||||||
|
|
||||||
def test_rotate(logs_rotation_trigger: LogsRotationTrigger, mocker: MockerFixture) -> None:
|
|
||||||
"""
|
|
||||||
must rotate logs
|
|
||||||
"""
|
|
||||||
client_mock = MagicMock()
|
|
||||||
context_mock = mocker.patch("ahriman.core._Context.get", return_value=client_mock)
|
|
||||||
|
|
||||||
logs_rotation_trigger.on_result(Result(), [])
|
|
||||||
context_mock.assert_called_once_with(Client)
|
|
||||||
client_mock.logs_rotate.assert_called_once_with(logs_rotation_trigger.keep_last_records)
|
|
@ -51,7 +51,7 @@ def test_login_failed(ahriman_client: SyncAhrimanClient, user: User, mocker: Moc
|
|||||||
must suppress any exception happened during login
|
must suppress any exception happened during login
|
||||||
"""
|
"""
|
||||||
ahriman_client.user = user
|
ahriman_client.user = user
|
||||||
mocker.patch("requests.Session.request", side_effect=Exception)
|
mocker.patch("requests.Session.request", side_effect=Exception())
|
||||||
ahriman_client._login(requests.Session())
|
ahriman_client._login(requests.Session())
|
||||||
|
|
||||||
|
|
||||||
@ -60,7 +60,7 @@ def test_login_failed_http_error(ahriman_client: SyncAhrimanClient, user: User,
|
|||||||
must suppress HTTP exception happened during login
|
must suppress HTTP exception happened during login
|
||||||
"""
|
"""
|
||||||
ahriman_client.user = user
|
ahriman_client.user = user
|
||||||
mocker.patch("requests.Session.request", side_effect=requests.HTTPError)
|
mocker.patch("requests.Session.request", side_effect=requests.HTTPError())
|
||||||
ahriman_client._login(requests.Session())
|
ahriman_client._login(requests.Session())
|
||||||
|
|
||||||
|
|
||||||
|
@ -124,7 +124,7 @@ def test_make_request_failed(mocker: MockerFixture) -> None:
|
|||||||
"""
|
"""
|
||||||
must process request errors
|
must process request errors
|
||||||
"""
|
"""
|
||||||
mocker.patch("requests.Session.request", side_effect=Exception)
|
mocker.patch("requests.Session.request", side_effect=Exception())
|
||||||
logging_mock = mocker.patch("logging.Logger.exception")
|
logging_mock = mocker.patch("logging.Logger.exception")
|
||||||
|
|
||||||
with pytest.raises(Exception):
|
with pytest.raises(Exception):
|
||||||
@ -136,7 +136,7 @@ def test_make_request_suppress_errors(mocker: MockerFixture) -> None:
|
|||||||
"""
|
"""
|
||||||
must suppress request errors correctly
|
must suppress request errors correctly
|
||||||
"""
|
"""
|
||||||
mocker.patch("requests.Session.request", side_effect=Exception)
|
mocker.patch("requests.Session.request", side_effect=Exception())
|
||||||
logging_mock = mocker.patch("logging.Logger.exception")
|
logging_mock = mocker.patch("logging.Logger.exception")
|
||||||
|
|
||||||
with pytest.raises(Exception):
|
with pytest.raises(Exception):
|
||||||
|
@ -20,12 +20,14 @@ def test_load(configuration: Configuration, mocker: MockerFixture) -> None:
|
|||||||
|
|
||||||
add_mock = mocker.patch("logging.Logger.addHandler")
|
add_mock = mocker.patch("logging.Logger.addHandler")
|
||||||
load_mock = mocker.patch("ahriman.core.status.Client.load")
|
load_mock = mocker.patch("ahriman.core.status.Client.load")
|
||||||
|
atexit_mock = mocker.patch("atexit.register")
|
||||||
|
|
||||||
_, repository_id = configuration.check_loaded()
|
_, repository_id = configuration.check_loaded()
|
||||||
handler = HttpLogHandler.load(repository_id, configuration, report=False)
|
handler = HttpLogHandler.load(repository_id, configuration, report=False)
|
||||||
assert handler
|
assert handler
|
||||||
add_mock.assert_called_once_with(handler)
|
add_mock.assert_called_once_with(handler)
|
||||||
load_mock.assert_called_once_with(repository_id, configuration, report=False)
|
load_mock.assert_called_once_with(repository_id, configuration, report=False)
|
||||||
|
atexit_mock.assert_called_once_with(handler.rotate)
|
||||||
|
|
||||||
|
|
||||||
def test_load_exist(configuration: Configuration) -> None:
|
def test_load_exist(configuration: Configuration) -> None:
|
||||||
@ -59,7 +61,7 @@ def test_emit_failed(configuration: Configuration, log_record: logging.LogRecord
|
|||||||
must call handle error on exception
|
must call handle error on exception
|
||||||
"""
|
"""
|
||||||
log_record.package_id = LogRecordId(package_ahriman.base, package_ahriman.version)
|
log_record.package_id = LogRecordId(package_ahriman.base, package_ahriman.version)
|
||||||
mocker.patch("ahriman.core.status.Client.package_logs_add", side_effect=Exception)
|
mocker.patch("ahriman.core.status.Client.package_logs_add", side_effect=Exception())
|
||||||
handle_error_mock = mocker.patch("logging.Handler.handleError")
|
handle_error_mock = mocker.patch("logging.Handler.handleError")
|
||||||
_, repository_id = configuration.check_loaded()
|
_, repository_id = configuration.check_loaded()
|
||||||
handler = HttpLogHandler(repository_id, configuration, report=False, suppress_errors=False)
|
handler = HttpLogHandler(repository_id, configuration, report=False, suppress_errors=False)
|
||||||
@ -74,7 +76,7 @@ def test_emit_suppress_failed(configuration: Configuration, log_record: logging.
|
|||||||
must not call handle error on exception if suppress flag is set
|
must not call handle error on exception if suppress flag is set
|
||||||
"""
|
"""
|
||||||
log_record.package_id = LogRecordId(package_ahriman.base, package_ahriman.version)
|
log_record.package_id = LogRecordId(package_ahriman.base, package_ahriman.version)
|
||||||
mocker.patch("ahriman.core.status.Client.package_logs_add", side_effect=Exception)
|
mocker.patch("ahriman.core.status.Client.package_logs_add", side_effect=Exception())
|
||||||
handle_error_mock = mocker.patch("logging.Handler.handleError")
|
handle_error_mock = mocker.patch("logging.Handler.handleError")
|
||||||
_, repository_id = configuration.check_loaded()
|
_, repository_id = configuration.check_loaded()
|
||||||
handler = HttpLogHandler(repository_id, configuration, report=False, suppress_errors=True)
|
handler = HttpLogHandler(repository_id, configuration, report=False, suppress_errors=True)
|
||||||
@ -94,3 +96,16 @@ def test_emit_skip(configuration: Configuration, log_record: logging.LogRecord,
|
|||||||
|
|
||||||
handler.emit(log_record)
|
handler.emit(log_record)
|
||||||
log_mock.assert_not_called()
|
log_mock.assert_not_called()
|
||||||
|
|
||||||
|
|
||||||
|
def test_rotate(configuration: Configuration, mocker: MockerFixture) -> None:
|
||||||
|
"""
|
||||||
|
must rotate logs
|
||||||
|
"""
|
||||||
|
rotate_mock = mocker.patch("ahriman.core.status.Client.logs_rotate")
|
||||||
|
|
||||||
|
_, repository_id = configuration.check_loaded()
|
||||||
|
handler = HttpLogHandler(repository_id, configuration, report=False, suppress_errors=False)
|
||||||
|
|
||||||
|
handler.rotate()
|
||||||
|
rotate_mock.assert_called_once_with(handler.keep_last_records)
|
||||||
|
@ -61,7 +61,7 @@ def test_load_fallback(configuration: Configuration, mocker: MockerFixture) -> N
|
|||||||
"""
|
"""
|
||||||
must fall back to stderr without errors
|
must fall back to stderr without errors
|
||||||
"""
|
"""
|
||||||
mocker.patch("ahriman.core.log.log_loader.fileConfig", side_effect=PermissionError)
|
mocker.patch("ahriman.core.log.log_loader.fileConfig", side_effect=PermissionError())
|
||||||
_, repository_id = configuration.check_loaded()
|
_, repository_id = configuration.check_loaded()
|
||||||
LogLoader.load(repository_id, configuration, LogHandler.Journald, quiet=False, report=False)
|
LogLoader.load(repository_id, configuration, LogHandler.Journald, quiet=False, report=False)
|
||||||
|
|
||||||
|
@ -13,7 +13,7 @@ def test_report_failure(configuration: Configuration, mocker: MockerFixture) ->
|
|||||||
"""
|
"""
|
||||||
must raise ReportFailed on errors
|
must raise ReportFailed on errors
|
||||||
"""
|
"""
|
||||||
mocker.patch("ahriman.core.report.html.HTML.generate", side_effect=Exception)
|
mocker.patch("ahriman.core.report.html.HTML.generate", side_effect=Exception())
|
||||||
_, repository_id = configuration.check_loaded()
|
_, repository_id = configuration.check_loaded()
|
||||||
|
|
||||||
with pytest.raises(ReportError):
|
with pytest.raises(ReportError):
|
||||||
|
@ -41,7 +41,7 @@ def test_send_failed(telegram: Telegram, mocker: MockerFixture) -> None:
|
|||||||
"""
|
"""
|
||||||
must reraise generic exception
|
must reraise generic exception
|
||||||
"""
|
"""
|
||||||
mocker.patch("requests.Session.request", side_effect=Exception)
|
mocker.patch("requests.Session.request", side_effect=Exception())
|
||||||
with pytest.raises(Exception):
|
with pytest.raises(Exception):
|
||||||
telegram._send("a text")
|
telegram._send("a text")
|
||||||
|
|
||||||
@ -50,7 +50,7 @@ def test_send_failed_http_error(telegram: Telegram, mocker: MockerFixture) -> No
|
|||||||
"""
|
"""
|
||||||
must reraise http exception
|
must reraise http exception
|
||||||
"""
|
"""
|
||||||
mocker.patch("requests.Session.request", side_effect=requests.HTTPError)
|
mocker.patch("requests.Session.request", side_effect=requests.HTTPError())
|
||||||
with pytest.raises(requests.HTTPError):
|
with pytest.raises(requests.HTTPError):
|
||||||
telegram._send("a text")
|
telegram._send("a text")
|
||||||
|
|
||||||
|
@ -67,7 +67,7 @@ def test_process_build_failure(executor: Executor, package_ahriman: Package, moc
|
|||||||
mocker.patch("ahriman.core.repository.executor.Executor.packages_built")
|
mocker.patch("ahriman.core.repository.executor.Executor.packages_built")
|
||||||
mocker.patch("ahriman.core.build_tools.task.Task.build", return_value=[Path(package_ahriman.base)])
|
mocker.patch("ahriman.core.build_tools.task.Task.build", return_value=[Path(package_ahriman.base)])
|
||||||
mocker.patch("ahriman.core.build_tools.task.Task.init")
|
mocker.patch("ahriman.core.build_tools.task.Task.init")
|
||||||
mocker.patch("shutil.move", side_effect=Exception)
|
mocker.patch("shutil.move", side_effect=Exception())
|
||||||
status_client_mock = mocker.patch("ahriman.core.status.Client.set_failed")
|
status_client_mock = mocker.patch("ahriman.core.status.Client.set_failed")
|
||||||
|
|
||||||
executor.process_build([package_ahriman])
|
executor.process_build([package_ahriman])
|
||||||
@ -151,7 +151,7 @@ def test_process_remove_failed(executor: Executor, package_ahriman: Package, moc
|
|||||||
must suppress tree clear errors during package base removal
|
must suppress tree clear errors during package base removal
|
||||||
"""
|
"""
|
||||||
mocker.patch("ahriman.core.repository.executor.Executor.packages", return_value=[package_ahriman])
|
mocker.patch("ahriman.core.repository.executor.Executor.packages", return_value=[package_ahriman])
|
||||||
mocker.patch("ahriman.core.status.local_client.LocalClient.package_remove", side_effect=Exception)
|
mocker.patch("ahriman.core.status.local_client.LocalClient.package_remove", side_effect=Exception())
|
||||||
executor.process_remove([package_ahriman.base])
|
executor.process_remove([package_ahriman.base])
|
||||||
|
|
||||||
|
|
||||||
@ -160,7 +160,7 @@ def test_process_remove_tree_clear_failed(executor: Executor, package_ahriman: P
|
|||||||
must suppress remove errors
|
must suppress remove errors
|
||||||
"""
|
"""
|
||||||
mocker.patch("ahriman.core.repository.executor.Executor.packages", return_value=[package_ahriman])
|
mocker.patch("ahriman.core.repository.executor.Executor.packages", return_value=[package_ahriman])
|
||||||
mocker.patch("ahriman.core.alpm.repo.Repo.remove", side_effect=Exception)
|
mocker.patch("ahriman.core.alpm.repo.Repo.remove", side_effect=Exception())
|
||||||
executor.process_remove([package_ahriman.base])
|
executor.process_remove([package_ahriman.base])
|
||||||
|
|
||||||
|
|
||||||
@ -277,7 +277,7 @@ def test_process_update_failed(executor: Executor, package_ahriman: Package, moc
|
|||||||
"""
|
"""
|
||||||
must process update for failed package
|
must process update for failed package
|
||||||
"""
|
"""
|
||||||
mocker.patch("shutil.move", side_effect=Exception)
|
mocker.patch("shutil.move", side_effect=Exception())
|
||||||
mocker.patch("ahriman.core.repository.executor.Executor.load_archives", return_value=[package_ahriman])
|
mocker.patch("ahriman.core.repository.executor.Executor.load_archives", return_value=[package_ahriman])
|
||||||
mocker.patch("ahriman.core.repository.executor.Executor.packages", return_value=[package_ahriman])
|
mocker.patch("ahriman.core.repository.executor.Executor.packages", return_value=[package_ahriman])
|
||||||
status_client_mock = mocker.patch("ahriman.core.status.Client.set_failed")
|
status_client_mock = mocker.patch("ahriman.core.status.Client.set_failed")
|
||||||
|
@ -40,7 +40,7 @@ def test_load_archives_failed(package_info: PackageInfo, mocker: MockerFixture)
|
|||||||
"""
|
"""
|
||||||
must skip packages which cannot be loaded
|
must skip packages which cannot be loaded
|
||||||
"""
|
"""
|
||||||
mocker.patch("ahriman.models.package.Package.from_archive", side_effect=Exception)
|
mocker.patch("ahriman.models.package.Package.from_archive", side_effect=Exception())
|
||||||
assert not package_info.load_archives([Path("a.pkg.tar.xz")])
|
assert not package_info.load_archives([Path("a.pkg.tar.xz")])
|
||||||
|
|
||||||
|
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user