from ...adapters import ( DNS_RECORDS, ApplyResult, Capability, CapabilityOperation, DiscoveredObject, NormalizedObject, PlanAction, PlanOperation, ) from ...models import DesiredRecord from ..providers.verizon 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) )