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