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