187 lines
5.5 KiB
Python
187 lines
5.5 KiB
Python
from dns_sync.adapters import (
|
|
DNS_RECORDS,
|
|
ApplyResult,
|
|
Capability,
|
|
CapabilityOperation,
|
|
DiscoveredObject,
|
|
NormalizedObject,
|
|
PlanAction,
|
|
PlanOperation,
|
|
)
|
|
from dns_sync.models import DesiredRecord
|
|
from .provider import Plugin as VerizonProvider
|
|
|
|
|
|
class Plugin:
|
|
name = "verizon"
|
|
capabilities = frozenset(
|
|
{
|
|
Capability(
|
|
DNS_RECORDS,
|
|
CapabilityOperation.DISCOVER,
|
|
),
|
|
Capability(
|
|
DNS_RECORDS,
|
|
CapabilityOperation.APPLY,
|
|
),
|
|
Capability(
|
|
DNS_RECORDS,
|
|
CapabilityOperation.VERIFY,
|
|
),
|
|
}
|
|
)
|
|
|
|
def __init__(self, config, endpoint):
|
|
if endpoint.plugin != self.name:
|
|
raise RuntimeError(
|
|
"Verizon adapter requires a verizon endpoint"
|
|
)
|
|
|
|
self.config = config
|
|
self.endpoint = endpoint
|
|
self.provider = VerizonProvider(config, endpoint)
|
|
|
|
def connect(self) -> None:
|
|
self.provider.connect()
|
|
|
|
def discover(
|
|
self,
|
|
object_type: str,
|
|
) -> list[DiscoveredObject]:
|
|
if object_type != DNS_RECORDS:
|
|
raise RuntimeError(
|
|
f"{self.endpoint.identity} cannot discover "
|
|
f"object type {object_type!r}"
|
|
)
|
|
|
|
discovered: list[DiscoveredObject] = []
|
|
records = sorted(
|
|
self.provider.list_records().values(),
|
|
key=lambda record: record.hostname.lower(),
|
|
)
|
|
|
|
for record in records:
|
|
slot = record.metadata.get("slot")
|
|
if isinstance(slot, bool) or not isinstance(slot, int):
|
|
raise RuntimeError(
|
|
f"Verizon DNS record {record.hostname!r} "
|
|
"has no valid endpoint-local slot"
|
|
)
|
|
|
|
discovered.append(
|
|
DiscoveredObject(
|
|
object_type=DNS_RECORDS,
|
|
local_key=str(slot),
|
|
payload={
|
|
"hostname": record.hostname,
|
|
"ip": record.ip,
|
|
"record_type": record.record_type,
|
|
},
|
|
)
|
|
)
|
|
|
|
return discovered
|
|
|
|
def _desired_record(
|
|
self,
|
|
object: NormalizedObject,
|
|
) -> DesiredRecord:
|
|
if object.object_type != DNS_RECORDS:
|
|
raise RuntimeError(
|
|
f"{self.endpoint.identity} cannot handle "
|
|
f"object type {object.object_type!r}"
|
|
)
|
|
|
|
payload = object.payload
|
|
hostname = str(payload.get("hostname") or "")
|
|
if hostname != object.logical_key:
|
|
raise RuntimeError(
|
|
"DNS object hostname does not match its logical key"
|
|
)
|
|
|
|
record_type = str(
|
|
payload.get("record_type") or ""
|
|
).upper()
|
|
if record_type != "A":
|
|
raise RuntimeError(
|
|
f"Verizon DNS does not support {record_type!r} records"
|
|
)
|
|
|
|
wildcard = payload.get("wildcard") is True
|
|
if wildcard:
|
|
raise RuntimeError(
|
|
"Verizon DNS does not support wildcard records"
|
|
)
|
|
|
|
return DesiredRecord(
|
|
hostname=hostname,
|
|
ip=str(payload.get("ip") or ""),
|
|
record_type=record_type,
|
|
wildcard=False,
|
|
)
|
|
|
|
def apply(self, action: PlanAction) -> ApplyResult | None:
|
|
if action.endpoint_id != self.endpoint.identity:
|
|
raise RuntimeError(
|
|
"Plan action belongs to a different endpoint"
|
|
)
|
|
|
|
if action.operation in {
|
|
PlanOperation.NOOP,
|
|
PlanOperation.SKIP,
|
|
}:
|
|
return None
|
|
|
|
if action.desired is None:
|
|
raise RuntimeError(
|
|
"Verizon apply action requires desired state"
|
|
)
|
|
|
|
record = self._desired_record(action.desired)
|
|
|
|
if action.operation == PlanOperation.CREATE:
|
|
detail = self.provider.add_record(record)
|
|
elif action.operation == PlanOperation.UPDATE:
|
|
detail = self.provider.update_record(record)
|
|
else:
|
|
raise RuntimeError(
|
|
f"Verizon adapter does not support "
|
|
f"{action.operation.value} actions"
|
|
)
|
|
|
|
records = self.provider.list_records()
|
|
applied_record = records.get(record.hostname.lower())
|
|
if applied_record is None:
|
|
applied_record = records.get(record.hostname)
|
|
|
|
if applied_record is None:
|
|
raise RuntimeError(
|
|
"Verizon mutation succeeded but the applied DNS "
|
|
f"record {record.hostname!r} could not be rediscovered"
|
|
)
|
|
|
|
slot = applied_record.metadata.get("slot")
|
|
if isinstance(slot, bool) or not isinstance(slot, int):
|
|
raise RuntimeError(
|
|
f"Verizon DNS record {record.hostname!r} "
|
|
"has no valid endpoint-local slot after apply"
|
|
)
|
|
|
|
applied = NormalizedObject(
|
|
object_type=action.desired.object_type,
|
|
logical_key=action.desired.logical_key,
|
|
local_key=str(slot),
|
|
payload=dict(action.desired.payload),
|
|
references=action.desired.references,
|
|
)
|
|
|
|
return ApplyResult(
|
|
object=applied,
|
|
detail=detail,
|
|
)
|
|
|
|
def verify(self, object: NormalizedObject) -> bool:
|
|
return self.provider.verify_record(
|
|
self._desired_record(object)
|
|
)
|