Expose persistent read-only reconciliation CLI
This commit is contained in:
@@ -0,0 +1,186 @@
|
||||
import io
|
||||
import os
|
||||
import tempfile
|
||||
import unittest
|
||||
from contextlib import redirect_stderr, redirect_stdout
|
||||
from pathlib import Path
|
||||
from types import SimpleNamespace
|
||||
from unittest.mock import patch
|
||||
|
||||
from dns_sync.adapters import (
|
||||
DNS_RECORDS,
|
||||
PROXY_HOSTS,
|
||||
Capability,
|
||||
CapabilityOperation,
|
||||
DiscoveredObject,
|
||||
Endpoint,
|
||||
)
|
||||
from dns_sync.cli import build_parser, run_reconcile
|
||||
|
||||
|
||||
class FakeAdapter:
|
||||
def __init__(
|
||||
self,
|
||||
endpoint,
|
||||
capabilities,
|
||||
discovered,
|
||||
):
|
||||
self.endpoint = endpoint
|
||||
self.capabilities = capabilities
|
||||
self.discovered = discovered
|
||||
self.apply_count = 0
|
||||
self.verify_count = 0
|
||||
|
||||
def connect(self):
|
||||
pass
|
||||
|
||||
def discover(self, object_type):
|
||||
return list(self.discovered.get(object_type, []))
|
||||
|
||||
def apply(self, action):
|
||||
self.apply_count += 1
|
||||
raise AssertionError("read-only CLI called apply")
|
||||
|
||||
def verify(self, object):
|
||||
self.verify_count += 1
|
||||
raise AssertionError("read-only CLI called verify")
|
||||
|
||||
|
||||
class ReconcileCliTests(unittest.TestCase):
|
||||
def test_parser_exposes_read_only_reconcile_command(self):
|
||||
args = build_parser().parse_args(
|
||||
[
|
||||
"reconcile",
|
||||
"--state-db",
|
||||
"/tmp/test-state.sqlite3",
|
||||
"--include-disabled",
|
||||
]
|
||||
)
|
||||
|
||||
self.assertEqual(args.command, "reconcile")
|
||||
self.assertEqual(
|
||||
args.state_db,
|
||||
"/tmp/test-state.sqlite3",
|
||||
)
|
||||
self.assertTrue(args.include_disabled)
|
||||
|
||||
def test_reconcile_builds_plan_without_applying_it(self):
|
||||
npm_endpoint = Endpoint("npm", "192.168.1.3:81")
|
||||
verizon_endpoint = Endpoint(
|
||||
"verizon",
|
||||
"192.168.1.1:443",
|
||||
)
|
||||
npm = FakeAdapter(
|
||||
npm_endpoint,
|
||||
frozenset(
|
||||
{
|
||||
Capability(
|
||||
PROXY_HOSTS,
|
||||
CapabilityOperation.DISCOVER,
|
||||
)
|
||||
}
|
||||
),
|
||||
{
|
||||
PROXY_HOSTS: [
|
||||
DiscoveredObject(
|
||||
object_type=PROXY_HOSTS,
|
||||
local_key="12",
|
||||
payload={
|
||||
"domain_names": [
|
||||
"app.example.com"
|
||||
],
|
||||
"forward_scheme": "http",
|
||||
"forward_host": "192.168.1.50",
|
||||
"forward_port": 8080,
|
||||
"enabled": True,
|
||||
},
|
||||
)
|
||||
]
|
||||
},
|
||||
)
|
||||
verizon = FakeAdapter(
|
||||
verizon_endpoint,
|
||||
frozenset(
|
||||
{
|
||||
Capability(
|
||||
DNS_RECORDS,
|
||||
CapabilityOperation.DISCOVER,
|
||||
),
|
||||
Capability(
|
||||
DNS_RECORDS,
|
||||
CapabilityOperation.APPLY,
|
||||
),
|
||||
}
|
||||
),
|
||||
{
|
||||
DNS_RECORDS: [
|
||||
DiscoveredObject(
|
||||
object_type=DNS_RECORDS,
|
||||
local_key="41",
|
||||
payload={
|
||||
"hostname": "app.example.com",
|
||||
"ip": "192.168.1.50",
|
||||
"record_type": "A",
|
||||
},
|
||||
)
|
||||
]
|
||||
},
|
||||
)
|
||||
adapters = {
|
||||
npm_endpoint.identity: npm,
|
||||
verizon_endpoint.identity: verizon,
|
||||
}
|
||||
|
||||
def loader(name, config, endpoint):
|
||||
return adapters[endpoint.identity]
|
||||
|
||||
with tempfile.TemporaryDirectory() as directory:
|
||||
state_path = Path(directory) / "state.sqlite3"
|
||||
stdout = io.StringIO()
|
||||
stderr = io.StringIO()
|
||||
|
||||
with patch.dict(
|
||||
os.environ,
|
||||
{
|
||||
"NPM_ENDPOINTS": "192.168.1.3",
|
||||
"VERIZON_ENDPOINTS": "192.168.1.1",
|
||||
"DNS_TARGET_IP": "192.168.1.101",
|
||||
},
|
||||
clear=True,
|
||||
), patch(
|
||||
"dns_sync.cli.load_adapter",
|
||||
side_effect=loader,
|
||||
), redirect_stdout(stdout), redirect_stderr(stderr):
|
||||
result = run_reconcile(
|
||||
SimpleNamespace(
|
||||
state_db=str(state_path),
|
||||
include_disabled=False,
|
||||
)
|
||||
)
|
||||
|
||||
self.assertTrue(state_path.exists())
|
||||
|
||||
output = stdout.getvalue()
|
||||
self.assertEqual(result, 0)
|
||||
self.assertEqual(stderr.getvalue(), "")
|
||||
self.assertIn(
|
||||
"Generic reconciliation mode: READ ONLY",
|
||||
output,
|
||||
)
|
||||
self.assertIn(
|
||||
"UPDATE dns_records app.example.com "
|
||||
"-> 192.168.1.101",
|
||||
output,
|
||||
)
|
||||
self.assertIn(
|
||||
"READ ONLY: no endpoint changes were applied.",
|
||||
output,
|
||||
)
|
||||
self.assertEqual(npm.apply_count, 0)
|
||||
self.assertEqual(verizon.apply_count, 0)
|
||||
self.assertEqual(npm.verify_count, 0)
|
||||
self.assertEqual(verizon.verify_count, 0)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
Reference in New Issue
Block a user