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