Enable safe NPM replica convergence
This commit is contained in:
@@ -655,6 +655,148 @@ class ExecutionTests(unittest.TestCase):
|
||||
)
|
||||
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(
|
||||
|
||||
Reference in New Issue
Block a user