Recover interrupted certificate replacement

This commit is contained in:
Lyncoln
2026-09-20 01:54:30 -04:00
parent 2668651a98
commit 387332b5db
5 changed files with 658 additions and 21 deletions
+211
View File
@@ -615,6 +615,217 @@ class EndpointPlanningTests(unittest.TestCase):
)
def test_restart_duplicate_certificate_uses_desired_copy_and_rebinds_proxy(self):
endpoint = Endpoint(
"generic",
"node.example:443",
)
generic_adapter = adapter(
"generic",
"node.example:443",
CERTIFICATES,
PROXY_HOSTS,
)
desired_certificate = NormalizedObject(
object_type=CERTIFICATES,
logical_key="app.example.com",
payload={
"domain_names": ["app.example.com"],
"provider": "letsencrypt",
"valid_from": "2026-09-20T00:00:00+00:00",
"expires_on": "2026-12-20T00:00:00+00:00",
},
)
desired_proxy = NormalizedObject(
object_type=PROXY_HOSTS,
logical_key="app.example.com",
payload={
"domain_names": ["app.example.com"],
"forward_scheme": "https",
"forward_host": "backend.internal",
"forward_port": 443,
"enabled": True,
},
references=(
ObjectReference(
relation="certificate",
object_type=CERTIFICATES,
logical_key="app.example.com",
),
),
)
old_certificate = NormalizedObject(
object_type=CERTIFICATES,
logical_key="app.example.com",
local_key="67",
payload={
"domain_names": ["app.example.com"],
"provider": "other",
"valid_from": "2026-08-20T00:00:00+00:00",
"expires_on": "2026-11-20T00:00:00+00:00",
},
)
new_certificate = NormalizedObject(
object_type=CERTIFICATES,
logical_key="app.example.com",
local_key="84",
payload={
"domain_names": ["app.example.com"],
"provider": "other",
"valid_from": "2026-09-20T00:00:00+00:00",
"expires_on": "2026-12-20T00:00:00+00:00",
},
)
current_proxy = NormalizedObject(
object_type=PROXY_HOSTS,
logical_key="app.example.com",
local_key="12",
payload=dict(desired_proxy.payload),
references=(
ObjectReference(
relation="certificate",
object_type=CERTIFICATES,
local_key="67",
),
),
)
plans = build_endpoint_plans(
[generic_adapter],
[
desired_certificate,
desired_proxy,
],
[
observed(
endpoint.identity,
old_certificate,
),
observed(
endpoint.identity,
new_certificate,
),
observed(
endpoint.identity,
current_proxy,
),
],
)
self.assertEqual(len(plans), 1)
plan = plans[0]
by_type = {
action.desired.object_type: action
for action in plan.actions
}
self.assertEqual(
by_type[CERTIFICATES].operation,
PlanOperation.NOOP,
)
self.assertEqual(
by_type[CERTIFICATES].current.local_key,
"84",
)
self.assertEqual(
by_type[PROXY_HOSTS].operation,
PlanOperation.UPDATE,
)
self.assertEqual(
by_type[PROXY_HOSTS].reason,
"referenced local object is being retired",
)
self.assertEqual(
tuple(
retirement.object.local_key
for retirement in plan.retirements
),
("67",),
)
def test_equivalent_duplicate_preserves_letsencrypt_copy(self):
endpoint = Endpoint(
"generic",
"node.example:443",
)
generic_adapter = adapter(
"generic",
"node.example:443",
CERTIFICATES,
)
desired = NormalizedObject(
object_type=CERTIFICATES,
logical_key="app.example.com",
payload={
"domain_names": ["app.example.com"],
"valid_from": "2026-09-20T00:00:00+00:00",
"expires_on": "2026-12-20T00:00:00+00:00",
},
)
imported = NormalizedObject(
object_type=CERTIFICATES,
logical_key="app.example.com",
local_key="90",
payload={
"domain_names": ["app.example.com"],
"provider": "other",
"valid_from": "2026-09-20T00:00:00+00:00",
"expires_on": "2026-12-20T00:00:00+00:00",
},
)
native = NormalizedObject(
object_type=CERTIFICATES,
logical_key="app.example.com",
local_key="12",
payload={
"domain_names": ["app.example.com"],
"provider": "letsencrypt",
"valid_from": "2026-09-20T00:00:00+00:00",
"expires_on": "2026-12-20T00:00:00+00:00",
},
)
plans = build_endpoint_plans(
[generic_adapter],
[desired],
[
observed(endpoint.identity, imported),
observed(endpoint.identity, native),
],
)
action = plans[0].actions[0]
self.assertEqual(
action.operation,
PlanOperation.NOOP,
)
self.assertEqual(
action.current.local_key,
"12",
)
self.assertEqual(
tuple(
retirement.object.local_key
for retirement in plans[0].retirements
),
("90",),
)
def test_duplicate_endpoint_objects_fail_without_payload_output(self):
desired = [
dns_object("app.example.com", "192.168.1.101")