1use crate::cloudfront::{CacheBehavior, CachePolicy, CachePolicyConfig, CachePolicyProperties, CachePolicyRef, CookiesConfig, CustomOriginConfig, DefaultCacheBehavior, Distribution, DistributionConfig, DistributionProperties, DistributionRef, HeadersConfig, Origin, OriginAccessControl, OriginAccessControlConfig, OriginAccessControlRef, OriginControlProperties, OriginCustomHeader, ParametersInCacheKeyAndForwardedToOrigin, QueryStringsConfig, S3OriginConfig, ViewerCertificate, VpcOriginConfig};
2use crate::iam::Principal::Service;
3use crate::iam::{Effect, PolicyDocumentBuilder, ServicePrincipal, StatementBuilder};
4use crate::intrinsic::{get_att, get_ref, join, AWS_ACCOUNT_PSEUDO_PARAM};
5use crate::s3::BucketPolicyBuilder;
6use crate::s3::BucketRef;
7use crate::shared::http::HttpMethod::{Delete, Get, Head, Options, Patch, Post, Put};
8use crate::shared::Id;
9use crate::stack::{Resource, StackBuilder};
10use crate::wrappers::{CfConnectionTimeout, ConnectionAttempts, DefaultRootObject, IamAction, OriginPath, S3OriginReadTimeout};
11use serde_json::{json, Value};
12use std::marker::PhantomData;
13use crate::type_state;
14
15pub enum SslSupportedMethod {
16 SniOnly,
17 Vip,
18 StaticIp,
19}
20
21impl From<SslSupportedMethod> for String {
22 fn from(value: SslSupportedMethod) -> Self {
23 match value {
24 SslSupportedMethod::SniOnly => "sni-only".to_string(),
25 SslSupportedMethod::Vip => "vip".to_string(),
26 SslSupportedMethod::StaticIp => "static-ip".to_string(),
27 }
28 }
29}
30
31pub enum MinProtocolVersion {
32 SSLV3,
33 TLSv1,
34 TLSv1_1,
35 TLSv1_2_2018,
36 TLSv1_2_2019,
37 TLSv1_2_2021,
38 TLSv1_2_2025,
39 TLSv1_3,
40}
41
42impl From<MinProtocolVersion> for String {
43 fn from(value: MinProtocolVersion) -> Self {
44 match value {
45 MinProtocolVersion::SSLV3 => "SSLv3".to_string(),
46 MinProtocolVersion::TLSv1 => "TLSv1".to_string(),
47 MinProtocolVersion::TLSv1_1 => "TLSv1.1_2016".to_string(),
48 MinProtocolVersion::TLSv1_2_2018 => "TLSv1.2_2018".to_string(),
49 MinProtocolVersion::TLSv1_2_2019 => "TLSv1.2_2019".to_string(),
50 MinProtocolVersion::TLSv1_2_2021 => "TLSv1.2_2021".to_string(),
51 MinProtocolVersion::TLSv1_2_2025 => "TLSv1.2_2025".to_string(),
52 MinProtocolVersion::TLSv1_3 => "TLSv1.3_2025".to_string(),
53 }
54 }
55}
56
57type_state!(
58 ViewerCertificateState,
59 ViewerCertificateStateStartState,
60 ViewerCertificateStateAcmOrIamState,
61 ViewerCertificateStateEndState,
62);
63
64pub struct ViewerCertificateBuilder<T: ViewerCertificateState> {
66 phantom_data: PhantomData<T>,
67 cloudfront_default_cert: Option<bool>,
68 acm_cert_arn: Option<String>,
69 iam_cert_id: Option<String>,
70 min_protocol_version: Option<String>,
71 ssl_support_method: Option<String>,
72}
73
74impl Default for ViewerCertificateBuilder<ViewerCertificateStateStartState> {
75 fn default() -> Self {
76 Self::new()
77 }
78}
79
80impl ViewerCertificateBuilder<ViewerCertificateStateStartState> {
81 pub fn new() -> ViewerCertificateBuilder<ViewerCertificateStateStartState> {
82 ViewerCertificateBuilder {
83 phantom_data: Default::default(),
84 acm_cert_arn: None,
85 cloudfront_default_cert: None,
86 iam_cert_id: None,
87 min_protocol_version: None,
88 ssl_support_method: None,
89 }
90 }
91
92 pub fn cloudfront_default_cert(self) -> ViewerCertificateBuilder<ViewerCertificateStateEndState> {
93 ViewerCertificateBuilder {
94 phantom_data: Default::default(),
95 cloudfront_default_cert: Some(true),
96 acm_cert_arn: self.acm_cert_arn,
97 iam_cert_id: self.iam_cert_id,
98 min_protocol_version: self.min_protocol_version,
99 ssl_support_method: self.ssl_support_method,
100 }
101 }
102
103 pub fn iam_cert_id(self, id: String) -> ViewerCertificateBuilder<ViewerCertificateStateAcmOrIamState> {
104 ViewerCertificateBuilder {
105 phantom_data: Default::default(),
106 cloudfront_default_cert: Some(true),
107 acm_cert_arn: self.acm_cert_arn,
108 iam_cert_id: Some(id),
109 min_protocol_version: self.min_protocol_version,
110 ssl_support_method: self.ssl_support_method,
111 }
112 }
113
114 pub fn acm_cert_arn(self, id: String) -> ViewerCertificateBuilder<ViewerCertificateStateAcmOrIamState> {
115 ViewerCertificateBuilder {
116 phantom_data: Default::default(),
117 cloudfront_default_cert: Some(true),
118 acm_cert_arn: Some(id),
119 iam_cert_id: self.iam_cert_id,
120 min_protocol_version: self.min_protocol_version,
121 ssl_support_method: self.ssl_support_method,
122 }
123 }
124}
125
126impl<T: ViewerCertificateState> ViewerCertificateBuilder<T> {
127 fn build_internal(self) -> ViewerCertificate {
128 ViewerCertificate {
129 cloudfront_default_cert: self.cloudfront_default_cert,
130 acm_cert_arn: self.acm_cert_arn,
131 iam_cert_id: self.iam_cert_id,
132 min_protocol_version: self.min_protocol_version,
133 ssl_support_method: self.ssl_support_method,
134 }
135 }
136}
137
138impl ViewerCertificateBuilder<ViewerCertificateStateAcmOrIamState> {
139 pub fn min_protocol_version(self, protocol_version: MinProtocolVersion) -> Self {
140 Self {
141 phantom_data: Default::default(),
142 min_protocol_version: Some(protocol_version.into()),
143 cloudfront_default_cert: self.cloudfront_default_cert,
144 acm_cert_arn: self.acm_cert_arn,
145 iam_cert_id: self.iam_cert_id,
146 ssl_support_method: self.ssl_support_method,
147 }
148 }
149
150 pub fn ssl_support_method(self, ssl_support: SslSupportedMethod) -> Self {
151 Self {
152 phantom_data: Default::default(),
153 ssl_support_method: Some(ssl_support.into()),
154 min_protocol_version: self.min_protocol_version,
155 cloudfront_default_cert: self.cloudfront_default_cert,
156 acm_cert_arn: self.acm_cert_arn,
157 iam_cert_id: self.iam_cert_id,
158 }
159 }
160
161 #[must_use]
162 pub fn build(self) -> ViewerCertificate {
163 self.build_internal()
164 }
165}
166
167impl ViewerCertificateBuilder<ViewerCertificateStateEndState> {
168 #[must_use]
169 pub fn build(self) -> ViewerCertificate {
170 self.build_internal()
171 }
172}
173
174pub enum Cookies {
175 None,
176 Whitelist(Vec<String>),
177 AllExcept(Vec<String>),
178 All,
179}
180pub enum QueryString {
181 None,
182 Whitelist(Vec<String>),
183 AllExcept(Vec<String>),
184 All,
185}
186pub enum Headers {
187 None,
188 Whitelist(Vec<String>),
189}
190
191pub struct ParametersInCacheKeyAndForwardedToOriginBuilder {
195 cookies_config: CookiesConfig,
196 headers_config: HeadersConfig,
197 query_strings_config: QueryStringsConfig,
198 accept_encoding_gzip: bool,
199 accept_encoding_brotli: Option<bool>,
200}
201
202impl ParametersInCacheKeyAndForwardedToOriginBuilder {
203 pub fn new(accept_encoding_gzip: bool, cookies: Cookies, query_string: QueryString, headers: Headers) -> Self {
204 let cookies_config = match cookies {
205 Cookies::None => CookiesConfig {
206 cookie_behavior: "none".to_string(),
207 cookies: None,
208 },
209 Cookies::Whitelist(list) => CookiesConfig {
210 cookie_behavior: "whitelist".to_string(),
211 cookies: Some(list),
212 },
213 Cookies::AllExcept(list) => CookiesConfig {
214 cookie_behavior: "allExcept".to_string(),
215 cookies: Some(list),
216 },
217 Cookies::All => CookiesConfig {
218 cookie_behavior: "all".to_string(),
219 cookies: None,
220 },
221 };
222 let query_strings_config = match query_string {
223 QueryString::None => QueryStringsConfig {
224 query_strings_behavior: "none".to_string(),
225 query_strings: None,
226 },
227 QueryString::Whitelist(list) => QueryStringsConfig {
228 query_strings_behavior: "whitelist".to_string(),
229 query_strings: Some(list),
230 },
231 QueryString::AllExcept(list) => QueryStringsConfig {
232 query_strings_behavior: "allExcept".to_string(),
233 query_strings: Some(list),
234 },
235 QueryString::All => QueryStringsConfig {
236 query_strings_behavior: "all".to_string(),
237 query_strings: None,
238 },
239 };
240 let headers_config = match headers {
241 Headers::None => HeadersConfig {
242 headers_behavior: "none".to_string(),
243 headers: None,
244 },
245 Headers::Whitelist(list) => HeadersConfig {
246 headers_behavior: "whitelist".to_string(),
247 headers: Some(list),
248 },
249 };
250
251 Self {
252 cookies_config,
253 headers_config,
254 query_strings_config,
255 accept_encoding_gzip,
256 accept_encoding_brotli: None,
257 }
258 }
259
260 pub fn accept_encoding_brotli(self, accept: bool) -> Self {
261 Self {
262 accept_encoding_brotli: Some(accept),
263 ..self
264 }
265 }
266
267 #[must_use]
268 pub fn build(self) -> ParametersInCacheKeyAndForwardedToOrigin {
269 ParametersInCacheKeyAndForwardedToOrigin {
270 cookies_config: self.cookies_config,
271 accept_encoding_brotli: self.accept_encoding_brotli,
272 accept_encoding_gzip: self.accept_encoding_gzip,
273 headers_config: self.headers_config,
274 query_strings_config: self.query_strings_config,
275 }
276 }
277}
278
279pub struct CachePolicyBuilder {
281 id: Id,
282 name: String,
283 default_ttl: u32,
284 min_ttl: u32,
285 max_ttl: u32,
286 cache_params: ParametersInCacheKeyAndForwardedToOrigin,
287}
288
289impl CachePolicyBuilder {
290 pub fn new(
300 id: &str,
301 unique_name: &str,
302 default_ttl: u32,
303 min_ttl: u32,
304 max_ttl: u32,
305 cache_params: ParametersInCacheKeyAndForwardedToOrigin,
306 ) -> Self {
307 Self {
308 id: Id(id.to_string()),
309 name: unique_name.to_string(),
310 default_ttl,
311 min_ttl,
312 max_ttl,
313 cache_params,
314 }
315 }
316
317 pub fn build(self, stack_builder: &mut StackBuilder) -> CachePolicyRef {
318 let resource_id = Resource::generate_id("CachePolicy");
319 stack_builder.add_resource(CachePolicy {
320 id: self.id,
321 resource_id: resource_id.clone(),
322 r#type: "AWS::CloudFront::CachePolicy".to_string(),
323 properties: CachePolicyProperties {
324 config: CachePolicyConfig {
325 default_ttl: self.default_ttl,
326 min_ttl: self.min_ttl,
327 max_ttl: self.max_ttl,
328 name: self.name,
329 params_in_cache_key_and_forwarded: self.cache_params,
330 },
331 },
332 });
333 CachePolicyRef::new(resource_id)
334 }
335}
336
337pub enum HttpVersion {
338 Http1,
339 Http2,
340 Http3,
341 Http2And3,
342}
343
344impl From<HttpVersion> for String {
345 fn from(value: HttpVersion) -> Self {
346 match value {
347 HttpVersion::Http1 => "http1.1".to_string(),
348 HttpVersion::Http2 => "http2".to_string(),
349 HttpVersion::Http3 => "http3".to_string(),
350 HttpVersion::Http2And3 => "http2and3".to_string(),
351 }
352 }
353}
354
355pub enum PriceClass {
356 PriceClass100,
357 PriceClass200,
358 PriceClassAll,
359 None,
360}
361
362impl From<PriceClass> for String {
363 fn from(value: PriceClass) -> Self {
364 match value {
365 PriceClass::PriceClass100 => "PriceClass_100".to_string(),
366 PriceClass::PriceClass200 => "PriceClass_200".to_string(),
367 PriceClass::PriceClassAll => "PriceClass_All".to_string(),
368 PriceClass::None => "None".to_string(),
369 }
370 }
371}
372
373pub enum OriginProtocolPolicy {
374 HttpOnly,
375 MatchViewer,
376 HttpsOnly,
377}
378
379impl From<OriginProtocolPolicy> for String {
380 fn from(value: OriginProtocolPolicy) -> Self {
381 match value {
382 OriginProtocolPolicy::HttpOnly => "http-only".to_string(),
383 OriginProtocolPolicy::MatchViewer => "match-viewer".to_string(),
384 OriginProtocolPolicy::HttpsOnly => "https-only".to_string(),
385 }
386 }
387}
388
389pub enum IpAddressType {
390 IPv4,
391 IPv6,
392 Dualstack,
393}
394
395impl From<IpAddressType> for String {
396 fn from(value: IpAddressType) -> Self {
397 match value {
398 IpAddressType::IPv4 => "ipv4".to_string(),
399 IpAddressType::IPv6 => "ipv6".to_string(),
400 IpAddressType::Dualstack => "dualstack".to_string(),
401 }
402 }
403}
404
405type_state!(
406 OriginState,
407 OriginStartState,
408 OriginS3OriginState,
409 OriginCustomOriginState,
410);
411
412pub struct OriginBuilder<T: OriginState> {
416 phantom_data: PhantomData<T>,
417 id: String,
418 bucket_arn: Option<Value>,
419 bucket_ref: Option<Value>,
420 domain_name: Option<Value>,
421 connection_attempts: Option<u8>,
422 connection_timeout: Option<u16>,
423 response_completion_timeout: Option<u16>,
424 origin_access_control_id: Option<Value>,
425 origin_path: Option<String>,
426 s3origin_config: Option<S3OriginConfig>,
427 origin_custom_headers: Option<Vec<OriginCustomHeader>>,
428 vpc_origin_config: Option<VpcOriginConfig>,
429 custom_origin_config: Option<CustomOriginConfig>,
430}
431
432impl OriginBuilder<OriginStartState> {
433 pub fn new(origin_id: &str) -> Self {
434 Self {
435 phantom_data: Default::default(),
436 id: origin_id.to_string(),
437 bucket_arn: None,
438 bucket_ref: None,
439 domain_name: None,
440 connection_attempts: None,
441 connection_timeout: None,
442 origin_access_control_id: None,
443 origin_path: None,
444 response_completion_timeout: None,
445 s3origin_config: None,
446 origin_custom_headers: None,
447 vpc_origin_config: None,
448 custom_origin_config: None,
449 }
450 }
451
452 pub fn s3_origin(
456 self,
457 bucket: &BucketRef,
458 oac: &OriginAccessControlRef,
459 origin_read_timeout: Option<S3OriginReadTimeout>,
460 ) -> OriginBuilder<OriginS3OriginState> {
461 let s3origin_config = S3OriginConfig {
462 origin_read_timeout: origin_read_timeout.map(|v| v.0),
463 };
464
465 let domain = bucket.get_att("RegionalDomainName");
466
467 OriginBuilder {
468 phantom_data: Default::default(),
469 id: self.id.to_string(),
470 bucket_arn: Some(bucket.get_arn()),
471 bucket_ref: Some(bucket.get_ref()),
472 domain_name: Some(domain),
473 connection_attempts: self.connection_attempts,
474 connection_timeout: self.connection_timeout,
475 origin_access_control_id: Some(oac.get_att("Id")),
476 origin_path: self.origin_path,
477 response_completion_timeout: self.response_completion_timeout,
478 origin_custom_headers: self.origin_custom_headers,
479 s3origin_config: Some(s3origin_config),
480 vpc_origin_config: None,
481 custom_origin_config: None,
482 }
483 }
484
485 pub fn custom_origin(self, domain: &str, policy: OriginProtocolPolicy) -> OriginBuilder<OriginCustomOriginState> {
490 let custom_origin_config = CustomOriginConfig {
491 origin_protocol_policy: policy.into(),
492 http_port: None,
493 https_port: None,
494 ip_address_type: None,
495 origin_keep_alive_timeout: None,
496 origin_read_timeout: None,
497 origin_ssl_protocols: None,
498 };
499
500 OriginBuilder {
501 phantom_data: Default::default(),
502 id: self.id.to_string(),
503 domain_name: Some(Value::String(domain.to_string())),
504 connection_attempts: self.connection_attempts,
505 connection_timeout: self.connection_timeout,
506 origin_path: self.origin_path,
507 response_completion_timeout: self.response_completion_timeout,
508 origin_custom_headers: self.origin_custom_headers,
509 custom_origin_config: Some(custom_origin_config),
510 vpc_origin_config: None,
511 origin_access_control_id: None,
512 s3origin_config: None,
513 bucket_arn: None,
514 bucket_ref: None,
515 }
516 }
517}
518
519impl<T: OriginState> OriginBuilder<T> {
520 pub fn connection_attempts(self, attempts: ConnectionAttempts) -> Self {
521 Self {
522 connection_attempts: Some(attempts.0),
523 ..self
524 }
525 }
526
527 pub fn timeouts(self, timeouts: CfConnectionTimeout) -> Self {
528 Self {
529 connection_timeout: timeouts.0,
530 response_completion_timeout: timeouts.1,
531 ..self
532 }
533 }
534
535 pub fn origin_path(self, path: OriginPath) -> Self {
536 Self {
537 origin_path: Some(path.0),
538 ..self
539 }
540 }
541
542 fn build_internal(self) -> Origin {
543 Origin {
544 id: self.id,
545 s3_bucket_policy: None,
546 domain_name: self.domain_name.expect("domain name should be present for cloudfront distribution"),
547 connection_attempts: self.connection_attempts,
548 connection_timeout: self.connection_timeout,
549 origin_access_control_id: self.origin_access_control_id,
550 origin_path: self.origin_path,
551 response_completion_timeout: self.response_completion_timeout,
552 s3origin_config: self.s3origin_config,
553 origin_custom_headers: self.origin_custom_headers,
554 vpc_origin_config: self.vpc_origin_config,
555 custom_origin_config: self.custom_origin_config,
556 }
557 }
558}
559
560impl OriginBuilder<OriginCustomOriginState> {
561 pub fn ip_address_type(self, address_type: IpAddressType) -> Self {
562 let mut config = self.custom_origin_config.expect("custom config to be present in Custom Origin State");
563 config.ip_address_type = Some(address_type.into());
564
565 OriginBuilder {
566 custom_origin_config: Some(config),
567 ..self
568 }
569 }
570 pub fn http_port(self, port: u16) -> Self {
571 let mut config = self.custom_origin_config.expect("custom config to be present in Custom Origin State");
572 config.http_port = Some(port);
573
574 OriginBuilder {
575 custom_origin_config: Some(config),
576 ..self
577 }
578 }
579
580 pub fn https_port(self, port: u16) -> Self {
581 let mut config = self.custom_origin_config.expect("custom config to be present in Custom Origin State");
582 config.https_port = Some(port);
583
584 OriginBuilder {
585 custom_origin_config: Some(config),
586 ..self
587 }
588 }
589
590 pub fn origin_keep_alive_timeout(self, timeout: u8) -> Self {
591 let mut config = self.custom_origin_config.expect("custom config to be present in Custom Origin State");
592 config.origin_keep_alive_timeout = Some(timeout);
593
594 OriginBuilder {
595 custom_origin_config: Some(config),
596 ..self
597 }
598 }
599
600 pub fn origin_read_timeout(self, timeout: u8) -> Self {
601 let mut config = self.custom_origin_config.expect("custom config to be present in Custom Origin State");
602 config.origin_read_timeout = Some(timeout);
603
604 OriginBuilder {
605 custom_origin_config: Some(config),
606 ..self
607 }
608 }
609
610 pub fn add_origin_ssl_protocol(self, protocol: String) -> Self {
611 let mut config = self.custom_origin_config.expect("custom config to be present in Custom Origin State");
612
613 let protocols = if let Some(mut protocols) = config.origin_ssl_protocols {
614 protocols.push(protocol);
615 protocols
616 } else {
617 vec![protocol]
618 };
619
620 config.origin_ssl_protocols = Some(protocols);
621
622 OriginBuilder {
623 custom_origin_config: Some(config),
624 ..self
625 }
626 }
627
628 pub fn build(self) -> Origin {
629 self.build_internal()
630 }
631}
632
633impl OriginBuilder<OriginS3OriginState> {
634 pub fn build(mut self) -> Origin {
635 let bucket_ref = self.bucket_ref.take().expect("bucket ref to be present in S3 origin state");
636 let bucket_arn = self.bucket_arn.take().expect("bucket arn to be present in S3 origin state");
637
638 let bucket_items = vec![join("", vec![bucket_arn, Value::String("/*".to_string())])];
639 let statement = StatementBuilder::new(vec![IamAction("s3:GetObject".to_string())], Effect::Allow)
640 .resources(bucket_items)
641 .principal(Service(ServicePrincipal {
642 service: "cloudfront.amazonaws.com".to_string(),
643 }))
644 .build();
645 let doc = PolicyDocumentBuilder::new(vec![statement]).build();
646 let bucket_policy_id = format!("{}-website-s3-policy", self.id);
647 let (_, s3_policy) = BucketPolicyBuilder::new_with_bucket_ref(bucket_policy_id.as_str(), bucket_ref, doc).raw_build();
648
649 let mut origin = self.build_internal();
650 origin.s3_bucket_policy = Some(s3_policy);
651
652 origin
653 }
654}
655
656pub enum DefaultCacheAllowedMethods {
657 GetHead,
658 GetHeadOptions,
659 All,
660}
661
662impl From<DefaultCacheAllowedMethods> for Vec<String> {
663 fn from(value: DefaultCacheAllowedMethods) -> Self {
664 match value {
665 DefaultCacheAllowedMethods::GetHead => vec![Get.into(), Head.into()],
666 DefaultCacheAllowedMethods::GetHeadOptions => vec![Get.into(), Head.into(), Options.into()],
667 DefaultCacheAllowedMethods::All => vec![
668 Get.into(),
669 Head.into(),
670 Options.into(),
671 Put.into(),
672 Patch.into(),
673 Post.into(),
674 Delete.into(),
675 ],
676 }
677 }
678}
679
680pub enum DefaultCacheCachedMethods {
681 GetHead,
682 GetHeadOptions,
683}
684
685impl From<DefaultCacheCachedMethods> for Vec<String> {
686 fn from(value: DefaultCacheCachedMethods) -> Self {
687 match value {
688 DefaultCacheCachedMethods::GetHead => vec![Get.into(), Head.into()],
689 DefaultCacheCachedMethods::GetHeadOptions => vec![Get.into(), Head.into(), Options.into()],
690 }
691 }
692}
693
694pub enum ViewerProtocolPolicy {
695 AllowAll,
696 RedirectToHttps,
697 HttpsOnly,
698}
699
700impl From<ViewerProtocolPolicy> for String {
701 fn from(value: ViewerProtocolPolicy) -> Self {
702 match value {
703 ViewerProtocolPolicy::AllowAll => "allow-all".to_string(),
704 ViewerProtocolPolicy::RedirectToHttps => "redirect-to-https".to_string(),
705 ViewerProtocolPolicy::HttpsOnly => "https-only".to_string(),
706 }
707 }
708}
709
710pub struct DefaultCacheBehaviorBuilder {
712 target_origin_id: String,
713 cache_policy_id: Value,
714 viewer_protocol_policy: String,
715 allowed_methods: Option<Vec<String>>,
716 cached_methods: Option<Vec<String>>,
717 compress: Option<bool>,
718}
719
720impl DefaultCacheBehaviorBuilder {
721 pub fn new(origin: &Origin, policy: &CachePolicyRef, viewer_protocol_policy: ViewerProtocolPolicy) -> Self {
722 Self {
723 target_origin_id: origin.get_origin_id().to_string(),
724 cache_policy_id: policy.get_att("Id"),
725 viewer_protocol_policy: viewer_protocol_policy.into(),
726 allowed_methods: None,
727 cached_methods: None,
728 compress: None,
729 }
730 }
731
732 pub fn allowed_methods(self, methods: DefaultCacheAllowedMethods) -> Self {
733 Self {
734 allowed_methods: Some(methods.into()),
735 target_origin_id: self.target_origin_id,
736 cache_policy_id: self.cache_policy_id,
737 viewer_protocol_policy: self.viewer_protocol_policy,
738 cached_methods: self.cached_methods,
739 compress: self.compress,
740 }
741 }
742
743 pub fn cached_methods(self, methods: DefaultCacheCachedMethods) -> Self {
744 Self {
745 cached_methods: Some(methods.into()),
746 target_origin_id: self.target_origin_id,
747 cache_policy_id: self.cache_policy_id,
748 viewer_protocol_policy: self.viewer_protocol_policy,
749 allowed_methods: self.allowed_methods,
750 compress: self.compress,
751 }
752 }
753
754 pub fn compress(self, compress: bool) -> Self {
755 Self {
756 compress: Some(compress),
757 target_origin_id: self.target_origin_id,
758 cache_policy_id: self.cache_policy_id,
759 viewer_protocol_policy: self.viewer_protocol_policy,
760 allowed_methods: self.allowed_methods,
761 cached_methods: self.cached_methods,
762 }
763 }
764
765 pub fn build(self) -> DefaultCacheBehavior {
766 DefaultCacheBehavior {
767 target_origin_id: self.target_origin_id,
768 cache_policy_id: self.cache_policy_id,
769 viewer_protocol_policy: self.viewer_protocol_policy,
770 allowed_methods: self.allowed_methods,
771 cached_methods: self.cached_methods,
772 compress: self.compress,
773 }
774 }
775}
776
777type_state!(
778 DistributionState,
779 DistributionStartState,
780 DistributionOriginState,
781);
782
783pub enum SigningBehavior {
784 Never,
785 NoOverride,
786 Always,
787}
788
789impl From<SigningBehavior> for String {
790 fn from(value: SigningBehavior) -> Self {
791 match value {
792 SigningBehavior::Never => "never".to_string(),
793 SigningBehavior::NoOverride => "no-override".to_string(),
794 SigningBehavior::Always => "always".to_string(),
795 }
796 }
797}
798
799pub enum SigningProtocol {
800 SigV4,
801}
802
803impl From<SigningProtocol> for String {
804 fn from(value: SigningProtocol) -> Self {
805 match value {
806 SigningProtocol::SigV4 => "sigv4".to_string(),
807 }
808 }
809}
810
811pub enum OriginAccessControlType {
812 S3,
813 MediaStore,
814 Lambda,
815 MediaPackageV2,
816}
817
818impl From<OriginAccessControlType> for String {
819 fn from(value: OriginAccessControlType) -> Self {
820 match value {
821 OriginAccessControlType::S3 => "s3".to_string(),
822 OriginAccessControlType::MediaStore => "mediastore".to_string(),
823 OriginAccessControlType::Lambda => "lambda".to_string(),
824 OriginAccessControlType::MediaPackageV2 => "mediapackagev2".to_string(),
825 }
826 }
827}
828
829pub struct OriginAccessControlBuilder {
833 id: Id,
834 name: String,
835 origin_access_control_type: OriginAccessControlType,
836 signing_behavior: SigningBehavior,
837 signing_protocol: SigningProtocol,
838}
839
840impl OriginAccessControlBuilder {
841 pub fn new(
850 id: &str,
851 name: &str,
852 origin_access_control_type: OriginAccessControlType,
853 signing_behavior: SigningBehavior,
854 signing_protocol: SigningProtocol,
855 ) -> Self {
856 Self {
857 id: Id(id.to_string()),
858 name: name.to_string(),
859 origin_access_control_type,
860 signing_behavior,
861 signing_protocol,
862 }
863 }
864
865 pub fn build(self, stack_builder: &mut StackBuilder) -> OriginAccessControlRef {
866 let resource_id = Resource::generate_id("OAC");
867 stack_builder.add_resource(OriginAccessControl {
868 id: self.id,
869 resource_id: resource_id.clone(),
870 r#type: "AWS::CloudFront::OriginAccessControl".to_string(),
871 properties: OriginControlProperties {
872 config: OriginAccessControlConfig {
873 name: self.name,
874 origin_access_control_type: self.origin_access_control_type.into(),
875 signing_behavior: self.signing_behavior.into(),
876 signing_protocol: self.signing_protocol.into(),
877 },
878 },
879 });
880 OriginAccessControlRef::new(resource_id)
881 }
882}
883
884pub struct DistributionBuilder<T: DistributionState> {
911 phantom_data: PhantomData<T>,
912 id: Id,
913 enabled: bool,
914 default_cache_behavior: DefaultCacheBehavior,
915 price_class: Option<String>,
916 http_version: Option<String>,
917 aliases: Option<Vec<String>>,
918 cnames: Option<Vec<String>>,
919 ipv6_enabled: Option<bool>,
920 viewer_certificate: Option<ViewerCertificate>,
921 cache_behaviors: Option<Vec<CacheBehavior>>,
922 default_root_object: Option<String>,
923 origins: Option<Vec<Origin>>,
926}
927
928impl DistributionBuilder<DistributionStartState> {
929 pub fn new(id: &str, default_cache_behavior: DefaultCacheBehavior) -> Self {
935 Self {
936 phantom_data: Default::default(),
937 id: Id(id.to_string()),
938 enabled: true,
939 default_cache_behavior,
940 aliases: None,
941 cache_behaviors: None,
942 cnames: None,
943 default_root_object: None,
944 http_version: None,
945 ipv6_enabled: None,
946 origins: None,
947 price_class: None,
948 viewer_certificate: None,
949 }
950 }
951
952 pub fn origins(self, origins: Vec<Origin>) -> DistributionBuilder<DistributionOriginState> {
953 DistributionBuilder {
954 phantom_data: Default::default(),
955 origins: Some(origins),
956 id: self.id,
957 enabled: self.enabled,
958 default_cache_behavior: self.default_cache_behavior,
959 price_class: self.price_class,
960 http_version: self.http_version,
961 aliases: self.aliases,
962 cnames: self.cnames,
963 ipv6_enabled: self.ipv6_enabled,
964 viewer_certificate: self.viewer_certificate,
965 cache_behaviors: self.cache_behaviors,
966 default_root_object: self.default_root_object,
967 }
968 }
969}
970
971impl DistributionBuilder<DistributionOriginState> {
972 pub fn build(mut self, stack_builder: &mut StackBuilder) -> DistributionRef {
973 let mut origins = self.origins.take().expect("origins to be present in distribution origin state");
974 let resource_id = Resource::generate_id("CloudFrontDistribution");
975
976 origins
977 .iter_mut()
978 .filter(|o| o.s3_bucket_policy.is_some())
979 .map(|s3| {
980 let mut policy = s3
981 .s3_bucket_policy
982 .take()
983 .expect("just checked that this was present, only need to use it this one time");
984 let distro_id = get_att(&resource_id, "Id");
985 let source_arn_value = join(
986 "",
987 vec![
988 Value::String("arn:aws:cloudfront::".to_string()),
989 get_ref(AWS_ACCOUNT_PSEUDO_PARAM),
990 Value::String(":distribution/".to_string()),
991 distro_id,
992 ],
993 );
994 let distro_condition = json!({
995 "StringEquals": {
996 "AWS:SourceArn": source_arn_value
997 }
998 });
999 policy
1000 .properties
1001 .policy_document
1002 .statements
1003 .iter_mut()
1004 .for_each(|v| v.condition = Some(distro_condition.clone()));
1005 policy
1006 })
1007 .for_each(|p| {
1008 stack_builder.add_resource(p);
1009 });
1010
1011 self.origins = Some(origins);
1012
1013 self.build_internal(resource_id, stack_builder)
1014 }
1015}
1016
1017impl<T: DistributionState> DistributionBuilder<T> {
1018 pub fn add_cache_behavior(mut self, behavior: CacheBehavior) -> Self {
1019 if let Some(mut behaviors) = self.cache_behaviors {
1020 behaviors.push(behavior);
1021 self.cache_behaviors = Some(behaviors);
1022 } else {
1023 self.cache_behaviors = Some(vec![behavior])
1024 }
1025 self
1026 }
1027
1028 pub fn aliases(self, aliases: Vec<String>) -> Self {
1029 Self {
1030 aliases: Some(aliases),
1031 ..self
1032 }
1033 }
1034
1035 pub fn cnames(self, cnames: Vec<String>) -> Self {
1037 Self {
1038 cnames: Some(cnames),
1039 ..self
1040 }
1041 }
1042
1043 pub fn price_class(self, price_class: PriceClass) -> Self {
1044 Self {
1045 price_class: Some(price_class.into()),
1046 ..self
1047 }
1048 }
1049
1050 pub fn http_version(self, http_version: HttpVersion) -> Self {
1051 Self {
1052 http_version: Some(http_version.into()),
1053 ..self
1054 }
1055 }
1056
1057 pub fn ipv6_enabled(self, enabled: bool) -> Self {
1058 Self {
1059 ipv6_enabled: Some(enabled),
1060 ..self
1061 }
1062 }
1063
1064 pub fn viewer_certificate(self, viewer_certificate: ViewerCertificate) -> Self {
1065 Self {
1066 viewer_certificate: Some(viewer_certificate),
1067 ..self
1068 }
1069 }
1070
1071 pub fn enabled(self, enabled: bool) -> Self {
1072 Self { enabled, ..self }
1073 }
1074
1075 pub fn default_root_object(self, default: DefaultRootObject) -> Self {
1076 Self {
1077 default_root_object: Some(default.0),
1078 ..self
1079 }
1080 }
1081
1082 fn build_internal(self, resource_id: String, stack_builder: &mut StackBuilder) -> DistributionRef {
1083 let config = DistributionConfig {
1084 enabled: self.enabled,
1085 default_cache_behavior: self.default_cache_behavior,
1086 aliases: self.aliases,
1087 cache_behaviors: self.cache_behaviors,
1088 cnames: self.cnames,
1089 default_root_object: self.default_root_object.unwrap_or_default(),
1090 http_version: self.http_version,
1091 ipv6_enabled: self.ipv6_enabled,
1092 price_class: self.price_class,
1093 viewer_certificate: self.viewer_certificate,
1094 origins: self.origins,
1095 origin_groups: None,
1096 };
1097 stack_builder.add_resource(Distribution {
1098 id: self.id,
1099 resource_id: resource_id.clone(),
1100 r#type: "AWS::CloudFront::Distribution".to_string(),
1101 properties: DistributionProperties { config },
1102 });
1103
1104 DistributionRef::new(resource_id)
1105 }
1106}