mirror of
https://github.com/arcan1s/ahriman.git
synced 2025-11-14 04:23:42 +00:00
Compare commits
4 Commits
939a94d889
...
release/2.
| Author | SHA1 | Date | |
|---|---|---|---|
| 6280c9dbe6 | |||
| 3d1fdd5517 | |||
| ab022071e8 | |||
| a01f76df42 |
@ -140,14 +140,6 @@ ahriman.web.schemas.logs\_schema module
|
|||||||
:no-undoc-members:
|
:no-undoc-members:
|
||||||
:show-inheritance:
|
:show-inheritance:
|
||||||
|
|
||||||
ahriman.web.schemas.logs\_search\_schema module
|
|
||||||
-----------------------------------------------
|
|
||||||
|
|
||||||
.. automodule:: ahriman.web.schemas.logs_search_schema
|
|
||||||
:members:
|
|
||||||
:no-undoc-members:
|
|
||||||
:show-inheritance:
|
|
||||||
|
|
||||||
ahriman.web.schemas.oauth2\_schema module
|
ahriman.web.schemas.oauth2\_schema module
|
||||||
-----------------------------------------
|
-----------------------------------------
|
||||||
|
|
||||||
|
|||||||
@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
pkgbase='ahriman'
|
pkgbase='ahriman'
|
||||||
pkgname=('ahriman' 'ahriman-core' 'ahriman-triggers' 'ahriman-web')
|
pkgname=('ahriman' 'ahriman-core' 'ahriman-triggers' 'ahriman-web')
|
||||||
pkgver=2.19.0
|
pkgver=2.19.1
|
||||||
pkgrel=1
|
pkgrel=1
|
||||||
pkgdesc="ArcH linux ReposItory MANager"
|
pkgdesc="ArcH linux ReposItory MANager"
|
||||||
arch=('any')
|
arch=('any')
|
||||||
|
|||||||
@ -101,8 +101,6 @@
|
|||||||
<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-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 %}
|
||||||
<input id="package-info-autoreload-button" type="checkbox" class="btn-check" autocomplete="off" onclick="togglePackageInfoAutoReload()" checked>
|
|
||||||
<label for="package-info-autoreload-button" class="btn btn-outline-secondary" title="toggle auto reload"><i class="bi bi-clock"></i></label>
|
|
||||||
<button type="button" class="btn btn-secondary" onclick="showPackageInfo()"><i class="bi bi-arrow-clockwise"></i><span class="d-none d-sm-inline"> reload</span></button>
|
<button type="button" class="btn btn-secondary" onclick="showPackageInfo()"><i class="bi bi-arrow-clockwise"></i><span class="d-none d-sm-inline"> reload</span></button>
|
||||||
<button type="button" class="btn btn-primary" data-bs-dismiss="modal"><i class="bi bi-x"></i><span class="d-none d-sm-inline"> close</span></button>
|
<button type="button" class="btn btn-primary" data-bs-dismiss="modal"><i class="bi bi-x"></i><span class="d-none d-sm-inline"> close</span></button>
|
||||||
</div>
|
</div>
|
||||||
@ -142,9 +140,6 @@
|
|||||||
|
|
||||||
const packageInfoRefreshInput = document.getElementById("package-info-refresh-input");
|
const packageInfoRefreshInput = document.getElementById("package-info-refresh-input");
|
||||||
|
|
||||||
const packageInfoAutoReloadButton = document.getElementById("package-info-autoreload-button");
|
|
||||||
let packageInfoAutoReloadTask = null;
|
|
||||||
|
|
||||||
function clearChart() {
|
function clearChart() {
|
||||||
packageInfoEventsUpdateChartCanvas.hidden = true;
|
packageInfoEventsUpdateChartCanvas.hidden = true;
|
||||||
if (packageInfoEventsUpdateChart) {
|
if (packageInfoEventsUpdateChart) {
|
||||||
@ -153,13 +148,6 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function convertLogs(data, filter) {
|
|
||||||
return data
|
|
||||||
.filter((filter || Boolean))
|
|
||||||
.map(log_record => `[${new Date(1000 * log_record.created).toISOString()}] ${log_record.message}`)
|
|
||||||
.join("\n");
|
|
||||||
}
|
|
||||||
|
|
||||||
async function copyChanges() {
|
async function copyChanges() {
|
||||||
const changes = packageInfoChangesInput.textContent;
|
const changes = packageInfoChangesInput.textContent;
|
||||||
await copyToClipboard(changes, packageInfoChangesCopyButton);
|
await copyToClipboard(changes, packageInfoChangesCopyButton);
|
||||||
@ -331,19 +319,15 @@
|
|||||||
const link = document.createElement("a");
|
const link = document.createElement("a");
|
||||||
link.classList.add("dropdown-item");
|
link.classList.add("dropdown-item");
|
||||||
|
|
||||||
link.dataset.version = version.version;
|
|
||||||
link.dataset.processId = version.process_id;
|
|
||||||
link.dataset.logs = convertLogs(data, log_record => log_record.version === version.version && log_record.process_id === version.process_id);
|
|
||||||
|
|
||||||
link.textContent = new Date(1000 * version.created).toISOStringShort();
|
link.textContent = new Date(1000 * version.created).toISOStringShort();
|
||||||
link.href = "#";
|
link.href = "#";
|
||||||
link.onclick = _ => {
|
link.onclick = _ => {
|
||||||
// check if we are at the bottom of the code block
|
const logs = data
|
||||||
const isScrolledToBottom = packageInfoLogsInput.scrollTop + packageInfoLogsInput.clientHeight >= packageInfoLogsInput.scrollHeight;
|
.filter(log_record => log_record.version === version.version && log_record.process_id === version.process_id)
|
||||||
packageInfoLogsInput.textContent = link.dataset.logs;
|
.map(log_record => `[${new Date(1000 * log_record.created).toISOString()}] ${log_record.message}`);
|
||||||
|
|
||||||
|
packageInfoLogsInput.textContent = logs.join("\n");
|
||||||
highlight(packageInfoLogsInput);
|
highlight(packageInfoLogsInput);
|
||||||
if (isScrolledToBottom)
|
|
||||||
packageInfoLogsInput.scrollTop = packageInfoLogsInput.scrollHeight; // scroll to the new end
|
|
||||||
|
|
||||||
Array.from(packageInfoLogsVersions.children).forEach(el => el.classList.remove("active"));
|
Array.from(packageInfoLogsVersions.children).forEach(el => el.classList.remove("active"));
|
||||||
link.classList.add("active");
|
link.classList.add("active");
|
||||||
@ -419,46 +403,23 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
function packageInfoRemove() {
|
function packageInfoRemove() {
|
||||||
const packageBase = packageInfoModal.dataset.package;
|
const packageBase = packageInfoModal.package;
|
||||||
packagesRemove([packageBase]);
|
packagesRemove([packageBase]);
|
||||||
}
|
}
|
||||||
|
|
||||||
function packageInfoUpdate() {
|
function packageInfoUpdate() {
|
||||||
const packageBase = packageInfoModal.dataset.package;
|
const packageBase = packageInfoModal.package;
|
||||||
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) {
|
||||||
// set package base as currently used
|
// set package base as currently used
|
||||||
packageInfoModal.dataset.package = packageBase;
|
packageInfoModal.package = packageBase;
|
||||||
} else {
|
} else {
|
||||||
// read package base from the current window attribute
|
// read package base from the current window attribute
|
||||||
packageBase = packageInfoModal.dataset.package;
|
packageBase = packageInfoModal.package;
|
||||||
}
|
}
|
||||||
|
|
||||||
const onFailure = error => {
|
const onFailure = error => {
|
||||||
@ -477,21 +438,6 @@
|
|||||||
|
|
||||||
if (isPackageBaseSet) {
|
if (isPackageBaseSet) {
|
||||||
bootstrap.Modal.getOrCreateInstance(packageInfoModal).show();
|
bootstrap.Modal.getOrCreateInstance(packageInfoModal).show();
|
||||||
togglePackageInfoAutoReload();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function togglePackageInfoAutoReload() {
|
|
||||||
clearInterval(packageInfoAutoReloadTask);
|
|
||||||
if (packageInfoAutoReloadButton.checked) {
|
|
||||||
packageInfoAutoReloadTask = setInterval(_ => {
|
|
||||||
if (!hasActiveSelection()) {
|
|
||||||
const packageBase = packageInfoModal.dataset.package;
|
|
||||||
// we only poll status and logs here
|
|
||||||
loadPackage(packageBase);
|
|
||||||
reloadActiveLogs(packageBase);
|
|
||||||
}
|
|
||||||
}, 5000);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -522,9 +468,6 @@
|
|||||||
packageInfoChangesInput.textContent = "";
|
packageInfoChangesInput.textContent = "";
|
||||||
packageInfoEventsTable.bootstrapTable("load", []);
|
packageInfoEventsTable.bootstrapTable("load", []);
|
||||||
clearChart();
|
clearChart();
|
||||||
|
|
||||||
clearInterval(packageInfoAutoReloadTask);
|
|
||||||
packageInfoAutoReloadTask = null; // not really required (?) but lets clear everything
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@ -58,10 +58,6 @@
|
|||||||
return value.includes(dataList[index].toLowerCase());
|
return value.includes(dataList[index].toLowerCase());
|
||||||
}
|
}
|
||||||
|
|
||||||
function hasActiveSelection() {
|
|
||||||
return !document.getSelection().isCollapsed; // not sure if it is a valid way, but I guess so
|
|
||||||
}
|
|
||||||
|
|
||||||
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"];
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
.TH AHRIMAN "1" "2025\-06\-29" "ahriman 2.19.0" "ArcH linux ReposItory MANager"
|
.TH AHRIMAN "1" "2025\-07\-14" "ahriman 2.19.1" "ArcH linux ReposItory MANager"
|
||||||
.SH NAME
|
.SH NAME
|
||||||
ahriman \- ArcH linux ReposItory MANager
|
ahriman \- ArcH linux ReposItory MANager
|
||||||
.SH SYNOPSIS
|
.SH SYNOPSIS
|
||||||
|
|||||||
@ -17,4 +17,4 @@
|
|||||||
# You should have received a copy of the GNU General Public License
|
# You should have received a copy of the GNU General Public License
|
||||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
#
|
#
|
||||||
__version__ = "2.19.0"
|
__version__ = "2.19.1"
|
||||||
|
|||||||
@ -267,7 +267,8 @@ 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:
|
||||||
return package_name in package.provides
|
provides = [trim_package(name) for 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.package_base, pacman=pacman)).provides
|
if package_name in (package := self.package_info(stub.name, 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]:
|
||||||
|
|||||||
@ -203,6 +203,8 @@ def migrate_package_repository(connection: Connection, configuration: Configurat
|
|||||||
configuration(Configuration): configuration instance
|
configuration(Configuration): configuration instance
|
||||||
"""
|
"""
|
||||||
_, repository_id = configuration.check_loaded()
|
_, repository_id = configuration.check_loaded()
|
||||||
|
if repository_id.is_empty:
|
||||||
|
return # no repository available yet
|
||||||
|
|
||||||
connection.execute("""update build_queue set repository = :repository""", {"repository": repository_id.id})
|
connection.execute("""update build_queue set repository = :repository""", {"repository": repository_id.id})
|
||||||
connection.execute("""update package_bases set repository = :repository""", {"repository": repository_id.id})
|
connection.execute("""update package_bases set repository = :repository""", {"repository": repository_id.id})
|
||||||
|
|||||||
@ -29,15 +29,13 @@ class LogsOperations(Operations):
|
|||||||
logs operations
|
logs operations
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def logs_get(self, package_base: str, version: str | None = None, process_id: str | None = None,
|
def logs_get(self, package_base: str, limit: int = -1, offset: int = 0,
|
||||||
limit: int = -1, offset: int = 0, repository_id: RepositoryId | None = None) -> list[LogRecord]:
|
repository_id: RepositoryId | None = None) -> list[LogRecord]:
|
||||||
"""
|
"""
|
||||||
extract logs for specified package base
|
extract logs for specified package base
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
package_base(str): package base to extract logs
|
package_base(str): package base to extract logs
|
||||||
version(str | None, optional): package version to filter (Default value = None)
|
|
||||||
process_id(str | None, optional): process identifier to filter (Default value = None)
|
|
||||||
limit(int, optional): limit records to the specified count, -1 means unlimited (Default value = -1)
|
limit(int, optional): limit records to the specified count, -1 means unlimited (Default value = -1)
|
||||||
offset(int, optional): records offset (Default value = 0)
|
offset(int, optional): records offset (Default value = 0)
|
||||||
repository_id(RepositoryId, optional): repository unique identifier override (Default value = None)
|
repository_id(RepositoryId, optional): repository unique identifier override (Default value = None)
|
||||||
@ -54,17 +52,12 @@ class LogsOperations(Operations):
|
|||||||
"""
|
"""
|
||||||
select created, message, version, process_id from (
|
select created, message, version, process_id from (
|
||||||
select * from logs
|
select * from logs
|
||||||
where package_base = :package_base
|
where package_base = :package_base and repository = :repository
|
||||||
and repository = :repository
|
|
||||||
and (:version is null or version = :version)
|
|
||||||
and (:process_id is null or process_id = :process_id)
|
|
||||||
order by created desc limit :limit offset :offset
|
order by created desc limit :limit offset :offset
|
||||||
) order by created asc
|
) order by created asc
|
||||||
""",
|
""",
|
||||||
{
|
{
|
||||||
"package_base": package_base,
|
"package_base": package_base,
|
||||||
"version": version,
|
|
||||||
"process_id": process_id,
|
|
||||||
"repository": repository_id.id,
|
"repository": repository_id.id,
|
||||||
"limit": limit,
|
"limit": limit,
|
||||||
"offset": offset,
|
"offset": offset,
|
||||||
|
|||||||
@ -72,8 +72,8 @@ class SyncHttpClient(LazyLogging):
|
|||||||
"""
|
"""
|
||||||
session = requests.Session()
|
session = requests.Session()
|
||||||
python_version = ".".join(map(str, sys.version_info[:3])) # just major.minor.patch
|
python_version = ".".join(map(str, sys.version_info[:3])) # just major.minor.patch
|
||||||
session.headers["User-Agent"] = f"ahriman/{__version__}" \
|
session.headers["User-Agent"] = f"ahriman/{__version__} " \
|
||||||
f"{requests.utils.default_user_agent()}" \
|
f"{requests.utils.default_user_agent()} " \
|
||||||
f"python/{python_version}"
|
f"python/{python_version}"
|
||||||
|
|
||||||
return session
|
return session
|
||||||
|
|||||||
@ -203,15 +203,12 @@ class Client:
|
|||||||
"""
|
"""
|
||||||
# this method does not raise NotImplementedError because it is actively used as dummy client for http log
|
# this method does not raise NotImplementedError because it is actively used as dummy client for http log
|
||||||
|
|
||||||
def package_logs_get(self, package_base: str, version: str | None = None, process_id: str | None = None,
|
def package_logs_get(self, package_base: str, limit: int = -1, offset: int = 0) -> list[LogRecord]:
|
||||||
limit: int = -1, offset: int = 0) -> list[LogRecord]:
|
|
||||||
"""
|
"""
|
||||||
get package logs
|
get package logs
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
package_base(str): package base
|
package_base(str): package base
|
||||||
version(str | None, optional): package version to search (Default value = None)
|
|
||||||
process_id(str | None, optional): process identifier to search (Default value = None)
|
|
||||||
limit(int, optional): limit records to the specified count, -1 means unlimited (Default value = -1)
|
limit(int, optional): limit records to the specified count, -1 means unlimited (Default value = -1)
|
||||||
offset(int, optional): records offset (Default value = 0)
|
offset(int, optional): records offset (Default value = 0)
|
||||||
|
|
||||||
|
|||||||
@ -152,22 +152,19 @@ class LocalClient(Client):
|
|||||||
"""
|
"""
|
||||||
self.database.logs_insert(log_record, self.repository_id)
|
self.database.logs_insert(log_record, self.repository_id)
|
||||||
|
|
||||||
def package_logs_get(self, package_base: str, version: str | None = None, process_id: str | None = None,
|
def package_logs_get(self, package_base: str, limit: int = -1, offset: int = 0) -> list[LogRecord]:
|
||||||
limit: int = -1, offset: int = 0) -> list[LogRecord]:
|
|
||||||
"""
|
"""
|
||||||
get package logs
|
get package logs
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
package_base(str): package base
|
package_base(str): package base
|
||||||
version(str | None, optional): package version to search (Default value = None)
|
|
||||||
process_id(str | None, optional): process identifier to search (Default value = None)
|
|
||||||
limit(int, optional): limit records to the specified count, -1 means unlimited (Default value = -1)
|
limit(int, optional): limit records to the specified count, -1 means unlimited (Default value = -1)
|
||||||
offset(int, optional): records offset (Default value = 0)
|
offset(int, optional): records offset (Default value = 0)
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
list[LogRecord]: package logs
|
list[LogRecord]: package logs
|
||||||
"""
|
"""
|
||||||
return self.database.logs_get(package_base, version, process_id, limit, offset, self.repository_id)
|
return self.database.logs_get(package_base, limit, offset, self.repository_id)
|
||||||
|
|
||||||
def package_logs_remove(self, package_base: str, version: str | None) -> None:
|
def package_logs_remove(self, package_base: str, version: str | None) -> None:
|
||||||
"""
|
"""
|
||||||
|
|||||||
@ -109,7 +109,7 @@ class Watcher(LazyLogging):
|
|||||||
|
|
||||||
package_logs_add: Callable[[LogRecord], None]
|
package_logs_add: Callable[[LogRecord], None]
|
||||||
|
|
||||||
package_logs_get: Callable[[str, str | None, str | None, int, int], list[LogRecord]]
|
package_logs_get: Callable[[str, int, int], list[LogRecord]]
|
||||||
|
|
||||||
package_logs_remove: Callable[[str, str | None], None]
|
package_logs_remove: Callable[[str, str | None], None]
|
||||||
|
|
||||||
|
|||||||
@ -326,15 +326,12 @@ class WebClient(Client, SyncAhrimanClient):
|
|||||||
self.make_request("POST", self._logs_url(log_record.log_record_id.package_base),
|
self.make_request("POST", self._logs_url(log_record.log_record_id.package_base),
|
||||||
params=self.repository_id.query(), json=payload, suppress_errors=True)
|
params=self.repository_id.query(), json=payload, suppress_errors=True)
|
||||||
|
|
||||||
def package_logs_get(self, package_base: str, version: str | None = None, process_id: str | None = None,
|
def package_logs_get(self, package_base: str, limit: int = -1, offset: int = 0) -> list[LogRecord]:
|
||||||
limit: int = -1, offset: int = 0) -> list[LogRecord]:
|
|
||||||
"""
|
"""
|
||||||
get package logs
|
get package logs
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
package_base(str): package base
|
package_base(str): package base
|
||||||
version(str | None, optional): package version to search (Default value = None)
|
|
||||||
process_id(str | None, optional): process identifier to search (Default value = None)
|
|
||||||
limit(int, optional): limit records to the specified count, -1 means unlimited (Default value = -1)
|
limit(int, optional): limit records to the specified count, -1 means unlimited (Default value = -1)
|
||||||
offset(int, optional): records offset (Default value = 0)
|
offset(int, optional): records offset (Default value = 0)
|
||||||
|
|
||||||
@ -342,10 +339,6 @@ class WebClient(Client, SyncAhrimanClient):
|
|||||||
list[LogRecord]: package logs
|
list[LogRecord]: package logs
|
||||||
"""
|
"""
|
||||||
query = self.repository_id.query() + [("limit", str(limit)), ("offset", str(offset))]
|
query = self.repository_id.query() + [("limit", str(limit)), ("offset", str(offset))]
|
||||||
if version is not None:
|
|
||||||
query.append(("version", version))
|
|
||||||
if process_id is not None:
|
|
||||||
query.append(("process_id", process_id))
|
|
||||||
|
|
||||||
with contextlib.suppress(Exception):
|
with contextlib.suppress(Exception):
|
||||||
response = self.make_request("GET", self._logs_url(package_base), params=query)
|
response = self.make_request("GET", self._logs_url(package_base), params=query)
|
||||||
|
|||||||
@ -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
|
from ahriman.core.utils import filter_json, full_version, trim_package
|
||||||
|
|
||||||
|
|
||||||
@dataclass(frozen=True, kw_only=True)
|
@dataclass(frozen=True, kw_only=True)
|
||||||
@ -103,6 +103,17 @@ 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,12 +83,13 @@ class PackageDescription:
|
|||||||
|
|
||||||
def __post_init__(self) -> None:
|
def __post_init__(self) -> None:
|
||||||
"""
|
"""
|
||||||
update dependencies list accordingly
|
update packages lists accordingly
|
||||||
"""
|
"""
|
||||||
self.depends = [trim_package(package) for package in self.depends]
|
self.depends = [trim_package(package) for package in self.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.make_depends = [trim_package(package) for package in self.make_depends]
|
||||||
|
self.opt_depends = [trim_package(package) for package in self.opt_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:
|
||||||
|
|||||||
@ -34,7 +34,6 @@ from ahriman.web.schemas.log_schema import LogSchema
|
|||||||
from ahriman.web.schemas.login_schema import LoginSchema
|
from ahriman.web.schemas.login_schema import LoginSchema
|
||||||
from ahriman.web.schemas.logs_rotate_schema import LogsRotateSchema
|
from ahriman.web.schemas.logs_rotate_schema import LogsRotateSchema
|
||||||
from ahriman.web.schemas.logs_schema import LogsSchema
|
from ahriman.web.schemas.logs_schema import LogsSchema
|
||||||
from ahriman.web.schemas.logs_search_schema import LogsSearchSchema
|
|
||||||
from ahriman.web.schemas.oauth2_schema import OAuth2Schema
|
from ahriman.web.schemas.oauth2_schema import OAuth2Schema
|
||||||
from ahriman.web.schemas.package_name_schema import PackageNameSchema
|
from ahriman.web.schemas.package_name_schema import PackageNameSchema
|
||||||
from ahriman.web.schemas.package_names_schema import PackageNamesSchema
|
from ahriman.web.schemas.package_names_schema import PackageNamesSchema
|
||||||
|
|||||||
@ -1,36 +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 import __version__
|
|
||||||
from ahriman.web.apispec import fields
|
|
||||||
from ahriman.web.schemas.pagination_schema import PaginationSchema
|
|
||||||
|
|
||||||
|
|
||||||
class LogsSearchSchema(PaginationSchema):
|
|
||||||
"""
|
|
||||||
request log search schema
|
|
||||||
"""
|
|
||||||
|
|
||||||
version = fields.String(metadata={
|
|
||||||
"description": "Package version to search",
|
|
||||||
"example": __version__,
|
|
||||||
})
|
|
||||||
process_id = fields.String(metadata={
|
|
||||||
"description": "Process unique identifier to search",
|
|
||||||
})
|
|
||||||
@ -90,7 +90,7 @@ class LogsView(StatusViewGuard, BaseView):
|
|||||||
|
|
||||||
try:
|
try:
|
||||||
_, status = self.service().package_get(package_base)
|
_, status = self.service().package_get(package_base)
|
||||||
logs = self.service(package_base=package_base).package_logs_get(package_base, None, None, -1, 0)
|
logs = self.service(package_base=package_base).package_logs_get(package_base, -1, 0)
|
||||||
except UnknownPackageError:
|
except UnknownPackageError:
|
||||||
raise HTTPNotFound(reason=f"Package {package_base} is unknown")
|
raise HTTPNotFound(reason=f"Package {package_base} is unknown")
|
||||||
|
|
||||||
|
|||||||
@ -22,7 +22,7 @@ from typing import ClassVar
|
|||||||
|
|
||||||
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
|
||||||
from ahriman.web.schemas import LogSchema, LogsSearchSchema, PackageNameSchema
|
from ahriman.web.schemas import LogSchema, PackageNameSchema, PaginationSchema
|
||||||
from ahriman.web.views.base import BaseView
|
from ahriman.web.views.base import BaseView
|
||||||
from ahriman.web.views.status_view_guard import StatusViewGuard
|
from ahriman.web.views.status_view_guard import StatusViewGuard
|
||||||
|
|
||||||
@ -47,7 +47,7 @@ class LogsView(StatusViewGuard, BaseView):
|
|||||||
error_404_description="Package base and/or repository are unknown",
|
error_404_description="Package base and/or repository are unknown",
|
||||||
schema=LogSchema(many=True),
|
schema=LogSchema(many=True),
|
||||||
match_schema=PackageNameSchema,
|
match_schema=PackageNameSchema,
|
||||||
query_schema=LogsSearchSchema,
|
query_schema=PaginationSchema,
|
||||||
)
|
)
|
||||||
async def get(self) -> Response:
|
async def get(self) -> Response:
|
||||||
"""
|
"""
|
||||||
@ -61,10 +61,8 @@ class LogsView(StatusViewGuard, BaseView):
|
|||||||
"""
|
"""
|
||||||
package_base = self.request.match_info["package"]
|
package_base = self.request.match_info["package"]
|
||||||
limit, offset = self.page()
|
limit, offset = self.page()
|
||||||
version = self.request.query.get("version", None)
|
|
||||||
process = self.request.query.get("process_id", None)
|
|
||||||
|
|
||||||
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, limit, offset)
|
||||||
|
|
||||||
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)
|
||||||
|
|||||||
@ -289,3 +289,4 @@ 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
|
||||||
|
|||||||
@ -6,6 +6,7 @@ from unittest.mock import call as MockCall
|
|||||||
|
|
||||||
from ahriman.core.configuration import Configuration
|
from ahriman.core.configuration import Configuration
|
||||||
from ahriman.core.database.migrations.m011_repository_name import migrate_data, migrate_package_repository, steps
|
from ahriman.core.database.migrations.m011_repository_name import migrate_data, migrate_package_repository, steps
|
||||||
|
from ahriman.models.repository_id import RepositoryId
|
||||||
|
|
||||||
|
|
||||||
def test_migration_repository_name() -> None:
|
def test_migration_repository_name() -> None:
|
||||||
@ -37,3 +38,13 @@ def test_migrate_package_repository(connection: Connection, configuration: Confi
|
|||||||
MockCall(pytest.helpers.anyvar(str, strict=True), {"repository": configuration.repository_id.id}),
|
MockCall(pytest.helpers.anyvar(str, strict=True), {"repository": configuration.repository_id.id}),
|
||||||
MockCall(pytest.helpers.anyvar(str, strict=True), {"repository": configuration.repository_id.id}),
|
MockCall(pytest.helpers.anyvar(str, strict=True), {"repository": configuration.repository_id.id}),
|
||||||
])
|
])
|
||||||
|
|
||||||
|
|
||||||
|
def test_migrate_package_repository_empty_id(connection: Connection, configuration: Configuration,
|
||||||
|
mocker: MockerFixture) -> None:
|
||||||
|
"""
|
||||||
|
must do nothing on empty repository id
|
||||||
|
"""
|
||||||
|
mocker.patch("ahriman.core.configuration.Configuration.check_loaded", return_value=("", RepositoryId("", "")))
|
||||||
|
migrate_package_repository(connection, configuration)
|
||||||
|
connection.execute.assert_not_called()
|
||||||
|
|||||||
@ -71,35 +71,11 @@ def test_logs_insert_get_pagination(database: SQLite, package_ahriman: Package)
|
|||||||
"""
|
"""
|
||||||
database.logs_insert(LogRecord(LogRecordId(package_ahriman.base, "1"), 42.0, "message 1"))
|
database.logs_insert(LogRecord(LogRecordId(package_ahriman.base, "1"), 42.0, "message 1"))
|
||||||
database.logs_insert(LogRecord(LogRecordId(package_ahriman.base, "1"), 43.0, "message 2"))
|
database.logs_insert(LogRecord(LogRecordId(package_ahriman.base, "1"), 43.0, "message 2"))
|
||||||
assert database.logs_get(package_ahriman.base, None, None, 1, 1) == [
|
assert database.logs_get(package_ahriman.base, 1, 1) == [
|
||||||
LogRecord(LogRecordId(package_ahriman.base, "1"), 42.0, "message 1"),
|
LogRecord(LogRecordId(package_ahriman.base, "1"), 42.0, "message 1"),
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
def test_logs_insert_get_filter_by_version(database: SQLite, package_ahriman: Package) -> None:
|
|
||||||
"""
|
|
||||||
must insert and get package logs with pagination
|
|
||||||
"""
|
|
||||||
database.logs_insert(LogRecord(LogRecordId(package_ahriman.base, "1", "p1"), 42.0, "message 1"))
|
|
||||||
database.logs_insert(LogRecord(LogRecordId(package_ahriman.base, "1", "p2"), 43.0, "message 2"))
|
|
||||||
database.logs_insert(LogRecord(LogRecordId(package_ahriman.base, "2", "p1"), 44.0, "message 3"))
|
|
||||||
|
|
||||||
assert database.logs_get(package_ahriman.base, "1", None) == [
|
|
||||||
LogRecord(LogRecordId(package_ahriman.base, "1", "p1"), 42.0, "message 1"),
|
|
||||||
LogRecord(LogRecordId(package_ahriman.base, "1", "p2"), 43.0, "message 2"),
|
|
||||||
]
|
|
||||||
assert database.logs_get(package_ahriman.base, "2", None) == [
|
|
||||||
LogRecord(LogRecordId(package_ahriman.base, "2", "p1"), 44.0, "message 3"),
|
|
||||||
]
|
|
||||||
assert database.logs_get(package_ahriman.base, None, "p1") == [
|
|
||||||
LogRecord(LogRecordId(package_ahriman.base, "1", "p1"), 42.0, "message 1"),
|
|
||||||
LogRecord(LogRecordId(package_ahriman.base, "2", "p1"), 44.0, "message 3"),
|
|
||||||
]
|
|
||||||
assert database.logs_get(package_ahriman.base, "1", "p1") == [
|
|
||||||
LogRecord(LogRecordId(package_ahriman.base, "1", "p1"), 42.0, "message 1"),
|
|
||||||
]
|
|
||||||
|
|
||||||
|
|
||||||
def test_logs_insert_get_multi(database: SQLite, package_ahriman: Package) -> None:
|
def test_logs_insert_get_multi(database: SQLite, package_ahriman: Package) -> None:
|
||||||
"""
|
"""
|
||||||
must insert and get package logs for multiple repositories
|
must insert and get package logs for multiple repositories
|
||||||
|
|||||||
@ -124,9 +124,8 @@ def test_package_logs_get(local_client: LocalClient, package_ahriman: Package, m
|
|||||||
must retrieve package logs
|
must retrieve package logs
|
||||||
"""
|
"""
|
||||||
logs_mock = mocker.patch("ahriman.core.database.SQLite.logs_get")
|
logs_mock = mocker.patch("ahriman.core.database.SQLite.logs_get")
|
||||||
local_client.package_logs_get(package_ahriman.base, package_ahriman.version, "process", 1, 2)
|
local_client.package_logs_get(package_ahriman.base, 1, 2)
|
||||||
logs_mock.assert_called_once_with(package_ahriman.base, package_ahriman.version, "process", 1, 2,
|
logs_mock.assert_called_once_with(package_ahriman.base, 1, 2, local_client.repository_id)
|
||||||
local_client.repository_id)
|
|
||||||
|
|
||||||
|
|
||||||
def test_package_logs_remove(local_client: LocalClient, package_ahriman: Package, mocker: MockerFixture) -> None:
|
def test_package_logs_remove(local_client: LocalClient, package_ahriman: Package, mocker: MockerFixture) -> None:
|
||||||
|
|||||||
@ -658,7 +658,7 @@ def test_package_logs_get(web_client: WebClient, package_ahriman: Package, mocke
|
|||||||
|
|
||||||
requests_mock = mocker.patch("ahriman.core.status.web_client.WebClient.make_request", return_value=response_obj)
|
requests_mock = mocker.patch("ahriman.core.status.web_client.WebClient.make_request", return_value=response_obj)
|
||||||
|
|
||||||
result = web_client.package_logs_get(package_ahriman.base, None, None, 1, 2)
|
result = web_client.package_logs_get(package_ahriman.base, 1, 2)
|
||||||
requests_mock.assert_called_once_with("GET", pytest.helpers.anyvar(str, True),
|
requests_mock.assert_called_once_with("GET", pytest.helpers.anyvar(str, True),
|
||||||
params=web_client.repository_id.query() + [("limit", "1"), ("offset", "2")])
|
params=web_client.repository_id.query() + [("limit", "1"), ("offset", "2")])
|
||||||
assert result == [
|
assert result == [
|
||||||
@ -666,21 +666,6 @@ def test_package_logs_get(web_client: WebClient, package_ahriman: Package, mocke
|
|||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
def test_package_logs_get_filter(web_client: WebClient, package_ahriman: Package, mocker: MockerFixture) -> None:
|
|
||||||
"""
|
|
||||||
must get logs with version and process id filter
|
|
||||||
"""
|
|
||||||
requests_mock = mocker.patch("ahriman.core.status.web_client.WebClient.make_request")
|
|
||||||
web_client.package_logs_get(package_ahriman.base, package_ahriman.version, LogRecordId.DEFAULT_PROCESS_ID, 1, 2)
|
|
||||||
requests_mock.assert_called_once_with("GET", pytest.helpers.anyvar(str, True),
|
|
||||||
params=web_client.repository_id.query() + [
|
|
||||||
("limit", "1"),
|
|
||||||
("offset", "2"),
|
|
||||||
("version", package_ahriman.version),
|
|
||||||
("process_id", LogRecordId.DEFAULT_PROCESS_ID),
|
|
||||||
])
|
|
||||||
|
|
||||||
|
|
||||||
def test_package_logs_get_failed(web_client: WebClient, package_ahriman: Package, mocker: MockerFixture) -> None:
|
def test_package_logs_get_failed(web_client: WebClient, package_ahriman: Package, mocker: MockerFixture) -> None:
|
||||||
"""
|
"""
|
||||||
must suppress any exception happened during logs fetch
|
must suppress any exception happened during logs fetch
|
||||||
|
|||||||
@ -2,7 +2,7 @@ import datetime
|
|||||||
import json
|
import json
|
||||||
import pyalpm # typing: ignore
|
import pyalpm # typing: ignore
|
||||||
|
|
||||||
from dataclasses import asdict, fields
|
from dataclasses import asdict, fields, replace
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from pytest_mock import MockerFixture
|
from pytest_mock import MockerFixture
|
||||||
from typing import Any
|
from typing import Any
|
||||||
@ -38,6 +38,25 @@ def _get_official_data(resource_path_root: Path) -> dict[str, Any]:
|
|||||||
return json.loads(response)["results"][0]
|
return json.loads(response)["results"][0]
|
||||||
|
|
||||||
|
|
||||||
|
def test_post_init(aur_package_ahriman: AURPackage) -> None:
|
||||||
|
"""
|
||||||
|
must trim versions and descriptions from packages list
|
||||||
|
"""
|
||||||
|
package = replace(
|
||||||
|
aur_package_ahriman,
|
||||||
|
depends=["a=1"],
|
||||||
|
make_depends=["b>=3"],
|
||||||
|
opt_depends=["c: a description"],
|
||||||
|
check_depends=["d=4"],
|
||||||
|
provides=["e=5"],
|
||||||
|
)
|
||||||
|
assert package.depends == ["a"]
|
||||||
|
assert package.make_depends == ["b"]
|
||||||
|
assert package.opt_depends == ["c"]
|
||||||
|
assert package.check_depends == ["d"]
|
||||||
|
assert package.provides == ["e"]
|
||||||
|
|
||||||
|
|
||||||
def test_from_json(aur_package_ahriman: AURPackage, resource_path_root: Path) -> None:
|
def test_from_json(aur_package_ahriman: AURPackage, resource_path_root: Path) -> None:
|
||||||
"""
|
"""
|
||||||
must load package from json
|
must load package from json
|
||||||
|
|||||||
@ -6,10 +6,15 @@ from ahriman.models.package_description import PackageDescription
|
|||||||
|
|
||||||
def test_post_init() -> None:
|
def test_post_init() -> None:
|
||||||
"""
|
"""
|
||||||
must trim versions and descriptions from dependencies list
|
must trim versions and descriptions from packages list
|
||||||
"""
|
"""
|
||||||
assert PackageDescription(depends=["a=1"], make_depends=["b>=3"], opt_depends=["c: a description"]) == \
|
assert PackageDescription(
|
||||||
PackageDescription(depends=["a"], make_depends=["b"], opt_depends=["c"])
|
depends=["a=1"],
|
||||||
|
make_depends=["b>=3"],
|
||||||
|
opt_depends=["c: a description"],
|
||||||
|
check_depends=["d=4"],
|
||||||
|
provides=["e=5"]
|
||||||
|
) == PackageDescription(depends=["a"], make_depends=["b"], opt_depends=["c"], check_depends=["d"], provides=["e"])
|
||||||
|
|
||||||
|
|
||||||
def test_filepath(package_description_ahriman: PackageDescription) -> None:
|
def test_filepath(package_description_ahriman: PackageDescription) -> None:
|
||||||
|
|||||||
@ -1 +0,0 @@
|
|||||||
# schema testing goes in view class tests
|
|
||||||
@ -86,31 +86,6 @@ async def test_get_with_pagination(client: TestClient, package_ahriman: Package)
|
|||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
async def test_get_with_filter(client: TestClient, package_ahriman: Package) -> None:
|
|
||||||
"""
|
|
||||||
must get logs with filter by version and process identifier
|
|
||||||
"""
|
|
||||||
await client.post(f"/api/v1/packages/{package_ahriman.base}",
|
|
||||||
json={"status": BuildStatusEnum.Success.value, "package": package_ahriman.view()})
|
|
||||||
await client.post(f"/api/v1/packages/{package_ahriman.base}/logs",
|
|
||||||
json={"created": 42.0, "message": "message 1", "version": "42"})
|
|
||||||
await client.post(f"/api/v1/packages/{package_ahriman.base}/logs",
|
|
||||||
json={"created": 43.0, "message": "message 2", "version": "43"})
|
|
||||||
request_schema = pytest.helpers.schema_request(LogsView.get, location="querystring")
|
|
||||||
response_schema = pytest.helpers.schema_response(LogsView.get)
|
|
||||||
|
|
||||||
payload = {"version": "42", "process_id": LogRecordId.DEFAULT_PROCESS_ID}
|
|
||||||
assert not request_schema.validate(payload)
|
|
||||||
response = await client.get(f"/api/v2/packages/{package_ahriman.base}/logs", params=payload)
|
|
||||||
assert response.status == 200
|
|
||||||
|
|
||||||
logs = await response.json()
|
|
||||||
assert not response_schema.validate(logs)
|
|
||||||
assert logs == [
|
|
||||||
{"created": 42.0, "message": "message 1", "version": "42", "process_id": LogRecordId.DEFAULT_PROCESS_ID},
|
|
||||||
]
|
|
||||||
|
|
||||||
|
|
||||||
async def test_get_bad_request(client: TestClient, package_ahriman: Package) -> None:
|
async def test_get_bad_request(client: TestClient, package_ahriman: Package) -> None:
|
||||||
"""
|
"""
|
||||||
must return bad request for invalid query parameters
|
must return bad request for invalid query parameters
|
||||||
|
|||||||
Reference in New Issue
Block a user