mirror of
https://github.com/minio/minio-rs.git
synced 2025-12-06 15:26:51 +08:00
Add bucket encryption/lifecycle and object legal-hold APIs. (#24)
Below APIs are added * DeleteBucketEncryption() * GetBucketEncryption() * SetBucketEncryption() * DisableObjectLegalHold() * EnableObjectLegalHold() * IsObjectLegalHoldEnabled() * DeleteBucketLifecycle() * GetBucketLifecycle() * SetBucketLifecycle() Signed-off-by: Bala.FA <bala@minio.io>
This commit is contained in:
parent
49452a0b73
commit
5fea81d68d
@ -16,7 +16,8 @@
|
||||
use crate::s3::error::Error;
|
||||
use crate::s3::sse::{Sse, SseCustomerKey};
|
||||
use crate::s3::types::{
|
||||
DeleteObject, Directive, Item, NotificationRecords, Part, Retention, SelectRequest,
|
||||
DeleteObject, Directive, Item, LifecycleConfig, NotificationRecords, Part, Retention,
|
||||
SelectRequest, SseConfig,
|
||||
};
|
||||
use crate::s3::utils::{
|
||||
check_bucket_name, merge, to_http_header_value, to_iso8601utc, urlencode, Multimap, UtcTime,
|
||||
@ -1344,3 +1345,51 @@ impl<'a> ComposeObjectArgs<'a> {
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
pub type DeleteBucketEncryptionArgs<'a> = BucketArgs<'a>;
|
||||
|
||||
pub type GetBucketEncryptionArgs<'a> = BucketArgs<'a>;
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct SetBucketEncryptionArgs<'a> {
|
||||
pub extra_headers: Option<&'a Multimap>,
|
||||
pub extra_query_params: Option<&'a Multimap>,
|
||||
pub region: Option<&'a str>,
|
||||
pub bucket: &'a str,
|
||||
pub config: &'a SseConfig,
|
||||
}
|
||||
|
||||
impl<'a> SetBucketEncryptionArgs<'a> {
|
||||
pub fn new(
|
||||
bucket_name: &'a str,
|
||||
config: &'a SseConfig,
|
||||
) -> Result<SetBucketEncryptionArgs<'a>, Error> {
|
||||
check_bucket_name(bucket_name, true)?;
|
||||
|
||||
Ok(SetBucketEncryptionArgs {
|
||||
extra_headers: None,
|
||||
extra_query_params: None,
|
||||
region: None,
|
||||
bucket: bucket_name,
|
||||
config: config,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub type EnableObjectLegalHoldArgs<'a> = ObjectVersionArgs<'a>;
|
||||
|
||||
pub type DisableObjectLegalHoldArgs<'a> = ObjectVersionArgs<'a>;
|
||||
|
||||
pub type IsObjectLegalHoldEnabledArgs<'a> = ObjectVersionArgs<'a>;
|
||||
|
||||
pub type DeleteBucketLifecycleArgs<'a> = BucketArgs<'a>;
|
||||
|
||||
pub type GetBucketLifecycleArgs<'a> = BucketArgs<'a>;
|
||||
|
||||
pub struct SetBucketLifecycleArgs<'a> {
|
||||
pub extra_headers: Option<&'a Multimap>,
|
||||
pub extra_query_params: Option<&'a Multimap>,
|
||||
pub region: Option<&'a str>,
|
||||
pub bucket: &'a str,
|
||||
pub config: &'a LifecycleConfig,
|
||||
}
|
||||
|
||||
428
src/s3/client.rs
428
src/s3/client.rs
@ -20,7 +20,9 @@ use crate::s3::http::{BaseUrl, Url};
|
||||
use crate::s3::response::*;
|
||||
use crate::s3::signer::sign_v4_s3;
|
||||
use crate::s3::sse::SseCustomerKey;
|
||||
use crate::s3::types::{Bucket, DeleteObject, Directive, Item, NotificationRecords, Part};
|
||||
use crate::s3::types::{
|
||||
Bucket, DeleteObject, Directive, Item, LifecycleConfig, NotificationRecords, Part, SseConfig,
|
||||
};
|
||||
use crate::s3::utils::{
|
||||
from_iso8601utc, get_default_text, get_option_text, get_text, md5sum_hash, merge, sha256_hash,
|
||||
to_amz_date, urldecode, utc_now, Multimap,
|
||||
@ -1176,12 +1178,132 @@ impl<'a> Client<'a> {
|
||||
})
|
||||
}
|
||||
|
||||
// DeleteBucketEncryptionResponse DeleteBucketEncryption(
|
||||
// DeleteBucketEncryptionArgs args);
|
||||
// DisableObjectLegalHoldResponse DisableObjectLegalHold(
|
||||
// DisableObjectLegalHoldArgs args);
|
||||
// DeleteBucketLifecycleResponse DeleteBucketLifecycle(
|
||||
// DeleteBucketLifecycleArgs args);
|
||||
pub async fn delete_bucket_encryption(
|
||||
&self,
|
||||
args: &DeleteBucketEncryptionArgs<'_>,
|
||||
) -> Result<DeleteBucketEncryptionResponse, Error> {
|
||||
let region = self.get_region(&args.bucket, args.region).await?;
|
||||
|
||||
let mut headers = Multimap::new();
|
||||
if let Some(v) = &args.extra_headers {
|
||||
merge(&mut headers, v);
|
||||
}
|
||||
|
||||
let mut query_params = Multimap::new();
|
||||
if let Some(v) = &args.extra_query_params {
|
||||
merge(&mut query_params, v);
|
||||
}
|
||||
query_params.insert(String::from("encryption"), String::new());
|
||||
|
||||
match self
|
||||
.execute(
|
||||
Method::DELETE,
|
||||
®ion,
|
||||
&mut headers,
|
||||
&query_params,
|
||||
Some(&args.bucket),
|
||||
None,
|
||||
None,
|
||||
)
|
||||
.await
|
||||
{
|
||||
Ok(resp) => Ok(DeleteBucketEncryptionResponse {
|
||||
headers: resp.headers().clone(),
|
||||
region: region.clone(),
|
||||
bucket_name: args.bucket.to_string(),
|
||||
}),
|
||||
Err(e) => match e {
|
||||
Error::S3Error(ref err) => {
|
||||
if err.code == "ServerSideEncryptionConfigurationNotFoundError" {
|
||||
return Ok(DeleteBucketEncryptionResponse {
|
||||
headers: HeaderMap::new(),
|
||||
region: region.clone(),
|
||||
bucket_name: args.bucket.to_string(),
|
||||
});
|
||||
}
|
||||
return Err(e);
|
||||
}
|
||||
_ => return Err(e),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn disable_object_legal_hold(
|
||||
&self,
|
||||
args: DisableObjectLegalHoldArgs<'_>,
|
||||
) -> Result<DisableObjectLegalHoldResponse, Error> {
|
||||
let region = self.get_region(&args.bucket, args.region).await?;
|
||||
|
||||
let mut headers = Multimap::new();
|
||||
if let Some(v) = &args.extra_headers {
|
||||
merge(&mut headers, v);
|
||||
}
|
||||
|
||||
let mut query_params = Multimap::new();
|
||||
if let Some(v) = &args.extra_query_params {
|
||||
merge(&mut query_params, v);
|
||||
}
|
||||
if let Some(v) = args.version_id {
|
||||
query_params.insert(String::from("versionId"), v.to_string());
|
||||
}
|
||||
query_params.insert(String::from("legal-hold"), String::new());
|
||||
|
||||
let resp = self
|
||||
.execute(
|
||||
Method::PUT,
|
||||
®ion,
|
||||
&mut headers,
|
||||
&query_params,
|
||||
Some(&args.bucket),
|
||||
Some(&args.object),
|
||||
Some(b"<LegalHold><Status>OFF</Status></LegalHold>"),
|
||||
)
|
||||
.await?;
|
||||
|
||||
Ok(DisableObjectLegalHoldResponse {
|
||||
headers: resp.headers().clone(),
|
||||
region: region.clone(),
|
||||
bucket_name: args.bucket.to_string(),
|
||||
object_name: args.object.to_string(),
|
||||
version_id: args.version_id.as_ref().map(|v| v.to_string()),
|
||||
})
|
||||
}
|
||||
|
||||
pub async fn delete_bucket_lifecycle(
|
||||
&self,
|
||||
args: DeleteBucketLifecycleArgs<'_>,
|
||||
) -> Result<DeleteBucketLifecycleResponse, Error> {
|
||||
let region = self.get_region(&args.bucket, args.region).await?;
|
||||
|
||||
let mut headers = Multimap::new();
|
||||
if let Some(v) = &args.extra_headers {
|
||||
merge(&mut headers, v);
|
||||
}
|
||||
|
||||
let mut query_params = Multimap::new();
|
||||
if let Some(v) = &args.extra_query_params {
|
||||
merge(&mut query_params, v);
|
||||
}
|
||||
query_params.insert(String::from("lifecycle"), String::new());
|
||||
|
||||
let resp = self
|
||||
.execute(
|
||||
Method::DELETE,
|
||||
®ion,
|
||||
&mut headers,
|
||||
&query_params,
|
||||
Some(&args.bucket),
|
||||
None,
|
||||
None,
|
||||
)
|
||||
.await?;
|
||||
|
||||
Ok(DeleteBucketLifecycleResponse {
|
||||
headers: resp.headers().clone(),
|
||||
region: region.clone(),
|
||||
bucket_name: args.bucket.to_string(),
|
||||
})
|
||||
}
|
||||
// DeleteBucketNotificationResponse DeleteBucketNotification(
|
||||
// DeleteBucketNotificationArgs args);
|
||||
// DeleteBucketPolicyResponse DeleteBucketPolicy(DeleteBucketPolicyArgs args);
|
||||
@ -1191,10 +1313,156 @@ impl<'a> Client<'a> {
|
||||
// DeleteObjectLockConfigResponse DeleteObjectLockConfig(
|
||||
// DeleteObjectLockConfigArgs args);
|
||||
// DeleteObjectTagsResponse DeleteObjectTags(DeleteObjectTagsArgs args);
|
||||
// EnableObjectLegalHoldResponse EnableObjectLegalHold(
|
||||
// EnableObjectLegalHoldArgs args);
|
||||
// GetBucketEncryptionResponse GetBucketEncryption(GetBucketEncryptionArgs args);
|
||||
// GetBucketLifecycleResponse GetBucketLifecycle(GetBucketLifecycleArgs args);
|
||||
pub async fn enable_object_legal_hold(
|
||||
&self,
|
||||
args: EnableObjectLegalHoldArgs<'_>,
|
||||
) -> Result<EnableObjectLegalHoldResponse, Error> {
|
||||
let region = self.get_region(&args.bucket, args.region).await?;
|
||||
|
||||
let mut headers = Multimap::new();
|
||||
if let Some(v) = &args.extra_headers {
|
||||
merge(&mut headers, v);
|
||||
}
|
||||
|
||||
let mut query_params = Multimap::new();
|
||||
if let Some(v) = &args.extra_query_params {
|
||||
merge(&mut query_params, v);
|
||||
}
|
||||
if let Some(v) = args.version_id {
|
||||
query_params.insert(String::from("versionId"), v.to_string());
|
||||
}
|
||||
query_params.insert(String::from("legal-hold"), String::new());
|
||||
|
||||
let resp = self
|
||||
.execute(
|
||||
Method::PUT,
|
||||
®ion,
|
||||
&mut headers,
|
||||
&query_params,
|
||||
Some(&args.bucket),
|
||||
Some(&args.object),
|
||||
Some(b"<LegalHold><Status>ON</Status></LegalHold>"),
|
||||
)
|
||||
.await?;
|
||||
|
||||
Ok(EnableObjectLegalHoldResponse {
|
||||
headers: resp.headers().clone(),
|
||||
region: region.clone(),
|
||||
bucket_name: args.bucket.to_string(),
|
||||
object_name: args.object.to_string(),
|
||||
version_id: args.version_id.as_ref().map(|v| v.to_string()),
|
||||
})
|
||||
}
|
||||
|
||||
pub async fn get_bucket_encryption(
|
||||
&self,
|
||||
args: &GetBucketEncryptionArgs<'_>,
|
||||
) -> Result<GetBucketEncryptionResponse, Error> {
|
||||
let region = self.get_region(&args.bucket, args.region).await?;
|
||||
|
||||
let mut headers = Multimap::new();
|
||||
if let Some(v) = &args.extra_headers {
|
||||
merge(&mut headers, v);
|
||||
}
|
||||
|
||||
let mut query_params = Multimap::new();
|
||||
if let Some(v) = &args.extra_query_params {
|
||||
merge(&mut query_params, v);
|
||||
}
|
||||
query_params.insert(String::from("encryption"), String::new());
|
||||
|
||||
let resp = self
|
||||
.execute(
|
||||
Method::GET,
|
||||
®ion,
|
||||
&mut headers,
|
||||
&query_params,
|
||||
Some(&args.bucket),
|
||||
None,
|
||||
None,
|
||||
)
|
||||
.await?;
|
||||
|
||||
let header_map = resp.headers().clone();
|
||||
let body = resp.bytes().await?;
|
||||
let mut root = Element::parse(body.reader())?;
|
||||
let rule = root
|
||||
.get_mut_child("Rule")
|
||||
.ok_or(Error::XmlError(String::from("<Rule> tag not found")))?;
|
||||
let sse_by_default = rule
|
||||
.get_mut_child("ApplyServerSideEncryptionByDefault")
|
||||
.ok_or(Error::XmlError(String::from(
|
||||
"<ApplyServerSideEncryptionByDefault> tag not found",
|
||||
)))?;
|
||||
|
||||
Ok(GetBucketEncryptionResponse {
|
||||
headers: header_map.clone(),
|
||||
region: region.clone(),
|
||||
bucket_name: args.bucket.to_string(),
|
||||
config: SseConfig {
|
||||
sse_algorithm: get_text(sse_by_default, "SSEAlgorithm")?,
|
||||
kms_master_key_id: get_option_text(sse_by_default, "KMSMasterKeyID")?,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
pub async fn get_bucket_lifecycle(
|
||||
&self,
|
||||
args: GetBucketLifecycleArgs<'_>,
|
||||
) -> Result<GetBucketLifecycleResponse, Error> {
|
||||
let region = self.get_region(&args.bucket, args.region).await?;
|
||||
|
||||
let mut headers = Multimap::new();
|
||||
if let Some(v) = &args.extra_headers {
|
||||
merge(&mut headers, v);
|
||||
}
|
||||
|
||||
let mut query_params = Multimap::new();
|
||||
if let Some(v) = &args.extra_query_params {
|
||||
merge(&mut query_params, v);
|
||||
}
|
||||
query_params.insert(String::from("lifecycle"), String::new());
|
||||
|
||||
match self
|
||||
.execute(
|
||||
Method::GET,
|
||||
®ion,
|
||||
&mut headers,
|
||||
&query_params,
|
||||
Some(&args.bucket),
|
||||
None,
|
||||
None,
|
||||
)
|
||||
.await
|
||||
{
|
||||
Ok(resp) => {
|
||||
let header_map = resp.headers().clone();
|
||||
let body = resp.bytes().await?;
|
||||
let root = Element::parse(body.reader())?;
|
||||
|
||||
return Ok(GetBucketLifecycleResponse {
|
||||
headers: header_map.clone(),
|
||||
region: region.clone(),
|
||||
bucket_name: args.bucket.to_string(),
|
||||
config: LifecycleConfig::from_xml(&root)?,
|
||||
});
|
||||
}
|
||||
Err(e) => match e {
|
||||
Error::S3Error(ref err) => {
|
||||
if err.code == "NoSuchLifecycleConfiguration" {
|
||||
return Ok(GetBucketLifecycleResponse {
|
||||
headers: HeaderMap::new(),
|
||||
region: region.clone(),
|
||||
bucket_name: args.bucket.to_string(),
|
||||
config: LifecycleConfig { rules: Vec::new() },
|
||||
});
|
||||
}
|
||||
return Err(e);
|
||||
}
|
||||
_ => return Err(e),
|
||||
},
|
||||
}
|
||||
}
|
||||
// GetBucketNotificationResponse GetBucketNotification(
|
||||
// GetBucketNotificationArgs args);
|
||||
// GetBucketPolicyResponse GetBucketPolicy(GetBucketPolicyArgs args);
|
||||
@ -1242,8 +1510,69 @@ impl<'a> Client<'a> {
|
||||
// GetPresignedObjectUrlResponse GetPresignedObjectUrl(
|
||||
// GetPresignedObjectUrlArgs args);
|
||||
// GetPresignedPostFormDataResponse GetPresignedPostFormData(PostPolicy policy);
|
||||
// IsObjectLegalHoldEnabledResponse IsObjectLegalHoldEnabled(
|
||||
// IsObjectLegalHoldEnabledArgs args);
|
||||
pub async fn is_object_legal_hold_enabled(
|
||||
&self,
|
||||
args: IsObjectLegalHoldEnabledArgs<'_>,
|
||||
) -> Result<IsObjectLegalHoldEnabledResponse, Error> {
|
||||
let region = self.get_region(&args.bucket, args.region).await?;
|
||||
|
||||
let mut headers = Multimap::new();
|
||||
if let Some(v) = &args.extra_headers {
|
||||
merge(&mut headers, v);
|
||||
}
|
||||
|
||||
let mut query_params = Multimap::new();
|
||||
if let Some(v) = &args.extra_query_params {
|
||||
merge(&mut query_params, v);
|
||||
}
|
||||
if let Some(v) = args.version_id {
|
||||
query_params.insert(String::from("versionId"), v.to_string());
|
||||
}
|
||||
query_params.insert(String::from("legal-hold"), String::new());
|
||||
|
||||
match self
|
||||
.execute(
|
||||
Method::GET,
|
||||
®ion,
|
||||
&mut headers,
|
||||
&query_params,
|
||||
Some(&args.bucket),
|
||||
Some(&args.object),
|
||||
None,
|
||||
)
|
||||
.await
|
||||
{
|
||||
Ok(resp) => {
|
||||
let header_map = resp.headers().clone();
|
||||
let body = resp.bytes().await?;
|
||||
let root = Element::parse(body.reader())?;
|
||||
Ok(IsObjectLegalHoldEnabledResponse {
|
||||
headers: header_map.clone(),
|
||||
region: region.clone(),
|
||||
bucket_name: args.bucket.to_string(),
|
||||
object_name: args.object.to_string(),
|
||||
version_id: args.version_id.as_ref().map(|v| v.to_string()),
|
||||
enabled: get_default_text(&root, "Status") == "ON",
|
||||
})
|
||||
}
|
||||
Err(e) => match e {
|
||||
Error::S3Error(ref err) => {
|
||||
if err.code == "NoSuchObjectLockConfiguration" {
|
||||
return Ok(IsObjectLegalHoldEnabledResponse {
|
||||
headers: HeaderMap::new(),
|
||||
region: region.clone(),
|
||||
bucket_name: args.bucket.to_string(),
|
||||
object_name: args.object.to_string(),
|
||||
version_id: args.version_id.as_ref().map(|v| v.to_string()),
|
||||
enabled: false,
|
||||
});
|
||||
}
|
||||
return Err(e);
|
||||
}
|
||||
_ => return Err(e),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn list_buckets(
|
||||
&self,
|
||||
@ -2191,8 +2520,77 @@ impl<'a> Client<'a> {
|
||||
})
|
||||
}
|
||||
|
||||
// SetBucketEncryptionResponse SetBucketEncryption(SetBucketEncryptionArgs args);
|
||||
// SetBucketLifecycleResponse SetBucketLifecycle(SetBucketLifecycleArgs args);
|
||||
pub async fn set_bucket_encryption(
|
||||
&self,
|
||||
args: &SetBucketEncryptionArgs<'_>,
|
||||
) -> Result<SetBucketEncryptionResponse, Error> {
|
||||
let region = self.get_region(&args.bucket, args.region).await?;
|
||||
|
||||
let mut headers = Multimap::new();
|
||||
if let Some(v) = &args.extra_headers {
|
||||
merge(&mut headers, v);
|
||||
}
|
||||
|
||||
let mut query_params = Multimap::new();
|
||||
if let Some(v) = &args.extra_query_params {
|
||||
merge(&mut query_params, v);
|
||||
}
|
||||
query_params.insert(String::from("encryption"), String::new());
|
||||
|
||||
let resp = self
|
||||
.execute(
|
||||
Method::PUT,
|
||||
®ion,
|
||||
&mut headers,
|
||||
&query_params,
|
||||
Some(&args.bucket),
|
||||
None,
|
||||
Some(args.config.to_xml().as_bytes()),
|
||||
)
|
||||
.await?;
|
||||
|
||||
Ok(SetBucketEncryptionResponse {
|
||||
headers: resp.headers().clone(),
|
||||
region: region.clone(),
|
||||
bucket_name: args.bucket.to_string(),
|
||||
})
|
||||
}
|
||||
|
||||
pub async fn set_bucket_lifecycle(
|
||||
&self,
|
||||
args: SetBucketLifecycleArgs<'_>,
|
||||
) -> Result<SetBucketLifecycleResponse, Error> {
|
||||
let region = self.get_region(&args.bucket, args.region).await?;
|
||||
|
||||
let mut headers = Multimap::new();
|
||||
if let Some(v) = &args.extra_headers {
|
||||
merge(&mut headers, v);
|
||||
}
|
||||
|
||||
let mut query_params = Multimap::new();
|
||||
if let Some(v) = &args.extra_query_params {
|
||||
merge(&mut query_params, v);
|
||||
}
|
||||
query_params.insert(String::from("lifecycle"), String::new());
|
||||
|
||||
let resp = self
|
||||
.execute(
|
||||
Method::PUT,
|
||||
®ion,
|
||||
&mut headers,
|
||||
&query_params,
|
||||
Some(&args.bucket),
|
||||
None,
|
||||
Some(args.config.to_xml().as_bytes()),
|
||||
)
|
||||
.await?;
|
||||
|
||||
Ok(SetBucketLifecycleResponse {
|
||||
headers: resp.headers().clone(),
|
||||
region: region.clone(),
|
||||
bucket_name: args.bucket.to_string(),
|
||||
})
|
||||
}
|
||||
// SetBucketNotificationResponse SetBucketNotification(
|
||||
// SetBucketNotificationArgs args);
|
||||
// SetBucketPolicyResponse SetBucketPolicy(SetBucketPolicyArgs args);
|
||||
|
||||
@ -97,6 +97,11 @@ pub enum Error {
|
||||
InvalidDirective(String),
|
||||
InvalidCopyDirective(String),
|
||||
InvalidMultipartCount(u16),
|
||||
MissingLifecycleAction,
|
||||
InvalidExpiredObjectDeleteMarker,
|
||||
InvalidDateAndDays(String),
|
||||
InvalidLifecycleRuleId,
|
||||
InvalidFilter,
|
||||
}
|
||||
|
||||
impl std::error::Error for Error {}
|
||||
@ -150,7 +155,12 @@ impl fmt::Display for Error {
|
||||
Error::InvalidComposeSourcePartSize(b, o, v, s, es) => write!(f, "source {}/{}{}: size {} must be greater than {}", b, o, v.as_ref().map_or(String::new(), |v| String::from("?versionId=") + v), s, es),
|
||||
Error::InvalidComposeSourceMultipart(b, o, v, s, es) => write!(f, "source {}/{}{}: size {} for multipart split upload of {}, last part size is less than {}", b, o, v.as_ref().map_or(String::new(), |v| String::from("?versionId=") + v), s, s, es),
|
||||
Error::InvalidMultipartCount(c) => write!(f, "Compose sources create more than allowed multipart count {}", c),
|
||||
}
|
||||
Error::MissingLifecycleAction => write!(f, "at least one of action (AbortIncompleteMultipartUpload, Expiration, NoncurrentVersionExpiration, NoncurrentVersionTransition or Transition) must be specified in a rule"),
|
||||
Error::InvalidExpiredObjectDeleteMarker => write!(f, "ExpiredObjectDeleteMarker must not be provided along with Date and Days"),
|
||||
Error::InvalidDateAndDays(m) => write!(f, "Only one of date or days of {} must be set", m),
|
||||
Error::InvalidLifecycleRuleId => write!(f, "id must be exceed 255 characters"),
|
||||
Error::InvalidFilter => write!(f, "only one of And, Prefix or Tag must be provided"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -14,7 +14,9 @@
|
||||
// limitations under the License.
|
||||
|
||||
use crate::s3::error::Error;
|
||||
use crate::s3::types::{parse_legal_hold, Bucket, Item, RetentionMode, SelectProgress};
|
||||
use crate::s3::types::{
|
||||
parse_legal_hold, Bucket, Item, LifecycleConfig, RetentionMode, SelectProgress, SseConfig,
|
||||
};
|
||||
use crate::s3::utils::{
|
||||
copy_slice, crc32, from_http_header_value, from_iso8601utc, get_text, uint32, UtcTime,
|
||||
};
|
||||
@ -620,3 +622,38 @@ impl ListenBucketNotificationResponse {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub type DeleteBucketEncryptionResponse = BucketResponse;
|
||||
|
||||
pub struct GetBucketEncryptionResponse {
|
||||
pub headers: HeaderMap,
|
||||
pub region: String,
|
||||
pub bucket_name: String,
|
||||
pub config: SseConfig,
|
||||
}
|
||||
|
||||
pub type SetBucketEncryptionResponse = BucketResponse;
|
||||
|
||||
pub type EnableObjectLegalHoldResponse = ObjectResponse;
|
||||
|
||||
pub type DisableObjectLegalHoldResponse = ObjectResponse;
|
||||
|
||||
pub struct IsObjectLegalHoldEnabledResponse {
|
||||
pub headers: HeaderMap,
|
||||
pub region: String,
|
||||
pub bucket_name: String,
|
||||
pub object_name: String,
|
||||
pub version_id: Option<String>,
|
||||
pub enabled: bool,
|
||||
}
|
||||
|
||||
pub type DeleteBucketLifecycleResponse = BucketResponse;
|
||||
|
||||
pub struct GetBucketLifecycleResponse {
|
||||
pub headers: HeaderMap,
|
||||
pub region: String,
|
||||
pub bucket_name: String,
|
||||
pub config: LifecycleConfig,
|
||||
}
|
||||
|
||||
pub type SetBucketLifecycleResponse = BucketResponse;
|
||||
|
||||
480
src/s3/types.rs
480
src/s3/types.rs
@ -14,10 +14,11 @@
|
||||
// limitations under the License.
|
||||
|
||||
use crate::s3::error::Error;
|
||||
use crate::s3::utils::UtcTime;
|
||||
use crate::s3::utils::{from_iso8601utc, get_default_text, get_text, to_iso8601utc, UtcTime};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::collections::HashMap;
|
||||
use std::fmt;
|
||||
use xmltree::Element;
|
||||
|
||||
#[derive(Clone, Debug, Default)]
|
||||
pub struct Item {
|
||||
@ -591,3 +592,480 @@ impl fmt::Display for Directive {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct SseConfig {
|
||||
pub sse_algorithm: String,
|
||||
pub kms_master_key_id: Option<String>,
|
||||
}
|
||||
|
||||
impl SseConfig {
|
||||
pub fn s3() -> SseConfig {
|
||||
SseConfig {
|
||||
sse_algorithm: String::from("AES256"),
|
||||
kms_master_key_id: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn kms(kms_master_key_id: Option<String>) -> SseConfig {
|
||||
SseConfig {
|
||||
sse_algorithm: String::from("aws:kms"),
|
||||
kms_master_key_id: kms_master_key_id,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn to_xml(&self) -> String {
|
||||
let mut data = String::from(
|
||||
"<ServerSideEncryptionConfiguration><Rule><ApplyServerSideEncryptionByDefault>",
|
||||
);
|
||||
data.push_str("<SSEAlgorithm>");
|
||||
data.push_str(&self.sse_algorithm);
|
||||
data.push_str("</SSEAlgorithm>");
|
||||
if self.kms_master_key_id.is_some() {
|
||||
data.push_str("<KMSMasterKeyID>");
|
||||
data.push_str(self.kms_master_key_id.as_ref().unwrap());
|
||||
data.push_str("</KMSMasterKeyID>");
|
||||
}
|
||||
data.push_str(
|
||||
"</ApplyServerSideEncryptionByDefault></Rule></ServerSideEncryptionConfiguration>",
|
||||
);
|
||||
return data;
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct Tag {
|
||||
pub key: String,
|
||||
pub value: String,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct AndOperator {
|
||||
pub prefix: Option<String>,
|
||||
pub tags: Option<HashMap<String, String>>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct Filter {
|
||||
pub and_operator: Option<AndOperator>,
|
||||
pub prefix: Option<String>,
|
||||
pub tag: Option<Tag>,
|
||||
}
|
||||
|
||||
impl Filter {
|
||||
pub fn parse_xml(element: &Element) -> Result<Filter, Error> {
|
||||
let and_operator = match element.get_child("And") {
|
||||
Some(v) => Some(AndOperator {
|
||||
prefix: match v.get_child("Prefix") {
|
||||
Some(p) => Some(
|
||||
p.get_text()
|
||||
.ok_or(Error::XmlError(format!("text of <Prefix> tag not found")))?
|
||||
.to_string(),
|
||||
),
|
||||
None => None,
|
||||
},
|
||||
tags: match v.get_child("Tag") {
|
||||
Some(tags) => {
|
||||
let mut map: HashMap<String, String> = HashMap::new();
|
||||
for xml_node in &tags.children {
|
||||
let tag = xml_node
|
||||
.as_element()
|
||||
.ok_or(Error::XmlError(format!("<Tag> element not found")))?;
|
||||
map.insert(get_text(tag, "Key")?, get_text(tag, "Value")?);
|
||||
}
|
||||
Some(map)
|
||||
}
|
||||
None => None,
|
||||
},
|
||||
}),
|
||||
None => None,
|
||||
};
|
||||
|
||||
let prefix = match element.get_child("Prefix") {
|
||||
Some(v) => Some(
|
||||
v.get_text()
|
||||
.ok_or(Error::XmlError(format!("text of <Prefix> tag not found")))?
|
||||
.to_string(),
|
||||
),
|
||||
None => None,
|
||||
};
|
||||
|
||||
let tag = match element.get_child("Tag") {
|
||||
Some(v) => Some(Tag {
|
||||
key: get_text(v, "Key")?,
|
||||
value: get_text(v, "Value")?,
|
||||
}),
|
||||
None => None,
|
||||
};
|
||||
|
||||
Ok(Filter {
|
||||
and_operator: and_operator,
|
||||
prefix: prefix,
|
||||
tag: tag,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn validate(&self) -> Result<(), Error> {
|
||||
if self.and_operator.is_some() ^ self.prefix.is_some() ^ self.tag.is_some() {
|
||||
return Ok(());
|
||||
}
|
||||
return Err(Error::InvalidFilter);
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct LifecycleRule {
|
||||
pub abort_incomplete_multipart_upload_days_after_initiation: Option<usize>,
|
||||
pub expiration_date: Option<UtcTime>,
|
||||
pub expiration_days: Option<usize>,
|
||||
pub expiration_expired_object_delete_marker: Option<bool>,
|
||||
pub filter: Filter,
|
||||
pub id: String,
|
||||
pub noncurrent_version_expiration_noncurrent_days: Option<usize>,
|
||||
pub noncurrent_version_transition_noncurrent_days: Option<usize>,
|
||||
pub noncurrent_version_transition_storage_class: Option<String>,
|
||||
pub status: bool,
|
||||
pub transition_date: Option<UtcTime>,
|
||||
pub transition_days: Option<usize>,
|
||||
pub transition_storage_class: Option<String>,
|
||||
}
|
||||
|
||||
impl LifecycleRule {
|
||||
pub fn from_xml(element: &Element) -> Result<LifecycleRule, Error> {
|
||||
let expiration = element.get_child("Expiration");
|
||||
let noncurrent_version_transition = element.get_child("NoncurrentVersionTransition");
|
||||
let transition = element.get_child("Transition");
|
||||
|
||||
Ok(LifecycleRule {
|
||||
abort_incomplete_multipart_upload_days_after_initiation: match element
|
||||
.get_child("AbortIncompleteMultipartUpload")
|
||||
{
|
||||
Some(v) => {
|
||||
let text = get_text(v, "DaysAfterInitiation")?;
|
||||
Some(text.parse::<usize>()?)
|
||||
}
|
||||
None => None,
|
||||
},
|
||||
expiration_date: match expiration {
|
||||
Some(v) => {
|
||||
let text = get_text(v, "Date")?;
|
||||
Some(from_iso8601utc(&text)?)
|
||||
}
|
||||
None => None,
|
||||
},
|
||||
expiration_days: match expiration {
|
||||
Some(v) => {
|
||||
let text = get_text(v, "Days")?;
|
||||
Some(text.parse::<usize>()?)
|
||||
}
|
||||
None => None,
|
||||
},
|
||||
expiration_expired_object_delete_marker: match expiration {
|
||||
Some(v) => Some(get_text(v, "ExpiredObjectDeleteMarker")?.to_lowercase() == "true"),
|
||||
None => None,
|
||||
},
|
||||
filter: Filter::parse_xml(
|
||||
element
|
||||
.get_child("Filter")
|
||||
.ok_or(Error::XmlError(format!("<Filter> tag not found")))?,
|
||||
)?,
|
||||
id: get_default_text(element, "ID"),
|
||||
noncurrent_version_expiration_noncurrent_days: match element
|
||||
.get_child("NoncurrentVersionExpiration")
|
||||
{
|
||||
Some(v) => {
|
||||
let text = get_text(v, "NoncurrentDays")?;
|
||||
Some(text.parse::<usize>()?)
|
||||
}
|
||||
None => None,
|
||||
},
|
||||
noncurrent_version_transition_noncurrent_days: match noncurrent_version_transition {
|
||||
Some(v) => {
|
||||
let text = get_text(v, "NoncurrentDays")?;
|
||||
Some(text.parse::<usize>()?)
|
||||
}
|
||||
None => None,
|
||||
},
|
||||
noncurrent_version_transition_storage_class: match noncurrent_version_transition {
|
||||
Some(v) => Some(get_text(v, "StorageClass")?),
|
||||
None => None,
|
||||
},
|
||||
status: get_text(element, "Status")?.to_lowercase() == "Enabled",
|
||||
transition_date: match transition {
|
||||
Some(v) => {
|
||||
let text = get_text(v, "Date")?;
|
||||
Some(from_iso8601utc(&text)?)
|
||||
}
|
||||
None => None,
|
||||
},
|
||||
transition_days: match transition {
|
||||
Some(v) => {
|
||||
let text = get_text(v, "Days")?;
|
||||
Some(text.parse::<usize>()?)
|
||||
}
|
||||
None => None,
|
||||
},
|
||||
transition_storage_class: match transition {
|
||||
Some(v) => Some(get_text(v, "StorageClass")?),
|
||||
None => None,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
pub fn validate(&self) -> Result<(), Error> {
|
||||
if self
|
||||
.abort_incomplete_multipart_upload_days_after_initiation
|
||||
.is_none()
|
||||
&& self.expiration_date.is_none()
|
||||
&& self.expiration_days.is_none()
|
||||
&& self.expiration_expired_object_delete_marker.is_none()
|
||||
&& self.noncurrent_version_expiration_noncurrent_days.is_none()
|
||||
&& self.noncurrent_version_transition_storage_class.is_none()
|
||||
&& self.transition_date.is_none()
|
||||
&& self.transition_days.is_none()
|
||||
&& self.transition_storage_class.is_none()
|
||||
{
|
||||
return Err(Error::MissingLifecycleAction);
|
||||
}
|
||||
|
||||
self.filter.validate()?;
|
||||
|
||||
if self.expiration_expired_object_delete_marker.is_some() {
|
||||
if self.expiration_date.is_some() || self.expiration_days.is_some() {
|
||||
return Err(Error::InvalidExpiredObjectDeleteMarker);
|
||||
}
|
||||
} else if self.expiration_date.is_some() && self.expiration_days.is_some() {
|
||||
return Err(Error::InvalidDateAndDays(String::from("expiration")));
|
||||
}
|
||||
|
||||
if self.transition_date.is_some() && self.transition_days.is_some() {
|
||||
return Err(Error::InvalidDateAndDays(String::from("transition")));
|
||||
}
|
||||
|
||||
if self.id.len() > 255 {
|
||||
return Err(Error::InvalidLifecycleRuleId);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct LifecycleConfig {
|
||||
pub rules: Vec<LifecycleRule>,
|
||||
}
|
||||
|
||||
impl LifecycleConfig {
|
||||
pub fn from_xml(root: &Element) -> Result<LifecycleConfig, Error> {
|
||||
let mut config = LifecycleConfig { rules: Vec::new() };
|
||||
|
||||
match root.get_child("Rule") {
|
||||
Some(v) => {
|
||||
for rule in &v.children {
|
||||
config.rules.push(LifecycleRule::from_xml(
|
||||
rule.as_element()
|
||||
.ok_or(Error::XmlError(format!("<Rule> tag not found")))?,
|
||||
)?);
|
||||
}
|
||||
}
|
||||
_ => todo!(),
|
||||
};
|
||||
|
||||
return Ok(config);
|
||||
}
|
||||
|
||||
pub fn validate(&self) -> Result<(), Error> {
|
||||
for rule in &self.rules {
|
||||
rule.validate()?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn to_xml(&self) -> String {
|
||||
let mut data = String::from("<LifecycleConfiguration>");
|
||||
|
||||
for rule in &self.rules {
|
||||
data.push_str("<Rule>");
|
||||
|
||||
if rule
|
||||
.abort_incomplete_multipart_upload_days_after_initiation
|
||||
.is_some()
|
||||
{
|
||||
data.push_str("<AbortIncompleteMultipartUpload><DaysAfterInitiation>");
|
||||
data.push_str(
|
||||
&rule
|
||||
.abort_incomplete_multipart_upload_days_after_initiation
|
||||
.unwrap()
|
||||
.to_string(),
|
||||
);
|
||||
data.push_str("</DaysAfterInitiation></AbortIncompleteMultipartUpload>");
|
||||
}
|
||||
|
||||
if rule.expiration_date.is_some()
|
||||
|| rule.expiration_days.is_some()
|
||||
|| rule.expiration_expired_object_delete_marker.is_some()
|
||||
{
|
||||
data.push_str("<Expiration>");
|
||||
if rule.expiration_date.is_some() {
|
||||
data.push_str("<Date>");
|
||||
data.push_str(&to_iso8601utc(rule.expiration_date.unwrap()));
|
||||
data.push_str("</Date>");
|
||||
}
|
||||
if rule.expiration_days.is_some() {
|
||||
data.push_str("<Days>");
|
||||
data.push_str(&rule.expiration_days.unwrap().to_string());
|
||||
data.push_str("</Days>");
|
||||
}
|
||||
if rule.expiration_expired_object_delete_marker.is_some() {
|
||||
data.push_str("<ExpiredObjectDeleteMarker>");
|
||||
data.push_str(
|
||||
&rule
|
||||
.expiration_expired_object_delete_marker
|
||||
.unwrap()
|
||||
.to_string(),
|
||||
);
|
||||
data.push_str("</ExpiredObjectDeleteMarker>");
|
||||
}
|
||||
data.push_str("</Expiration>");
|
||||
}
|
||||
|
||||
data.push_str("<Filter>");
|
||||
if rule.filter.and_operator.is_some() {
|
||||
data.push_str("<And>");
|
||||
if rule.filter.and_operator.as_ref().unwrap().prefix.is_some() {
|
||||
data.push_str("<Prefix>");
|
||||
data.push_str(
|
||||
&rule
|
||||
.filter
|
||||
.and_operator
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.prefix
|
||||
.as_ref()
|
||||
.unwrap(),
|
||||
);
|
||||
data.push_str("</Prefix>");
|
||||
}
|
||||
if rule.filter.and_operator.as_ref().unwrap().tags.is_some() {
|
||||
for (key, value) in rule
|
||||
.filter
|
||||
.and_operator
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.tags
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
{
|
||||
data.push_str("<Tag>");
|
||||
data.push_str("<Key>");
|
||||
data.push_str(&key);
|
||||
data.push_str("</Key>");
|
||||
data.push_str("<Value>");
|
||||
data.push_str(&value);
|
||||
data.push_str("</Value>");
|
||||
data.push_str("</Tag>");
|
||||
}
|
||||
}
|
||||
data.push_str("</And>");
|
||||
}
|
||||
if rule.filter.prefix.is_some() {
|
||||
data.push_str("<Prefix>");
|
||||
data.push_str(&rule.filter.prefix.as_ref().unwrap());
|
||||
data.push_str("</Prefix>");
|
||||
}
|
||||
if rule.filter.tag.is_some() {
|
||||
data.push_str("<Tag>");
|
||||
data.push_str("<Key>");
|
||||
data.push_str(&rule.filter.tag.as_ref().unwrap().key);
|
||||
data.push_str("</Key>");
|
||||
data.push_str("<Value>");
|
||||
data.push_str(&rule.filter.tag.as_ref().unwrap().value);
|
||||
data.push_str("</Value>");
|
||||
data.push_str("</Tag>");
|
||||
}
|
||||
data.push_str("</Filter>");
|
||||
|
||||
if !rule.id.is_empty() {
|
||||
data.push_str("<ID>");
|
||||
data.push_str(&rule.id);
|
||||
data.push_str("</ID>");
|
||||
}
|
||||
|
||||
if rule.noncurrent_version_expiration_noncurrent_days.is_some() {
|
||||
data.push_str("<NoncurrentVersionExpiration><NoncurrentDays>");
|
||||
data.push_str(
|
||||
&rule
|
||||
.noncurrent_version_expiration_noncurrent_days
|
||||
.unwrap()
|
||||
.to_string(),
|
||||
);
|
||||
data.push_str("</NoncurrentDays></NoncurrentVersionExpiration>");
|
||||
}
|
||||
|
||||
if rule.noncurrent_version_transition_noncurrent_days.is_some()
|
||||
|| rule.noncurrent_version_transition_storage_class.is_some()
|
||||
{
|
||||
data.push_str("<NoncurrentVersionTransition>");
|
||||
if rule.noncurrent_version_transition_noncurrent_days.is_some() {
|
||||
data.push_str("<NoncurrentDays>");
|
||||
data.push_str(
|
||||
&rule
|
||||
.noncurrent_version_expiration_noncurrent_days
|
||||
.unwrap()
|
||||
.to_string(),
|
||||
);
|
||||
data.push_str("</NoncurrentDays>");
|
||||
}
|
||||
if rule.noncurrent_version_transition_storage_class.is_some() {
|
||||
data.push_str("<StorageClass>");
|
||||
data.push_str(
|
||||
&rule
|
||||
.noncurrent_version_transition_storage_class
|
||||
.as_ref()
|
||||
.unwrap(),
|
||||
);
|
||||
data.push_str("</StorageClass>");
|
||||
}
|
||||
data.push_str("</NoncurrentVersionTransition>");
|
||||
}
|
||||
|
||||
data.push_str("<Status>");
|
||||
if rule.status {
|
||||
data.push_str("Enabled");
|
||||
} else {
|
||||
data.push_str("Disabled");
|
||||
}
|
||||
data.push_str("</Status>");
|
||||
|
||||
if rule.transition_date.is_some()
|
||||
|| rule.transition_days.is_some()
|
||||
|| rule.transition_storage_class.is_some()
|
||||
{
|
||||
data.push_str("<Transition>");
|
||||
if rule.transition_date.is_some() {
|
||||
data.push_str("<Date>");
|
||||
data.push_str(&to_iso8601utc(rule.transition_date.unwrap()));
|
||||
data.push_str("</Date>");
|
||||
}
|
||||
if rule.transition_days.is_some() {
|
||||
data.push_str("<Days>");
|
||||
data.push_str(&rule.transition_days.unwrap().to_string());
|
||||
data.push_str("</Days>");
|
||||
}
|
||||
if rule.transition_storage_class.is_some() {
|
||||
data.push_str("<StorageClass>");
|
||||
data.push_str(&rule.transition_storage_class.as_ref().unwrap());
|
||||
data.push_str("</StorageClass>");
|
||||
}
|
||||
data.push_str("</Transition>");
|
||||
}
|
||||
|
||||
data.push_str("</Rule>");
|
||||
}
|
||||
|
||||
data.push_str("</LifecycleConfiguration>");
|
||||
|
||||
return data;
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user