Skip to content

Commit

Permalink
Feat: bucket lifecycle configuration impl (#394)
Browse files Browse the repository at this point in the history
* feat: bucketLifecycleConfiguration impl

* get bucket lifecycle

* public some struct

* add LifecycleRuleBuilder

* add DeleteBucketLifecycle

* fix update

* Remove returns

* Bump to alpha.3

---------

Co-authored-by: RWDai <[email protected]>
  • Loading branch information
durch and RWDai committed Jun 24, 2024
1 parent bcdd67d commit e61a410
Show file tree
Hide file tree
Showing 7 changed files with 461 additions and 21 deletions.
2 changes: 1 addition & 1 deletion s3/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "rust-s3"
version = "0.35.0-alpha.2"
version = "0.35.0-alpha.3"
authors = ["Drazen Urch"]
description = "Rust library for working with AWS S3 and compatible object storage APIs"
repository = "https://github.com/durch/rust-s3"
Expand Down
32 changes: 30 additions & 2 deletions s3/src/bucket.rs
Original file line number Diff line number Diff line change
Expand Up @@ -52,8 +52,9 @@ use crate::error::S3Error;
use crate::post_policy::PresignedPost;
use crate::request::Request;
use crate::serde_types::{
BucketLocationResult, CompleteMultipartUploadData, CorsConfiguration, HeadObjectResult,
InitiateMultipartUploadResponse, ListBucketResult, ListMultipartUploadsResult, Part,
BucketLifecycleConfiguration, BucketLocationResult, CompleteMultipartUploadData,
CorsConfiguration, HeadObjectResult, InitiateMultipartUploadResponse, ListBucketResult,
ListMultipartUploadsResult, Part,
};
#[allow(unused_imports)]
use crate::utils::{error_from_response_data, PutStreamResponse};
Expand Down Expand Up @@ -780,6 +781,33 @@ impl Bucket {
request.response_data(false).await
}

#[maybe_async::maybe_async]
pub async fn get_bucket_lifecycle(&self) -> Result<BucketLifecycleConfiguration, S3Error> {
let request = RequestImpl::new(self, "", Command::GetBucketLifecycle).await?;
let response = request.response_data(false).await?;
Ok(quick_xml::de::from_str::<BucketLifecycleConfiguration>(
response.as_str()?,
)?)
}

#[maybe_async::maybe_async]
pub async fn put_bucket_lifecycle(
&self,
lifecycle_config: BucketLifecycleConfiguration,
) -> Result<ResponseData, S3Error> {
let command = Command::PutBucketLifecycle {
configuration: lifecycle_config,
};
let request = RequestImpl::new(self, "", command).await?;
request.response_data(false).await
}

#[maybe_async::maybe_async]
pub async fn delete_bucket_lifecycle(&self) -> Result<ResponseData, S3Error> {
let request = RequestImpl::new(self, "", Command::DeleteBucket).await?;
request.response_data(false).await
}

/// Gets torrent from an S3 path.
///
/// # Example:
Expand Down
38 changes: 28 additions & 10 deletions s3/src/command.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
use std::collections::HashMap;

use crate::serde_types::{CompleteMultipartUploadData, CorsConfiguration};
use crate::error::S3Error;
use crate::serde_types::{
BucketLifecycleConfiguration, CompleteMultipartUploadData, CorsConfiguration,
};

use crate::EMPTY_PAYLOAD_SHA;
use sha2::{Digest, Sha256};
Expand Down Expand Up @@ -129,6 +132,11 @@ pub enum Command<'a> {
PutBucketCors {
configuration: CorsConfiguration,
},
GetBucketLifecycle,
PutBucketLifecycle {
configuration: BucketLifecycleConfiguration,
},
DeleteBucketLifecycle,
}

