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