use std::collections::HashSet; use std::str::FromStr; use anyhow::{anyhow, Result}; use aws_sdk_route53 as route53; use aws_sdk_route53::types::{HostedZone, ResourceRecordSet, RrType}; use trust_dns_proto::rr::Name; use crate::dns::suffixes; use crate::hashable::Hashable; pub trait Route53 { fn route53(&self) -> &route53::Client; } pub async fn zone_for_domain(aws_context: &C, name: &Name) -> Result where C: Route53, { let names = suffixes(name.clone()); // Outer pagination loop needs to be pulled out to a trait - this is some hot nonsense. let mut zone = None; let mut depth = None; let mut zones_marker = None; loop { let zones_resp = aws_context .route53() .list_hosted_zones() .set_marker(zones_marker) .send() .await?; let zones = zones_resp.hosted_zones().unwrap_or(&[]); for candidate_zone in zones.iter() { let zone_name = match candidate_zone.name() { None => continue, Some(name) => name, }; let zone_name = Name::from_str(zone_name)?; let match_position = names.iter().position(|name| *name == zone_name); match (depth, match_position) { (None, Some(matched_depth)) => { zone = Some(candidate_zone.clone()); depth = Some(matched_depth); } (Some(found_depth), Some(matched_depth)) => { if matched_depth < found_depth { zone = Some(candidate_zone.clone()); depth = Some(matched_depth); } } (_, _) => {} } } if zones_resp.is_truncated() { zones_marker = zones_resp.next_marker().map(String::from); } else { break; } } zone.ok_or(anyhow!("No Route53 zone found for DNS suffix: {}", name)) } pub async fn zone_suffix_recordsets( aws_context: &C, zone_id: &str, dns_name: &Name, ) -> Result>> where C: Route53, { let mut suffix_records = HashSet::new(); let mut next_record_name = Some(dns_name.to_ascii()); let mut next_record_type = None; let mut next_record_identifier = None; loop { let records_resp = aws_context .route53() .list_resource_record_sets() .hosted_zone_id(zone_id) .set_start_record_name(next_record_name) .set_start_record_type(next_record_type) .set_start_record_identifier(next_record_identifier) .send() .await?; let recordsets = records_resp.resource_record_sets().unwrap_or(&[]); for recordset in recordsets { let recordset_name = recordset.name().ok_or(anyhow!( "Record set with no name found in zone: {}", zone_id ))?; let recordset_name = Name::from_str(recordset_name)?; if &recordset_name != dns_name { break; } if let Some(rr_type) = recordset.r#type() { if [RrType::A, RrType::Aaaa].contains(rr_type) { suffix_records.insert(recordset.clone().into()); } } } if records_resp.is_truncated() { next_record_name = records_resp.next_record_name().map(String::from); next_record_type = records_resp.next_record_type().cloned(); next_record_identifier = records_resp.next_record_identifier().map(String::from); } else { break; } } Ok(suffix_records) }