Compare commits

...

3 Commits

10 changed files with 72 additions and 16 deletions

View File

@@ -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 },
}, },
}} }}
/>; />;

View File

@@ -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>

View File

@@ -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)
""" """
server = next(iter(self.database.servers)) try:
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}"

View File

@@ -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

View File

@@ -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():

View File

@@ -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:
fingerprint = next(filter(lambda line: line[:3] == "fpr", metadata.splitlines())) metadata = check_output("gpg", "--with-colons", "--fingerprint", key, logger=self.logger)
return fingerprint.split(":")[-2]
try:
fingerprint = next(filter(lambda line: line[:3] == "fpr", metadata.splitlines()))
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:
""" """

View File

@@ -88,8 +88,12 @@ class User:
""" """
if not self.password: if not self.password:
return None return None
algo = next(segment for segment in self.password.split("$") if segment)
return f"${algo}$" try:
algo = next(segment for segment in self.password.split("$") if segment)
return f"${algo}$"
except StopIteration:
return None
@staticmethod @staticmethod
def generate_password(length: int) -> str: def generate_password(length: int) -> str:

View File

@@ -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

View File

@@ -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

View File

@@ -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",