use std::collections::HashSet; use anyhow::Result; use aws_sdk_ec2 as ec2; use aws_sdk_ec2::types::Filter; use aws_sdk_route53::types::{ResourceRecordSet, RrType}; use crate::hashable::Hashable; use crate::route53::Target; pub trait Ec2 { fn ec2(&self) -> &ec2::Client; } pub async fn asg_instances_proposal( aws_context: &C, target: &Target, asg_name: &str, live_instance_ids: &[String], ) -> Result>> where C: Ec2, { // If there's nothing running, then (a) we don't need to ask AWS about // running instances, and (b) we can't anyways as the API call requires at // least one instance ID. Abort here. if live_instance_ids.is_empty() { return Ok(HashSet::new()); } let asg_filter = Filter::builder() .name("tag:aws:autoscaling:groupName") .values(asg_name) .build(); let mut ip4 = HashSet::new(); let mut ip6 = HashSet::new(); let mut instances_paginator = aws_context .ec2() .describe_instances() .set_instance_ids(Some(live_instance_ids.to_owned())) .filters(asg_filter) .into_paginator() .items() .send(); while let Some(reservation) = instances_paginator.try_next().await? { let instances = reservation.instances(); for instance in instances { // Mild abuse of the fact that optional values are also iterable ip4.extend(instance.public_ip_address().map(String::from)); let instance_interfaces = instance.network_interfaces(); let instance_ip6: Vec<_> = instance_interfaces .iter() .flat_map(|interface| interface.ipv6_addresses()) // Flatmap here to drop the None values, unwrap the Some values .flat_map(|ipv6| ipv6.ipv6_address()) .to_owned() .collect(); ip6.extend(instance_ip6.iter().map(ToOwned::to_owned).map(String::from)); } } let ip4_proposal = target.host_proposal(RrType::A, ip4)?; let ip6_proposal = target.host_proposal(RrType::Aaaa, ip6)?; Ok(ip4_proposal .into_iter() .chain(ip6_proposal.into_iter()) .map(Hashable::from) .collect()) }