#![allow(clippy::too_many_lines)]
use super::*;
impl ResourceProvisioner {
pub(crate) fn create_cf_origin_access_identity(
&self,
resource: &ResourceDefinition,
) -> Result<ProvisionResult, String> {
let props = &resource.properties;
let cfg = props
.get("CloudFrontOriginAccessIdentityConfig")
.ok_or("CloudFrontOriginAccessIdentityConfig is required")?;
let comment = cfg
.get("Comment")
.and_then(|v| v.as_str())
.unwrap_or("")
.to_string();
let caller_reference = format!("cfn-{}", resource.logical_id);
let id = format!("E{}", fakecloud_core::ids::short_id(13).to_uppercase());
let etag = format!("E{}", fakecloud_core::ids::short_id(7).to_uppercase());
let s3_canonical_user_id = format!(
"{:0<64}",
Uuid::new_v4().simple().to_string().to_lowercase()
);
let oai = StoredOriginAccessIdentity {
id: id.clone(),
etag,
s3_canonical_user_id: s3_canonical_user_id.clone(),
config: CloudFrontOriginAccessIdentityConfig {
caller_reference,
comment,
},
};
let mut accounts = self.cloudfront_state.write();
let state = accounts.entry("000000000000");
state.origin_access_identities.insert(id.clone(), oai);
Ok(ProvisionResult::new(id.clone())
.with("Id", id)
.with("S3CanonicalUserId", s3_canonical_user_id))
}
pub(crate) fn delete_cf_origin_access_identity(&self, physical_id: &str) -> Result<(), String> {
let mut accounts = self.cloudfront_state.write();
let state = accounts.entry("000000000000");
state.origin_access_identities.remove(physical_id);
Ok(())
}
pub(crate) fn create_cf_distribution(
&self,
resource: &ResourceDefinition,
) -> Result<ProvisionResult, String> {
let cfg = resource
.properties
.get("DistributionConfig")
.ok_or_else(|| "DistributionConfig is required".to_string())?;
let origin_entries: Vec<Origin> = cfg
.get("Origins")
.and_then(|v| v.as_array())
.ok_or_else(|| "DistributionConfig.Origins is required".to_string())?
.iter()
.map(|o| {
serde_json::from_value::<Origin>(o.clone())
.map_err(|e| format!("Invalid Origin entry: {e}"))
})
.collect::<Result<Vec<_>, _>>()?;
if origin_entries.is_empty() {
return Err("DistributionConfig.Origins must contain at least one origin".to_string());
}
let origins = Origins {
quantity: origin_entries.len() as i32,
items: Some(OriginItems {
origin: origin_entries,
}),
};
let dcb_value = cfg
.get("DefaultCacheBehavior")
.ok_or_else(|| "DistributionConfig.DefaultCacheBehavior is required".to_string())?;
let default_cache_behavior: DefaultCacheBehavior =
serde_json::from_value(dcb_value.clone())
.map_err(|e| format!("Invalid DefaultCacheBehavior: {e}"))?;
let comment = cfg
.get("Comment")
.and_then(|v| v.as_str())
.unwrap_or("")
.to_string();
let enabled = cfg.get("Enabled").and_then(|v| v.as_bool()).unwrap_or(true);
let price_class = cfg
.get("PriceClass")
.and_then(|v| v.as_str())
.map(|s| s.to_string());
let http_version = cfg
.get("HttpVersion")
.and_then(|v| v.as_str())
.map(|s| s.to_string());
let is_ipv6_enabled = cfg.get("IPV6Enabled").and_then(|v| v.as_bool());
let default_root_object = cfg
.get("DefaultRootObject")
.and_then(|v| v.as_str())
.map(|s| s.to_string());
let web_acl_id = cfg
.get("WebACLId")
.and_then(|v| v.as_str())
.map(|s| s.to_string());
let viewer_certificate: Option<ViewerCertificate> = cfg
.get("ViewerCertificate")
.map(|v| serde_json::from_value(v.clone()))
.transpose()
.map_err(|e| format!("Invalid ViewerCertificate: {e}"))?;
let caller_reference = format!("cfn-{}-{}", resource.logical_id, Uuid::new_v4().simple());
let mut config = DistributionConfig {
caller_reference,
comment,
enabled,
origins,
default_cache_behavior,
..Default::default()
};
config.price_class = price_class;
config.http_version = http_version;
config.is_ipv6_enabled = is_ipv6_enabled;
config.default_root_object = default_root_object;
config.web_acl_id = web_acl_id;
config.viewer_certificate = viewer_certificate;
let id_suffix: String = Uuid::new_v4()
.simple()
.to_string()
.chars()
.take(13)
.collect::<String>()
.to_uppercase();
let id = format!("E{id_suffix}");
let etag_suffix: String = Uuid::new_v4()
.simple()
.to_string()
.chars()
.take(7)
.collect::<String>()
.to_uppercase();
let etag = format!("E{etag_suffix}");
let domain_name = format!("{}.cloudfront.net", id.to_lowercase());
let arn = format!(
"arn:aws:cloudfront::{}:distribution/{}",
self.account_id, id
);
let stored = StoredDistribution {
id: id.clone(),
arn: arn.clone(),
status: "InProgress".to_string(),
last_modified_time: Utc::now(),
domain_name: domain_name.clone(),
in_progress_invalidation_batches: 0,
etag,
config,
};
let mut accounts = self.cloudfront_state.write();
let state = accounts.entry("000000000000");
state.distributions.insert(id.clone(), stored);
Ok(ProvisionResult::new(id.clone())
.with("Id", id)
.with("DomainName", domain_name)
.with("Arn", arn))
}
pub(crate) fn delete_cf_distribution(&self, physical_id: &str) -> Result<(), String> {
let mut accounts = self.cloudfront_state.write();
let state = accounts.entry("000000000000");
state.distributions.remove(physical_id);
Ok(())
}
pub(crate) fn create_cf_origin_access_control(
&self,
resource: &ResourceDefinition,
) -> Result<ProvisionResult, String> {
let props = &resource.properties;
let cfg = props
.get("OriginAccessControlConfig")
.ok_or("OriginAccessControlConfig is required")?;
let name = cfg
.get("Name")
.and_then(|v| v.as_str())
.ok_or("OriginAccessControlConfig.Name is required")?
.to_string();
let signing_protocol = cfg
.get("SigningProtocol")
.and_then(|v| v.as_str())
.unwrap_or("sigv4")
.to_string();
let signing_behavior = cfg
.get("SigningBehavior")
.and_then(|v| v.as_str())
.unwrap_or("always")
.to_string();
let origin_type = cfg
.get("OriginAccessControlOriginType")
.and_then(|v| v.as_str())
.ok_or("OriginAccessControlConfig.OriginAccessControlOriginType is required")?
.to_string();
let description = cfg
.get("Description")
.and_then(|v| v.as_str())
.map(String::from);
let id = format!("E{}", fakecloud_core::ids::short_id(13).to_uppercase());
let etag = format!("E{}", fakecloud_core::ids::short_id(7).to_uppercase());
let oac = StoredOriginAccessControl {
id: id.clone(),
etag,
config: OriginAccessControlConfig {
name,
description,
signing_protocol,
signing_behavior,
origin_access_control_origin_type: origin_type,
},
};
let mut accounts = self.cloudfront_state.write();
let state = accounts.entry("000000000000");
state.origin_access_controls.insert(id.clone(), oac);
Ok(ProvisionResult::new(id.clone()).with("Id", id))
}
pub(crate) fn delete_cf_origin_access_control(&self, physical_id: &str) -> Result<(), String> {
let mut accounts = self.cloudfront_state.write();
let state = accounts.entry("000000000000");
state.origin_access_controls.remove(physical_id);
Ok(())
}
pub(crate) fn create_cf_public_key(
&self,
resource: &ResourceDefinition,
) -> Result<ProvisionResult, String> {
let props = &resource.properties;
let cfg = props
.get("PublicKeyConfig")
.ok_or("PublicKeyConfig is required")?;
let name = cfg
.get("Name")
.and_then(|v| v.as_str())
.ok_or("PublicKeyConfig.Name is required")?
.to_string();
let encoded_key = cfg
.get("EncodedKey")
.and_then(|v| v.as_str())
.ok_or("PublicKeyConfig.EncodedKey is required")?
.to_string();
let comment = cfg
.get("Comment")
.and_then(|v| v.as_str())
.map(String::from);
let caller_reference = cfg
.get("CallerReference")
.and_then(|v| v.as_str())
.unwrap_or("")
.to_string();
let caller_reference = if caller_reference.is_empty() {
format!("cfn-{}", resource.logical_id)
} else {
caller_reference
};
let id = format!("K{}", fakecloud_core::ids::short_id(13).to_uppercase());
let etag = format!("E{}", fakecloud_core::ids::short_id(7).to_uppercase());
let pk = StoredPublicKey {
id: id.clone(),
etag,
created_time: Utc::now(),
config: PublicKeyConfig {
caller_reference,
name,
encoded_key,
comment,
},
};
let mut accounts = self.cloudfront_state.write();
let state = accounts.entry("000000000000");
state.public_keys.insert(id.clone(), pk);
Ok(ProvisionResult::new(id.clone()).with("Id", id))
}
pub(crate) fn delete_cf_public_key(&self, physical_id: &str) -> Result<(), String> {
let mut accounts = self.cloudfront_state.write();
let state = accounts.entry("000000000000");
state.public_keys.remove(physical_id);
Ok(())
}
pub(crate) fn create_cf_key_group(
&self,
resource: &ResourceDefinition,
) -> Result<ProvisionResult, String> {
let props = &resource.properties;
let cfg = props
.get("KeyGroupConfig")
.ok_or("KeyGroupConfig is required")?;
let name = cfg
.get("Name")
.and_then(|v| v.as_str())
.ok_or("KeyGroupConfig.Name is required")?
.to_string();
let items: Vec<String> = cfg
.get("Items")
.and_then(|v| v.as_array())
.map(|arr| {
arr.iter()
.filter_map(|v| v.as_str().map(String::from))
.collect()
})
.unwrap_or_default();
let comment = cfg
.get("Comment")
.and_then(|v| v.as_str())
.map(String::from);
let id = format!("KG{}", fakecloud_core::ids::short_id(12).to_uppercase());
let etag = format!("E{}", fakecloud_core::ids::short_id(7).to_uppercase());
let kg = StoredKeyGroup {
id: id.clone(),
etag,
last_modified_time: Utc::now(),
config: KeyGroupConfig {
name,
items: KeyGroupItems { public_key: items },
comment,
},
};
let mut accounts = self.cloudfront_state.write();
let state = accounts.entry("000000000000");
state.key_groups.insert(id.clone(), kg);
Ok(ProvisionResult::new(id.clone()).with("Id", id))
}
pub(crate) fn delete_cf_key_group(&self, physical_id: &str) -> Result<(), String> {
let mut accounts = self.cloudfront_state.write();
let state = accounts.entry("000000000000");
state.key_groups.remove(physical_id);
Ok(())
}
pub(crate) fn create_cf_function(
&self,
resource: &ResourceDefinition,
) -> Result<ProvisionResult, String> {
let props = &resource.properties;
let name = props
.get("Name")
.and_then(|v| v.as_str())
.ok_or("Name is required")?
.to_string();
let function_code = props
.get("FunctionCode")
.and_then(|v| v.as_str())
.ok_or("FunctionCode is required")?
.to_string();
let cfg = props
.get("FunctionConfig")
.ok_or("FunctionConfig is required")?;
let runtime = cfg
.get("Runtime")
.and_then(|v| v.as_str())
.unwrap_or("cloudfront-js-2.0")
.to_string();
let comment = cfg
.get("Comment")
.and_then(|v| v.as_str())
.map(String::from);
let id = format!("FN{}", fakecloud_core::ids::short_id(12).to_uppercase());
let etag = format!("E{}", fakecloud_core::ids::short_id(7).to_uppercase());
let function_arn =
Arn::global("cloudfront", &self.account_id, &format!("function/{name}")).to_string();
let now = Utc::now();
let func = StoredFunction {
name: name.clone(),
etag,
status: "UNPUBLISHED".to_string(),
stage: "DEVELOPMENT".to_string(),
function_arn: function_arn.clone(),
created_time: now,
last_modified_time: now,
config: FunctionConfig {
comment,
runtime,
key_value_store_associations: None,
},
function_code,
live_function_code: None,
};
let mut accounts = self.cloudfront_state.write();
let state = accounts.entry("000000000000");
state.functions.insert(name.clone(), func);
Ok(ProvisionResult::new(name.clone())
.with("FunctionARN", function_arn)
.with("FunctionMetadata.FunctionARN", id)
.with("Stage", "DEVELOPMENT"))
}
pub(crate) fn delete_cf_function(&self, physical_id: &str) -> Result<(), String> {
let mut accounts = self.cloudfront_state.write();
let state = accounts.entry("000000000000");
state.functions.remove(physical_id);
Ok(())
}
pub(crate) fn create_cf_cache_policy(
&self,
resource: &ResourceDefinition,
) -> Result<ProvisionResult, String> {
let props = &resource.properties;
let cfg = props
.get("CachePolicyConfig")
.ok_or("CachePolicyConfig is required")?;
let name = cfg
.get("Name")
.and_then(|v| v.as_str())
.ok_or("CachePolicyConfig.Name is required")?
.to_string();
let min_ttl = cfg
.get("MinTTL")
.and_then(|v| {
v.as_i64()
.or_else(|| v.as_str().and_then(|s| s.parse::<i64>().ok()))
})
.unwrap_or(0);
let default_ttl = cfg.get("DefaultTTL").and_then(|v| {
v.as_i64()
.or_else(|| v.as_str().and_then(|s| s.parse::<i64>().ok()))
});
let max_ttl = cfg.get("MaxTTL").and_then(|v| {
v.as_i64()
.or_else(|| v.as_str().and_then(|s| s.parse::<i64>().ok()))
});
let comment = cfg
.get("Comment")
.and_then(|v| v.as_str())
.map(String::from);
let id = format!("CP{}", fakecloud_core::ids::short_id(12).to_uppercase());
let etag = format!("E{}", fakecloud_core::ids::short_id(7).to_uppercase());
let cache_policy = StoredCachePolicy {
id: id.clone(),
etag,
last_modified_time: Utc::now(),
config: CachePolicyConfig {
comment,
name,
default_ttl,
max_ttl,
min_ttl,
parameters_in_cache_key_and_forwarded_to_origin: None,
},
policy_type: "custom".to_string(),
};
let mut accounts = self.cloudfront_state.write();
let state = accounts.entry("000000000000");
state.cache_policies.insert(id.clone(), cache_policy);
Ok(ProvisionResult::new(id.clone()).with("Id", id))
}
pub(crate) fn delete_cf_cache_policy(&self, physical_id: &str) -> Result<(), String> {
let mut accounts = self.cloudfront_state.write();
let state = accounts.entry("000000000000");
state.cache_policies.remove(physical_id);
Ok(())
}
pub(crate) fn create_cf_origin_request_policy(
&self,
resource: &ResourceDefinition,
) -> Result<ProvisionResult, String> {
let props = &resource.properties;
let cfg = props
.get("OriginRequestPolicyConfig")
.ok_or("OriginRequestPolicyConfig is required")?;
let name = cfg
.get("Name")
.and_then(|v| v.as_str())
.ok_or("OriginRequestPolicyConfig.Name is required")?
.to_string();
let header_behavior = cfg
.get("HeadersConfig")
.and_then(|v| v.get("HeaderBehavior"))
.and_then(|v| v.as_str())
.unwrap_or("none")
.to_string();
let cookie_behavior = cfg
.get("CookiesConfig")
.and_then(|v| v.get("CookieBehavior"))
.and_then(|v| v.as_str())
.unwrap_or("none")
.to_string();
let query_string_behavior = cfg
.get("QueryStringsConfig")
.and_then(|v| v.get("QueryStringBehavior"))
.and_then(|v| v.as_str())
.unwrap_or("none")
.to_string();
let comment = cfg
.get("Comment")
.and_then(|v| v.as_str())
.map(String::from);
let id = format!("ORP{}", fakecloud_core::ids::short_id(11).to_uppercase());
let etag = format!("E{}", fakecloud_core::ids::short_id(7).to_uppercase());
let policy = StoredOriginRequestPolicy {
id: id.clone(),
etag,
last_modified_time: Utc::now(),
config: OriginRequestPolicyConfig {
comment,
name,
headers_config: OriginRequestPolicyHeadersConfig {
header_behavior,
headers: None,
},
cookies_config: OriginRequestPolicyCookiesConfig {
cookie_behavior,
cookies: None,
},
query_strings_config: OriginRequestPolicyQueryStringsConfig {
query_string_behavior,
query_strings: None,
},
},
policy_type: "custom".to_string(),
};
let mut accounts = self.cloudfront_state.write();
let state = accounts.entry("000000000000");
state.origin_request_policies.insert(id.clone(), policy);
Ok(ProvisionResult::new(id.clone()).with("Id", id))
}
pub(crate) fn delete_cf_origin_request_policy(&self, physical_id: &str) -> Result<(), String> {
let mut accounts = self.cloudfront_state.write();
let state = accounts.entry("000000000000");
state.origin_request_policies.remove(physical_id);
Ok(())
}
pub(crate) fn create_cf_response_headers_policy(
&self,
resource: &ResourceDefinition,
) -> Result<ProvisionResult, String> {
let props = &resource.properties;
let cfg = props
.get("ResponseHeadersPolicyConfig")
.ok_or("ResponseHeadersPolicyConfig is required")?;
let name = cfg
.get("Name")
.and_then(|v| v.as_str())
.ok_or("ResponseHeadersPolicyConfig.Name is required")?
.to_string();
let comment = cfg
.get("Comment")
.and_then(|v| v.as_str())
.map(String::from);
let id = format!("RHP{}", fakecloud_core::ids::short_id(11).to_uppercase());
let etag = format!("E{}", fakecloud_core::ids::short_id(7).to_uppercase());
let policy = StoredResponseHeadersPolicy {
id: id.clone(),
etag,
last_modified_time: Utc::now(),
config: ResponseHeadersPolicyConfig {
comment,
name,
cors_config: None,
security_headers_config: None,
server_timing_headers_config: None,
custom_headers_config: None,
remove_headers_config: None,
},
policy_type: "custom".to_string(),
};
let mut accounts = self.cloudfront_state.write();
let state = accounts.entry("000000000000");
state.response_headers_policies.insert(id.clone(), policy);
Ok(ProvisionResult::new(id.clone()).with("Id", id))
}
pub(crate) fn delete_cf_response_headers_policy(
&self,
physical_id: &str,
) -> Result<(), String> {
let mut accounts = self.cloudfront_state.write();
let state = accounts.entry("000000000000");
state.response_headers_policies.remove(physical_id);
Ok(())
}
}