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:
Bala FA 2022-09-28 06:12:09 +05:30 committed by GitHub
parent 49452a0b73
commit 5fea81d68d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 991 additions and 19 deletions

View File

@ -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,
}

View File

@ -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,
&region,
&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,
&region,
&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,
&region,
&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,
&region,
&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,
&region,
&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,
&region,
&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,
&region,
&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,
&region,
&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,
&region,
&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);

View File

@ -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"),
}
}
}

View File

@ -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;

View File

@ -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;
}
}