1use crate::custom_resource::{BucketNotificationBuilder, BUCKET_NOTIFICATION_HANDLER_CODE};
2use crate::iam::{
3 CustomPermission, Effect, Permission, PolicyDocument, PolicyDocumentBuilder, Principal, PrincipalBuilder, ServicePrincipal,
4 StatementBuilder,
5};
6use crate::intrinsic::join;
7use crate::lambda::{Architecture, Runtime};
8use crate::lambda::{Code, FunctionBuilder, FunctionRef, PermissionBuilder};
9use crate::s3::dto;
10use crate::s3::{
11 Bucket, BucketEncryption, BucketPolicy, BucketPolicyRef, BucketProperties, BucketRef, CorsConfiguration, CorsRule,
12 LifecycleConfiguration, LifecycleRule, LifecycleRuleTransition, NonCurrentVersionTransition, PublicAccessBlockConfiguration,
13 RedirectAllRequestsTo, S3BucketPolicyProperties, ServerSideEncryptionByDefault, ServerSideEncryptionRule, WebsiteConfiguration,
14};
15use crate::shared::http::{HttpMethod, Protocol};
16use crate::shared::Id;
17use crate::sns::{TopicPolicyBuilder, TopicRef};
18use crate::stack::{Resource, StackBuilder};
19use crate::type_state;
20use crate::wrappers::{BucketName, IamAction, LambdaPermissionAction, LifecycleTransitionInDays, Memory, S3LifecycleObjectSizes, Timeout};
21use serde_json::{Map, Value};
22use std::marker::PhantomData;
23use std::time::Duration;
24use crate::sqs::{QueuePolicyBuilder, QueueRef};
25
26pub struct BucketPolicyBuilder {
58 id: Id,
59 bucket_name: Value,
60 policy_document: PolicyDocument,
61}
62
63impl BucketPolicyBuilder {
64 pub fn new(id: &str, bucket: &BucketRef, policy_document: PolicyDocument) -> Self {
71 Self {
72 id: Id(id.to_string()),
73 bucket_name: bucket.get_ref(),
74 policy_document,
75 }
76 }
77
78 pub(crate) fn new_with_bucket_ref(id: &str, bucket_name: Value, policy_document: PolicyDocument) -> Self {
79 Self {
80 id: Id(id.to_string()),
81 bucket_name,
82 policy_document,
83 }
84 }
85
86 pub(crate) fn raw_build(self) -> (String, BucketPolicy) {
87 let resource_id = Resource::generate_id("S3BucketPolicy");
88 let policy = BucketPolicy {
89 id: self.id,
90 resource_id: resource_id.to_string(),
91 r#type: "AWS::S3::BucketPolicy".to_string(),
92 properties: S3BucketPolicyProperties {
93 bucket_name: self.bucket_name,
94 policy_document: self.policy_document,
95 },
96 };
97 (resource_id, policy)
98 }
99
100 pub fn build(self, stack_builder: &mut StackBuilder) -> BucketPolicyRef {
101 let (resource_id, policy) = self.raw_build();
102 stack_builder.add_resource(policy);
103 BucketPolicyRef::new(resource_id)
104 }
105}
106
107pub enum VersioningConfiguration {
108 Enabled,
109 Suspended,
110}
111
112impl From<VersioningConfiguration> for String {
113 fn from(value: VersioningConfiguration) -> Self {
114 match value {
115 VersioningConfiguration::Enabled => "Enabled".to_string(),
116 VersioningConfiguration::Suspended => "Suspended".to_string(),
117 }
118 }
119}
120
121pub enum Encryption {
122 S3Managed,
123 KmsManaged,
124 DsseManaged,
125 }
128
129impl From<Encryption> for String {
130 fn from(value: Encryption) -> Self {
131 match value {
132 Encryption::S3Managed => "AES256".to_string(),
133 Encryption::KmsManaged => "aws:kms".to_string(),
134 Encryption::DsseManaged => "aws:kms:dsse".to_string(),
135 }
136 }
137}
138
139pub enum NotificationDestination<'a> {
140 Lambda(&'a FunctionRef, NotificationEventType),
141 Sns(&'a TopicRef, NotificationEventType),
142 Sqs(&'a QueueRef, NotificationEventType),
143}
144
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
175impl From<NotificationEventType> for String {
176 fn from(value: NotificationEventType) -> Self {
177 match value {
178 NotificationEventType::ObjectCreated => "s3:ObjectCreated:*".to_string(),
179 NotificationEventType::ObjectCreatedPut => "s3:ObjectCreated:Put".to_string(),
180 NotificationEventType::ObjectCreatedPost => "s3:ObjectCreated:Post".to_string(),
181 NotificationEventType::ObjectCreatedCopy => "s3:ObjectCreated:Copy".to_string(),
182 NotificationEventType::ObjectCreatedCompleteMultipartUpload => "s3:ObjectCreated:CompleteMultipartUpload".to_string(),
183 NotificationEventType::ObjectRemoved => "s3:ObjectRemoved:*".to_string(),
184 NotificationEventType::ObjectRemovedDelete => "s3:ObjectRemoved:Delete".to_string(),
185 NotificationEventType::ObjectRemovedDeleteMarkerCreated => "s3:ObjectRemoved:DeleteMarkerCreated".to_string(),
186 NotificationEventType::ObjectRestorePost => "s3:ObjectRestore:Post".to_string(),
187 NotificationEventType::ObjectRestoreCompleted => "s3:ObjectRestore:Completed".to_string(),
188 NotificationEventType::ObjectRestoreDelete => "s3:ObjectRestore:Delete".to_string(),
189 NotificationEventType::ReducedRedundancyLostObject => "s3:ReducedRedundancyLostObject".to_string(),
190 NotificationEventType::ReplicationOperationFailedReplication => "s3:Replication:OperationFailedReplication".to_string(),
191 NotificationEventType::ReplicationOperationMissedThreshold => "s3:Replication:OperationMissedThreshold".to_string(),
192 NotificationEventType::ReplicationOperationReplicatedAfterThreshold => "s3:Replication:OperationReplicatedAfterThreshold".to_string(),
193 NotificationEventType::ReplicationOperationNotTracked => "s3:Replication:OperationNotTracked".to_string(),
194 NotificationEventType::LifecycleExpiration => "s3:LifecycleExpiration:*".to_string(),
195 NotificationEventType::LifecycleExpirationDelete => "s3:LifecycleExpiration:Delete".to_string(),
196 NotificationEventType::LifecycleExpirationDeleteMarkerCreated => "s3:LifecycleExpiration:DeleteMarkerCreated".to_string(),
197 NotificationEventType::LifecycleTransition => "s3:LifecycleTransition".to_string(),
198 NotificationEventType::IntelligentTiering => "s3:IntelligentTiering".to_string(),
199 NotificationEventType::ObjectTagging => "s3:ObjectTagging:*".to_string(),
200 NotificationEventType::ObjectTaggingPut => "s3:ObjectTagging:Put".to_string(),
201 NotificationEventType::ObjectTaggingDelete => "s3:ObjectTagging:Delete".to_string(),
202 NotificationEventType::ObjectAclPut => "s3:ObjectAcl:Put".to_string(),
203 NotificationEventType::ObjectRestore => "s3:ObjectRestore:*".to_string(),
204 NotificationEventType::REPLICATION => "s3:Replication:*".to_string(),
205 }
206 }
207}
208
209type_state!(BucketBuilderState, StartState, WebsiteState,);
210
211pub struct BucketBuilder<T: BucketBuilderState> {
239 phantom_data: PhantomData<T>,
240 id: Id,
241 name: Option<String>,
242 access: Option<PublicAccessBlockConfiguration>,
243 versioning_configuration: Option<VersioningConfiguration>,
244 lifecycle_configuration: Option<LifecycleConfiguration>,
245 index_document: Option<String>,
246 error_document: Option<String>,
247 redirect_all_requests_to: Option<(String, Option<Protocol>)>,
248 cors_config: Option<CorsConfiguration>,
249 bucket_encryption: Option<Encryption>,
250 bucket_notification_lambda_destinations: Vec<(Value, String)>,
251 bucket_notification_sns_destinations: Vec<(Value, String)>,
252 bucket_notification_sqs_destinations: Vec<(Value, Value, String)>,
253}
254
255impl BucketBuilder<StartState> {
256 pub fn new(id: &str) -> Self {
261 Self {
262 id: Id(id.to_string()),
263 phantom_data: Default::default(),
264 name: None,
265 access: None,
266 versioning_configuration: None,
267 lifecycle_configuration: None,
268 index_document: None,
269 error_document: None,
270 redirect_all_requests_to: None,
271 cors_config: None,
272 bucket_encryption: None,
273 bucket_notification_lambda_destinations: vec![],
274 bucket_notification_sns_destinations: vec![],
275 bucket_notification_sqs_destinations: vec![],
276 }
277 }
278
279 pub fn build(self, stack_builder: &mut StackBuilder) -> BucketRef {
280 let (bucket, _) = self.build_internal(false, stack_builder);
281 bucket
282 }
283}
284
285impl<T: BucketBuilderState> BucketBuilder<T> {
286 pub fn name(self, name: BucketName) -> Self {
287 Self {
288 name: Some(name.0),
289 ..self
290 }
291 }
292
293 pub fn versioning_configuration(self, config: VersioningConfiguration) -> Self {
294 Self {
295 versioning_configuration: Some(config),
296 ..self
297 }
298 }
299
300 pub fn lifecycle_configuration(self, config: LifecycleConfiguration) -> Self {
301 Self {
302 lifecycle_configuration: Some(config),
303 ..self
304 }
305 }
306
307 pub fn public_access_block_configuration(self, access: PublicAccessBlockConfiguration) -> Self {
308 Self {
309 access: Some(access),
310 ..self
311 }
312 }
313
314 pub fn encryption(self, encryption: Encryption) -> Self {
315 Self {
316 bucket_encryption: Some(encryption),
317 ..self
318 }
319 }
320
321 pub fn add_notification(mut self, destination: NotificationDestination) -> Self {
322 match destination {
323 NotificationDestination::Lambda(l, e) => self.bucket_notification_lambda_destinations.push((l.get_arn(), e.into())),
324 NotificationDestination::Sns(s, e) => self.bucket_notification_sns_destinations.push((s.get_ref(), e.into())),
325 NotificationDestination::Sqs(q, e) => self.bucket_notification_sqs_destinations.push((q.get_ref(), q.get_arn(), e.into())),
326 }
327 self
328 }
329
330 pub fn website<I: Into<String>>(self, index_document: I) -> BucketBuilder<WebsiteState> {
335 BucketBuilder {
336 phantom_data: Default::default(),
337 id: self.id,
338 name: self.name,
339 access: self.access,
340 versioning_configuration: self.versioning_configuration,
341 lifecycle_configuration: self.lifecycle_configuration,
342 index_document: Some(index_document.into()),
343 error_document: self.error_document,
344 redirect_all_requests_to: self.redirect_all_requests_to,
345 cors_config: self.cors_config,
346 bucket_encryption: self.bucket_encryption,
347 bucket_notification_lambda_destinations: self.bucket_notification_lambda_destinations,
348 bucket_notification_sns_destinations: self.bucket_notification_sns_destinations,
349 bucket_notification_sqs_destinations: self.bucket_notification_sqs_destinations,
350 }
351 }
352
353 fn build_internal(self, website: bool, stack_builder: &mut StackBuilder) -> (BucketRef, Option<BucketPolicyRef>) {
354 let resource_id = Resource::generate_id("S3Bucket");
355
356 let versioning_configuration = self.versioning_configuration.map(|c| dto::VersioningConfig { status: c.into() });
357
358 let website_configuration = if website {
359 let redirect_all_requests_to = self.redirect_all_requests_to.map(|r| RedirectAllRequestsTo {
360 host_name: r.0,
361 protocol: r.1.map(Into::into),
362 });
363
364 Some(WebsiteConfiguration {
365 index_document: self.index_document,
366 error_document: self.error_document,
367 redirect_all_requests_to,
368 })
369 } else {
370 None
371 };
372
373 let access = if self.access.is_none() && website {
374 Some(PublicAccessBlockConfiguration {
376 block_public_acls: Some(false),
377 block_public_policy: Some(false),
378 ignore_public_acls: Some(false),
379 restrict_public_buckets: Some(false),
380 })
381 } else {
382 self.access
383 };
384
385 let encryption = self.bucket_encryption.map(|v| {
386 let rule = ServerSideEncryptionRule {
387 server_side_encryption_by_default: ServerSideEncryptionByDefault {
388 sse_algorithm: v.into(),
389 kms_master_key_id: None,
390 },
391 bucket_key_enabled: None,
392 };
393
394 BucketEncryption {
395 server_side_encryption_configuration: vec![rule],
396 }
397 });
398
399 let properties = BucketProperties {
400 bucket_name: self.name,
401 cors_configuration: self.cors_config,
402 lifecycle_configuration: self.lifecycle_configuration,
403 public_access_block_configuration: access,
404 versioning_configuration,
405 website_configuration,
406 bucket_encryption: encryption,
407 notification_configuration: None,
408 };
409
410 stack_builder.add_resource(Bucket {
411 id: self.id.clone(),
412 resource_id: resource_id.clone(),
413 r#type: "AWS::S3::Bucket".to_string(),
414 properties,
415 });
416
417 let bucket = BucketRef::new(resource_id);
418
419 let policy = if website {
420 let bucket_resource = vec![join("", vec![bucket.get_arn(), Value::String("/*".to_string())])];
422 let statement = StatementBuilder::new(vec![IamAction("s3:GetObject".to_string())], Effect::Allow)
423 .resources(bucket_resource)
424 .principal(PrincipalBuilder::new().normal("*").build())
425 .build();
426 let policy_doc = PolicyDocumentBuilder::new(vec![statement]).build();
427 let bucket_policy_id = format!("{}-website-s3-policy", self.id);
428 let s3_policy = BucketPolicyBuilder::new(bucket_policy_id.as_str(), &bucket, policy_doc).build(stack_builder);
429 Some(s3_policy)
430 } else {
431 None
432 };
433
434 for (i, (arn, event)) in self.bucket_notification_lambda_destinations.into_iter().enumerate() {
435 let permission = PermissionBuilder::new(
436 &format!("{}-lambda-destination-perm-{}", self.id, i),
437 LambdaPermissionAction("lambda:InvokeFunction".to_string()),
438 arn.clone(),
439 "s3.amazonaws.com",
440 )
441 .source_arn(bucket.get_arn())
442 .current_account()
443 .build(stack_builder);
444 let handler = Self::notification_handler(&self.id, "lambda", i, stack_builder);
445 BucketNotificationBuilder::new(
446 &format!("{}-lambda-bucket-notification-{}", self.id, i),
447 handler.get_arn(),
448 bucket.get_ref(),
449 event,
450 permission.get_id(),
451 )
452 .lambda(arn)
453 .build(stack_builder);
454 }
455
456 for (i, (reference, event)) in self.bucket_notification_sns_destinations.into_iter().enumerate() {
457 let handler = Self::notification_handler(&self.id, "sns", i, stack_builder);
458
459 let mut source_arn = Map::new();
460 source_arn.insert("aws:SourceArn".to_string(), bucket.get_arn());
461 let mut condition = Map::new();
462 condition.insert("ArnLike".to_string(), Value::Object(source_arn));
463 let statement = StatementBuilder::new(vec![IamAction("sns:Publish".to_string())], Effect::Allow)
464 .principal(Principal::Service(ServicePrincipal {
465 service: "s3.amazonaws.com".to_string(),
466 }))
467 .condition(Value::Object(condition))
468 .resources(vec![reference.clone()])
469 .build();
470 let doc = PolicyDocumentBuilder::new(vec![statement]).build();
471 let topic_ref = TopicPolicyBuilder::new(&format!("{}-sns-destination-policy-{}", self.id, i), doc, vec![reference.clone()])
472 .build(stack_builder);
473
474 BucketNotificationBuilder::new(
475 &format!("{}-sns-bucket-notification-{}", self.id, i),
476 handler.get_arn(),
477 bucket.get_ref(),
478 event,
479 topic_ref.get_id(),
480 )
481 .sns(reference)
482 .build(stack_builder);
483 }
484
485 for (i, (reference, arn, event)) in self.bucket_notification_sqs_destinations.into_iter().enumerate() {
486 let handler = Self::notification_handler(&self.id, "sqs", i, stack_builder);
487
488 let mut source_arn = Map::new();
489 source_arn.insert("aws:SourceArn".to_string(), bucket.get_arn());
490 let mut condition = Map::new();
491 condition.insert("ArnLike".to_string(), Value::Object(source_arn));
492 let statement = StatementBuilder::new(vec![IamAction("sqs:GetQueueAttributes".to_string()), IamAction("sqs:GetQueueUrl".to_string()), IamAction("sqs:SendMessage".to_string())], Effect::Allow)
493 .principal(Principal::Service(ServicePrincipal {
494 service: "s3.amazonaws.com".to_string(),
495 }))
496 .condition(Value::Object(condition))
497 .resources(vec![arn.clone()])
498 .build();
499 let doc = PolicyDocumentBuilder::new(vec![statement]).build();
500 let queue_policy_ref = QueuePolicyBuilder::new(&format!("{}-sqs-destination-policy-{}", self.id, i), doc, vec![reference.clone()])
501 .build(stack_builder);
502
503 BucketNotificationBuilder::new(
504 &format!("{}-sqs-bucket-notification-{}", self.id, i),
505 handler.get_arn(),
506 bucket.get_ref(),
507 event,
508 queue_policy_ref.get_id(),
509 )
510 .sqs(arn)
511 .build(stack_builder);
512 }
513
514 (bucket, policy)
515 }
516
517 fn notification_handler(id: &Id, target: &str, num: usize, stack_builder: &mut StackBuilder) -> FunctionRef {
518 let (handler, ..) = FunctionBuilder::new(
519 &format!("{}-{}-handler-{}", id, target, num),
520 Architecture::X86_64,
521 Memory(128),
522 Timeout(300),
523 )
524 .code(Code::Inline(BUCKET_NOTIFICATION_HANDLER_CODE.to_string()))
525 .handler("index.handler")
526 .runtime(Runtime::Python313)
527 .add_permission(Permission::Custom(CustomPermission::new(
528 "NotificationPermission",
529 StatementBuilder::new(vec![IamAction("s3:PutBucketNotification".to_string())], Effect::Allow)
530 .all_resources()
531 .build(),
532 )))
533 .build(stack_builder);
534 handler
535 }
536}
537
538impl BucketBuilder<WebsiteState> {
539 pub fn error_document<I: Into<String>>(self, error: I) -> Self {
540 Self {
541 error_document: Some(error.into()),
542 ..self
543 }
544 }
545
546 pub fn redirect_all<I: Into<String>>(self, hostname: I, protocol: Option<Protocol>) -> Self {
547 Self {
548 redirect_all_requests_to: Some((hostname.into(), protocol)),
549 ..self
550 }
551 }
552
553 pub fn cors_config(self, config: CorsConfiguration) -> Self {
554 Self {
555 cors_config: Some(config),
556 ..self
557 }
558 }
559
560 pub fn build(self, stack_builder: &mut StackBuilder) -> (BucketRef, BucketPolicyRef) {
565 let (bucket, policy) = self.build_internal(true, stack_builder);
566 (bucket, policy.expect("for website, bucket policy should always be present"))
567 }
568}
569
570pub struct CorsConfigurationBuilder {
572 rules: Vec<CorsRule>,
573}
574
575impl CorsConfigurationBuilder {
576 pub fn new(rules: Vec<CorsRule>) -> CorsConfigurationBuilder {
577 CorsConfigurationBuilder { rules }
578 }
579
580 pub fn build(self) -> CorsConfiguration {
581 CorsConfiguration { cors_rules: self.rules }
582 }
583}
584
585pub struct CorsRuleBuilder {
587 allow_origins: Vec<String>,
588 allow_methods: Vec<HttpMethod>,
589 allow_headers: Option<Vec<String>>,
590 expose_headers: Option<Vec<String>>,
591 max_age: Option<u64>,
592}
593
594impl CorsRuleBuilder {
595 pub fn new<T: Into<String>>(allow_origins: Vec<T>, allow_methods: Vec<HttpMethod>) -> Self {
596 Self {
597 allow_origins: allow_origins.into_iter().map(Into::into).collect(),
598 allow_methods,
599 allow_headers: None,
600 expose_headers: None,
601 max_age: None,
602 }
603 }
604
605 pub fn allow_headers(self, headers: Vec<String>) -> Self {
606 Self {
607 allow_headers: Some(headers),
608 ..self
609 }
610 }
611
612 pub fn expose_headers(self, headers: Vec<String>) -> Self {
613 Self {
614 expose_headers: Some(headers),
615 ..self
616 }
617 }
618
619 pub fn max_age(self, age: Duration) -> Self {
620 Self {
621 max_age: Some(age.as_secs()),
622 ..self
623 }
624 }
625
626 #[must_use]
627 pub fn build(self) -> CorsRule {
628 CorsRule {
629 allowed_headers: self.allow_headers,
630 allowed_methods: self.allow_methods.into_iter().map(Into::into).collect(),
631 allowed_origins: self.allow_origins,
632 exposed_headers: self.expose_headers,
633 max_age: self.max_age,
634 }
635 }
636}
637
638pub enum TransitionDefaultMinimumObjectSize {
639 VariesByStorageClass,
640 AllStorageClasses128k,
641}
642
643impl From<TransitionDefaultMinimumObjectSize> for String {
644 fn from(value: TransitionDefaultMinimumObjectSize) -> Self {
645 match value {
646 TransitionDefaultMinimumObjectSize::VariesByStorageClass => "varies_by_storage_class".to_string(),
647 TransitionDefaultMinimumObjectSize::AllStorageClasses128k => "all_storage_classes_128K".to_string(),
648 }
649 }
650}
651
652pub enum LifecycleStorageClass {
653 IntelligentTiering,
654 OneZoneIA,
655 StandardIA,
656 GlacierDeepArchive,
657 Glacier,
658 GlacierInstantRetrieval,
659}
660
661impl From<LifecycleStorageClass> for String {
662 fn from(value: LifecycleStorageClass) -> Self {
663 match value {
664 LifecycleStorageClass::GlacierDeepArchive => "DEEP_ARCHIVE".to_string(),
665 LifecycleStorageClass::Glacier => "GLACIER".to_string(),
666 LifecycleStorageClass::GlacierInstantRetrieval => "GLACIER_IR".to_string(),
667 LifecycleStorageClass::IntelligentTiering => "INTELLIGENT_TIERING".to_string(),
668 LifecycleStorageClass::OneZoneIA => "ONEZONE_IA".to_string(),
669 LifecycleStorageClass::StandardIA => "STANDARD_IA".to_string(),
670 }
671 }
672}
673
674pub struct LifecycleRuleTransitionBuilder {
678 storage_class: LifecycleStorageClass,
679 transition_in_days: Option<u16>,
680}
681
682impl LifecycleRuleTransitionBuilder {
683 pub fn new(storage_class: LifecycleStorageClass) -> Self {
684 Self {
685 storage_class,
686 transition_in_days: None,
687 }
688 }
689
690 pub fn transition_in_days(self, days: LifecycleTransitionInDays) -> Self {
691 Self {
692 transition_in_days: Some(days.0),
693 ..self
694 }
695 }
696
697 #[must_use]
698 pub fn build(self) -> LifecycleRuleTransition {
699 LifecycleRuleTransition {
700 storage_class: self.storage_class.into(),
701 transition_in_days: self.transition_in_days.unwrap_or(0),
702 }
703 }
704}
705
706pub struct NonCurrentVersionTransitionBuilder {
710 storage_class: LifecycleStorageClass,
711 transition_in_days: u32,
712 newer_non_current_versions: Option<u32>,
713}
714
715impl NonCurrentVersionTransitionBuilder {
716 pub fn new(storage_class: LifecycleStorageClass, transition_in_days: u32) -> Self {
717 Self {
718 storage_class,
719 transition_in_days,
720 newer_non_current_versions: None,
721 }
722 }
723
724 pub fn newer_non_current_versions(self, versions: u32) -> Self {
725 Self {
726 newer_non_current_versions: Some(versions),
727 ..self
728 }
729 }
730
731 #[must_use]
732 pub fn build(self) -> NonCurrentVersionTransition {
733 NonCurrentVersionTransition {
734 storage_class: self.storage_class.into(),
735 transition_in_days: self.transition_in_days,
736 newer_non_current_versions: self.newer_non_current_versions,
737 }
738 }
739}
740
741pub enum LifecycleRuleStatus {
742 Enabled,
743 Disabled,
744}
745
746impl From<LifecycleRuleStatus> for String {
747 fn from(value: LifecycleRuleStatus) -> Self {
748 match value {
749 LifecycleRuleStatus::Enabled => "Enabled".to_string(),
750 LifecycleRuleStatus::Disabled => "Disabled".to_string(),
751 }
752 }
753}
754
755pub struct LifecycleRuleBuilder {
759 id: Option<String>,
760 status: LifecycleRuleStatus,
761 expiration_in_days: Option<u16>, prefix: Option<String>,
763 object_size_greater_than: Option<u32>,
764 object_size_less_than: Option<u32>,
765 abort_incomplete_multipart_upload: Option<u16>,
766 non_current_version_expiration: Option<u16>,
767 transitions: Option<Vec<LifecycleRuleTransition>>,
768 non_current_version_transitions: Option<Vec<NonCurrentVersionTransition>>,
769}
770
771impl LifecycleRuleBuilder {
772 pub fn new(status: LifecycleRuleStatus) -> Self {
773 Self {
774 status,
775 id: None,
776 expiration_in_days: None,
777 prefix: None,
778 object_size_greater_than: None,
779 object_size_less_than: None,
780 abort_incomplete_multipart_upload: None,
781 non_current_version_expiration: None,
782 transitions: None,
783 non_current_version_transitions: None,
784 }
785 }
786
787 pub fn id<T: Into<String>>(self, id: T) -> Self {
788 Self {
789 id: Some(id.into()),
790 ..self
791 }
792 }
793
794 pub fn expiration_in_days(self, days: u16) -> Self {
795 Self {
796 expiration_in_days: Some(days),
797 ..self
798 }
799 }
800
801 pub fn prefix<T: Into<String>>(self, prefix: T) -> Self {
802 Self {
803 prefix: Some(prefix.into()),
804 ..self
805 }
806 }
807
808 pub fn object_size(self, sizes: S3LifecycleObjectSizes) -> Self {
809 Self {
810 object_size_less_than: sizes.0,
811 object_size_greater_than: sizes.1,
812 ..self
813 }
814 }
815
816 pub fn abort_incomplete_multipart_upload(self, days: u16) -> Self {
817 Self {
818 abort_incomplete_multipart_upload: Some(days),
819 ..self
820 }
821 }
822
823 pub fn non_current_version_expiration(self, days: u16) -> Self {
824 Self {
825 non_current_version_expiration: Some(days),
826 ..self
827 }
828 }
829
830 pub fn add_transition(mut self, transition: LifecycleRuleTransition) -> Self {
831 if let Some(mut transitions) = self.transitions {
832 transitions.push(transition);
833 self.transitions = Some(transitions);
834 } else {
835 self.transitions = Some(vec![transition]);
836 }
837
838 Self { ..self }
839 }
840
841 pub fn add_non_current_version_transitions(mut self, transition: NonCurrentVersionTransition) -> Self {
842 if let Some(mut transitions) = self.non_current_version_transitions {
843 transitions.push(transition);
844 self.non_current_version_transitions = Some(transitions);
845 } else {
846 self.non_current_version_transitions = Some(vec![transition]);
847 }
848
849 Self { ..self }
850 }
851
852 pub fn build(self) -> LifecycleRule {
853 LifecycleRule {
854 id: self.id,
855 status: self.status.into(),
856 expiration_in_days: self.expiration_in_days,
857 prefix: self.prefix,
858 object_size_greater_than: self.object_size_greater_than,
859 object_size_less_than: self.object_size_less_than,
860 transitions: self.transitions,
861 abort_incomplete_multipart_upload: self.abort_incomplete_multipart_upload,
862 non_current_version_expiration: self.non_current_version_expiration,
863 non_current_version_transitions: self.non_current_version_transitions,
864 }
865 }
866}
867
868pub struct LifecycleConfigurationBuilder {
872 rules: Vec<LifecycleRule>,
873 transition_minimum_size: Option<TransitionDefaultMinimumObjectSize>,
874}
875
876impl Default for LifecycleConfigurationBuilder {
877 fn default() -> Self {
878 Self::new()
879 }
880}
881
882impl LifecycleConfigurationBuilder {
883 pub fn new() -> Self {
884 Self {
885 rules: vec![],
886 transition_minimum_size: None,
887 }
888 }
889
890 pub fn transition_minimum_size(self, size: TransitionDefaultMinimumObjectSize) -> Self {
891 Self {
892 transition_minimum_size: Some(size),
893 ..self
894 }
895 }
896
897 pub fn add_rule(mut self, rule: LifecycleRule) -> Self {
898 self.rules.push(rule);
899 self
900 }
901
902 #[must_use]
903 pub fn build(self) -> LifecycleConfiguration {
904 LifecycleConfiguration {
905 rules: self.rules,
906 transition_minimum_size: self.transition_minimum_size.map(|v| v.into()),
907 }
908 }
909}
910
911pub struct PublicAccessBlockConfigurationBuilder {
915 block_public_acls: Option<bool>,
916 block_public_policy: Option<bool>,
917 ignore_public_acls: Option<bool>,
918 restrict_public_buckets: Option<bool>,
919}
920
921impl Default for PublicAccessBlockConfigurationBuilder {
922 fn default() -> Self {
923 Self::new()
924 }
925}
926
927impl PublicAccessBlockConfigurationBuilder {
928 pub fn new() -> Self {
929 Self {
930 block_public_acls: None,
931 block_public_policy: None,
932 ignore_public_acls: None,
933 restrict_public_buckets: None,
934 }
935 }
936
937 pub fn block_public_acls(self, config: bool) -> Self {
938 Self {
939 block_public_acls: Some(config),
940 ..self
941 }
942 }
943
944 pub fn block_public_policy(self, config: bool) -> Self {
945 Self {
946 block_public_policy: Some(config),
947 ..self
948 }
949 }
950
951 pub fn ignore_public_acls(self, config: bool) -> Self {
952 Self {
953 ignore_public_acls: Some(config),
954 ..self
955 }
956 }
957
958 pub fn restrict_public_buckets(self, config: bool) -> Self {
959 Self {
960 restrict_public_buckets: Some(config),
961 ..self
962 }
963 }
964
965 #[must_use]
966 pub fn build(self) -> PublicAccessBlockConfiguration {
967 PublicAccessBlockConfiguration {
968 block_public_acls: self.block_public_acls,
969 block_public_policy: self.block_public_policy,
970 ignore_public_acls: self.ignore_public_acls,
971 restrict_public_buckets: self.restrict_public_buckets,
972 }
973 }
974}