diff options
| author | Owen Jacobson <owen@grimoire.ca> | 2023-10-17 21:17:55 -0400 |
|---|---|---|
| committer | Owen Jacobson <owen@grimoire.ca> | 2023-10-17 22:32:21 -0400 |
| commit | 288205e302d9f6afa06b8602184e983d2080a5b6 (patch) | |
| tree | dfb307e8f3cb82d280e5a0392f11318194e09ef1 /src/route53.rs | |
CLI tool for updating Route53 DNS for an ASG.
Diffstat (limited to 'src/route53.rs')
| -rw-r--r-- | src/route53.rs | 143 |
1 files changed, 143 insertions, 0 deletions
diff --git a/src/route53.rs b/src/route53.rs new file mode 100644 index 0000000..22e4126 --- /dev/null +++ b/src/route53.rs @@ -0,0 +1,143 @@ +use std::collections::HashSet; +use std::str::FromStr; + +use anyhow::anyhow; +use aws_sdk_route53 as route53; +use aws_sdk_route53::types::{HostedZone, ResourceRecord, ResourceRecordSet, RrType}; +use trust_dns_proto::rr::Name; + +use crate::dns::suffixes; +use crate::hashable::Hashable; +use crate::result::Result; + +pub trait Route53 { + fn route53(&self) -> &route53::Client; +} + +pub async fn zone_for_domain<C>(name: &Name, aws_context: &C) -> Result<HostedZone> +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<C>( + dns_suffix: &Name, + zone_id: &str, + aws_context: &C, +) -> Result<HashSet<Hashable<ResourceRecordSet>>> +where + C: Route53, +{ + let mut suffix_records = HashSet::new(); + + let mut next_record_name = Some(dns_suffix.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)?; + let recordset_names = suffixes(recordset_name); + + if !recordset_names.iter().any(|name| name == dns_suffix) { + break; + } + + 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().map(Clone::clone); + next_record_identifier = records_resp.next_record_identifier().map(String::from); + } else { + break; + } + } + + Ok(suffix_records) +} + +pub fn recordset<I, S>( + apex_hostname: &str, + dns_ttl: i64, + rr_type: RrType, + addresses: I, +) -> ResourceRecordSet +where + I: IntoIterator<Item = S>, + S: Into<String>, +{ + let apex_ip4_records = addresses + .into_iter() + .map(|address| address.into()) + .map(|address| ResourceRecord::builder().value(address).build()) + .collect(); + + ResourceRecordSet::builder() + .name(apex_hostname) + .r#type(rr_type) + .ttl(dns_ttl) + .set_resource_records(Some(apex_ip4_records)) + .build() +} |
