1use crate::custom_resource::{BucketNotificationBuilder, BUCKET_NOTIFICATION_HANDLER_CODE};
2use crate::iam::{CustomPermission, Effect, Permission, PolicyDocument, PolicyDocumentBuilder, PrincipalBuilder, StatementBuilder};
3use crate::intrinsic::join;
4use crate::lambda::{Architecture, Runtime};
5use crate::lambda::{Code, FunctionBuilder, FunctionRef, PermissionBuilder};
6use crate::s3::{
7 dto, AccelerateConfiguration, IntelligentTieringConfiguration, InventoryTableConfiguration,
8 JournalTableConfiguration, MetadataConfiguration, MetadataDestination, RecordExpiration, TagFilter, Tiering,
9};
10use crate::s3::{
11 Bucket, BucketEncryption, BucketPolicy, BucketPolicyRef, BucketProperties, BucketRef, CorsConfiguration, CorsRule,
12 LifecycleConfiguration, LifecycleRule, LifecycleRuleTransition, NonCurrentVersionTransition, PublicAccessBlockConfiguration,
13 RedirectAllRequestsTo, S3BucketPolicyProperties, ServerSideEncryptionByDefault, ServerSideEncryptionRule, WebsiteConfiguration,
14};
15use crate::shared::http::{HttpMethod, Protocol};
16use crate::shared::{DeletionPolicy, Id, UpdateDeletePolicyDTO, UpdateReplacePolicy, QUEUE_POLICY_ID_SUFFIX, TOPIC_POLICY_ID_SUFFIX};
17use crate::sns::{TopicPolicyBuilder, TopicRef};
18use crate::sqs::{QueuePolicyBuilder, QueueRef};
19use crate::stack::{Resource, StackBuilder};
20use crate::type_state;
21use crate::wrappers::{
22 BucketName, BucketTiering, IamAction, LambdaPermissionAction, LifecycleTransitionInDays, Memory, RecordExpirationDays,
23 S3LifecycleObjectSizes, Timeout,
24};
25use serde_json::{json, Value};
26use std::marker::PhantomData;
27use std::time::Duration;
28
29pub struct BucketPolicyBuilder {
61 id: Id,
62 bucket_name: Value,
63 policy_document: PolicyDocument,
64}
65
66impl BucketPolicyBuilder {
67 pub fn new(id: &str, bucket: &BucketRef, policy_document: PolicyDocument) -> Self {
74 Self {
75 id: Id(id.to_string()),
76 bucket_name: bucket.get_ref(),
77 policy_document,
78 }
79 }
80
81 pub(crate) fn new_with_bucket_ref(id: &str, bucket_name: Value, policy_document: PolicyDocument) -> Self {
82 Self {
83 id: Id(id.to_string()),
84 bucket_name,
85 policy_document,
86 }
87 }
88
89 pub(crate) fn raw_build(self) -> (String, BucketPolicy) {
90 let resource_id = Resource::generate_id("S3BucketPolicy");
91 let policy = BucketPolicy {
92 id: self.id,
93 resource_id: resource_id.to_string(),
94 r#type: "AWS::S3::BucketPolicy".to_string(),
95 properties: S3BucketPolicyProperties {
96 bucket_name: self.bucket_name,
97 policy_document: self.policy_document,
98 },
99 };
100 (resource_id, policy)
101 }
102
103 pub fn build(self, stack_builder: &mut StackBuilder) -> BucketPolicyRef {
104 let (resource_id, policy) = self.raw_build();
105 stack_builder.add_resource(policy);
106 BucketPolicyRef::new(resource_id)
107 }
108}
109
110#[derive(Debug, Clone)]
111pub enum VersioningConfiguration {
112 Enabled,
113 Suspended,
114}
115
116impl From<VersioningConfiguration> for String {
117 fn from(value: VersioningConfiguration) -> Self {
118 match value {
119 VersioningConfiguration::Enabled => "Enabled".to_string(),
120 VersioningConfiguration::Suspended => "Suspended".to_string(),
121 }
122 }
123}
124
125#[derive(Debug, Clone)]
126pub enum Encryption {
127 S3Managed,
128 KmsManaged,
129 DsseManaged,
130 }
133
134impl From<Encryption> for String {
135 fn from(value: Encryption) -> Self {
136 match value {
137 Encryption::S3Managed => "AES256".to_string(),
138 Encryption::KmsManaged => "aws:kms".to_string(),
139 Encryption::DsseManaged => "aws:kms:dsse".to_string(),
140 }
141 }
142}
143
144pub enum NotificationDestination<'a> {
145 Lambda(&'a FunctionRef, NotificationEventType),
146 Sns(&'a TopicRef, NotificationEventType),
147 Sqs(&'a QueueRef, NotificationEventType),
148}
149
150#[derive(Debug, Clone)]
151pub enum NotificationEventType {
152 ObjectCreated,
153 ObjectCreatedPut,
154 ObjectCreatedPost,
155 ObjectCreatedCopy,
156 ObjectCreatedCompleteMultipartUpload,
157 ObjectRemoved,
158 ObjectRemovedDelete,
159 ObjectRemovedDeleteMarkerCreated,
160 ObjectRestorePost,
161 ObjectRestoreCompleted,
162 ObjectRestoreDelete,
163 ReducedRedundancyLostObject,
164 ReplicationOperationFailedReplication,
165 ReplicationOperationMissedThreshold,
166 ReplicationOperationReplicatedAfterThreshold,
167 ReplicationOperationNotTracked,
168 LifecycleExpiration,
169 LifecycleExpirationDelete,
170 LifecycleExpirationDeleteMarkerCreated,
171 LifecycleTransition,
172 IntelligentTiering,
173 ObjectTagging,
174 ObjectTaggingPut,
175 ObjectTaggingDelete,
176 ObjectAclPut,
177 ObjectRestore,
178 REPLICATION,
179}
180
181#[derive(Debug, Clone)]
182pub enum AbacStatus {
183 Enabled,
184 Disabled,
185}
186
187impl From<AbacStatus> for String {
188 fn from(value: AbacStatus) -> Self {
189 match value {
190 AbacStatus::Enabled => "Enabled".to_string(),
191 AbacStatus::Disabled => "Disabled".to_string(),
192 }
193 }
194}
195#[derive(Debug, Clone)]
196pub enum AccelerationStatus {
197 Enabled,
198 Suspended,
199}
200
201impl From<AccelerationStatus> for String {
202 fn from(value: AccelerationStatus) -> Self {
203 match value {
204 AccelerationStatus::Enabled => "Enabled".to_string(),
205 AccelerationStatus::Suspended => "Suspended".to_string(),
206 }
207 }
208}
209
210impl From<NotificationEventType> for String {
211 fn from(value: NotificationEventType) -> Self {
212 match value {
213 NotificationEventType::ObjectCreated => "s3:ObjectCreated:*".to_string(),
214 NotificationEventType::ObjectCreatedPut => "s3:ObjectCreated:Put".to_string(),
215 NotificationEventType::ObjectCreatedPost => "s3:ObjectCreated:Post".to_string(),
216 NotificationEventType::ObjectCreatedCopy => "s3:ObjectCreated:Copy".to_string(),
217 NotificationEventType::ObjectCreatedCompleteMultipartUpload => "s3:ObjectCreated:CompleteMultipartUpload".to_string(),
218 NotificationEventType::ObjectRemoved => "s3:ObjectRemoved:*".to_string(),
219 NotificationEventType::ObjectRemovedDelete => "s3:ObjectRemoved:Delete".to_string(),
220 NotificationEventType::ObjectRemovedDeleteMarkerCreated => "s3:ObjectRemoved:DeleteMarkerCreated".to_string(),
221 NotificationEventType::ObjectRestorePost => "s3:ObjectRestore:Post".to_string(),
222 NotificationEventType::ObjectRestoreCompleted => "s3:ObjectRestore:Completed".to_string(),
223 NotificationEventType::ObjectRestoreDelete => "s3:ObjectRestore:Delete".to_string(),
224 NotificationEventType::ReducedRedundancyLostObject => "s3:ReducedRedundancyLostObject".to_string(),
225 NotificationEventType::ReplicationOperationFailedReplication => "s3:Replication:OperationFailedReplication".to_string(),
226 NotificationEventType::ReplicationOperationMissedThreshold => "s3:Replication:OperationMissedThreshold".to_string(),
227 NotificationEventType::ReplicationOperationReplicatedAfterThreshold => {
228 "s3:Replication:OperationReplicatedAfterThreshold".to_string()
229 }
230 NotificationEventType::ReplicationOperationNotTracked => "s3:Replication:OperationNotTracked".to_string(),
231 NotificationEventType::LifecycleExpiration => "s3:LifecycleExpiration:*".to_string(),
232 NotificationEventType::LifecycleExpirationDelete => "s3:LifecycleExpiration:Delete".to_string(),
233 NotificationEventType::LifecycleExpirationDeleteMarkerCreated => "s3:LifecycleExpiration:DeleteMarkerCreated".to_string(),
234 NotificationEventType::LifecycleTransition => "s3:LifecycleTransition".to_string(),
235 NotificationEventType::IntelligentTiering => "s3:IntelligentTiering".to_string(),
236 NotificationEventType::ObjectTagging => "s3:ObjectTagging:*".to_string(),
237 NotificationEventType::ObjectTaggingPut => "s3:ObjectTagging:Put".to_string(),
238 NotificationEventType::ObjectTaggingDelete => "s3:ObjectTagging:Delete".to_string(),
239 NotificationEventType::ObjectAclPut => "s3:ObjectAcl:Put".to_string(),
240 NotificationEventType::ObjectRestore => "s3:ObjectRestore:*".to_string(),
241 NotificationEventType::REPLICATION => "s3:Replication:*".to_string(),
242 }
243 }
244}
245
246type_state!(BucketBuilderState, StartState, WebsiteState,);
247
248pub struct BucketBuilder<T: BucketBuilderState> {
276 phantom_data: PhantomData<T>,
277 id: Id,
278 abac_status: Option<String>,
279 acceleration_status: Option<String>,
280 name: Option<String>,
281 access: Option<PublicAccessBlockConfiguration>,
282 intelligent_tiering_configurations: Option<Vec<IntelligentTieringConfiguration>>,
283 metadata_configuration: Option<MetadataConfiguration>,
284 versioning_configuration: Option<VersioningConfiguration>,
285 lifecycle_configuration: Option<LifecycleConfiguration>,
286 index_document: Option<String>,
287 error_document: Option<String>,
288 redirect_all_requests_to: Option<(String, Option<Protocol>)>,
289 cors_config: Option<CorsConfiguration>,
290 bucket_encryption: Option<Encryption>,
291 bucket_notification_lambda_destinations: Vec<(Value, String)>,
292 bucket_notification_sns_destinations: Vec<(Id, Value, String)>,
293 bucket_notification_sqs_destinations: Vec<(Id, Value, Value, String)>,
294 deletion_policy: Option<String>,
295 update_replace_policy: Option<String>,
296}
297
298impl BucketBuilder<StartState> {
299 pub fn new(id: &str) -> Self {
304 Self {
305 id: Id(id.to_string()),
306 phantom_data: Default::default(),
307 abac_status: None,
308 acceleration_status: None,
309 name: None,
310 access: None,
311 intelligent_tiering_configurations: None,
312 metadata_configuration: None,
313 versioning_configuration: None,
314 lifecycle_configuration: None,
315 index_document: None,
316 error_document: None,
317 redirect_all_requests_to: None,
318 cors_config: None,
319 bucket_encryption: None,
320 bucket_notification_lambda_destinations: vec![],
321 bucket_notification_sns_destinations: vec![],
322 bucket_notification_sqs_destinations: vec![],
323 deletion_policy: None,
324 update_replace_policy: None,
325 }
326 }
327
328 pub fn build(self, stack_builder: &mut StackBuilder) -> BucketRef {
329 let (bucket, _) = self.build_internal(false, stack_builder);
330 bucket
331 }
332}
333
334impl<T: BucketBuilderState> BucketBuilder<T> {
335 pub fn name(self, name: BucketName) -> Self {
336 Self {
337 name: Some(name.0),
338 ..self
339 }
340 }
341
342 pub fn abac_status(self, abac_status: AbacStatus) -> Self {
343 Self {
344 abac_status: Some(abac_status.into()),
345 ..self
346 }
347 }
348
349 pub fn acceleration_status(self, acceleration_status: AccelerationStatus) -> Self {
350 Self {
351 acceleration_status: Some(acceleration_status.into()),
352 ..self
353 }
354 }
355
356 pub fn metadata_configuration(self, config: MetadataConfiguration) -> Self {
357 Self {
358 metadata_configuration: Some(config),
359 ..self
360 }
361 }
362
363 pub fn versioning_configuration(self, config: VersioningConfiguration) -> Self {
364 Self {
365 versioning_configuration: Some(config),
366 ..self
367 }
368 }
369
370 pub fn lifecycle_configuration(self, config: LifecycleConfiguration) -> Self {
371 Self {
372 lifecycle_configuration: Some(config),
373 ..self
374 }
375 }
376
377 pub fn public_access_block_configuration(self, access: PublicAccessBlockConfiguration) -> Self {
378 Self {
379 access: Some(access),
380 ..self
381 }
382 }
383
384 pub fn encryption(self, encryption: Encryption) -> Self {
385 Self {
386 bucket_encryption: Some(encryption),
387 ..self
388 }
389 }
390
391 pub fn update_replace_and_deletion_policy(self, update_replace_policy: UpdateReplacePolicy, deletion_policy: DeletionPolicy) -> Self {
392 Self {
393 deletion_policy: Some(deletion_policy.into()),
394 update_replace_policy: Some(update_replace_policy.into()),
395 ..self
396 }
397 }
398
399 pub fn add_intelligent_tiering(mut self, tiering: IntelligentTieringConfiguration) -> Self {
400 if let Some(mut config) = self.intelligent_tiering_configurations {
401 config.push(tiering);
402 self.intelligent_tiering_configurations = Some(config);
403 } else {
404 self.intelligent_tiering_configurations = Some(vec![tiering]);
405 }
406 self
407 }
408
409 pub fn add_notification(mut self, destination: NotificationDestination) -> Self {
410 match destination {
411 NotificationDestination::Lambda(l, e) => self.bucket_notification_lambda_destinations.push((l.get_arn(), e.into())),
412 NotificationDestination::Sns(s, e) => {
413 self.bucket_notification_sns_destinations
414 .push((s.get_id().clone(), s.get_ref(), e.into()))
415 }
416 NotificationDestination::Sqs(q, e) => {
417 self.bucket_notification_sqs_destinations
418 .push((q.get_id().clone(), q.get_ref(), q.get_arn(), e.into()))
419 }
420 }
421 self
422 }
423
424 pub fn website<I: Into<String>>(self, index_document: I) -> BucketBuilder<WebsiteState> {
429 BucketBuilder {
430 phantom_data: Default::default(),
431 id: self.id,
432 abac_status: self.abac_status,
433 acceleration_status: self.acceleration_status,
434 name: self.name,
435 access: self.access,
436 intelligent_tiering_configurations: self.intelligent_tiering_configurations,
437 metadata_configuration: self.metadata_configuration,
438 versioning_configuration: self.versioning_configuration,
439 lifecycle_configuration: self.lifecycle_configuration,
440 index_document: Some(index_document.into()),
441 error_document: self.error_document,
442 redirect_all_requests_to: self.redirect_all_requests_to,
443 cors_config: self.cors_config,
444 bucket_encryption: self.bucket_encryption,
445 bucket_notification_lambda_destinations: self.bucket_notification_lambda_destinations,
446 bucket_notification_sns_destinations: self.bucket_notification_sns_destinations,
447 bucket_notification_sqs_destinations: self.bucket_notification_sqs_destinations,
448 deletion_policy: self.deletion_policy,
449 update_replace_policy: self.update_replace_policy,
450 }
451 }
452
453 fn build_internal(self, website: bool, stack_builder: &mut StackBuilder) -> (BucketRef, Option<BucketPolicyRef>) {
454 let resource_id = Resource::generate_id("S3Bucket");
455
456 let versioning_configuration = self.versioning_configuration.map(|c| dto::VersioningConfig { status: c.into() });
457
458 let website_configuration = if website {
459 let redirect_all_requests_to = self.redirect_all_requests_to.map(|r| RedirectAllRequestsTo {
460 host_name: r.0,
461 protocol: r.1.map(Into::into),
462 });
463
464 Some(WebsiteConfiguration {
465 index_document: self.index_document,
466 error_document: self.error_document,
467 redirect_all_requests_to,
468 })
469 } else {
470 None
471 };
472
473 let access = if self.access.is_none() && website {
474 Some(PublicAccessBlockConfiguration {
476 block_public_policy: Some(false),
477 restrict_public_buckets: Some(false),
478 block_public_acls: Some(true),
479 ignore_public_acls: Some(true),
480 })
481 } else {
482 self.access
483 };
484
485 let encryption = self.bucket_encryption.map(|v| {
486 let rule = ServerSideEncryptionRule {
487 server_side_encryption_by_default: ServerSideEncryptionByDefault {
488 sse_algorithm: v.into(),
489 kms_master_key_id: None,
490 },
491 bucket_key_enabled: None,
492 };
493
494 BucketEncryption {
495 server_side_encryption_configuration: vec![rule],
496 }
497 });
498
499 let properties = BucketProperties {
500 abac_status: self.abac_status,
501 accelerate_configuration: self.acceleration_status.map(|v| AccelerateConfiguration { acceleration_status: v }),
502 bucket_name: self.name,
503 cors_configuration: self.cors_config,
504 intelligent_tiering_configurations: self.intelligent_tiering_configurations,
505 lifecycle_configuration: self.lifecycle_configuration,
506 public_access_block_configuration: access,
507 versioning_configuration,
508 website_configuration,
509 bucket_encryption: encryption,
510 metadata_configuration: self.metadata_configuration,
511 };
512
513 stack_builder.add_resource(Bucket {
514 id: self.id.clone(),
515 resource_id: resource_id.clone(),
516 r#type: "AWS::S3::Bucket".to_string(),
517 properties,
518 update_delete_policy_dto: UpdateDeletePolicyDTO {
519 deletion_policy: self.deletion_policy,
520 update_replace_policy: self.update_replace_policy,
521 },
522 });
523
524 let bucket = BucketRef::new(resource_id);
525
526 let policy = if website {
527 let bucket_resource = vec![join("", vec![bucket.get_arn(), Value::String("/*".to_string())])];
529 let statement = StatementBuilder::new(vec![IamAction("s3:GetObject".to_string())], Effect::Allow)
530 .resources(bucket_resource)
531 .principal(PrincipalBuilder::new().normal("*").build())
532 .build();
533 let policy_doc = PolicyDocumentBuilder::new(vec![statement]).build();
534 let bucket_policy_id = Id::generate_id(&self.id, "S3Policy");
535 let s3_policy = BucketPolicyBuilder::new(&bucket_policy_id, &bucket, policy_doc).build(stack_builder);
536 Some(s3_policy)
537 } else {
538 None
539 };
540
541 for (i, (arn, event)) in self.bucket_notification_lambda_destinations.into_iter().enumerate() {
542 let permission_id = Id::generate_id(&self.id, format!("LambdaDestPerm{}", i).as_str());
543 let permission = PermissionBuilder::new(
544 &permission_id,
545 LambdaPermissionAction("lambda:InvokeFunction".to_string()),
546 arn.clone(),
547 "s3.amazonaws.com",
548 )
549 .source_arn(bucket.get_arn())
550 .current_account()
551 .build(stack_builder);
552 let handler = Self::notification_handler(&self.id, "Lambda", i, stack_builder);
553 let notification_id = Id::generate_id(&self.id, &format!("LambdaNotification{}", i));
554 BucketNotificationBuilder::new(
555 ¬ification_id,
556 handler.get_arn(),
557 bucket.get_ref(),
558 event,
559 Some(permission.get_id().clone()),
560 )
561 .lambda(arn)
562 .build(stack_builder);
563 }
564
565 for (i, (id, reference, event)) in self.bucket_notification_sns_destinations.into_iter().enumerate() {
566 let handler = Self::notification_handler(&self.id, "SNS", i, stack_builder);
567
568 let bucket_arn = bucket.get_arn();
569 let condition = json!({
570 "ArnLike": {
571 "aws:SourceArn": bucket_arn
572 }
573 });
574 let principal = PrincipalBuilder::new().service("s3.amazonaws.com".to_string()).build();
575 let statement = StatementBuilder::new(vec![IamAction("sns:Publish".to_string())], Effect::Allow)
576 .principal(principal)
577 .condition(condition)
578 .resources(vec![reference.clone()])
579 .build();
580
581 let topic_policy_id = Id::generate_id(&id, TOPIC_POLICY_ID_SUFFIX);
582 let topic_policy_ref_id = match stack_builder.get_resource(&topic_policy_id) {
583 None => {
584 let doc = PolicyDocumentBuilder::new(vec![statement]).build();
586 let topic_policy_id = Id::generate_id(&self.id, &format!("SNSDestinationPolicy{}", i));
587 TopicPolicyBuilder::new_with_values(topic_policy_id.clone(), doc, vec![reference.clone()]).build(stack_builder);
588 topic_policy_id
589 }
590 Some(Resource::TopicPolicy(pol)) => {
591 pol.properties.doc.statements.push(statement);
593 topic_policy_id
594 }
595 _ => unreachable!("topic policy id should point to optional topic policy"),
596 };
597
598 let notification_id = Id::generate_id(&self.id, &format!("SNSNotification{}", i));
599 BucketNotificationBuilder::new(
600 ¬ification_id,
601 handler.get_arn(),
602 bucket.get_ref(),
603 event,
604 Some(topic_policy_ref_id),
605 )
606 .sns(reference)
607 .build(stack_builder);
608 }
609
610 for (i, (id, reference, arn, event)) in self.bucket_notification_sqs_destinations.into_iter().enumerate() {
611 let handler = Self::notification_handler(&self.id, "SQS", i, stack_builder);
612
613 let bucket_arn = bucket.get_arn();
614 let condition = json!({
615 "ArnLike": {
616 "aws:SourceArn": bucket_arn
617 }
618 });
619 let principal = PrincipalBuilder::new().service("s3.amazonaws.com".to_string()).build();
620 let statement = StatementBuilder::new(
621 vec![
622 IamAction("sqs:GetQueueAttributes".to_string()),
623 IamAction("sqs:GetQueueUrl".to_string()),
624 IamAction("sqs:SendMessage".to_string()),
625 ],
626 Effect::Allow,
627 )
628 .principal(principal)
629 .condition(condition)
630 .resources(vec![arn.clone()])
631 .build();
632
633 let queue_policy_id = Id::generate_id(&id, QUEUE_POLICY_ID_SUFFIX);
634
635 let queue_policy_ref_id = match stack_builder.get_resource(&queue_policy_id) {
636 None => {
637 let doc = PolicyDocumentBuilder::new(vec![statement]).build();
639 let queue_policy_id = Id::generate_id(&self.id, &format!("SQSDestinationPolicy{}", i));
640 QueuePolicyBuilder::new_with_values(queue_policy_id.clone(), doc, vec![reference.clone()]).build(stack_builder);
641 queue_policy_id
642 }
643 Some(Resource::QueuePolicy(pol)) => {
644 pol.properties.doc.statements.push(statement);
646 queue_policy_id
647 }
648 _ => unreachable!("queue policy id should point to optional queue policy"),
649 };
650
651 let notification_id = Id::generate_id(&self.id, format!("SQSNotification{}", i).as_str());
652 BucketNotificationBuilder::new(
653 ¬ification_id,
654 handler.get_arn(),
655 bucket.get_ref(),
656 event,
657 Some(queue_policy_ref_id),
658 )
659 .sqs(arn)
660 .build(stack_builder);
661 }
662
663 (bucket, policy)
664 }
665
666 fn notification_handler(id: &Id, target: &str, num: usize, stack_builder: &mut StackBuilder) -> FunctionRef {
667 let handler_id = Id::generate_id(id, &format!("{}Handler{}", target, num));
668 let (handler, ..) = FunctionBuilder::new(&handler_id, Architecture::X86_64, Memory(128), Timeout(300))
669 .code(Code::Inline(BUCKET_NOTIFICATION_HANDLER_CODE.to_string()))
670 .handler("index.handler")
671 .runtime(Runtime::Python313)
672 .add_permission(Permission::Custom(CustomPermission::new(
673 "NotificationPermission",
674 StatementBuilder::new(vec![IamAction("s3:PutBucketNotification".to_string())], Effect::Allow)
675 .all_resources()
676 .build(),
677 )))
678 .build(stack_builder);
679 handler
680 }
681}
682
683impl BucketBuilder<WebsiteState> {
684 pub fn error_document<I: Into<String>>(self, error: I) -> Self {
685 Self {
686 error_document: Some(error.into()),
687 ..self
688 }
689 }
690
691 pub fn redirect_all<I: Into<String>>(self, hostname: I, protocol: Option<Protocol>) -> Self {
692 Self {
693 redirect_all_requests_to: Some((hostname.into(), protocol)),
694 ..self
695 }
696 }
697
698 pub fn cors_config(self, config: CorsConfiguration) -> Self {
699 Self {
700 cors_config: Some(config),
701 ..self
702 }
703 }
704
705 pub fn build(self, stack_builder: &mut StackBuilder) -> (BucketRef, BucketPolicyRef) {
710 let (bucket, policy) = self.build_internal(true, stack_builder);
711 (bucket, policy.expect("for website, bucket policy should always be present"))
712 }
713}
714
715pub struct CorsConfigurationBuilder {
717 rules: Vec<CorsRule>,
718}
719
720impl CorsConfigurationBuilder {
721 pub fn new(rules: Vec<CorsRule>) -> CorsConfigurationBuilder {
722 CorsConfigurationBuilder { rules }
723 }
724
725 pub fn build(self) -> CorsConfiguration {
726 CorsConfiguration { cors_rules: self.rules }
727 }
728}
729
730pub struct CorsRuleBuilder {
732 allow_origins: Vec<String>,
733 allow_methods: Vec<HttpMethod>,
734 allow_headers: Option<Vec<String>>,
735 expose_headers: Option<Vec<String>>,
736 max_age: Option<u64>,
737}
738
739impl CorsRuleBuilder {
740 pub fn new<T: Into<String>>(allow_origins: Vec<T>, allow_methods: Vec<HttpMethod>) -> Self {
741 Self {
742 allow_origins: allow_origins.into_iter().map(Into::into).collect(),
743 allow_methods,
744 allow_headers: None,
745 expose_headers: None,
746 max_age: None,
747 }
748 }
749
750 pub fn allow_headers(self, headers: Vec<String>) -> Self {
751 Self {
752 allow_headers: Some(headers),
753 ..self
754 }
755 }
756
757 pub fn expose_headers(self, headers: Vec<String>) -> Self {
758 Self {
759 expose_headers: Some(headers),
760 ..self
761 }
762 }
763
764 pub fn max_age(self, age: Duration) -> Self {
765 Self {
766 max_age: Some(age.as_secs()),
767 ..self
768 }
769 }
770
771 #[must_use]
772 pub fn build(self) -> CorsRule {
773 CorsRule {
774 allowed_headers: self.allow_headers,
775 allowed_methods: self.allow_methods.into_iter().map(Into::into).collect(),
776 allowed_origins: self.allow_origins,
777 exposed_headers: self.expose_headers,
778 max_age: self.max_age,
779 }
780 }
781}
782
783#[derive(Debug, Clone)]
784pub enum TransitionDefaultMinimumObjectSize {
785 VariesByStorageClass,
786 AllStorageClasses128k,
787}
788
789impl From<TransitionDefaultMinimumObjectSize> for String {
790 fn from(value: TransitionDefaultMinimumObjectSize) -> Self {
791 match value {
792 TransitionDefaultMinimumObjectSize::VariesByStorageClass => "varies_by_storage_class".to_string(),
793 TransitionDefaultMinimumObjectSize::AllStorageClasses128k => "all_storage_classes_128K".to_string(),
794 }
795 }
796}
797
798#[derive(Debug, Clone)]
799pub enum LifecycleStorageClass {
800 IntelligentTiering,
801 OneZoneIA,
802 StandardIA,
803 GlacierDeepArchive,
804 Glacier,
805 GlacierInstantRetrieval,
806}
807
808impl From<LifecycleStorageClass> for String {
809 fn from(value: LifecycleStorageClass) -> Self {
810 match value {
811 LifecycleStorageClass::GlacierDeepArchive => "DEEP_ARCHIVE".to_string(),
812 LifecycleStorageClass::Glacier => "GLACIER".to_string(),
813 LifecycleStorageClass::GlacierInstantRetrieval => "GLACIER_IR".to_string(),
814 LifecycleStorageClass::IntelligentTiering => "INTELLIGENT_TIERING".to_string(),
815 LifecycleStorageClass::OneZoneIA => "ONEZONE_IA".to_string(),
816 LifecycleStorageClass::StandardIA => "STANDARD_IA".to_string(),
817 }
818 }
819}
820
821pub struct LifecycleRuleTransitionBuilder {
825 storage_class: LifecycleStorageClass,
826 transition_in_days: Option<u16>,
827}
828
829impl LifecycleRuleTransitionBuilder {
830 pub fn new(storage_class: LifecycleStorageClass) -> Self {
831 Self {
832 storage_class,
833 transition_in_days: None,
834 }
835 }
836
837 pub fn transition_in_days(self, days: LifecycleTransitionInDays) -> Self {
838 Self {
839 transition_in_days: Some(days.0),
840 ..self
841 }
842 }
843
844 #[must_use]
845 pub fn build(self) -> LifecycleRuleTransition {
846 LifecycleRuleTransition {
847 storage_class: self.storage_class.into(),
848 transition_in_days: self.transition_in_days.unwrap_or(0),
849 }
850 }
851}
852
853pub struct NonCurrentVersionTransitionBuilder {
857 storage_class: LifecycleStorageClass,
858 transition_in_days: u32,
859 newer_non_current_versions: Option<u32>,
860}
861
862impl NonCurrentVersionTransitionBuilder {
863 pub fn new(storage_class: LifecycleStorageClass, transition_in_days: u32) -> Self {
864 Self {
865 storage_class,
866 transition_in_days,
867 newer_non_current_versions: None,
868 }
869 }
870
871 pub fn newer_non_current_versions(self, versions: u32) -> Self {
872 Self {
873 newer_non_current_versions: Some(versions),
874 ..self
875 }
876 }
877
878 #[must_use]
879 pub fn build(self) -> NonCurrentVersionTransition {
880 NonCurrentVersionTransition {
881 storage_class: self.storage_class.into(),
882 transition_in_days: self.transition_in_days,
883 newer_non_current_versions: self.newer_non_current_versions,
884 }
885 }
886}
887
888#[derive(Debug, Clone)]
889pub enum LifecycleRuleStatus {
890 Enabled,
891 Disabled,
892}
893
894impl From<LifecycleRuleStatus> for String {
895 fn from(value: LifecycleRuleStatus) -> Self {
896 match value {
897 LifecycleRuleStatus::Enabled => "Enabled".to_string(),
898 LifecycleRuleStatus::Disabled => "Disabled".to_string(),
899 }
900 }
901}
902
903pub struct LifecycleRuleBuilder {
907 id: Option<String>,
908 status: LifecycleRuleStatus,
909 expiration_in_days: Option<u16>, prefix: Option<String>,
911 object_size_greater_than: Option<u32>,
912 object_size_less_than: Option<u32>,
913 abort_incomplete_multipart_upload: Option<u16>,
914 non_current_version_expiration: Option<u16>,
915 transitions: Option<Vec<LifecycleRuleTransition>>,
916 non_current_version_transitions: Option<Vec<NonCurrentVersionTransition>>,
917}
918
919impl LifecycleRuleBuilder {
920 pub fn new(status: LifecycleRuleStatus) -> Self {
921 Self {
922 status,
923 id: None,
924 expiration_in_days: None,
925 prefix: None,
926 object_size_greater_than: None,
927 object_size_less_than: None,
928 abort_incomplete_multipart_upload: None,
929 non_current_version_expiration: None,
930 transitions: None,
931 non_current_version_transitions: None,
932 }
933 }
934
935 pub fn id<T: Into<String>>(self, id: T) -> Self {
936 Self {
937 id: Some(id.into()),
938 ..self
939 }
940 }
941
942 pub fn expiration_in_days(self, days: u16) -> Self {
943 Self {
944 expiration_in_days: Some(days),
945 ..self
946 }
947 }
948
949 pub fn prefix<T: Into<String>>(self, prefix: T) -> Self {
950 Self {
951 prefix: Some(prefix.into()),
952 ..self
953 }
954 }
955
956 pub fn object_size(self, sizes: S3LifecycleObjectSizes) -> Self {
957 Self {
958 object_size_less_than: sizes.0,
959 object_size_greater_than: sizes.1,
960 ..self
961 }
962 }
963
964 pub fn abort_incomplete_multipart_upload(self, days: u16) -> Self {
965 Self {
966 abort_incomplete_multipart_upload: Some(days),
967 ..self
968 }
969 }
970
971 pub fn non_current_version_expiration(self, days: u16) -> Self {
972 Self {
973 non_current_version_expiration: Some(days),
974 ..self
975 }
976 }
977
978 pub fn add_transition(mut self, transition: LifecycleRuleTransition) -> Self {
979 if let Some(mut transitions) = self.transitions {
980 transitions.push(transition);
981 self.transitions = Some(transitions);
982 } else {
983 self.transitions = Some(vec![transition]);
984 }
985
986 Self { ..self }
987 }
988
989 pub fn add_non_current_version_transitions(mut self, transition: NonCurrentVersionTransition) -> Self {
990 if let Some(mut transitions) = self.non_current_version_transitions {
991 transitions.push(transition);
992 self.non_current_version_transitions = Some(transitions);
993 } else {
994 self.non_current_version_transitions = Some(vec![transition]);
995 }
996
997 Self { ..self }
998 }
999
1000 pub fn build(self) -> LifecycleRule {
1001 LifecycleRule {
1002 id: self.id,
1003 status: self.status.into(),
1004 expiration_in_days: self.expiration_in_days,
1005 prefix: self.prefix,
1006 object_size_greater_than: self.object_size_greater_than,
1007 object_size_less_than: self.object_size_less_than,
1008 transitions: self.transitions,
1009 abort_incomplete_multipart_upload: self.abort_incomplete_multipart_upload,
1010 non_current_version_expiration: self.non_current_version_expiration,
1011 non_current_version_transitions: self.non_current_version_transitions,
1012 }
1013 }
1014}
1015
1016pub struct LifecycleConfigurationBuilder {
1020 rules: Vec<LifecycleRule>,
1021 transition_minimum_size: Option<TransitionDefaultMinimumObjectSize>,
1022}
1023
1024impl Default for LifecycleConfigurationBuilder {
1025 fn default() -> Self {
1026 Self::new()
1027 }
1028}
1029
1030impl LifecycleConfigurationBuilder {
1031 pub fn new() -> Self {
1032 Self {
1033 rules: vec![],
1034 transition_minimum_size: None,
1035 }
1036 }
1037
1038 pub fn transition_minimum_size(self, size: TransitionDefaultMinimumObjectSize) -> Self {
1039 Self {
1040 transition_minimum_size: Some(size),
1041 ..self
1042 }
1043 }
1044
1045 pub fn add_rule(mut self, rule: LifecycleRule) -> Self {
1046 self.rules.push(rule);
1047 self
1048 }
1049
1050 #[must_use]
1051 pub fn build(self) -> LifecycleConfiguration {
1052 LifecycleConfiguration {
1053 rules: self.rules,
1054 transition_minimum_size: self.transition_minimum_size.map(|v| v.into()),
1055 }
1056 }
1057}
1058
1059pub struct PublicAccessBlockConfigurationBuilder {
1063 block_public_acls: Option<bool>,
1064 block_public_policy: Option<bool>,
1065 ignore_public_acls: Option<bool>,
1066 restrict_public_buckets: Option<bool>,
1067}
1068
1069impl Default for PublicAccessBlockConfigurationBuilder {
1070 fn default() -> Self {
1071 Self::new()
1072 }
1073}
1074
1075impl PublicAccessBlockConfigurationBuilder {
1076 pub fn new() -> Self {
1077 Self {
1078 block_public_acls: None,
1079 block_public_policy: None,
1080 ignore_public_acls: None,
1081 restrict_public_buckets: None,
1082 }
1083 }
1084
1085 pub fn block_public_acls(self, config: bool) -> Self {
1086 Self {
1087 block_public_acls: Some(config),
1088 ..self
1089 }
1090 }
1091
1092 pub fn block_public_policy(self, config: bool) -> Self {
1093 Self {
1094 block_public_policy: Some(config),
1095 ..self
1096 }
1097 }
1098
1099 pub fn ignore_public_acls(self, config: bool) -> Self {
1100 Self {
1101 ignore_public_acls: Some(config),
1102 ..self
1103 }
1104 }
1105
1106 pub fn restrict_public_buckets(self, config: bool) -> Self {
1107 Self {
1108 restrict_public_buckets: Some(config),
1109 ..self
1110 }
1111 }
1112
1113 #[must_use]
1114 pub fn build(self) -> PublicAccessBlockConfiguration {
1115 PublicAccessBlockConfiguration {
1116 block_public_acls: self.block_public_acls,
1117 block_public_policy: self.block_public_policy,
1118 ignore_public_acls: self.ignore_public_acls,
1119 restrict_public_buckets: self.restrict_public_buckets,
1120 }
1121 }
1122}
1123
1124pub enum IntelligentTieringStatus {
1125 Enabled,
1126 Disabled,
1127}
1128
1129impl From<IntelligentTieringStatus> for String {
1130 fn from(value: IntelligentTieringStatus) -> String {
1131 match value {
1132 IntelligentTieringStatus::Enabled => "Enabled".to_string(),
1133 IntelligentTieringStatus::Disabled => "Disabled".to_string(),
1134 }
1135 }
1136}
1137
1138pub struct TagFilterBuilder {
1139 key: String,
1140 value: String,
1141}
1142
1143impl TagFilterBuilder {
1144 pub fn new<T: Into<String>>(key: T, value: T) -> Self {
1145 Self {
1146 key: key.into(),
1147 value: value.into(),
1148 }
1149 }
1150
1151 pub fn build(self) -> TagFilter {
1152 TagFilter {
1153 key: self.key,
1154 value: self.value,
1155 }
1156 }
1157}
1158
1159pub struct IntelligentTieringConfigurationBuilder {
1160 id: String,
1161 status: String,
1162 prefix: Option<String>,
1163 tag_filters: Option<Vec<TagFilter>>,
1164 tierings: Vec<Tiering>,
1165}
1166
1167impl IntelligentTieringConfigurationBuilder {
1168 pub fn new(id: &str, status: IntelligentTieringStatus, tierings: Vec<BucketTiering>) -> Self {
1169 IntelligentTieringConfigurationBuilder {
1170 id: id.to_string(),
1171 status: status.into(),
1172 prefix: None,
1173 tag_filters: None,
1174 tierings: tierings
1175 .into_iter()
1176 .map(|t| Tiering {
1177 access_tier: t.0,
1178 days: t.1,
1179 })
1180 .collect(),
1181 }
1182 }
1183
1184 pub fn prefix<T: Into<String>>(self, prefix: T) -> Self {
1185 Self {
1186 prefix: Some(prefix.into()),
1187 ..self
1188 }
1189 }
1190
1191 pub fn add_tag_filter(mut self, tag_filter: TagFilter) -> Self {
1192 if let Some(mut filters) = self.tag_filters {
1193 filters.push(tag_filter);
1194 self.tag_filters = Some(filters);
1195 } else {
1196 self.tag_filters = Some(vec![tag_filter]);
1197 }
1198
1199 self
1200 }
1201
1202 pub fn build(self) -> IntelligentTieringConfiguration {
1203 IntelligentTieringConfiguration {
1204 id: self.id,
1205 prefix: self.prefix,
1206 status: self.status,
1207 tag_filters: self.tag_filters,
1208 tierings: self.tierings,
1209 }
1210 }
1211}
1212
1213pub enum TableBucketType {
1214 Aws,
1215 Customer,
1216}
1217
1218impl From<TableBucketType> for String {
1219 fn from(value: TableBucketType) -> String {
1220 match value {
1221 TableBucketType::Aws => "aws".to_string(),
1222 TableBucketType::Customer => "customer".to_string(),
1223 }
1224 }
1225}
1226
1227pub enum Expiration {
1228 Enabled,
1229 Disabled,
1230}
1231
1232impl From<Expiration> for String {
1233 fn from(value: Expiration) -> String {
1234 match value {
1235 Expiration::Enabled => "ENABLED".to_string(),
1236 Expiration::Disabled => "DISABLED".to_string(),
1237 }
1238 }
1239}
1240
1241pub struct RecordExpirationBuilder {
1242 days: Option<u32>,
1243 expiration: String,
1244}
1245
1246impl RecordExpirationBuilder {
1247 pub fn new(expiration: Expiration) -> Self {
1248 Self {
1249 expiration: expiration.into(),
1250 days: None,
1251 }
1252 }
1253
1254 pub fn days(self, days: RecordExpirationDays) -> Self {
1255 Self {
1256 days: Some(days.0),
1257 ..self
1258 }
1259 }
1260
1261 pub fn build(self) -> RecordExpiration {
1262 RecordExpiration {
1263 days: self.days,
1264 expiration: self.expiration,
1265 }
1266 }
1267}
1268
1269pub struct JournalTableConfigurationBuilder {
1270 record_expiration: RecordExpiration,
1271 table_arn: Option<Value>,
1272 table_name: Option<String>,
1273}
1274
1275impl JournalTableConfigurationBuilder {
1276 pub fn new(record_expiration: RecordExpiration) -> Self {
1277 Self {
1278 record_expiration,
1279 table_arn: None,
1280 table_name: None,
1281 }
1282 }
1283
1284 pub fn table_name<T: Into<String>>(self, name: T) -> Self {
1285 Self {
1286 table_name: Some(name.into()),
1287 ..self
1288 }
1289 }
1290
1291 pub fn table_arn(self, arn: Value) -> Self {
1292 Self {
1293 table_arn: Some(arn),
1294 ..self
1295 }
1296 }
1297
1298 pub fn build(self) -> JournalTableConfiguration {
1299 JournalTableConfiguration {
1300 record_expiration: self.record_expiration,
1301 table_arn: self.table_arn,
1302 table_name: self.table_name,
1303 }
1304 }
1305}
1306
1307pub struct MetadataDestinationBuilder {
1308 table_bucket_type: String,
1309 table_bucket_arn: Option<Value>,
1310 table_namespace: Option<String>,
1311}
1312
1313impl MetadataDestinationBuilder {
1314 pub fn new(table_bucket_type: TableBucketType) -> Self {
1315 Self {
1316 table_bucket_type: table_bucket_type.into(),
1317 table_bucket_arn: None,
1318 table_namespace: None,
1319 }
1320 }
1321
1322 pub fn table_bucket_arn(self, table_bucket_arn: Value) -> Self {
1323 Self {
1324 table_bucket_arn: Some(table_bucket_arn),
1325 ..self
1326 }
1327 }
1328
1329 pub fn table_namespace<T: Into<String>>(self, table_namespace: T) -> Self {
1330 Self {
1331 table_namespace: Some(table_namespace.into()),
1332 ..self
1333 }
1334 }
1335
1336 pub fn build(self) -> MetadataDestination {
1337 MetadataDestination {
1338 table_bucket_arn: self.table_bucket_arn,
1339 table_bucket_type: self.table_bucket_type,
1340 table_namespace: self.table_namespace,
1341 }
1342 }
1343}
1344
1345pub enum ConfigurationState {
1346 Enabled,
1347 Disabled,
1348}
1349
1350impl From<ConfigurationState> for String {
1351 fn from(value: ConfigurationState) -> String {
1352 match value {
1353 ConfigurationState::Enabled => "ENABLED".to_string(),
1354 ConfigurationState::Disabled => "DISABLED".to_string(),
1355 }
1356 }
1357}
1358
1359pub struct InventoryTableConfigurationBuilder {
1360 configuration_state: String,
1361 table_arn: Option<Value>,
1362 table_name: Option<String>,
1363}
1364
1365impl InventoryTableConfigurationBuilder {
1366 pub fn new(configuration_state: ConfigurationState) -> Self {
1367 Self {
1368 configuration_state: configuration_state.into(),
1369 table_arn: None,
1370 table_name: None,
1371 }
1372 }
1373
1374 pub fn table_arn(self, table_arn: Value) -> Self {
1375 Self {
1376 table_arn: Some(table_arn),
1377 ..self
1378 }
1379 }
1380
1381 pub fn table_name<T: Into<String>>(self, table_name: T) -> Self {
1382 Self {
1383 table_name: Some(table_name.into()),
1384 ..self
1385 }
1386 }
1387
1388 pub fn build(self) -> InventoryTableConfiguration {
1389 InventoryTableConfiguration {
1390 configuration_state: self.configuration_state,
1391 table_arn: self.table_arn,
1392 table_name: self.table_name,
1393 }
1394 }
1395}
1396
1397pub struct MetadataConfigurationBuilder {
1398 destination: Option<MetadataDestination>,
1399 inventory_table_configuration: Option<InventoryTableConfiguration>,
1400 journal_table_configuration: JournalTableConfiguration,
1401}
1402
1403impl MetadataConfigurationBuilder {
1404 pub fn new(journal_table_configuration: JournalTableConfiguration) -> Self {
1405 MetadataConfigurationBuilder {
1406 journal_table_configuration,
1407 destination: None,
1408 inventory_table_configuration: None,
1409 }
1410 }
1411
1412 pub fn destination(self, destination: MetadataDestination) -> Self {
1413 Self {
1414 destination: Some(destination),
1415 ..self
1416 }
1417 }
1418
1419 pub fn inventory_table_configuration(self, inventory_table_configuration: InventoryTableConfiguration) -> Self {
1420 Self {
1421 inventory_table_configuration: Some(inventory_table_configuration),
1422 ..self
1423 }
1424 }
1425
1426 pub fn build(self) -> MetadataConfiguration {
1427 MetadataConfiguration {
1428 destination: self.destination,
1429 inventory_table_configuration: self.inventory_table_configuration,
1430 journal_table_configuration: self.journal_table_configuration,
1431 }
1432 }
1433}