impl<'a> Command<'a> {
Expand All @@ -142,6 +150,7 @@ impl<'a> Command<'a> {
| Command::ListObjectsV2 { .. }
| Command::GetBucketLocation
| Command::GetObjectTagging
| Command::GetBucketLifecycle
| Command::ListMultipartUploads { .. }
| Command::PresignGet { .. } => HttpMethod::Get,
Command::PutObject { .. }
Expand All @@ -150,21 +159,23 @@ impl<'a> Command<'a> {
| Command::PresignPut { .. }
| Command::UploadPart { .. }
| Command::PutBucketCors { .. }
| Command::CreateBucket { .. } => HttpMethod::Put,
| Command::CreateBucket { .. }
| Command::PutBucketLifecycle { .. } => HttpMethod::Put,
Command::DeleteObject
| Command::DeleteObjectTagging
| Command::AbortMultipartUpload { .. }
| Command::PresignDelete { .. }
| Command::DeleteBucket => HttpMethod::Delete,
| Command::DeleteBucket
| Command::DeleteBucketLifecycle => HttpMethod::Delete,
Command::InitiateMultipartUpload { .. } | Command::CompleteMultipartUpload { .. } => {
HttpMethod::Post
}
Command::HeadObject => HttpMethod::Head,
}
}

pub fn content_length(&self) -> usize {
match &self {
pub fn content_length(&self) -> Result<usize, S3Error> {
let result = match &self {
Command::CopyObject { from: _ } => 0,
Command::PutObject { content, .. } => content.len(),
Command::PutObjectTagging { tags } => tags.len(),
Expand All @@ -177,21 +188,27 @@ impl<'a> Command<'a> {
0
}
}
Command::PutBucketLifecycle { configuration } => {
quick_xml::se::to_string(configuration)?.as_bytes().len()
}
_ => 0,
}
};
Ok(result)
}

pub fn content_type(&self) -> String {
match self {
Command::InitiateMultipartUpload { content_type } => content_type.to_string(),
Command::PutObject { content_type, .. } => content_type.to_string(),
Command::CompleteMultipartUpload { .. } => "application/xml".into(),
Command::CompleteMultipartUpload { .. } | Command::PutBucketLifecycle { .. } => {
"application/xml".into()
}
_ => "text/plain".into(),
}
}

pub fn sha256(&self) -> String {
match &self {
pub fn sha256(&self) -> Result<String, S3Error> {
let result = match &self {
Command::PutObject { content, .. } => {
let mut sha = Sha256::default();
sha.update(content);
Expand All @@ -217,6 +234,7 @@ impl<'a> Command<'a> {
}
}
_ => EMPTY_PAYLOAD_SHA.into(),
}
};
Ok(result)
}
}
2 changes: 1 addition & 1 deletion s3/src/request/async_std_backend.rs
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ impl<'a> Request for SurfRequest<'a> {
HttpMethod::Head => surf::Request::builder(Method::Head, self.url()?),
};

let mut request = request.body(self.request_body());
let mut request = request.body(self.request_body()?);

for (name, value) in headers.iter() {
request = request.header(
Expand Down
26 changes: 20 additions & 6 deletions s3/src/request/request_trait.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
use base64::engine::general_purpose;
use base64::Engine;
use hmac::Mac;
use quick_xml::se::to_string;
use std::collections::HashMap;
#[cfg(any(feature = "with-tokio", feature = "with-async-std"))]
use std::pin::Pin;
Expand Down Expand Up @@ -153,8 +154,8 @@ pub trait Request {
)
}

fn request_body(&self) -> Vec<u8> {
if let Command::PutObject { content, .. } = self.command() {
fn request_body(&self) -> Result<Vec<u8>, S3Error> {
let result = if let Command::PutObject { content, .. } = self.command() {
Vec::from(content)
} else if let Command::PutObjectTagging { tags } = self.command() {
Vec::from(tags)
Expand All @@ -169,9 +170,12 @@ pub trait Request {
} else {
Vec::new()
}
} else if let Command::PutBucketLifecycle { configuration } = &self.command() {
quick_xml::se::to_string(configuration)?.as_bytes().to_vec()
} else {
Vec::new()
}
};
Ok(result)
}

fn long_date(&self) -> Result<String, S3Error> {
Expand Down Expand Up @@ -377,6 +381,11 @@ pub trait Request {
url_str.push_str(&multipart.query_string())
}
}
Command::GetBucketLifecycle
| Command::PutBucketLifecycle { .. }
| Command::DeleteBucketLifecycle => {
url_str.push_str("?lifecycle");
}
_ => {}
}

Expand Down Expand Up @@ -464,7 +473,7 @@ pub trait Request {
&self.command().http_verb().to_string(),
&self.url()?,
headers,
&self.command().sha256(),
&self.command().sha256()?,
)
}

Expand Down Expand Up @@ -492,7 +501,7 @@ pub trait Request {
#[maybe_async::maybe_async]
async fn headers(&self) -> Result<HeaderMap, S3Error> {
// Generate this once, but it's used in more than one place.
let sha256 = self.command().sha256();
let sha256 = self.command().sha256()?;

// Start with extra_headers, that way our headers replace anything with
// the same name.
Expand Down Expand Up @@ -531,7 +540,7 @@ pub trait Request {
_ => {
headers.insert(
CONTENT_LENGTH,
self.command().content_length().to_string().parse()?,
self.command().content_length()?.to_string().parse()?,
);
headers.insert(CONTENT_TYPE, self.command().content_type().parse()?);
}
Expand Down Expand Up @@ -584,6 +593,11 @@ pub trait Request {
headers.insert(RANGE, range.parse()?);
} else if let Command::CreateBucket { ref config } = self.command() {
config.add_headers(&mut headers)?;
} else if let Command::PutBucketLifecycle { ref configuration } = self.command() {
let digest = md5::compute(to_string(configuration)?.as_bytes());
let hash = general_purpose::STANDARD.encode(digest.as_ref());
headers.insert(HeaderName::from_static("content-md5"), hash.parse()?);
headers.remove("x-amz-content-sha256");
}

// This must be last, as it signs the other headers, omitted if no secret key is provided
Expand Down
2 changes: 1 addition & 1 deletion s3/src/request/tokio_backend.rs
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,7 @@ impl<'a> Request for HyperRequest<'a> {
request = request.header(header, value);
}

request.body(Body::from(self.request_body()))?
request.body(Body::from(self.request_body()?))?
};
let response = client.request(request).await?;

Expand Down
Loading

0 comments on commit e61a410

Please sign in to comment.