64 lines
2.0 KiB
Python
64 lines
2.0 KiB
Python
from typing import Dict, List, Any
|
|
|
|
from dns import resolver, exception
|
|
|
|
|
|
def resolve_cname(hostname: str) -> List[str]:
|
|
"""Resolve CNAME records for a hostname and return targets without trailing dots.
|
|
|
|
If there is no CNAME record, this returns an empty list.
|
|
Raises dns.exception.DNSException on resolver errors.
|
|
"""
|
|
answers = resolver.resolve(hostname, "CNAME")
|
|
targets: List[str] = []
|
|
for rdata in answers:
|
|
# rdata.target may be a Name object; to_text() yields with trailing dot
|
|
target = getattr(rdata, "target", None)
|
|
if target is None:
|
|
# some dnspython versions use .to_text() directly on rdata
|
|
text = rdata.to_text()
|
|
else:
|
|
text = target.to_text()
|
|
targets.append(text.rstrip("."))
|
|
return targets
|
|
|
|
|
|
def check_cname_map(cname_map: Dict[str, str]) -> List[Dict[str, Any]]:
|
|
"""Check a mapping of hostname -> expected CNAME target.
|
|
|
|
Returns a list of result dicts: {
|
|
'hostname': str,
|
|
'expected': str,
|
|
'actual': List[str],
|
|
'ok': bool,
|
|
'error': Optional[str]
|
|
}
|
|
"""
|
|
results: List[Dict[str, Any]] = []
|
|
for hostname, expected in cname_map.items():
|
|
expected_norm = expected.rstrip(".").lower()
|
|
try:
|
|
actual_targets = resolve_cname(hostname)
|
|
actual_norm = [t.lower() for t in actual_targets]
|
|
ok = expected_norm in actual_norm
|
|
results.append({
|
|
"hostname": hostname,
|
|
"expected": expected,
|
|
"actual": actual_targets,
|
|
"ok": ok,
|
|
"error": None,
|
|
})
|
|
except exception.DNSException as e:
|
|
results.append({
|
|
"hostname": hostname,
|
|
"expected": expected,
|
|
"actual": [],
|
|
"ok": False,
|
|
"error": str(e),
|
|
})
|
|
return results
|
|
|
|
|
|
def bytes_to_gb(num_bytes: int) -> float:
|
|
"""Convert bytes to gigabytes (GB, 1024^3)."""
|
|
return round(num_bytes / (1024 ** 3), 3) |