pub const VPC_SERVICE_CODE: &str = "vpc";
pub const SECURITY_GROUPS_PER_INTERFACE: &str = "L-2AFB9258";
pub const RULES_PER_SECURITY_GROUP: &str = "L-0EA8095F";
pub const DEFAULT_SECURITY_GROUPS_PER_INTERFACE: usize = 5;
pub const DEFAULT_RULES_PER_SECURITY_GROUP: usize = 60;
pub const IAM_SERVICE_CODE: &str = "iam";
#[derive(Debug, Clone, Copy)]
pub struct IamSummaryQuota {
pub summary_key: &'static str,
pub quota_code: &'static str,
pub default: f64,
}
pub const IAM_SUMMARY_QUOTAS: &[IamSummaryQuota] = &[
IamSummaryQuota {
summary_key: "UsersQuota",
quota_code: "L-F55AF5E4",
default: 5000.0,
},
IamSummaryQuota {
summary_key: "GroupsQuota",
quota_code: "L-F4A5425F",
default: 300.0,
},
IamSummaryQuota {
summary_key: "ServerCertificatesQuota",
quota_code: "L-BF35879D",
default: 20.0,
},
IamSummaryQuota {
summary_key: "PoliciesQuota",
quota_code: "L-E95E4862",
default: 1500.0,
},
IamSummaryQuota {
summary_key: "RolesQuota",
quota_code: "L-FE177D64",
default: 1000.0,
},
IamSummaryQuota {
summary_key: "InstanceProfilesQuota",
quota_code: "L-6E65F664",
default: 1000.0,
},
IamSummaryQuota {
summary_key: "AttachedPoliciesPerGroupQuota",
quota_code: "L-384571C4",
default: 10.0,
},
IamSummaryQuota {
summary_key: "AttachedPoliciesPerRoleQuota",
quota_code: "L-0DA4ABF3",
default: 20.0,
},
IamSummaryQuota {
summary_key: "AttachedPoliciesPerUserQuota",
quota_code: "L-4019AD8B",
default: 10.0,
},
IamSummaryQuota {
summary_key: "AssumeRolePolicySizeQuota",
quota_code: "L-C07B4B0D",
default: 2048.0,
},
];
pub trait QuotaProvider: Send + Sync {
fn applied_value(
&self,
account_id: &str,
region: &str,
service_code: &str,
quota_code: &str,
) -> Option<f64>;
fn enforced_limit(
&self,
account_id: &str,
region: &str,
service_code: &str,
quota_code: &str,
) -> Option<f64>;
}
pub trait QuotaUsageSource: Send + Sync {
fn service_codes(&self) -> &[&str];
fn usage(
&self,
account_id: &str,
region: &str,
service_code: &str,
quota_code: &str,
) -> Option<f64>;
}
pub fn enforced_count(
provider: Option<&std::sync::Arc<dyn QuotaProvider>>,
account_id: &str,
region: &str,
service_code: &str,
quota_code: &str,
) -> Option<usize> {
provider
.and_then(|p| p.enforced_limit(account_id, region, service_code, quota_code))
.map(|v| v.max(0.0) as usize)
}
pub fn has_room(limit: Option<usize>, existing: usize) -> bool {
limit.is_none_or(|limit| existing < limit)
}
pub fn refusal_reason(service: &str, err: &crate::service::AwsServiceError) -> String {
format!(
"{} (Service: {service}, Status Code: {}, Error Code: {})",
err.message(),
err.status().as_u16(),
err.code()
)
}
#[derive(Debug, Clone, Default)]
pub struct FixedQuotas {
quotas: Vec<(String, String, f64)>,
}
impl FixedQuotas {
pub fn with(mut self, service_code: &str, quota_code: &str, value: f64) -> Self {
self.quotas
.retain(|(s, q, _)| !(s == service_code && q == quota_code));
self.quotas
.push((service_code.to_string(), quota_code.to_string(), value));
self
}
pub fn security_group_defaults() -> Self {
Self::default()
.with(
VPC_SERVICE_CODE,
SECURITY_GROUPS_PER_INTERFACE,
DEFAULT_SECURITY_GROUPS_PER_INTERFACE as f64,
)
.with(
VPC_SERVICE_CODE,
RULES_PER_SECURITY_GROUP,
DEFAULT_RULES_PER_SECURITY_GROUP as f64,
)
}
}
impl QuotaProvider for FixedQuotas {
fn applied_value(&self, _: &str, _: &str, service_code: &str, quota_code: &str) -> Option<f64> {
self.quotas
.iter()
.find(|(s, q, _)| s == service_code && q == quota_code)
.map(|(_, _, v)| *v)
}
fn enforced_limit(
&self,
account_id: &str,
region: &str,
service_code: &str,
quota_code: &str,
) -> Option<f64> {
self.applied_value(account_id, region, service_code, quota_code)
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::sync::Arc;
#[test]
fn enforced_count_saturates_and_floors_at_zero() {
let p: Arc<dyn QuotaProvider> = Arc::new(
FixedQuotas::default()
.with("s", "neg", -3.0)
.with("s", "big", f64::MAX)
.with("s", "frac", 2.9),
);
let get = |code| enforced_count(Some(&p), "a", "r", "s", code);
assert_eq!(get("neg"), Some(0));
assert_eq!(get("big"), Some(usize::MAX));
assert_eq!(get("frac"), Some(2));
assert_eq!(get("missing"), None);
assert_eq!(enforced_count(None, "a", "r", "s", "neg"), None);
}
#[test]
fn has_room_below_the_limit_only() {
assert!(has_room(None, usize::MAX));
assert!(has_room(Some(2), 1));
assert!(!has_room(Some(2), 2));
assert!(!has_room(Some(0), 0));
}
}