use fakecloud_aws::ec2query::{ec2_elem, ec2_list, ec2_return};
use fakecloud_core::service::{AwsRequest, AwsResponse, AwsServiceError};
use crate::service::Ec2Service;
use crate::service_helpers::{gen_id, require, validate_enum, validate_max_results};
use crate::state::{
Ec2State, IpamPolicy, IpamPrefixListResolver, IpamPrefixListResolverTarget, Tag,
};
fn mr(req: &AwsRequest) -> Result<(), AwsServiceError> {
validate_max_results(&req.query_params, 5, 1000)
}
fn region_of(req: &AwsRequest) -> String {
if req.region.is_empty() {
"us-east-1".to_string()
} else {
req.region.clone()
}
}
const RESOURCE_TYPES: &[&str] = &["alb", "eip", "rds", "rnat"];
fn policy_xml(p: &IpamPolicy, tags: &[Tag], owner: &str, region: &str) -> String {
format!(
"{}{}{}{}<state>create-complete</state>{}",
ec2_elem("ipamPolicyId", &p.id),
ec2_elem(
"ipamPolicyArn",
&format!("arn:aws:ec2::{owner}:ipam-policy/{}", p.id)
),
ec2_elem("ipamPolicyRegion", region),
ec2_elem("ipamId", &p.ipam_id) + &ec2_elem("ownerId", owner),
super::tags::tag_set_xml(tags),
)
}
pub(crate) fn create_ipam_policy(
svc: &Ec2Service,
req: &AwsRequest,
) -> Result<AwsResponse, AwsServiceError> {
let ipam = require(&req.query_params, "IpamId")?;
let id = gen_id("ipam-policy");
let p = IpamPolicy {
id: id.clone(),
ipam_id: ipam,
};
let owner = req.account_id.clone();
let region = region_of(req);
let tags = {
let mut accounts = svc.state.write();
let state = accounts.get_or_create(&req.account_id);
crate::service::tags::apply_tag_specifications(
state,
&req.query_params,
&id,
"ipam-policy",
);
let t = state.tags_for(&id).to_vec();
state.ipam_policies.insert(id.clone(), p.clone());
t
};
Ok(Ec2Service::respond(
"CreateIpamPolicy",
&req.request_id,
&format!(
"<ipamPolicy>{}</ipamPolicy>",
policy_xml(&p, &tags, &owner, ®ion)
),
))
}
pub(crate) fn delete_ipam_policy(
svc: &Ec2Service,
req: &AwsRequest,
) -> Result<AwsResponse, AwsServiceError> {
let id = require(&req.query_params, "IpamPolicyId")?;
let owner = req.account_id.clone();
let region = region_of(req);
let mut accounts = svc.state.write();
let state = accounts.get_or_create(&req.account_id);
let p = state.ipam_policies.remove(&id).unwrap_or(IpamPolicy {
id: id.clone(),
ipam_id: "ipam-0".to_string(),
});
let tags = state.tags_for(&id).to_vec();
state.tags.remove(&id);
state.ipam_policy_alloc_rules.remove(&id);
if state.ipam_enabled_policy.as_deref() == Some(id.as_str()) {
state.ipam_enabled_policy = None;
}
Ok(Ec2Service::respond(
"DeleteIpamPolicy",
&req.request_id,
&format!(
"<ipamPolicy>{}</ipamPolicy>",
policy_xml(&p, &tags, &owner, ®ion)
),
))
}
pub(crate) fn describe_ipam_policies(
svc: &Ec2Service,
req: &AwsRequest,
) -> Result<AwsResponse, AwsServiceError> {
mr(req)?;
let owner = req.account_id.clone();
let region = region_of(req);
let accounts = svc.state.read();
let empty = Ec2State::new(&req.account_id, &req.region);
let state = accounts.get(&req.account_id).unwrap_or(&empty);
let mut items: Vec<String> = state
.ipam_policies
.values()
.map(|p| policy_xml(p, state.tags_for(&p.id), &owner, ®ion))
.collect();
items.sort();
Ok(Ec2Service::respond(
"DescribeIpamPolicies",
&req.request_id,
&ec2_list("ipamPolicySet", &items),
))
}
fn alloc_rule_xml(id: &str, locale: &str, rt: &str) -> String {
format!(
"<ipamPolicyDocument>{}{}{}</ipamPolicyDocument>",
ec2_elem("ipamPolicyId", id),
ec2_elem("locale", locale),
ec2_elem("resourceType", rt),
)
}
pub(crate) fn enable_ipam_policy(
svc: &Ec2Service,
req: &AwsRequest,
) -> Result<AwsResponse, AwsServiceError> {
let id = require(&req.query_params, "IpamPolicyId")?;
{
let mut accounts = svc.state.write();
accounts.get_or_create(&req.account_id).ipam_enabled_policy = Some(id.clone());
}
Ok(Ec2Service::respond(
"EnableIpamPolicy",
&req.request_id,
&ec2_elem("ipamPolicyId", &id),
))
}
pub(crate) fn disable_ipam_policy(
svc: &Ec2Service,
req: &AwsRequest,
) -> Result<AwsResponse, AwsServiceError> {
let id = require(&req.query_params, "IpamPolicyId")?;
{
let mut accounts = svc.state.write();
let enabled = &mut accounts.get_or_create(&req.account_id).ipam_enabled_policy;
if enabled.as_deref() == Some(id.as_str()) {
*enabled = None;
}
}
Ok(Ec2Service::respond(
"DisableIpamPolicy",
&req.request_id,
&ec2_return(true),
))
}
pub(crate) fn get_enabled_ipam_policy(
svc: &Ec2Service,
req: &AwsRequest,
) -> Result<AwsResponse, AwsServiceError> {
let accounts = svc.state.read();
let enabled = accounts
.get(&req.account_id)
.and_then(|s| s.ipam_enabled_policy.clone());
let body = match enabled {
Some(id) => format!(
"<ipamPolicyEnabled>true</ipamPolicyEnabled>{}",
ec2_elem("ipamPolicyId", &id)
),
None => "<ipamPolicyEnabled>false</ipamPolicyEnabled>".to_string(),
};
Ok(Ec2Service::respond(
"GetEnabledIpamPolicy",
&req.request_id,
&body,
))
}
pub(crate) fn get_ipam_policy_allocation_rules(
svc: &Ec2Service,
req: &AwsRequest,
) -> Result<AwsResponse, AwsServiceError> {
let id = require(&req.query_params, "IpamPolicyId")?;
validate_enum(&req.query_params, "ResourceType", RESOURCE_TYPES)?;
mr(req)?;
let accounts = svc.state.read();
let items: Vec<String> = accounts
.get(&req.account_id)
.and_then(|s| s.ipam_policy_alloc_rules.get(&id))
.map(|rules| {
rules
.iter()
.map(|(locale, rt)| alloc_rule_xml(&id, locale, rt))
.collect()
})
.unwrap_or_default();
Ok(Ec2Service::respond(
"GetIpamPolicyAllocationRules",
&req.request_id,
&ec2_list("ipamPolicyDocumentSet", &items),
))
}
pub(crate) fn modify_ipam_policy_allocation_rules(
svc: &Ec2Service,
req: &AwsRequest,
) -> Result<AwsResponse, AwsServiceError> {
let id = require(&req.query_params, "IpamPolicyId")?;
let locale = require(&req.query_params, "Locale")?;
let rt = require(&req.query_params, "ResourceType")?;
validate_enum(&req.query_params, "ResourceType", RESOURCE_TYPES)?;
{
let mut accounts = svc.state.write();
let rules = accounts
.get_or_create(&req.account_id)
.ipam_policy_alloc_rules
.entry(id.clone())
.or_default();
if let Some(existing) = rules.iter_mut().find(|(l, r)| l == &locale && r == &rt) {
existing.0 = locale.clone();
existing.1 = rt.clone();
} else {
rules.push((locale.clone(), rt.clone()));
}
}
Ok(Ec2Service::respond(
"ModifyIpamPolicyAllocationRules",
&req.request_id,
&alloc_rule_xml(&id, &locale, &rt),
))
}
pub(crate) fn get_ipam_policy_organization_targets(
_svc: &Ec2Service,
req: &AwsRequest,
) -> Result<AwsResponse, AwsServiceError> {
require(&req.query_params, "IpamPolicyId")?;
mr(req)?;
Ok(Ec2Service::respond(
"GetIpamPolicyOrganizationTargets",
&req.request_id,
&ec2_list("organizationTargetSet", &[]),
))
}
fn resolver_xml(r: &IpamPrefixListResolver, tags: &[Tag], owner: &str, region: &str) -> String {
format!(
"{}{}{}{}{}<addressFamily>{}</addressFamily><state>create-complete</state>{}",
ec2_elem("ipamPrefixListResolverId", &r.id),
ec2_elem(
"ipamPrefixListResolverArn",
&format!("arn:aws:ec2::{owner}:ipam-prefix-list-resolver/{}", r.id)
),
ec2_elem(
"ipamArn",
&format!("arn:aws:ec2::{owner}:ipam/{}", r.ipam_id)
),
ec2_elem("ipamRegion", region) + &ec2_elem("ownerId", owner),
ec2_elem("description", &r.description),
r.address_family,
super::tags::tag_set_xml(tags),
)
}
pub(crate) fn create_ipam_prefix_list_resolver(
svc: &Ec2Service,
req: &AwsRequest,
) -> Result<AwsResponse, AwsServiceError> {
let ipam = require(&req.query_params, "IpamId")?;
let af = require(&req.query_params, "AddressFamily")?;
validate_enum(&req.query_params, "AddressFamily", &["ipv4", "ipv6"])?;
let id = gen_id("ipam-pl-res");
let r = IpamPrefixListResolver {
id: id.clone(),
ipam_id: ipam,
address_family: af,
description: req
.query_params
.get("Description")
.cloned()
.unwrap_or_default(),
};
let owner = req.account_id.clone();
let region = region_of(req);
let tags = {
let mut accounts = svc.state.write();
let state = accounts.get_or_create(&req.account_id);
crate::service::tags::apply_tag_specifications(
state,
&req.query_params,
&id,
"ipam-prefix-list-resolver",
);
let t = state.tags_for(&id).to_vec();
state.ipam_pl_resolvers.insert(id.clone(), r.clone());
t
};
Ok(Ec2Service::respond(
"CreateIpamPrefixListResolver",
&req.request_id,
&format!(
"<ipamPrefixListResolver>{}</ipamPrefixListResolver>",
resolver_xml(&r, &tags, &owner, ®ion)
),
))
}
pub(crate) fn delete_ipam_prefix_list_resolver(
svc: &Ec2Service,
req: &AwsRequest,
) -> Result<AwsResponse, AwsServiceError> {
let id = require(&req.query_params, "IpamPrefixListResolverId")?;
let owner = req.account_id.clone();
let region = region_of(req);
let mut accounts = svc.state.write();
let state = accounts.get_or_create(&req.account_id);
let r = state
.ipam_pl_resolvers
.remove(&id)
.unwrap_or(IpamPrefixListResolver {
id: id.clone(),
ipam_id: "ipam-0".to_string(),
address_family: "ipv4".to_string(),
description: String::new(),
});
let tags = state.tags_for(&id).to_vec();
state.tags.remove(&id);
Ok(Ec2Service::respond(
"DeleteIpamPrefixListResolver",
&req.request_id,
&format!(
"<ipamPrefixListResolver>{}</ipamPrefixListResolver>",
resolver_xml(&r, &tags, &owner, ®ion)
),
))
}
pub(crate) fn describe_ipam_prefix_list_resolvers(
svc: &Ec2Service,
req: &AwsRequest,
) -> Result<AwsResponse, AwsServiceError> {
mr(req)?;
let owner = req.account_id.clone();
let region = region_of(req);
let accounts = svc.state.read();
let empty = Ec2State::new(&req.account_id, &req.region);
let state = accounts.get(&req.account_id).unwrap_or(&empty);
let mut items: Vec<String> = state
.ipam_pl_resolvers
.values()
.map(|r| resolver_xml(r, state.tags_for(&r.id), &owner, ®ion))
.collect();
items.sort();
Ok(Ec2Service::respond(
"DescribeIpamPrefixListResolvers",
&req.request_id,
&ec2_list("ipamPrefixListResolverSet", &items),
))
}
pub(crate) fn modify_ipam_prefix_list_resolver(
svc: &Ec2Service,
req: &AwsRequest,
) -> Result<AwsResponse, AwsServiceError> {
let id = require(&req.query_params, "IpamPrefixListResolverId")?;
let owner = req.account_id.clone();
let region = region_of(req);
let mut accounts = svc.state.write();
let state = accounts.get_or_create(&req.account_id);
let r = match state.ipam_pl_resolvers.get_mut(&id) {
Some(e) => {
if let Some(d) = req.query_params.get("Description") {
e.description = d.clone();
}
e.clone()
}
None => IpamPrefixListResolver {
id: id.clone(),
ipam_id: "ipam-0".to_string(),
address_family: "ipv4".to_string(),
description: req
.query_params
.get("Description")
.cloned()
.unwrap_or_default(),
},
};
let tags = state.tags_for(&id).to_vec();
Ok(Ec2Service::respond(
"ModifyIpamPrefixListResolver",
&req.request_id,
&format!(
"<ipamPrefixListResolver>{}</ipamPrefixListResolver>",
resolver_xml(&r, &tags, &owner, ®ion)
),
))
}
fn target_xml(t: &IpamPrefixListResolverTarget, tags: &[Tag], owner: &str) -> String {
format!(
"{}{}{}{}{}<trackLatestVersion>{}</trackLatestVersion><state>create-complete</state>{}",
ec2_elem("ipamPrefixListResolverTargetId", &t.id),
ec2_elem(
"ipamPrefixListResolverTargetArn",
&format!(
"arn:aws:ec2::{owner}:ipam-prefix-list-resolver-target/{}",
t.id
)
),
ec2_elem("ipamPrefixListResolverId", &t.resolver_id),
ec2_elem("prefixListId", &t.prefix_list_id),
ec2_elem("prefixListRegion", &t.prefix_list_region) + &ec2_elem("ownerId", owner),
t.track_latest_version,
super::tags::tag_set_xml(tags),
)
}
pub(crate) fn create_ipam_prefix_list_resolver_target(
svc: &Ec2Service,
req: &AwsRequest,
) -> Result<AwsResponse, AwsServiceError> {
let resolver = require(&req.query_params, "IpamPrefixListResolverId")?;
let pl = require(&req.query_params, "PrefixListId")?;
let pl_region = require(&req.query_params, "PrefixListRegion")?;
let track = require(&req.query_params, "TrackLatestVersion")?;
let id = gen_id("ipam-pl-res-target");
let t = IpamPrefixListResolverTarget {
id: id.clone(),
resolver_id: resolver,
prefix_list_id: pl,
prefix_list_region: pl_region,
track_latest_version: track == "true",
};
let owner = req.account_id.clone();
let tags = {
let mut accounts = svc.state.write();
let state = accounts.get_or_create(&req.account_id);
crate::service::tags::apply_tag_specifications(
state,
&req.query_params,
&id,
"ipam-prefix-list-resolver-target",
);
let t2 = state.tags_for(&id).to_vec();
state.ipam_pl_resolver_targets.insert(id.clone(), t.clone());
t2
};
Ok(Ec2Service::respond(
"CreateIpamPrefixListResolverTarget",
&req.request_id,
&format!(
"<ipamPrefixListResolverTarget>{}</ipamPrefixListResolverTarget>",
target_xml(&t, &tags, &owner)
),
))
}
pub(crate) fn delete_ipam_prefix_list_resolver_target(
svc: &Ec2Service,
req: &AwsRequest,
) -> Result<AwsResponse, AwsServiceError> {
let id = require(&req.query_params, "IpamPrefixListResolverTargetId")?;
let owner = req.account_id.clone();
let mut accounts = svc.state.write();
let state = accounts.get_or_create(&req.account_id);
let t = state
.ipam_pl_resolver_targets
.remove(&id)
.unwrap_or(IpamPrefixListResolverTarget {
id: id.clone(),
resolver_id: "ipam-pl-res-0".to_string(),
prefix_list_id: "pl-0".to_string(),
prefix_list_region: "us-east-1".to_string(),
track_latest_version: true,
});
let tags = state.tags_for(&id).to_vec();
state.tags.remove(&id);
Ok(Ec2Service::respond(
"DeleteIpamPrefixListResolverTarget",
&req.request_id,
&format!(
"<ipamPrefixListResolverTarget>{}</ipamPrefixListResolverTarget>",
target_xml(&t, &tags, &owner)
),
))
}
pub(crate) fn describe_ipam_prefix_list_resolver_targets(
svc: &Ec2Service,
req: &AwsRequest,
) -> Result<AwsResponse, AwsServiceError> {
mr(req)?;
let owner = req.account_id.clone();
let accounts = svc.state.read();
let empty = Ec2State::new(&req.account_id, &req.region);
let state = accounts.get(&req.account_id).unwrap_or(&empty);
let mut items: Vec<String> = state
.ipam_pl_resolver_targets
.values()
.map(|t| target_xml(t, state.tags_for(&t.id), &owner))
.collect();
items.sort();
Ok(Ec2Service::respond(
"DescribeIpamPrefixListResolverTargets",
&req.request_id,
&ec2_list("ipamPrefixListResolverTargetSet", &items),
))
}
pub(crate) fn modify_ipam_prefix_list_resolver_target(
svc: &Ec2Service,
req: &AwsRequest,
) -> Result<AwsResponse, AwsServiceError> {
let id = require(&req.query_params, "IpamPrefixListResolverTargetId")?;
let owner = req.account_id.clone();
let accounts = svc.state.read();
let t = accounts
.get(&req.account_id)
.and_then(|s| s.ipam_pl_resolver_targets.get(&id).cloned())
.unwrap_or(IpamPrefixListResolverTarget {
id: id.clone(),
resolver_id: "ipam-pl-res-0".to_string(),
prefix_list_id: "pl-0".to_string(),
prefix_list_region: "us-east-1".to_string(),
track_latest_version: true,
});
let tags = accounts
.get(&req.account_id)
.map(|s| s.tags_for(&id).to_vec())
.unwrap_or_default();
Ok(Ec2Service::respond(
"ModifyIpamPrefixListResolverTarget",
&req.request_id,
&format!(
"<ipamPrefixListResolverTarget>{}</ipamPrefixListResolverTarget>",
target_xml(&t, &tags, &owner)
),
))
}
pub(crate) fn get_ipam_prefix_list_resolver_rules(
_svc: &Ec2Service,
req: &AwsRequest,
) -> Result<AwsResponse, AwsServiceError> {
require(&req.query_params, "IpamPrefixListResolverId")?;
mr(req)?;
Ok(Ec2Service::respond(
"GetIpamPrefixListResolverRules",
&req.request_id,
&ec2_list("ruleSet", &[]),
))
}
pub(crate) fn get_ipam_prefix_list_resolver_versions(
_svc: &Ec2Service,
req: &AwsRequest,
) -> Result<AwsResponse, AwsServiceError> {
require(&req.query_params, "IpamPrefixListResolverId")?;
mr(req)?;
Ok(Ec2Service::respond(
"GetIpamPrefixListResolverVersions",
&req.request_id,
&ec2_list("ipamPrefixListResolverVersionSet", &[]),
))
}
pub(crate) fn get_ipam_prefix_list_resolver_version_entries(
_svc: &Ec2Service,
req: &AwsRequest,
) -> Result<AwsResponse, AwsServiceError> {
require(&req.query_params, "IpamPrefixListResolverId")?;
require(&req.query_params, "IpamPrefixListResolverVersion")?;
mr(req)?;
Ok(Ec2Service::respond(
"GetIpamPrefixListResolverVersionEntries",
&req.request_id,
&ec2_list("entrySet", &[]),
))
}