Document packaging and release artifacts
This commit is contained in:
parent
aee984d862
commit
e0e5cd62d9
14 changed files with 470 additions and 13 deletions
146
tests/test_packaging.py
Normal file
146
tests/test_packaging.py
Normal file
|
|
@ -0,0 +1,146 @@
|
|||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
"""Static checks for package metadata and install hardening."""
|
||||
import hashlib
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
import subprocess
|
||||
import tempfile
|
||||
import tomllib
|
||||
import unittest
|
||||
from pathlib import Path
|
||||
|
||||
from enodia_sentinel import __version__
|
||||
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
|
||||
|
||||
def _read(path: str) -> str:
|
||||
return (ROOT / path).read_text()
|
||||
|
||||
|
||||
class TestArchPackaging(unittest.TestCase):
|
||||
def test_pkgbuild_version_matches_project(self):
|
||||
pkgbuild = _read("packaging/PKGBUILD")
|
||||
pyproject = tomllib.loads(_read("pyproject.toml"))
|
||||
docs_version = json.loads(_read("docs/VERSION.json"))
|
||||
|
||||
pkgver = re.search(r"^pkgver=([^\n]+)$", pkgbuild, re.M)
|
||||
self.assertIsNotNone(pkgver)
|
||||
self.assertEqual(pkgver.group(1), pyproject["project"]["version"])
|
||||
self.assertEqual(pkgver.group(1), __version__)
|
||||
self.assertEqual(docs_version["package_version"], pkgver.group(1))
|
||||
|
||||
def test_pkgbuild_installs_hardened_runtime_files(self):
|
||||
pkgbuild = _read("packaging/PKGBUILD")
|
||||
for expected in (
|
||||
"/usr/bin/enodia-sentinel",
|
||||
"/usr/lib/systemd/system/enodia-sentinel.service",
|
||||
"/usr/lib/systemd/system/enodia-sentinel-web.service",
|
||||
"/usr/share/libalpm/hooks/enodia-sentinel-fim.hook",
|
||||
"/usr/lib/tmpfiles.d/enodia-sentinel.conf",
|
||||
"/usr/share/doc/$pkgname/PACKAGING.md",
|
||||
"/usr/share/doc/$pkgname/VERSION.json",
|
||||
"/usr/share/doc/$pkgname/examples/enodia-sentinel-ebpf.conf",
|
||||
"/var/log/enodia-sentinel",
|
||||
):
|
||||
self.assertIn(expected, pkgbuild)
|
||||
self.assertIn("backup=('etc/enodia-sentinel.toml')", pkgbuild)
|
||||
self.assertIn("install -dm750", pkgbuild)
|
||||
|
||||
def test_alpm_hook_uses_absolute_sentinel_path(self):
|
||||
hook = _read("packaging/enodia-sentinel-fim.hook")
|
||||
self.assertIn("Exec = /usr/bin/enodia-sentinel fim-update", hook)
|
||||
self.assertNotIn("/usr/bin/env enodia-sentinel", hook)
|
||||
self.assertIn("When = PostTransaction", hook)
|
||||
|
||||
def test_tmpfiles_declares_private_log_dir(self):
|
||||
tmpfiles = _read("packaging/enodia-sentinel.tmpfiles")
|
||||
self.assertIn("d /var/log/enodia-sentinel 0750 root root -", tmpfiles)
|
||||
|
||||
def test_build_script_uses_clean_package_build(self):
|
||||
script = _read("packaging/build-package.sh")
|
||||
self.assertIn("set -eu", script)
|
||||
self.assertIn("command -v makepkg", script)
|
||||
self.assertIn("--cleanbuild", script)
|
||||
|
||||
|
||||
class TestReleaseArtifacts(unittest.TestCase):
|
||||
def test_release_script_generates_checksummed_artifacts(self):
|
||||
with tempfile.TemporaryDirectory() as d:
|
||||
env = os.environ.copy()
|
||||
env.update({
|
||||
"DISTDIR": d,
|
||||
"RELEASE_ALLOW_DIRTY": "1",
|
||||
"RELEASE_SIGN": "0",
|
||||
})
|
||||
result = subprocess.run(
|
||||
[str(ROOT / "packaging" / "release-artifacts.sh")],
|
||||
cwd=ROOT,
|
||||
env=env,
|
||||
capture_output=True,
|
||||
text=True,
|
||||
timeout=30,
|
||||
check=True,
|
||||
)
|
||||
self.assertIn("Signing skipped", result.stdout)
|
||||
|
||||
version = tomllib.loads(_read("pyproject.toml"))["project"]["version"]
|
||||
archive = Path(d) / f"enodia-sentinel-{version}.tar.gz"
|
||||
manifest = Path(d) / "RELEASE_MANIFEST.json"
|
||||
checksums = Path(d) / "SHA256SUMS"
|
||||
|
||||
self.assertTrue(archive.is_file())
|
||||
self.assertTrue(manifest.is_file())
|
||||
self.assertTrue(checksums.is_file())
|
||||
self.assertFalse((Path(d) / "SHA256SUMS.asc").exists())
|
||||
|
||||
data = json.loads(manifest.read_text())
|
||||
self.assertEqual(data["project"], "enodia-sentinel")
|
||||
self.assertEqual(data["version"], version)
|
||||
self.assertEqual(data["dirty"], 1)
|
||||
self.assertIn(archive.name, data["artifacts"])
|
||||
self.assertIn("SHA256SUMS", data["artifacts"])
|
||||
|
||||
expected = {
|
||||
archive.name: hashlib.sha256(archive.read_bytes()).hexdigest(),
|
||||
manifest.name: hashlib.sha256(manifest.read_bytes()).hexdigest(),
|
||||
}
|
||||
actual = {}
|
||||
for line in checksums.read_text().splitlines():
|
||||
digest, name = line.split(maxsplit=1)
|
||||
actual[name] = digest
|
||||
self.assertEqual(actual, expected)
|
||||
|
||||
def test_release_script_supports_gpg_signing(self):
|
||||
script = _read("packaging/release-artifacts.sh")
|
||||
self.assertIn("RELEASE_SIGN", script)
|
||||
self.assertIn("RELEASE_SIGNING_KEY", script)
|
||||
self.assertIn("--armor --detach-sign", script)
|
||||
self.assertIn("SHA256SUMS.asc", script)
|
||||
|
||||
|
||||
class TestSourceInstallPackaging(unittest.TestCase):
|
||||
def test_makefile_installs_docs_and_tmpfiles(self):
|
||||
makefile = _read("Makefile")
|
||||
self.assertIn("TMPFILESDIR := /etc/tmpfiles.d", makefile)
|
||||
self.assertIn("packaging/enodia-sentinel.tmpfiles", makefile)
|
||||
self.assertIn("docs/PACKAGING.md", makefile)
|
||||
self.assertIn("docs/VERSION.json", makefile)
|
||||
self.assertIn("systemd/enodia-sentinel-ebpf.conf", makefile)
|
||||
|
||||
def test_packaging_guide_documents_distro_paths(self):
|
||||
guide = _read("docs/PACKAGING.md")
|
||||
for phrase in (
|
||||
"Arch is the primary packaged target",
|
||||
"sudo pacman -U enodia-sentinel-*.pkg.tar.zst",
|
||||
"There is no native `.deb` package yet",
|
||||
"There is no native RPM package yet",
|
||||
"Package-manager hooks use absolute paths",
|
||||
):
|
||||
self.assertIn(phrase, guide)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
Loading…
Add table
Add a link
Reference in a new issue