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