Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3b0dabf2a7 | ||
|
|
697370c906 | ||
|
|
bc57172d92 | ||
|
|
0e7e23dce5 | ||
|
|
9d53f4c6f5 | ||
|
|
a46d85b541 | ||
|
|
acaea11eb6 | ||
|
|
056d21a859 | ||
|
|
612ba5069d | ||
|
|
551e245218 | ||
|
|
814523eac2 |
No files matched your search
@@ -2,8 +2,8 @@ name: CI
|
||||
|
||||
on:
|
||||
push:
|
||||
branches-ignore:
|
||||
- main
|
||||
branches:
|
||||
- '**'
|
||||
pull_request:
|
||||
|
||||
concurrency:
|
||||
|
||||
@@ -6,56 +6,64 @@ concurrency:
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main # still run tests for main
|
||||
tags:
|
||||
- 'v*'
|
||||
jobs:
|
||||
test-unit:
|
||||
uses: ./.github/workflows/test-unit.yml
|
||||
|
||||
test-integration:
|
||||
uses: ./.github/workflows/test-integration.yml
|
||||
|
||||
test-env-virtual:
|
||||
uses: ./.github/workflows/test-env-virtual.yml
|
||||
|
||||
test-env-nix:
|
||||
uses: ./.github/workflows/test-env-nix.yml
|
||||
|
||||
test-e2e:
|
||||
uses: ./.github/workflows/test-e2e.yml
|
||||
|
||||
test-virgin-user:
|
||||
uses: ./.github/workflows/test-virgin-user.yml
|
||||
|
||||
test-virgin-root:
|
||||
uses: ./.github/workflows/test-virgin-root.yml
|
||||
|
||||
lint-shell:
|
||||
uses: ./.github/workflows/lint-shell.yml
|
||||
|
||||
lint-python:
|
||||
uses: ./.github/workflows/lint-python.yml
|
||||
|
||||
mark-stable:
|
||||
needs:
|
||||
- lint-shell
|
||||
- lint-python
|
||||
- test-unit
|
||||
- test-integration
|
||||
- test-env-nix
|
||||
- test-env-virtual
|
||||
- test-e2e
|
||||
- test-virgin-user
|
||||
- test-virgin-root
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
# Only run this job if the push is for a version tag (v*)
|
||||
if: startsWith(github.ref, 'refs/tags/v')
|
||||
|
||||
permissions:
|
||||
contents: write # Required to move/update the tag
|
||||
actions: read
|
||||
contents: write
|
||||
|
||||
steps:
|
||||
- name: Wait for CI success on main for this commit
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
|
||||
SHA="${GITHUB_SHA}"
|
||||
API_URL="https://api.github.com/repos/${GITHUB_REPOSITORY}/actions/workflows/ci.yml/runs?head_sha=${SHA}&event=push&per_page=20"
|
||||
WAIT_INTERVAL_SECONDS=20
|
||||
MAX_ATTEMPTS=360 # 2 hours max wait
|
||||
|
||||
STATUS=""
|
||||
CONCLUSION=""
|
||||
|
||||
echo "Waiting for CI on main for ${SHA} (up to 2 hours)..."
|
||||
for attempt in $(seq 1 "${MAX_ATTEMPTS}"); do
|
||||
RESPONSE="$(curl -fsSL \
|
||||
-H "Authorization: Bearer ${GH_TOKEN}" \
|
||||
-H "Accept: application/vnd.github+json" \
|
||||
"${API_URL}")"
|
||||
|
||||
STATUS="$(printf '%s' "${RESPONSE}" | jq -r '.workflow_runs[] | select(.head_branch=="main") | .status' | head -n1)"
|
||||
CONCLUSION="$(printf '%s' "${RESPONSE}" | jq -r '.workflow_runs[] | select(.head_branch=="main") | .conclusion' | head -n1)"
|
||||
|
||||
if [[ -n "${STATUS}" ]]; then
|
||||
echo "CI status=${STATUS} conclusion=${CONCLUSION:-none} (attempt ${attempt}/${MAX_ATTEMPTS})"
|
||||
else
|
||||
echo "No CI run for main found yet (attempt ${attempt}/${MAX_ATTEMPTS})"
|
||||
fi
|
||||
|
||||
if [[ "${STATUS}" == "completed" ]]; then
|
||||
if [[ "${CONCLUSION}" == "success" ]]; then
|
||||
echo "CI succeeded for ${SHA}."
|
||||
break
|
||||
fi
|
||||
echo "CI failed for ${SHA} (conclusion=${CONCLUSION})."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
sleep "${WAIT_INTERVAL_SECONDS}"
|
||||
done
|
||||
|
||||
if [[ "${STATUS}" != "completed" || "${CONCLUSION}" != "success" ]]; then
|
||||
echo "Timed out waiting for successful CI on main for ${SHA}."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
|
||||
@@ -1,3 +1,28 @@
|
||||
## [1.13.2] - 2026-03-26
|
||||
|
||||
* Fail fast with a clear error when the Nix bootstrap or nix binary is unavailable instead of continuing with a broken startup path.
|
||||
|
||||
|
||||
## [1.13.1] - 2026-03-20
|
||||
|
||||
* Fixed misleading GPG verification failures by adding explicit git and gnupg runtime dependencies and surfacing signing-key lookup errors accurately.
|
||||
|
||||
|
||||
## [1.13.0] - 2026-03-20
|
||||
|
||||
* Set CentOS docker image to latest
|
||||
|
||||
|
||||
## [1.12.5] - 2026-02-24
|
||||
|
||||
* The stable-tag workflow now waits up to two hours for a successful main-branch CI run on the same commit before updating stable.
|
||||
|
||||
|
||||
## [1.12.4] - 2026-02-24
|
||||
|
||||
* The release pipeline now updates the stable tag only for v* tags after a successful CI run on main for the same commit, while avoiding duplicate test executions.
|
||||
|
||||
|
||||
## [1.12.3] - 2026-02-24
|
||||
|
||||
* Stabilized Nix-based builds by switching to nixos-25.11 and committing flake.lock, ensuring reproducible pkgmgr test/runtime environments (with pip) and avoiding transient sphinx/Python 3.11 breakage.
|
||||
|
||||
@@ -32,7 +32,7 @@
|
||||
rec {
|
||||
pkgmgr = pyPkgs.buildPythonApplication {
|
||||
pname = "package-manager";
|
||||
version = "1.12.3";
|
||||
version = "1.13.2";
|
||||
|
||||
# Use the git repo as source
|
||||
src = ./.;
|
||||
@@ -51,6 +51,8 @@
|
||||
pyPkgs.pyyaml
|
||||
pyPkgs.jinja2
|
||||
pyPkgs.pip
|
||||
pkgs.git
|
||||
pkgs.gnupg
|
||||
];
|
||||
|
||||
doCheck = false;
|
||||
@@ -87,6 +89,7 @@
|
||||
buildInputs = [
|
||||
pythonWithDeps
|
||||
pkgs.git
|
||||
pkgs.gnupg
|
||||
ansiblePkg
|
||||
];
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# Maintainer: Kevin Veen-Birkenbach <info@veen.world>
|
||||
|
||||
pkgname=package-manager
|
||||
pkgver=1.12.3
|
||||
pkgver=1.13.2
|
||||
pkgrel=1
|
||||
pkgdesc="Local-flake wrapper for Kevin's package-manager (Nix-based)."
|
||||
arch=('any')
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
post_install() {
|
||||
/usr/lib/package-manager/nix/init.sh || echo ">>> ERROR: /usr/lib/package-manager/nix/init.sh not found or not executable."
|
||||
/usr/lib/package-manager/nix/init.sh
|
||||
}
|
||||
|
||||
post_upgrade() {
|
||||
/usr/lib/package-manager/nix/init.sh || echo ">>> ERROR: /usr/lib/package-manager/nix/init.sh not found or not executable."
|
||||
/usr/lib/package-manager/nix/init.sh
|
||||
}
|
||||
|
||||
post_remove() {
|
||||
|
||||
@@ -1,3 +1,33 @@
|
||||
package-manager (1.13.2-1) unstable; urgency=medium
|
||||
|
||||
* Fail fast with a clear error when the Nix bootstrap or nix binary is unavailable instead of continuing with a broken startup path.
|
||||
|
||||
-- Kevin Veen-Birkenbach <kevin@veen.world> Thu, 26 Mar 2026 12:26:55 +0100
|
||||
|
||||
package-manager (1.13.1-1) unstable; urgency=medium
|
||||
|
||||
* Fixed misleading GPG verification failures by adding explicit git and gnupg runtime dependencies and surfacing signing-key lookup errors accurately.
|
||||
|
||||
-- Kevin Veen-Birkenbach <kevin@veen.world> Fri, 20 Mar 2026 02:57:25 +0100
|
||||
|
||||
package-manager (1.13.0-1) unstable; urgency=medium
|
||||
|
||||
* Set CentOS docker image to latest
|
||||
|
||||
-- Kevin Veen-Birkenbach <kevin@veen.world> Fri, 20 Mar 2026 01:29:38 +0100
|
||||
|
||||
package-manager (1.12.5-1) unstable; urgency=medium
|
||||
|
||||
* The stable-tag workflow now waits up to two hours for a successful main-branch CI run on the same commit before updating stable.
|
||||
|
||||
-- Kevin Veen-Birkenbach <kevin@veen.world> Tue, 24 Feb 2026 09:35:39 +0100
|
||||
|
||||
package-manager (1.12.4-1) unstable; urgency=medium
|
||||
|
||||
* The release pipeline now updates the stable tag only for v* tags after a successful CI run on main for the same commit, while avoiding duplicate test executions.
|
||||
|
||||
-- Kevin Veen-Birkenbach <kevin@veen.world> Tue, 24 Feb 2026 09:32:01 +0100
|
||||
|
||||
package-manager (1.12.3-1) unstable; urgency=medium
|
||||
|
||||
* Stabilized Nix-based builds by switching to nixos-25.11 and committing flake.lock, ensuring reproducible pkgmgr test/runtime environments (with pip) and avoiding transient sphinx/Python 3.11 breakage.
|
||||
|
||||
@@ -3,7 +3,7 @@ set -e
|
||||
|
||||
case "$1" in
|
||||
configure)
|
||||
/usr/lib/package-manager/nix/init.sh || echo ">>> ERROR: /usr/lib/package-manager/nix/init.sh not found or not executable."
|
||||
/usr/lib/package-manager/nix/init.sh
|
||||
;;
|
||||
esac
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
Name: package-manager
|
||||
Version: 1.12.3
|
||||
Version: 1.13.2
|
||||
Release: 1%{?dist}
|
||||
Summary: Wrapper that runs Kevin's package-manager via Nix flake
|
||||
|
||||
@@ -62,7 +62,7 @@ rm -rf \
|
||||
%{buildroot}/usr/lib/package-manager/.gitkeep || true
|
||||
|
||||
%post
|
||||
/usr/lib/package-manager/nix/init.sh || echo ">>> ERROR: /usr/lib/package-manager/nix/init.sh not found or not executable."
|
||||
/usr/lib/package-manager/nix/init.sh
|
||||
|
||||
%postun
|
||||
echo ">>> package-manager removed. Nix itself was not removed."
|
||||
@@ -74,6 +74,21 @@ echo ">>> package-manager removed. Nix itself was not removed."
|
||||
/usr/lib/package-manager/
|
||||
|
||||
%changelog
|
||||
* Thu Mar 26 2026 Kevin Veen-Birkenbach <kevin@veen.world> - 1.13.2-1
|
||||
- Fail fast with a clear error when the Nix bootstrap or nix binary is unavailable instead of continuing with a broken startup path.
|
||||
|
||||
* Fri Mar 20 2026 Kevin Veen-Birkenbach <kevin@veen.world> - 1.13.1-1
|
||||
- Fixed misleading GPG verification failures by adding explicit git and gnupg runtime dependencies and surfacing signing-key lookup errors accurately.
|
||||
|
||||
* Fri Mar 20 2026 Kevin Veen-Birkenbach <kevin@veen.world> - 1.13.0-1
|
||||
- Set CentOS docker image to latest
|
||||
|
||||
* Tue Feb 24 2026 Kevin Veen-Birkenbach <kevin@veen.world> - 1.12.5-1
|
||||
- The stable-tag workflow now waits up to two hours for a successful main-branch CI run on the same commit before updating stable.
|
||||
|
||||
* Tue Feb 24 2026 Kevin Veen-Birkenbach <kevin@veen.world> - 1.12.4-1
|
||||
- The release pipeline now updates the stable tag only for v* tags after a successful CI run on main for the same commit, while avoiding duplicate test executions.
|
||||
|
||||
* Tue Feb 24 2026 Kevin Veen-Birkenbach <kevin@veen.world> - 1.12.3-1
|
||||
- Stabilized Nix-based builds by switching to nixos-25.11 and committing flake.lock, ensuring reproducible pkgmgr test/runtime environments (with pip) and avoiding transient sphinx/Python 3.11 breakage.
|
||||
|
||||
|
||||
+1
-1
@@ -7,7 +7,7 @@ build-backend = "setuptools.build_meta"
|
||||
|
||||
[project]
|
||||
name = "kpmx"
|
||||
version = "1.12.3"
|
||||
version = "1.13.2"
|
||||
description = "Kevin's package-manager tool (pkgmgr)"
|
||||
readme = "README.md"
|
||||
requires-python = ">=3.9"
|
||||
|
||||
@@ -5,7 +5,7 @@ set -euo pipefail
|
||||
: "${BASE_IMAGE_DEBIAN:=debian:stable-slim}"
|
||||
: "${BASE_IMAGE_UBUNTU:=ubuntu:latest}"
|
||||
: "${BASE_IMAGE_FEDORA:=fedora:latest}"
|
||||
: "${BASE_IMAGE_CENTOS:=quay.io/centos/centos:stream9}"
|
||||
: "${BASE_IMAGE_CENTOS:=quay.io/centos/centos:latest}"
|
||||
|
||||
resolve_base_image() {
|
||||
local PKGMGR_DISTRO="$1"
|
||||
|
||||
@@ -16,6 +16,7 @@ fi
|
||||
pacman -S --noconfirm --needed \
|
||||
base-devel \
|
||||
git \
|
||||
gnupg \
|
||||
rsync \
|
||||
curl \
|
||||
ca-certificates \
|
||||
|
||||
@@ -6,6 +6,7 @@ echo "[centos/dependencies] Installing CentOS build dependencies..."
|
||||
dnf -y update
|
||||
dnf -y install \
|
||||
git \
|
||||
gnupg2 \
|
||||
rsync \
|
||||
rpm-build \
|
||||
make \
|
||||
|
||||
@@ -9,6 +9,7 @@ DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \
|
||||
debhelper \
|
||||
dpkg-dev \
|
||||
git \
|
||||
gnupg \
|
||||
rsync \
|
||||
bash \
|
||||
curl \
|
||||
|
||||
@@ -6,6 +6,7 @@ echo "[fedora/dependencies] Installing Fedora build dependencies..."
|
||||
dnf -y update
|
||||
dnf -y install \
|
||||
git \
|
||||
gnupg2 \
|
||||
rsync \
|
||||
rpm-build \
|
||||
make \
|
||||
|
||||
@@ -9,6 +9,7 @@ DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \
|
||||
debhelper \
|
||||
dpkg-dev \
|
||||
git \
|
||||
gnupg \
|
||||
tzdata \
|
||||
lsb-release \
|
||||
rsync \
|
||||
|
||||
+7
-5
@@ -37,10 +37,16 @@ fi
|
||||
# ---------------------------------------------------------------------------
|
||||
if ! command -v nix >/dev/null 2>&1; then
|
||||
if [[ -x "${FLAKE_DIR}/nix/init.sh" ]]; then
|
||||
"${FLAKE_DIR}/nix/init.sh" || true
|
||||
"${FLAKE_DIR}/nix/init.sh"
|
||||
fi
|
||||
fi
|
||||
|
||||
if ! command -v nix >/dev/null 2>&1; then
|
||||
echo "[launcher] ERROR: 'nix' binary not found on PATH after init." >&2
|
||||
echo "[launcher] Nix is required to run pkgmgr (no Python fallback)." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Primary path: use Nix flake if available (with GitHub 403 retry)
|
||||
# ---------------------------------------------------------------------------
|
||||
@@ -51,7 +57,3 @@ if declare -F run_with_github_403_retry >/dev/null; then
|
||||
else
|
||||
exec nix run "${FLAKE_DIR}#pkgmgr" -- "$@"
|
||||
fi
|
||||
|
||||
echo "[launcher] ERROR: 'nix' binary not found on PATH after init."
|
||||
echo "[launcher] Nix is required to run pkgmgr (no Python fallback)."
|
||||
exit 1
|
||||
@@ -1,13 +1,33 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from ..errors import GitQueryError, GitRunError
|
||||
from ..run import run
|
||||
import subprocess
|
||||
|
||||
from ..errors import GitNotRepositoryError, GitQueryError
|
||||
|
||||
|
||||
class GitLatestSigningKeyQueryError(GitQueryError):
|
||||
"""Raised when querying the latest commit signing key fails."""
|
||||
|
||||
|
||||
def _is_not_repository(stderr: str) -> bool:
|
||||
return "not a git repository" in (stderr or "").lower()
|
||||
|
||||
|
||||
def _looks_like_gpg_runtime_error(stderr: str) -> bool:
|
||||
lowered = (stderr or "").lower()
|
||||
markers = (
|
||||
"cannot run gpg",
|
||||
"can't check signature",
|
||||
"no public key",
|
||||
"failed to create temporary file",
|
||||
"can't connect to the keyboxd",
|
||||
"error opening key db",
|
||||
"gpg failed",
|
||||
"no such file or directory",
|
||||
)
|
||||
return any(marker in lowered for marker in markers)
|
||||
|
||||
|
||||
def get_latest_signing_key(*, cwd: str = ".") -> str:
|
||||
"""
|
||||
Return the GPG signing key ID of the latest commit, via:
|
||||
@@ -17,9 +37,46 @@ def get_latest_signing_key(*, cwd: str = ".") -> str:
|
||||
Returns:
|
||||
The key id string (may be empty if commit is not signed).
|
||||
"""
|
||||
cmd = ["git", "log", "-1", "--format=%GK"]
|
||||
try:
|
||||
return run(["log", "-1", "--format=%GK"], cwd=cwd).strip()
|
||||
except GitRunError as exc:
|
||||
result = subprocess.run(
|
||||
cmd,
|
||||
cwd=cwd,
|
||||
check=False,
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE,
|
||||
text=True,
|
||||
)
|
||||
except OSError as exc:
|
||||
raise GitLatestSigningKeyQueryError(
|
||||
"Failed to query latest signing key.",
|
||||
"Failed to query latest signing key.\n"
|
||||
f"Command: {' '.join(cmd)}\n"
|
||||
f"Reason: {exc}"
|
||||
) from exc
|
||||
|
||||
stdout = (result.stdout or "").strip()
|
||||
stderr = (result.stderr or "").strip()
|
||||
|
||||
if result.returncode != 0:
|
||||
if _is_not_repository(stderr):
|
||||
raise GitNotRepositoryError(
|
||||
f"Not a git repository: {cwd!r}\n"
|
||||
f"Command: {' '.join(cmd)}\n"
|
||||
f"STDERR:\n{stderr}"
|
||||
)
|
||||
raise GitLatestSigningKeyQueryError(
|
||||
"Failed to query latest signing key.\n"
|
||||
f"Command: {' '.join(cmd)}\n"
|
||||
f"Exit code: {result.returncode}\n"
|
||||
f"STDOUT:\n{stdout}\n"
|
||||
f"STDERR:\n{stderr}"
|
||||
)
|
||||
|
||||
if not stdout and stderr and _looks_like_gpg_runtime_error(stderr):
|
||||
raise GitLatestSigningKeyQueryError(
|
||||
"Failed to query latest signing key.\n"
|
||||
f"Command: {' '.join(cmd)}\n"
|
||||
f"STDERR:\n{stderr}"
|
||||
)
|
||||
|
||||
return stdout
|
||||
@@ -16,6 +16,7 @@ def verify_repository(repo, repo_dir, mode="local", no_verification=False):
|
||||
|
||||
commit_hash = ""
|
||||
signing_key = ""
|
||||
signing_key_query_failed = False
|
||||
|
||||
# best-effort info collection
|
||||
try:
|
||||
@@ -59,6 +60,7 @@ def verify_repository(repo, repo_dir, mode="local", no_verification=False):
|
||||
except GitLatestSigningKeyQueryError as exc:
|
||||
error_details.append(str(exc))
|
||||
signing_key = ""
|
||||
signing_key_query_failed = True
|
||||
|
||||
commit_check_passed = True
|
||||
gpg_check_passed = True
|
||||
@@ -78,6 +80,7 @@ def verify_repository(repo, repo_dir, mode="local", no_verification=False):
|
||||
if expected_gpg_keys:
|
||||
if not signing_key:
|
||||
gpg_check_passed = False
|
||||
if not signing_key_query_failed:
|
||||
error_details.append(
|
||||
f"Expected one of GPG keys: {expected_gpg_keys}, but no signing key was found."
|
||||
)
|
||||
|
||||
@@ -0,0 +1,57 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import re
|
||||
import unittest
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
def _find_repo_root() -> Path:
|
||||
here = Path(__file__).resolve()
|
||||
for parent in here.parents:
|
||||
if (parent / "pyproject.toml").is_file() and (
|
||||
parent / "src" / "pkgmgr"
|
||||
).is_dir():
|
||||
return parent
|
||||
raise RuntimeError(
|
||||
"Could not determine repository root for pkgmgr integration test"
|
||||
)
|
||||
|
||||
|
||||
class TestGitVerificationRuntimeDependencies(unittest.TestCase):
|
||||
def test_flake_app_includes_git_and_gpg_runtime_tools(self) -> None:
|
||||
repo_root = _find_repo_root()
|
||||
flake_text = (repo_root / "flake.nix").read_text(encoding="utf-8")
|
||||
|
||||
self.assertIn("pkgs.git", flake_text)
|
||||
self.assertIn("pkgs.gnupg", flake_text)
|
||||
|
||||
def test_distro_dependency_scripts_install_gpg_tools(self) -> None:
|
||||
repo_root = _find_repo_root()
|
||||
expected_packages = {
|
||||
"arch": "gnupg",
|
||||
"debian": "gnupg",
|
||||
"ubuntu": "gnupg",
|
||||
"fedora": "gnupg2",
|
||||
"centos": "gnupg2",
|
||||
}
|
||||
|
||||
missing: list[str] = []
|
||||
for distro, package_name in expected_packages.items():
|
||||
script_path = (
|
||||
repo_root / "scripts" / "installation" / distro / "dependencies.sh"
|
||||
)
|
||||
content = script_path.read_text(encoding="utf-8")
|
||||
if not re.search(rf"\b{re.escape(package_name)}\b", content):
|
||||
missing.append(
|
||||
f"{distro}: expected package {package_name} in {script_path}"
|
||||
)
|
||||
|
||||
if missing:
|
||||
self.fail(
|
||||
"Git signature verification runtime dependencies are incomplete:\n"
|
||||
+ "\n".join(f" - {item}" for item in missing)
|
||||
)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
@@ -1,7 +1,8 @@
|
||||
import unittest
|
||||
import subprocess
|
||||
from unittest.mock import patch
|
||||
|
||||
from pkgmgr.core.git.errors import GitNotRepositoryError, GitRunError
|
||||
from pkgmgr.core.git.errors import GitNotRepositoryError
|
||||
from pkgmgr.core.git.queries.get_latest_signing_key import (
|
||||
GitLatestSigningKeyQueryError,
|
||||
get_latest_signing_key,
|
||||
@@ -10,25 +11,53 @@ from pkgmgr.core.git.queries.get_latest_signing_key import (
|
||||
|
||||
class TestGetLatestSigningKey(unittest.TestCase):
|
||||
@patch(
|
||||
"pkgmgr.core.git.queries.get_latest_signing_key.run",
|
||||
return_value="ABCDEF1234567890\n",
|
||||
"pkgmgr.core.git.queries.get_latest_signing_key.subprocess.run",
|
||||
return_value=subprocess.CompletedProcess(
|
||||
args=["git", "log", "-1", "--format=%GK"],
|
||||
returncode=0,
|
||||
stdout="ABCDEF1234567890\n",
|
||||
stderr="",
|
||||
),
|
||||
)
|
||||
def test_strips_output(self, _mock_run) -> None:
|
||||
out = get_latest_signing_key(cwd="/tmp/repo")
|
||||
self.assertEqual(out, "ABCDEF1234567890")
|
||||
|
||||
@patch(
|
||||
"pkgmgr.core.git.queries.get_latest_signing_key.run",
|
||||
side_effect=GitRunError("boom"),
|
||||
"pkgmgr.core.git.queries.get_latest_signing_key.subprocess.run",
|
||||
return_value=subprocess.CompletedProcess(
|
||||
args=["git", "log", "-1", "--format=%GK"],
|
||||
returncode=1,
|
||||
stdout="",
|
||||
stderr="boom",
|
||||
),
|
||||
)
|
||||
def test_wraps_git_run_error(self, _mock_run) -> None:
|
||||
with self.assertRaises(GitLatestSigningKeyQueryError):
|
||||
with self.assertRaisesRegex(GitLatestSigningKeyQueryError, "boom"):
|
||||
get_latest_signing_key(cwd="/tmp/repo")
|
||||
|
||||
@patch(
|
||||
"pkgmgr.core.git.queries.get_latest_signing_key.run",
|
||||
side_effect=GitNotRepositoryError("no repo"),
|
||||
"pkgmgr.core.git.queries.get_latest_signing_key.subprocess.run",
|
||||
return_value=subprocess.CompletedProcess(
|
||||
args=["git", "log", "-1", "--format=%GK"],
|
||||
returncode=128,
|
||||
stdout="",
|
||||
stderr="fatal: not a git repository",
|
||||
),
|
||||
)
|
||||
def test_does_not_catch_not_repository_error(self, _mock_run) -> None:
|
||||
with self.assertRaises(GitNotRepositoryError):
|
||||
get_latest_signing_key(cwd="/tmp/no-repo")
|
||||
|
||||
@patch(
|
||||
"pkgmgr.core.git.queries.get_latest_signing_key.subprocess.run",
|
||||
return_value=subprocess.CompletedProcess(
|
||||
args=["git", "log", "-1", "--format=%GK"],
|
||||
returncode=0,
|
||||
stdout="",
|
||||
stderr="error: cannot run gpg: No such file or directory",
|
||||
),
|
||||
)
|
||||
def test_raises_when_git_reports_gpg_runtime_error(self, _mock_run) -> None:
|
||||
with self.assertRaisesRegex(GitLatestSigningKeyQueryError, "cannot run gpg"):
|
||||
get_latest_signing_key(cwd="/tmp/repo")
|
||||
@@ -77,6 +77,23 @@ class TestVerifyRepository(unittest.TestCase):
|
||||
self.assertEqual(commit, "")
|
||||
self.assertEqual(key, "")
|
||||
|
||||
def test_verified_gpg_query_error_does_not_add_missing_key_fallback(self) -> None:
|
||||
repo = {"verified": {"commit": None, "gpg_keys": ["ABC"]}}
|
||||
with (
|
||||
patch("pkgmgr.core.repository.verify.get_head_commit", return_value=""),
|
||||
patch(
|
||||
"pkgmgr.core.repository.verify.get_latest_signing_key",
|
||||
side_effect=GitLatestSigningKeyQueryError("cannot run gpg"),
|
||||
),
|
||||
):
|
||||
ok, errors, commit, key = verify_repository(repo, "/tmp/repo", mode="local")
|
||||
|
||||
self.assertFalse(ok)
|
||||
self.assertIn("cannot run gpg", " ".join(errors))
|
||||
self.assertFalse(any("no signing key was found" in e for e in errors))
|
||||
self.assertEqual(commit, "")
|
||||
self.assertEqual(key, "")
|
||||
|
||||
def test_strict_pull_collects_remote_error_message(self) -> None:
|
||||
repo = {"verified": {"commit": "expected", "gpg_keys": None}}
|
||||
with (
|
||||
|
||||
Reference in new issue
Block a user