Files
dns-sync/tests/test_executor.py

1502 lines
42 KiB
Python

import unittest
from dataclasses import dataclass, field
from datetime import datetime, timezone
from dns_sync.adapters import (
CERTIFICATES,
ApplyResult,
DNS_RECORDS,
PROXY_HOSTS,
Capability,
CapabilityOperation,
Endpoint,
EndpointPlan,
NormalizedObject,
ObjectReference,
Observation,
PlanAction,
PlanOperation,
RetireRequest,
)
from dns_sync.executor import execute_endpoint_plans
from dns_sync.persistence import SQLiteStateStore
NOW = datetime(2026, 9, 19, 23, 15, tzinfo=timezone.utc)
def object_for(
object_type: str,
logical_key: str,
*,
references=(),
) -> NormalizedObject:
if object_type == DNS_RECORDS:
payload = {
"hostname": logical_key,
"ip": "192.168.1.101",
"record_type": "A",
"wildcard": False,
}
elif object_type == CERTIFICATES:
payload = {
"domain_names": [logical_key],
"expires_on": "2026-12-15T00:00:00+00:00",
}
else:
payload = {
"domain_names": [logical_key],
"forward_scheme": "http",
"forward_host": "192.168.1.50",
"forward_port": 8080,
"enabled": True,
}
return NormalizedObject(
object_type=object_type,
logical_key=logical_key,
payload=payload,
references=references,
)
def action(
endpoint_id: str,
operation: PlanOperation,
object_type: str = DNS_RECORDS,
logical_key: str = "app.example.com",
*,
references=(),
) -> PlanAction:
return PlanAction(
endpoint_id=endpoint_id,
operation=operation,
desired=object_for(
object_type,
logical_key,
references=references,
),
)
def capabilities(
*object_types: str,
verify: bool = True,
) -> frozenset[Capability]:
result = {
Capability(
object_type,
CapabilityOperation.APPLY,
)
for object_type in object_types
}
if verify:
result.update(
Capability(
object_type,
CapabilityOperation.VERIFY,
)
for object_type in object_types
)
return frozenset(result)
@dataclass
class FakeAdapter:
endpoint: Endpoint
capabilities: frozenset[Capability]
verify_result: bool = True
apply_error: Exception | None = None
verify_error: Exception | None = None
apply_local_keys: dict[
tuple[str, str],
str,
] = field(default_factory=dict)
applied_actions: list[PlanAction] = field(
default_factory=list
)
events: list[tuple[str, str, str]] = field(
default_factory=list
)
retired: list[RetireRequest] = field(
default_factory=list
)
def apply(self, plan_action):
desired = plan_action.desired
self.events.append(
(
"apply",
desired.object_type,
desired.logical_key,
)
)
if self.apply_error is not None:
raise self.apply_error
self.applied_actions.append(plan_action)
local_key = self.apply_local_keys.get(
(
desired.object_type,
desired.logical_key,
),
desired.local_key or "test-local",
)
return ApplyResult(
object=NormalizedObject(
object_type=desired.object_type,
logical_key=desired.logical_key,
local_key=local_key,
payload=dict(desired.payload),
references=desired.references,
),
detail="test detail",
)
def verify(self, object):
self.events.append(
(
"verify",
object.object_type,
object.logical_key,
)
)
if self.verify_error is not None:
raise self.verify_error
return self.verify_result
def retire(self, request):
self.events.append(
(
"retire",
request.object.object_type,
request.object.logical_key,
)
)
self.retired.append(request)
class ExecutionTests(unittest.TestCase):
def test_successful_actions_are_applied_verified_and_persisted(self):
endpoint = Endpoint("verizon", "192.168.1.1:443")
adapter = FakeAdapter(
endpoint,
capabilities(DNS_RECORDS),
)
plan = EndpointPlan(
endpoint.identity,
(
action(
endpoint.identity,
PlanOperation.CREATE,
logical_key="add.example.com",
),
action(
endpoint.identity,
PlanOperation.NOOP,
logical_key="keep.example.com",
),
action(
endpoint.identity,
PlanOperation.UPDATE,
logical_key="update.example.com",
),
),
)
with SQLiteStateStore(":memory:") as store:
result = execute_endpoint_plans(
[adapter],
[plan],
store,
executed_at=NOW,
)
persisted = store.load_applied_state(
endpoint.identity,
DNS_RECORDS,
)
self.assertEqual(len(result.applied), 2)
self.assertEqual(len(result.skipped), 1)
self.assertTrue(all(row.is_verified for row in persisted))
self.assertEqual(
{row.object.logical_key for row in persisted},
{"add.example.com", "update.example.com"},
)
self.assertEqual(
tuple(event[0] for event in adapter.events),
("apply", "verify", "apply", "verify"),
)
def test_replace_rebinds_dependent_before_retiring_old_object(self):
endpoint = Endpoint(
"npm",
"192.168.1.4:81",
)
class ReplacementAdapter(FakeAdapter):
def apply(self, plan_action):
result = super().apply(plan_action)
if (
plan_action.operation
== PlanOperation.REPLACE
):
return ApplyResult(
object=result.object,
detail="replacement",
retirements=(
RetireRequest(
object=plan_action.current,
),
),
)
return result
adapter = ReplacementAdapter(
endpoint,
capabilities(
CERTIFICATES,
PROXY_HOSTS,
),
apply_local_keys={
(
CERTIFICATES,
"app.example.com",
): "84",
(
PROXY_HOSTS,
"app.example.com",
): "12",
},
)
old_certificate = NormalizedObject(
object_type=CERTIFICATES,
logical_key="app.example.com",
local_key="67",
payload={
"domain_names": ["app.example.com"],
"valid_from": "2026-08-01T00:00:00+00:00",
"expires_on": "2026-11-01T00:00:00+00:00",
},
)
desired_certificate = NormalizedObject(
object_type=CERTIFICATES,
logical_key="app.example.com",
payload={
"domain_names": ["app.example.com"],
"valid_from": "2026-09-01T00:00:00+00:00",
"expires_on": "2026-12-01T00:00:00+00:00",
},
)
old_proxy = NormalizedObject(
object_type=PROXY_HOSTS,
logical_key="app.example.com",
local_key="12",
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,
local_key="67",
),
),
)
desired_proxy = NormalizedObject(
object_type=PROXY_HOSTS,
logical_key="app.example.com",
payload=dict(old_proxy.payload),
references=(
ObjectReference(
relation="certificate",
object_type=CERTIFICATES,
logical_key="app.example.com",
),
),
)
plan = EndpointPlan(
endpoint.identity,
(
PlanAction(
endpoint_id=endpoint.identity,
operation=PlanOperation.REPLACE,
desired=desired_certificate,
current=old_certificate,
),
PlanAction(
endpoint_id=endpoint.identity,
operation=PlanOperation.UPDATE,
desired=desired_proxy,
current=old_proxy,
),
),
)
with SQLiteStateStore(":memory:") as store:
store.replace_observations(
endpoint.identity,
CERTIFICATES,
[
Observation(
endpoint.identity,
old_certificate,
NOW,
)
],
NOW,
)
store.replace_observations(
endpoint.identity,
PROXY_HOSTS,
[
Observation(
endpoint.identity,
old_proxy,
NOW,
)
],
NOW,
)
execute_endpoint_plans(
[adapter],
[plan],
store,
executed_at=NOW,
)
self.assertEqual(
tuple(event[0] for event in adapter.events),
(
"apply",
"verify",
"apply",
"verify",
"retire",
),
)
proxy_apply = adapter.applied_actions[1]
self.assertEqual(
proxy_apply.desired.references,
(
ObjectReference(
relation="certificate",
object_type=CERTIFICATES,
local_key="84",
),
),
)
self.assertEqual(
adapter.retired,
[
RetireRequest(
object=old_certificate,
)
],
)
def test_restart_recovery_rebinds_before_planned_retirement(self):
endpoint = Endpoint(
"npm",
"192.168.1.4:81",
)
adapter = FakeAdapter(
endpoint,
capabilities(
CERTIFICATES,
PROXY_HOSTS,
),
apply_local_keys={
(
PROXY_HOSTS,
"app.example.com",
): "12",
},
)
old_certificate = NormalizedObject(
object_type=CERTIFICATES,
logical_key="app.example.com",
local_key="67",
payload={
"domain_names": ["app.example.com"],
"valid_from": "2026-08-01T00:00:00+00:00",
"expires_on": "2026-11-01T00:00:00+00:00",
},
)
new_certificate = NormalizedObject(
object_type=CERTIFICATES,
logical_key="app.example.com",
local_key="84",
payload={
"domain_names": ["app.example.com"],
"valid_from": "2026-09-01T00:00:00+00:00",
"expires_on": "2026-12-01T00:00:00+00:00",
},
)
old_proxy = NormalizedObject(
object_type=PROXY_HOSTS,
logical_key="app.example.com",
local_key="12",
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,
local_key="67",
),
),
)
desired_certificate = NormalizedObject(
object_type=CERTIFICATES,
logical_key="app.example.com",
payload=dict(new_certificate.payload),
)
desired_proxy = NormalizedObject(
object_type=PROXY_HOSTS,
logical_key="app.example.com",
payload=dict(old_proxy.payload),
references=(
ObjectReference(
relation="certificate",
object_type=CERTIFICATES,
logical_key="app.example.com",
),
),
)
plan = EndpointPlan(
endpoint.identity,
(
PlanAction(
endpoint_id=endpoint.identity,
operation=PlanOperation.NOOP,
desired=desired_certificate,
current=new_certificate,
),
PlanAction(
endpoint_id=endpoint.identity,
operation=PlanOperation.UPDATE,
desired=desired_proxy,
current=old_proxy,
reason=(
"referenced local object is being retired"
),
),
),
retirements=(
RetireRequest(
object=old_certificate,
),
),
)
with SQLiteStateStore(":memory:") as store:
store.replace_observations(
endpoint.identity,
CERTIFICATES,
[
Observation(
endpoint.identity,
old_certificate,
NOW,
),
Observation(
endpoint.identity,
new_certificate,
NOW,
),
],
NOW,
)
store.replace_observations(
endpoint.identity,
PROXY_HOSTS,
[
Observation(
endpoint.identity,
old_proxy,
NOW,
)
],
NOW,
)
result = execute_endpoint_plans(
[adapter],
[plan],
store,
executed_at=NOW,
)
self.assertEqual(
tuple(event[0] for event in adapter.events),
(
"apply",
"verify",
"retire",
),
)
proxy_action = adapter.applied_actions[0]
self.assertEqual(
proxy_action.desired.references,
(
ObjectReference(
relation="certificate",
object_type=CERTIFICATES,
local_key="84",
),
),
)
self.assertEqual(
adapter.retired,
[
RetireRequest(
object=old_certificate,
)
],
)
self.assertEqual(
result.retired,
(
(
endpoint.identity,
RetireRequest(
object=old_certificate,
),
),
),
)
def test_failed_verification_is_persisted_and_raises(self):
endpoint = Endpoint("verizon", "192.168.1.1:443")
adapter = FakeAdapter(
endpoint,
capabilities(DNS_RECORDS),
verify_result=False,
)
plan = EndpointPlan(
endpoint.identity,
(
action(
endpoint.identity,
PlanOperation.CREATE,
),
),
)
with SQLiteStateStore(":memory:") as store:
with self.assertRaisesRegex(
RuntimeError,
"verification failed",
):
execute_endpoint_plans(
[adapter],
[plan],
store,
executed_at=NOW,
)
persisted = store.load_applied_state()
self.assertEqual(len(persisted), 1)
self.assertFalse(persisted[0].is_verified)
self.assertEqual(
persisted[0].verification_error,
"adapter returned false",
)
def test_apply_failure_does_not_record_applied_state(self):
endpoint = Endpoint("verizon", "192.168.1.1:443")
adapter = FakeAdapter(
endpoint,
capabilities(DNS_RECORDS),
apply_error=RuntimeError("write failed"),
)
plan = EndpointPlan(
endpoint.identity,
(
action(
endpoint.identity,
PlanOperation.CREATE,
),
),
)
with SQLiteStateStore(":memory:") as store:
with self.assertRaisesRegex(RuntimeError, "write failed"):
execute_endpoint_plans(
[adapter],
[plan],
store,
executed_at=NOW,
)
self.assertEqual(store.load_applied_state(), [])
def test_preflight_requires_retire_for_planned_retirement_before_mutation(
self,
):
endpoint = Endpoint("npm", "192.168.1.4:81")
inner = FakeAdapter(
endpoint,
capabilities(CERTIFICATES),
)
class NoRetireAdapter:
def __init__(self, wrapped):
self.endpoint = wrapped.endpoint
self.capabilities = wrapped.capabilities
self.wrapped = wrapped
def apply(self, plan_action):
return self.wrapped.apply(plan_action)
def verify(self, object):
return self.wrapped.verify(object)
adapter = NoRetireAdapter(inner)
old_certificate = NormalizedObject(
object_type=CERTIFICATES,
logical_key="app.example.com",
local_key="67",
payload={
"domain_names": ["app.example.com"],
},
)
plan = EndpointPlan(
endpoint.identity,
(
action(
endpoint.identity,
PlanOperation.CREATE,
object_type=CERTIFICATES,
logical_key="other.example.com",
),
),
retirements=(
RetireRequest(
object=old_certificate,
),
),
)
with SQLiteStateStore(":memory:") as store:
with self.assertRaisesRegex(
RuntimeError,
"does not support deferred retirement",
):
execute_endpoint_plans(
[adapter],
[plan],
store,
executed_at=NOW,
)
self.assertEqual(
store.load_applied_state(),
[],
)
self.assertEqual(inner.events, [])
self.assertEqual(inner.applied_actions, [])
def test_preflight_requires_retire_for_replace_before_mutation(
self,
):
endpoint = Endpoint("npm", "192.168.1.4:81")
inner = FakeAdapter(
endpoint,
capabilities(CERTIFICATES),
)
class NoRetireAdapter:
def __init__(self, wrapped):
self.endpoint = wrapped.endpoint
self.capabilities = wrapped.capabilities
self.wrapped = wrapped
def apply(self, plan_action):
return self.wrapped.apply(plan_action)
def verify(self, object):
return self.wrapped.verify(object)
adapter = NoRetireAdapter(inner)
current = NormalizedObject(
object_type=CERTIFICATES,
logical_key="app.example.com",
local_key="67",
payload={
"domain_names": ["app.example.com"],
"expires_on": "2026-11-01T00:00:00+00:00",
},
)
desired = NormalizedObject(
object_type=CERTIFICATES,
logical_key="app.example.com",
payload={
"domain_names": ["app.example.com"],
"expires_on": "2026-12-01T00:00:00+00:00",
},
)
plan = EndpointPlan(
endpoint.identity,
(
PlanAction(
endpoint_id=endpoint.identity,
operation=PlanOperation.REPLACE,
desired=desired,
current=current,
),
),
)
with SQLiteStateStore(":memory:") as store:
with self.assertRaisesRegex(
RuntimeError,
"does not support deferred retirement",
):
execute_endpoint_plans(
[adapter],
[plan],
store,
executed_at=NOW,
)
self.assertEqual(
store.load_applied_state(),
[],
)
self.assertEqual(inner.events, [])
self.assertEqual(inner.applied_actions, [])
def test_preflight_requires_verify_before_any_mutation(self):
endpoint = Endpoint("verizon", "192.168.1.1:443")
adapter = FakeAdapter(
endpoint,
capabilities(DNS_RECORDS, verify=False),
)
plan = EndpointPlan(
endpoint.identity,
(
action(
endpoint.identity,
PlanOperation.CREATE,
),
),
)
with SQLiteStateStore(":memory:") as store:
with self.assertRaisesRegex(
RuntimeError,
"lacks verify capability",
):
execute_endpoint_plans(
[adapter],
[plan],
store,
executed_at=NOW,
)
self.assertEqual(adapter.events, [])
def test_certificates_execute_before_proxy_hosts(self):
events: list[tuple[str, str, str]] = []
endpoint = Endpoint("npm", "192.168.1.4:81")
adapter = FakeAdapter(
endpoint,
capabilities(CERTIFICATES, PROXY_HOSTS),
events=events,
)
plan = EndpointPlan(
endpoint.identity,
(
action(
endpoint.identity,
PlanOperation.CREATE,
PROXY_HOSTS,
"app.example.com",
references=(
ObjectReference(
relation="certificate",
object_type=CERTIFICATES,
logical_key="app.example.com",
),
),
),
action(
endpoint.identity,
PlanOperation.CREATE,
CERTIFICATES,
"app.example.com",
),
),
)
with SQLiteStateStore(":memory:") as store:
execute_endpoint_plans(
[adapter],
[plan],
store,
executed_at=NOW,
)
self.assertEqual(
tuple((event[0], event[1]) for event in events),
(
("apply", CERTIFICATES),
("verify", CERTIFICATES),
("apply", PROXY_HOSTS),
("verify", PROXY_HOSTS),
),
)
def test_existing_reference_resolves_from_observation(self):
endpoint = Endpoint("npm", "192.168.1.4:81")
adapter = FakeAdapter(
endpoint,
capabilities(PROXY_HOSTS),
)
reference = ObjectReference(
relation="certificate",
object_type=CERTIFICATES,
logical_key="app.example.com",
)
proxy_action = action(
endpoint.identity,
PlanOperation.CREATE,
PROXY_HOSTS,
"app.example.com",
references=(reference,),
)
observed_certificate = NormalizedObject(
object_type=CERTIFICATES,
logical_key="app.example.com",
local_key="67",
payload={
"domain_names": ["app.example.com"],
"expires_on": "2026-12-15T00:00:00+00:00",
},
)
with SQLiteStateStore(":memory:") as store:
store.replace_observations(
endpoint.identity,
CERTIFICATES,
[
Observation(
endpoint.identity,
observed_certificate,
NOW,
),
],
NOW,
)
execute_endpoint_plans(
[adapter],
[
EndpointPlan(
endpoint.identity,
(proxy_action,),
)
],
store,
executed_at=NOW,
)
applied_reference = (
adapter.applied_actions[0]
.desired
.references[0]
)
self.assertEqual(applied_reference.local_key, "67")
self.assertIsNone(applied_reference.logical_key)
def test_created_reference_uses_apply_result_local_key(self):
endpoint = Endpoint("npm", "192.168.1.4:81")
adapter = FakeAdapter(
endpoint,
capabilities(CERTIFICATES, PROXY_HOSTS),
apply_local_keys={
(
CERTIFICATES,
"app.example.com",
): "68",
(
PROXY_HOSTS,
"app.example.com",
): "12",
},
)
certificate_action = action(
endpoint.identity,
PlanOperation.CREATE,
CERTIFICATES,
"app.example.com",
)
proxy_action = action(
endpoint.identity,
PlanOperation.CREATE,
PROXY_HOSTS,
"app.example.com",
references=(
ObjectReference(
relation="certificate",
object_type=CERTIFICATES,
logical_key="app.example.com",
),
),
)
with SQLiteStateStore(":memory:") as store:
execute_endpoint_plans(
[adapter],
[
EndpointPlan(
endpoint.identity,
(
proxy_action,
certificate_action,
),
)
],
store,
executed_at=NOW,
)
persisted = store.load_applied_state(
endpoint.identity,
)
self.assertEqual(
tuple(
item.desired.object_type
for item in adapter.applied_actions
),
(CERTIFICATES, PROXY_HOSTS),
)
resolved = (
adapter.applied_actions[1]
.desired
.references[0]
)
self.assertEqual(resolved.local_key, "68")
self.assertIsNone(resolved.logical_key)
persisted_by_type = {
row.object.object_type: row.object
for row in persisted
}
self.assertEqual(
persisted_by_type[CERTIFICATES].local_key,
"68",
)
self.assertEqual(
persisted_by_type[PROXY_HOSTS]
.references[0]
.local_key,
"68",
)
def test_unresolved_reference_fails_before_dependent_apply(self):
endpoint = Endpoint("npm", "192.168.1.4:81")
adapter = FakeAdapter(
endpoint,
capabilities(PROXY_HOSTS),
)
proxy_action = action(
endpoint.identity,
PlanOperation.CREATE,
PROXY_HOSTS,
"app.example.com",
references=(
ObjectReference(
relation="certificate",
object_type=CERTIFICATES,
logical_key="missing.example.com",
),
),
)
with SQLiteStateStore(":memory:") as store:
with self.assertRaisesRegex(
RuntimeError,
"Unresolved endpoint-local reference",
):
execute_endpoint_plans(
[adapter],
[
EndpointPlan(
endpoint.identity,
(proxy_action,),
)
],
store,
executed_at=NOW,
)
self.assertEqual(
store.load_applied_state(),
[],
)
self.assertEqual(adapter.events, [])
self.assertEqual(adapter.applied_actions, [])
def test_existing_reference_falls_back_to_verified_applied_state(self):
endpoint = Endpoint("npm", "192.168.1.4:81")
adapter = FakeAdapter(
endpoint,
capabilities(PROXY_HOSTS),
)
certificate = NormalizedObject(
object_type=CERTIFICATES,
logical_key="app.example.com",
local_key="71",
payload={
"domain_names": ["app.example.com"],
},
)
proxy_action = action(
endpoint.identity,
PlanOperation.CREATE,
PROXY_HOSTS,
"app.example.com",
references=(
ObjectReference(
relation="certificate",
object_type=CERTIFICATES,
logical_key="app.example.com",
),
),
)
with SQLiteStateStore(":memory:") as store:
store.record_applied(
endpoint.identity,
certificate,
NOW,
)
store.record_verification_success(
endpoint.identity,
certificate,
NOW,
)
execute_endpoint_plans(
[adapter],
[
EndpointPlan(
endpoint.identity,
(proxy_action,),
)
],
store,
executed_at=NOW,
)
resolved = (
adapter.applied_actions[0]
.desired
.references[0]
)
self.assertEqual(resolved.local_key, "71")
def test_ambiguous_logical_reference_fails_closed(self):
endpoint = Endpoint("npm", "192.168.1.4:81")
adapter = FakeAdapter(
endpoint,
capabilities(PROXY_HOSTS),
)
proxy_action = action(
endpoint.identity,
PlanOperation.CREATE,
PROXY_HOSTS,
"app.example.com",
references=(
ObjectReference(
relation="certificate",
object_type=CERTIFICATES,
logical_key="app.example.com",
),
),
)
first = NormalizedObject(
object_type=CERTIFICATES,
logical_key="app.example.com",
local_key="63",
payload={
"domain_names": ["app.example.com"],
},
)
second = NormalizedObject(
object_type=CERTIFICATES,
logical_key="app.example.com",
local_key="67",
payload={
"domain_names": ["app.example.com"],
},
)
with SQLiteStateStore(":memory:") as store:
store.replace_observations(
endpoint.identity,
CERTIFICATES,
[
Observation(
endpoint.identity,
first,
NOW,
),
Observation(
endpoint.identity,
second,
NOW,
),
],
NOW,
)
with self.assertRaisesRegex(
RuntimeError,
"Ambiguous endpoint-local reference",
):
execute_endpoint_plans(
[adapter],
[
EndpointPlan(
endpoint.identity,
(proxy_action,),
)
],
store,
executed_at=NOW,
)
self.assertEqual(adapter.events, [])
self.assertEqual(adapter.applied_actions, [])
def test_unrelated_duplicate_identity_does_not_block_apply(self):
endpoint = Endpoint("npm", "192.168.1.4:81")
adapter = FakeAdapter(
endpoint,
capabilities(PROXY_HOSTS),
)
proxy_action = action(
endpoint.identity,
PlanOperation.CREATE,
PROXY_HOSTS,
"unrelated.example.com",
)
first = NormalizedObject(
object_type=CERTIFICATES,
logical_key="duplicate.example.com",
local_key="63",
payload={
"domain_names": ["duplicate.example.com"],
},
)
second = NormalizedObject(
object_type=CERTIFICATES,
logical_key="duplicate.example.com",
local_key="67",
payload={
"domain_names": ["duplicate.example.com"],
},
)
with SQLiteStateStore(":memory:") as store:
store.replace_observations(
endpoint.identity,
CERTIFICATES,
[
Observation(
endpoint.identity,
first,
NOW,
),
Observation(
endpoint.identity,
second,
NOW,
),
],
NOW,
)
result = execute_endpoint_plans(
[adapter],
[
EndpointPlan(
endpoint.identity,
(proxy_action,),
)
],
store,
executed_at=NOW,
)
self.assertEqual(len(result.applied), 1)
self.assertEqual(len(adapter.applied_actions), 1)
def test_unknown_local_reference_is_not_trusted(self):
endpoint = Endpoint("npm", "192.168.1.4:81")
adapter = FakeAdapter(
endpoint,
capabilities(PROXY_HOSTS),
)
proxy_action = action(
endpoint.identity,
PlanOperation.CREATE,
PROXY_HOSTS,
"app.example.com",
references=(
ObjectReference(
relation="certificate",
object_type=CERTIFICATES,
local_key="999",
),
),
)
with SQLiteStateStore(":memory:") as store:
with self.assertRaisesRegex(
RuntimeError,
"local key '999'",
):
execute_endpoint_plans(
[adapter],
[
EndpointPlan(
endpoint.identity,
(proxy_action,),
)
],
store,
executed_at=NOW,
)
self.assertEqual(adapter.events, [])
self.assertEqual(adapter.applied_actions, [])
def test_known_local_reference_is_preserved(self):
endpoint = Endpoint("npm", "192.168.1.4:81")
adapter = FakeAdapter(
endpoint,
capabilities(PROXY_HOSTS),
)
certificate = NormalizedObject(
object_type=CERTIFICATES,
logical_key="app.example.com",
local_key="67",
payload={
"domain_names": ["app.example.com"],
},
)
local_reference = ObjectReference(
relation="certificate",
object_type=CERTIFICATES,
local_key="67",
)
proxy_action = action(
endpoint.identity,
PlanOperation.CREATE,
PROXY_HOSTS,
"app.example.com",
references=(local_reference,),
)
with SQLiteStateStore(":memory:") as store:
store.replace_observations(
endpoint.identity,
CERTIFICATES,
[
Observation(
endpoint.identity,
certificate,
NOW,
),
],
NOW,
)
execute_endpoint_plans(
[adapter],
[
EndpointPlan(
endpoint.identity,
(proxy_action,),
)
],
store,
executed_at=NOW,
)
self.assertEqual(
adapter.applied_actions[0].desired.references,
(local_reference,),
)
def test_dependency_cycle_fails_before_any_mutation(self):
endpoint = Endpoint("generic", "node.example:443")
object_a = "object_a"
object_b = "object_b"
adapter = FakeAdapter(
endpoint,
capabilities(object_a, object_b),
)
action_a = PlanAction(
endpoint_id=endpoint.identity,
operation=PlanOperation.CREATE,
desired=NormalizedObject(
object_type=object_a,
logical_key="a",
payload={},
references=(
ObjectReference(
relation="b",
object_type=object_b,
logical_key="b",
),
),
),
)
action_b = PlanAction(
endpoint_id=endpoint.identity,
operation=PlanOperation.CREATE,
desired=NormalizedObject(
object_type=object_b,
logical_key="b",
payload={},
references=(
ObjectReference(
relation="a",
object_type=object_a,
logical_key="a",
),
),
),
)
with SQLiteStateStore(":memory:") as store:
with self.assertRaisesRegex(
RuntimeError,
"dependency cycle",
):
execute_endpoint_plans(
[adapter],
[
EndpointPlan(
endpoint.identity,
(
action_a,
action_b,
),
)
],
store,
executed_at=NOW,
)
self.assertEqual(
store.load_applied_state(),
[],
)
self.assertEqual(adapter.events, [])
self.assertEqual(adapter.applied_actions, [])
def test_invalid_plan_fails_preflight_without_mutation(self):
endpoint = Endpoint("verizon", "192.168.1.1:443")
adapter = FakeAdapter(
endpoint,
capabilities(DNS_RECORDS),
)
invalid = EndpointPlan(
endpoint.identity,
(
action(
"verizon://different:443",
PlanOperation.CREATE,
),
),
)
with SQLiteStateStore(":memory:") as store:
with self.assertRaisesRegex(
RuntimeError,
"does not match",
):
execute_endpoint_plans(
[adapter],
[invalid],
store,
executed_at=NOW,
)
self.assertEqual(adapter.events, [])
if __name__ == "__main__":
unittest.main()