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