Files
computer-playbook/tests/unit/python/library/test_cert_link_domains.py
T
kevinveenbirkenbachandClaude Opus 5.5 a32f241051 fix(sys-svc-certs): link each domain to the certbundle lineage that covers it
certbundle names its lineages certbundle-<hash>-<n>, while the cert lookup
reads live/<domain>/fullchain.pem. Since 16023dbc9b removed cert_folder_find,
nothing joined the two, so every letsencrypt deploy with the SAN flavor
stopped at "Let's Encrypt certificate files not found after issuance".

library/cert_link_domains.py points live/<domain> at the newest lineage
whose SAN covers the domain, using the same CertUtils lookup as
cert_check_exists, and swaps the link atomically. A real lineage directory
is left alone, and a domain without a certificate is only reported, so the
letsencrypt re-check still fails loudly on it.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-23 16:05:27 +02:00

80 lines
2.5 KiB
Python

from __future__ import annotations
import importlib.util
import sys
import tempfile
import unittest
import unittest.mock as mock
from pathlib import Path
from typing import TYPE_CHECKING
from . import PROJECT_ROOT
if TYPE_CHECKING:
from types import ModuleType
sys.path.insert(0, str(PROJECT_ROOT))
from utils import cert_utils
MODULE_PATH = PROJECT_ROOT / "library" / "cert_link_domains.py"
def _load_cert_link_domains() -> ModuleType:
sys.modules.setdefault("ansible.module_utils.cert_utils", cert_utils)
spec = importlib.util.spec_from_file_location(
"library_cert_link_domains", str(MODULE_PATH)
)
if spec is None or spec.loader is None:
raise ImportError(f"Could not load: {MODULE_PATH}")
mod = importlib.util.module_from_spec(spec)
spec.loader.exec_module(mod) # type: ignore[attr-defined]
return mod
_MOD = _load_cert_link_domains()
class LinkDomainTests(unittest.TestCase):
def setUp(self):
self._tmp = tempfile.TemporaryDirectory()
self.base = Path(self._tmp.name)
(self.base / "certbundle-abc-00001").mkdir()
(self.base / "certbundle-abc-00002").mkdir()
self.link = self.base / "meet.example"
def tearDown(self):
self._tmp.cleanup()
def _link(self, folder):
with mock.patch.object(
_MOD.CertUtils, "find_cert_for_domain", return_value=folder
):
return _MOD.link_domain("meet.example", str(self.base))
def test_links_domain_to_covering_bundle(self):
self.assertEqual(self._link("certbundle-abc-00001"), "linked")
self.assertEqual(str(self.link.readlink()), "certbundle-abc-00001")
def test_repoints_link_to_newer_bundle(self):
self.link.symlink_to("certbundle-abc-00001")
self.assertEqual(self._link("certbundle-abc-00002"), "linked")
self.assertEqual(str(self.link.readlink()), "certbundle-abc-00002")
def test_keeps_link_already_on_covering_bundle(self):
self.link.symlink_to("certbundle-abc-00001")
self.assertEqual(self._link("certbundle-abc-00001"), "kept")
def test_keeps_dedicated_lineage_directory(self):
self.link.mkdir()
self.assertEqual(self._link("certbundle-abc-00001"), "kept")
self.assertFalse(self.link.is_symlink())
def test_reports_domain_without_covering_cert(self):
self.assertEqual(self._link(None), "missing")
self.assertFalse(self.link.exists(follow_symlinks=False))
if __name__ == "__main__":
unittest.main()