mirror of
https://github.com/arcan1s/ahriman.git
synced 2026-03-06 18:23:38 +00:00
Compare commits
5 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 10661f3127 | |||
| 88ac3d82a0 | |||
| 386765ab4e | |||
| 78998182b8 | |||
| 2563391aaf |
@@ -2,7 +2,7 @@
|
|||||||
"name": "ahriman-frontend",
|
"name": "ahriman-frontend",
|
||||||
"private": true,
|
"private": true,
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"version": "2.20.0-rc4",
|
"version": "2.20.0-rc6",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "tsc && vite build",
|
"build": "tsc && vite build",
|
||||||
"dev": "vite",
|
"dev": "vite",
|
||||||
|
|||||||
@@ -31,16 +31,16 @@ export default function PackageCountBarChart({ stats }: PackageCountBarChartProp
|
|||||||
data={{
|
data={{
|
||||||
labels: ["packages"],
|
labels: ["packages"],
|
||||||
datasets: [
|
datasets: [
|
||||||
{
|
|
||||||
label: "archives",
|
|
||||||
data: [stats.packages ?? 0],
|
|
||||||
backgroundColor: blue[500],
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
label: "bases",
|
label: "bases",
|
||||||
data: [stats.bases ?? 0],
|
data: [stats.bases ?? 0],
|
||||||
backgroundColor: indigo[300],
|
backgroundColor: indigo[300],
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
label: "archives",
|
||||||
|
data: [stats.packages ?? 0],
|
||||||
|
backgroundColor: blue[500],
|
||||||
|
},
|
||||||
],
|
],
|
||||||
}}
|
}}
|
||||||
options={{
|
options={{
|
||||||
@@ -48,7 +48,7 @@ export default function PackageCountBarChart({ stats }: PackageCountBarChartProp
|
|||||||
responsive: true,
|
responsive: true,
|
||||||
scales: {
|
scales: {
|
||||||
x: { stacked: true },
|
x: { stacked: true },
|
||||||
y: { stacked: true },
|
y: { stacked: false },
|
||||||
},
|
},
|
||||||
}}
|
}}
|
||||||
/>;
|
/>;
|
||||||
|
|||||||
@@ -86,12 +86,12 @@ export default function DashboardDialog({ open, onClose }: DashboardDialogProps)
|
|||||||
|
|
||||||
<Grid container spacing={2} sx={{ mt: 2 }}>
|
<Grid container spacing={2} sx={{ mt: 2 }}>
|
||||||
<Grid size={{ xs: 12, md: 6 }}>
|
<Grid size={{ xs: 12, md: 6 }}>
|
||||||
<Box sx={{ maxHeight: 300 }}>
|
<Box sx={{ height: 300 }}>
|
||||||
<PackageCountBarChart stats={status.stats} />
|
<PackageCountBarChart stats={status.stats} />
|
||||||
</Box>
|
</Box>
|
||||||
</Grid>
|
</Grid>
|
||||||
<Grid size={{ xs: 12, md: 6 }}>
|
<Grid size={{ xs: 12, md: 6 }}>
|
||||||
<Box sx={{ maxHeight: 300, display: "flex", justifyContent: "center", alignItems: "center" }}>
|
<Box sx={{ height: 300, display: "flex", justifyContent: "center", alignItems: "center" }}>
|
||||||
<StatusPieChart counters={status.packages} />
|
<StatusPieChart counters={status.packages} />
|
||||||
</Box>
|
</Box>
|
||||||
</Grid>
|
</Grid>
|
||||||
|
|||||||
@@ -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.20.0rc5
|
pkgver=2.20.0rc6
|
||||||
pkgrel=1
|
pkgrel=1
|
||||||
pkgdesc="ArcH linux ReposItory MANager"
|
pkgdesc="ArcH linux ReposItory MANager"
|
||||||
arch=('any')
|
arch=('any')
|
||||||
@@ -17,11 +17,10 @@ source=("https://github.com/arcan1s/ahriman/releases/download/$pkgver/$pkgbase-$
|
|||||||
build() {
|
build() {
|
||||||
cd "$pkgbase-$pkgver"
|
cd "$pkgbase-$pkgver"
|
||||||
|
|
||||||
python -m build --wheel --no-isolation
|
npm --prefix "frontend" install --cache "$srcdir/npm-cache"
|
||||||
|
npm --prefix "frontend" run build
|
||||||
|
|
||||||
cd "frontend"
|
python -m build --wheel --no-isolation
|
||||||
npm install --cache "$srcdir/npm-cache"
|
|
||||||
npm run build
|
|
||||||
}
|
}
|
||||||
|
|
||||||
package_ahriman() {
|
package_ahriman() {
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
.TH AHRIMAN "1" "2026\-03\-06" "ahriman 2.20.0rc5" "ArcH linux ReposItory MANager"
|
.TH AHRIMAN "1" "2026\-03\-06" "ahriman 2.20.0rc6" "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.20.0rc5"
|
__version__ = "2.20.0rc6"
|
||||||
|
|||||||
@@ -138,8 +138,14 @@ class PacmanDatabase(SyncHttpClient):
|
|||||||
|
|
||||||
Args:
|
Args:
|
||||||
force(bool): force database synchronization (same as ``pacman -Syy``)
|
force(bool): force database synchronization (same as ``pacman -Syy``)
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
PacmanError: on operation error (invalid scheme or incomplete configuration)
|
||||||
"""
|
"""
|
||||||
|
try:
|
||||||
server = next(iter(self.database.servers))
|
server = next(iter(self.database.servers))
|
||||||
|
except StopIteration:
|
||||||
|
raise PacmanError("No configured servers available for database") from None
|
||||||
filename = f"{self.database.name}.files.tar.gz"
|
filename = f"{self.database.name}.files.tar.gz"
|
||||||
url = f"{server}/{filename}"
|
url = f"{server}/{filename}"
|
||||||
|
|
||||||
|
|||||||
@@ -116,6 +116,19 @@ class GitRemoteError(RuntimeError):
|
|||||||
RuntimeError.__init__(self, "Git remote failed")
|
RuntimeError.__init__(self, "Git remote failed")
|
||||||
|
|
||||||
|
|
||||||
|
class GPGError(RuntimeError):
|
||||||
|
"""
|
||||||
|
PGP/GPG related exception
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, details: str) -> None:
|
||||||
|
"""
|
||||||
|
Args:
|
||||||
|
details(str): details of the exception
|
||||||
|
"""
|
||||||
|
RuntimeError.__init__(self, f"GPG operation failed: {details}")
|
||||||
|
|
||||||
|
|
||||||
class InitializeError(RuntimeError):
|
class InitializeError(RuntimeError):
|
||||||
"""
|
"""
|
||||||
base service initialization exception
|
base service initialization exception
|
||||||
|
|||||||
@@ -86,6 +86,11 @@ class ArchiveRotationTrigger(Trigger):
|
|||||||
package(Package): package which has been updated to check for older versions
|
package(Package): package which has been updated to check for older versions
|
||||||
pacman(Pacman): alpm wrapper instance
|
pacman(Pacman): alpm wrapper instance
|
||||||
"""
|
"""
|
||||||
|
# explicit guard to skip process in case if rotation is disabled
|
||||||
|
# this guard is supposed to speedup process
|
||||||
|
if self.keep_built_packages == 0:
|
||||||
|
return
|
||||||
|
|
||||||
packages: dict[tuple[str, str], Package] = {}
|
packages: dict[tuple[str, str], Package] = {}
|
||||||
# we can't use here load_archives, because it ignores versions
|
# we can't use here load_archives, because it ignores versions
|
||||||
for full_path in filter(package_like, self.paths.archive_for(package.base).iterdir()):
|
for full_path in filter(package_like, self.paths.archive_for(package.base).iterdir()):
|
||||||
@@ -94,7 +99,7 @@ class ArchiveRotationTrigger(Trigger):
|
|||||||
|
|
||||||
comparator: Callable[[Package, Package], int] = lambda left, right: left.vercmp(right.version)
|
comparator: Callable[[Package, Package], int] = lambda left, right: left.vercmp(right.version)
|
||||||
to_remove = sorted(packages.values(), key=cmp_to_key(comparator))
|
to_remove = sorted(packages.values(), key=cmp_to_key(comparator))
|
||||||
# 0 will implicitly be translated into [:0], meaning we keep all packages
|
|
||||||
for single in to_remove[:-self.keep_built_packages]:
|
for single in to_remove[:-self.keep_built_packages]:
|
||||||
self.logger.info("removing version %s of package %s", single.version, single.base)
|
self.logger.info("removing version %s of package %s", single.version, single.base)
|
||||||
for archive in single.packages.values():
|
for archive in single.packages.values():
|
||||||
|
|||||||
@@ -20,7 +20,7 @@
|
|||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
from ahriman.core.configuration import Configuration
|
from ahriman.core.configuration import Configuration
|
||||||
from ahriman.core.exceptions import BuildError
|
from ahriman.core.exceptions import BuildError, GPGError
|
||||||
from ahriman.core.http import SyncHttpClient
|
from ahriman.core.http import SyncHttpClient
|
||||||
from ahriman.core.utils import check_output
|
from ahriman.core.utils import check_output
|
||||||
from ahriman.models.sign_settings import SignSettings
|
from ahriman.models.sign_settings import SignSettings
|
||||||
@@ -147,12 +147,19 @@ class GPG(SyncHttpClient):
|
|||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
str: full PGP key fingerprint
|
str: full PGP key fingerprint
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
GPGError: if key is in wrong format
|
||||||
"""
|
"""
|
||||||
metadata = check_output("gpg", "--with-colons", "--fingerprint", key, logger=self.logger)
|
|
||||||
# fingerprint line will be like
|
# fingerprint line will be like
|
||||||
# fpr:::::::::43A663569A07EE1E4ECC55CC7E3A4240CE3C45C2:
|
# fpr:::::::::43A663569A07EE1E4ECC55CC7E3A4240CE3C45C2:
|
||||||
|
metadata = check_output("gpg", "--with-colons", "--fingerprint", key, logger=self.logger)
|
||||||
|
|
||||||
|
try:
|
||||||
fingerprint = next(filter(lambda line: line[:3] == "fpr", metadata.splitlines()))
|
fingerprint = next(filter(lambda line: line[:3] == "fpr", metadata.splitlines()))
|
||||||
return fingerprint.split(":")[-2]
|
return fingerprint.split(":")[-2]
|
||||||
|
except (IndexError, StopIteration):
|
||||||
|
raise GPGError(f"key {key} has invalid metadata") from None
|
||||||
|
|
||||||
def key_import(self, server: str, key: str) -> None:
|
def key_import(self, server: str, key: str) -> None:
|
||||||
"""
|
"""
|
||||||
|
|||||||
@@ -88,8 +88,12 @@ class User:
|
|||||||
"""
|
"""
|
||||||
if not self.password:
|
if not self.password:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
try:
|
||||||
algo = next(segment for segment in self.password.split("$") if segment)
|
algo = next(segment for segment in self.password.split("$") if segment)
|
||||||
return f"${algo}$"
|
return f"${algo}$"
|
||||||
|
except StopIteration:
|
||||||
|
return None
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def generate_password(length: int) -> str:
|
def generate_password(length: int) -> str:
|
||||||
|
|||||||
@@ -183,6 +183,15 @@ def test_sync_files_local(pacman_database: PacmanDatabase, mocker: MockerFixture
|
|||||||
copy_mock.assert_called_once_with(Path("/var/core.files.tar.gz"), pytest.helpers.anyvar(int))
|
copy_mock.assert_called_once_with(Path("/var/core.files.tar.gz"), pytest.helpers.anyvar(int))
|
||||||
|
|
||||||
|
|
||||||
|
def test_sync_files_no_servers(pacman_database: PacmanDatabase) -> None:
|
||||||
|
"""
|
||||||
|
must raise PacmanError if no servers are configured
|
||||||
|
"""
|
||||||
|
pacman_database.database.servers = []
|
||||||
|
with pytest.raises(PacmanError):
|
||||||
|
pacman_database.sync_files(force=False)
|
||||||
|
|
||||||
|
|
||||||
def test_sync_files_unknown_source(pacman_database: PacmanDatabase) -> None:
|
def test_sync_files_unknown_source(pacman_database: PacmanDatabase) -> None:
|
||||||
"""
|
"""
|
||||||
must raise an exception in case if server scheme is unsupported
|
must raise an exception in case if server scheme is unsupported
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ from pathlib import Path
|
|||||||
from pytest_mock import MockerFixture
|
from pytest_mock import MockerFixture
|
||||||
|
|
||||||
from ahriman.core.configuration import Configuration
|
from ahriman.core.configuration import Configuration
|
||||||
|
from ahriman.core.exceptions import GPGError
|
||||||
from ahriman.core.sign.gpg import GPG
|
from ahriman.core.sign.gpg import GPG
|
||||||
from ahriman.models.sign_settings import SignSettings
|
from ahriman.models.sign_settings import SignSettings
|
||||||
|
|
||||||
@@ -113,6 +114,15 @@ fpr:::::::::43A663569A07EE1E4ECC55CC7E3A4240CE3C45C2:""")
|
|||||||
check_output_mock.assert_called_once_with("gpg", "--with-colons", "--fingerprint", key, logger=gpg.logger)
|
check_output_mock.assert_called_once_with("gpg", "--with-colons", "--fingerprint", key, logger=gpg.logger)
|
||||||
|
|
||||||
|
|
||||||
|
def test_key_fingerprint_invalid(gpg: GPG, mocker: MockerFixture) -> None:
|
||||||
|
"""
|
||||||
|
must raise GPGError if no fingerprint found in output
|
||||||
|
"""
|
||||||
|
mocker.patch("ahriman.core.sign.gpg.check_output", return_value="no fingerprint here")
|
||||||
|
with pytest.raises(GPGError):
|
||||||
|
gpg.key_fingerprint("0xCE3C45C2")
|
||||||
|
|
||||||
|
|
||||||
def test_key_import(gpg: GPG, mocker: MockerFixture) -> None:
|
def test_key_import(gpg: GPG, mocker: MockerFixture) -> None:
|
||||||
"""
|
"""
|
||||||
must import PGP key from the server
|
must import PGP key from the server
|
||||||
|
|||||||
@@ -12,6 +12,8 @@ def test_algo() -> None:
|
|||||||
"""
|
"""
|
||||||
assert User(username="user", password=None, access=UserAccess.Read).algo is None
|
assert User(username="user", password=None, access=UserAccess.Read).algo is None
|
||||||
assert User(username="user", password="", access=UserAccess.Read).algo is None
|
assert User(username="user", password="", access=UserAccess.Read).algo is None
|
||||||
|
assert User(username="user", password="$$$", access=UserAccess.Read).algo is None
|
||||||
|
|
||||||
assert User(
|
assert User(
|
||||||
username="user",
|
username="user",
|
||||||
password="$6$rounds=656000$mWBiecMPrHAL1VgX$oU4Y5HH8HzlvMaxwkNEJjK13ozElyU1wAHBoO/WW5dAaE4YEfnB0X3FxbynKMl4FBdC3Ovap0jINz4LPkNADg0",
|
password="$6$rounds=656000$mWBiecMPrHAL1VgX$oU4Y5HH8HzlvMaxwkNEJjK13ozElyU1wAHBoO/WW5dAaE4YEfnB0X3FxbynKMl4FBdC3Ovap0jINz4LPkNADg0",
|
||||||
|
|||||||
5
tox.toml
5
tox.toml
@@ -268,12 +268,13 @@ commands = [
|
|||||||
[
|
[
|
||||||
"git",
|
"git",
|
||||||
"add",
|
"add",
|
||||||
"package/archlinux/PKGBUILD",
|
|
||||||
"src/ahriman/__init__.py",
|
|
||||||
"docs/_static/architecture.dot",
|
"docs/_static/architecture.dot",
|
||||||
|
"frontend/package.json",
|
||||||
|
"package/archlinux/PKGBUILD",
|
||||||
"package/share/man/man1/ahriman.1",
|
"package/share/man/man1/ahriman.1",
|
||||||
"package/share/bash-completion/completions/_ahriman",
|
"package/share/bash-completion/completions/_ahriman",
|
||||||
"package/share/zsh/site-functions/_ahriman",
|
"package/share/zsh/site-functions/_ahriman",
|
||||||
|
"src/ahriman/__init__.py",
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
"git",
|
"git",
|
||||||
|
|||||||
Reference in New Issue
Block a user