mirror of
https://github.com/minio/minio-rs.git
synced 2025-12-06 15:26:51 +08:00
Fixed xml parsing of bucket_lifecycle, added support for with_updated_at (#153)
This commit is contained in:
parent
9495c5dcce
commit
f6ca7dba03
@ -15,11 +15,11 @@
|
||||
|
||||
use chrono::{DateTime, Utc};
|
||||
use minio::s3::builders::PostPolicy;
|
||||
use minio::s3::lifecycle_config::{LifecycleConfig, LifecycleRule};
|
||||
use minio::s3::types::{
|
||||
AndOperator, CsvInputSerialization, CsvOutputSerialization, Destination, FileHeaderInfo,
|
||||
Filter, LifecycleConfig, LifecycleRule, NotificationConfig, ObjectLockConfig, PrefixFilterRule,
|
||||
QueueConfig, QuoteFields, ReplicationConfig, ReplicationRule, RetentionMode, SelectRequest,
|
||||
SuffixFilterRule,
|
||||
Filter, NotificationConfig, ObjectLockConfig, PrefixFilterRule, QueueConfig, QuoteFields,
|
||||
ReplicationConfig, ReplicationRule, RetentionMode, SelectRequest, SuffixFilterRule,
|
||||
};
|
||||
use minio::s3::utils::utc_now;
|
||||
use std::collections::HashMap;
|
||||
@ -27,29 +27,19 @@ use std::collections::HashMap;
|
||||
pub fn create_bucket_lifecycle_config_examples() -> LifecycleConfig {
|
||||
LifecycleConfig {
|
||||
rules: vec![LifecycleRule {
|
||||
abort_incomplete_multipart_upload_days_after_initiation: None,
|
||||
expiration_date: None,
|
||||
expiration_days: Some(365),
|
||||
expiration_expired_object_delete_marker: None,
|
||||
filter: Filter {
|
||||
and_operator: None,
|
||||
prefix: Some(String::from("logs/")),
|
||||
tag: None,
|
||||
},
|
||||
id: String::from("rule1"),
|
||||
noncurrent_version_expiration_noncurrent_days: None,
|
||||
noncurrent_version_transition_noncurrent_days: None,
|
||||
noncurrent_version_transition_storage_class: None,
|
||||
expiration_days: Some(365),
|
||||
filter: Filter {
|
||||
prefix: Some(String::from("logs/")),
|
||||
..Default::default()
|
||||
},
|
||||
status: true,
|
||||
transition_date: None,
|
||||
transition_days: None,
|
||||
transition_storage_class: None,
|
||||
..Default::default()
|
||||
}],
|
||||
}
|
||||
}
|
||||
pub fn create_bucket_notification_config_example() -> NotificationConfig {
|
||||
NotificationConfig {
|
||||
cloud_func_config_list: None,
|
||||
queue_config_list: Some(vec![QueueConfig {
|
||||
events: vec![
|
||||
String::from("s3:ObjectCreated:Put"),
|
||||
@ -64,7 +54,7 @@ pub fn create_bucket_notification_config_example() -> NotificationConfig {
|
||||
}),
|
||||
queue: String::from("arn:minio:sqs::miniojavatest:webhook"),
|
||||
}]),
|
||||
topic_config_list: None,
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
pub fn create_bucket_policy_config_example(bucket_name: &str) -> String {
|
||||
@ -148,31 +138,22 @@ pub fn create_bucket_replication_config_example(dst_bucket: &str) -> Replication
|
||||
ReplicationConfig {
|
||||
role: Some("example1".to_string()),
|
||||
rules: vec![ReplicationRule {
|
||||
id: Some(String::from("rule1")),
|
||||
destination: Destination {
|
||||
bucket_arn: String::from(&format!("arn:aws:s3:::{}", dst_bucket)),
|
||||
access_control_translation: None,
|
||||
account: None,
|
||||
encryption_config: None,
|
||||
metrics: None,
|
||||
replication_time: None,
|
||||
storage_class: None,
|
||||
..Default::default()
|
||||
},
|
||||
delete_marker_replication_status: None,
|
||||
existing_object_replication_status: None,
|
||||
filter: Some(Filter {
|
||||
and_operator: Some(AndOperator {
|
||||
prefix: Some(String::from("TaxDocs")),
|
||||
tags: Some(tags),
|
||||
}),
|
||||
prefix: None,
|
||||
tag: None,
|
||||
..Default::default()
|
||||
}),
|
||||
id: Some(String::from("rule1")),
|
||||
prefix: None,
|
||||
priority: Some(1),
|
||||
source_selection_criteria: None,
|
||||
delete_replication_status: Some(false),
|
||||
status: true,
|
||||
..Default::default()
|
||||
}],
|
||||
}
|
||||
}
|
||||
|
||||
@ -17,10 +17,11 @@ mod common;
|
||||
|
||||
use crate::common::{create_bucket_if_not_exists, create_client_on_play};
|
||||
use minio::s3::Client;
|
||||
use minio::s3::lifecycle_config::{LifecycleConfig, LifecycleRule};
|
||||
use minio::s3::response::{
|
||||
DeleteBucketLifecycleResponse, GetBucketLifecycleResponse, PutBucketLifecycleResponse,
|
||||
};
|
||||
use minio::s3::types::{Filter, LifecycleConfig, LifecycleRule, S3Api};
|
||||
use minio::s3::types::{Filter, S3Api};
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
|
||||
@ -38,23 +39,14 @@ async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
|
||||
}
|
||||
|
||||
let rules: Vec<LifecycleRule> = vec![LifecycleRule {
|
||||
abort_incomplete_multipart_upload_days_after_initiation: None,
|
||||
expiration_date: None,
|
||||
expiration_days: Some(365),
|
||||
expiration_expired_object_delete_marker: None,
|
||||
filter: Filter {
|
||||
and_operator: None,
|
||||
prefix: Some(String::from("logs/")),
|
||||
tag: None,
|
||||
},
|
||||
id: String::from("rule1"),
|
||||
noncurrent_version_expiration_noncurrent_days: None,
|
||||
noncurrent_version_transition_noncurrent_days: None,
|
||||
noncurrent_version_transition_storage_class: None,
|
||||
expiration_days: Some(365),
|
||||
filter: Filter {
|
||||
prefix: Some(String::from("logs/")),
|
||||
..Default::default()
|
||||
},
|
||||
status: true,
|
||||
transition_date: None,
|
||||
transition_days: None,
|
||||
transition_storage_class: None,
|
||||
..Default::default()
|
||||
}];
|
||||
|
||||
let resp: PutBucketLifecycleResponse = client
|
||||
|
||||
@ -13,8 +13,9 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
use crate::s3::builders::BucketCommon;
|
||||
use crate::s3::Client;
|
||||
use crate::s3::error::Error;
|
||||
use crate::s3::multimap::{Multimap, MultimapExt};
|
||||
use crate::s3::response::GetBucketLifecycleResponse;
|
||||
use crate::s3::types::{S3Api, S3Request, ToS3Request};
|
||||
use crate::s3::utils::{check_bucket_name, insert};
|
||||
@ -23,10 +24,42 @@ use http::Method;
|
||||
/// Argument builder for the [`GetBucketLifecycle`](https://docs.aws.amazon.com/AmazonS3/latest/API/API_GetBucketLifecycle.html) S3 API operation.
|
||||
///
|
||||
/// This struct constructs the parameters required for the [`Client::get_bucket_lifecycle`](crate::s3::client::Client::get_bucket_lifecycle) method.
|
||||
pub type GetBucketLifecycle = BucketCommon<GetBucketLifecyclePhantomData>;
|
||||
|
||||
#[derive(Clone, Debug, Default)]
|
||||
pub struct GetBucketLifecyclePhantomData;
|
||||
pub struct GetBucketLifecycle {
|
||||
client: Client,
|
||||
|
||||
extra_headers: Option<Multimap>,
|
||||
extra_query_params: Option<Multimap>,
|
||||
region: Option<String>,
|
||||
bucket: String,
|
||||
|
||||
with_updated_at: bool,
|
||||
}
|
||||
|
||||
impl GetBucketLifecycle {
|
||||
pub fn new(client: Client, bucket: String) -> Self {
|
||||
Self {
|
||||
client,
|
||||
bucket,
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn extra_headers(mut self, extra_headers: Option<Multimap>) -> Self {
|
||||
self.extra_headers = extra_headers;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn extra_query_params(mut self, extra_query_params: Option<Multimap>) -> Self {
|
||||
self.extra_query_params = extra_query_params;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_updated_at(mut self, with_updated_at: bool) -> Self {
|
||||
self.with_updated_at = with_updated_at;
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl S3Api for GetBucketLifecycle {
|
||||
type S3Response = GetBucketLifecycleResponse;
|
||||
@ -36,10 +69,15 @@ impl ToS3Request for GetBucketLifecycle {
|
||||
fn to_s3request(self) -> Result<S3Request, Error> {
|
||||
check_bucket_name(&self.bucket, true)?;
|
||||
|
||||
let mut query_params: Multimap = insert(self.extra_query_params, "lifecycle");
|
||||
if self.with_updated_at {
|
||||
query_params.add("withUpdatedAt", "true");
|
||||
}
|
||||
|
||||
Ok(S3Request::new(self.client, Method::GET)
|
||||
.region(self.region)
|
||||
.bucket(Some(self.bucket))
|
||||
.query_params(insert(self.extra_query_params, "lifecycle"))
|
||||
.query_params(query_params)
|
||||
.headers(self.extra_headers.unwrap_or_default()))
|
||||
}
|
||||
}
|
||||
|
||||
@ -15,15 +15,18 @@
|
||||
|
||||
use crate::s3::Client;
|
||||
use crate::s3::error::Error;
|
||||
use crate::s3::lifecycle_config::LifecycleConfig;
|
||||
use crate::s3::multimap::{Multimap, MultimapExt};
|
||||
use crate::s3::response::PutBucketLifecycleResponse;
|
||||
use crate::s3::segmented_bytes::SegmentedBytes;
|
||||
use crate::s3::types::{LifecycleConfig, S3Api, S3Request, ToS3Request};
|
||||
use crate::s3::types::{S3Api, S3Request, ToS3Request};
|
||||
use crate::s3::utils::{check_bucket_name, insert, md5sum_hash};
|
||||
use bytes::Bytes;
|
||||
use http::Method;
|
||||
|
||||
/// Argument builder for [put_bucket_lifecycle()](crate::s3::client::Client::put_bucket_lifecycle) API
|
||||
/// Argument builder for the [`PutBucketLifecycle`](https://docs.aws.amazon.com/AmazonS3/latest/API/API_PutBucketLifecycle.html) S3 API operation.
|
||||
///
|
||||
/// This struct constructs the parameters required for the [`Client::put_bucket_lifecycle`](crate::s3::client::Client::put_bucket_lifecycle) method.
|
||||
#[derive(Clone, Debug, Default)]
|
||||
pub struct PutBucketLifecycle {
|
||||
client: Client,
|
||||
|
||||
@ -31,26 +31,19 @@ impl Client {
|
||||
/// use minio::s3::Client;
|
||||
/// use minio::s3::builders::VersioningStatus;
|
||||
/// use minio::s3::response::PutBucketLifecycleResponse;
|
||||
/// use minio::s3::types::{Filter, LifecycleConfig, LifecycleRule, S3Api};
|
||||
/// use minio::s3::types::{Filter, S3Api};
|
||||
/// use minio::s3::lifecycle_config::{LifecycleRule, LifecycleConfig};
|
||||
///
|
||||
/// #[tokio::main]
|
||||
/// async fn main() {
|
||||
/// let client: Client = Default::default(); // configure your client here
|
||||
/// let client: Client = Default::default(); // configure your client here
|
||||
///
|
||||
/// let rules: Vec<LifecycleRule> = vec![LifecycleRule {
|
||||
/// abort_incomplete_multipart_upload_days_after_initiation: None,
|
||||
/// expiration_date: None,
|
||||
/// expiration_days: Some(365),
|
||||
/// expiration_expired_object_delete_marker: None,
|
||||
/// filter: Filter {and_operator: None, prefix: Some(String::from("logs/")), tag: None},
|
||||
/// id: String::from("rule1"),
|
||||
/// noncurrent_version_expiration_noncurrent_days: None,
|
||||
/// noncurrent_version_transition_noncurrent_days: None,
|
||||
/// noncurrent_version_transition_storage_class: None,
|
||||
/// filter: Filter {and_operator: None, prefix: Some(String::from("logs/")), tag: None},
|
||||
/// expiration_days: Some(365),
|
||||
/// status: true,
|
||||
/// transition_date: None,
|
||||
/// transition_days: None,
|
||||
/// transition_storage_class: None,
|
||||
/// ..Default::default()
|
||||
/// }];
|
||||
///
|
||||
/// let resp: PutBucketLifecycleResponse = client
|
||||
|
||||
@ -37,6 +37,7 @@ pub enum ErrorCode {
|
||||
ServerSideEncryptionConfigurationNotFoundError,
|
||||
NoSuchTagSet,
|
||||
NoSuchObjectLockConfiguration,
|
||||
NoSuchLifecycleConfiguration,
|
||||
NoSuchKey,
|
||||
ResourceNotFound,
|
||||
MethodNotAllowed,
|
||||
@ -66,6 +67,7 @@ impl ErrorCode {
|
||||
}
|
||||
"nosuchtagset" => ErrorCode::NoSuchTagSet,
|
||||
"nosuchobjectlockconfiguration" => ErrorCode::NoSuchObjectLockConfiguration,
|
||||
"nosuchlifecycleconfiguration" => ErrorCode::NoSuchLifecycleConfiguration,
|
||||
"nosuchkey" => ErrorCode::NoSuchKey,
|
||||
"resourcenotfound" => ErrorCode::ResourceNotFound,
|
||||
"methodnotallowed" => ErrorCode::MethodNotAllowed,
|
||||
|
||||
484
src/s3/lifecycle_config.rs
Normal file
484
src/s3/lifecycle_config.rs
Normal file
@ -0,0 +1,484 @@
|
||||
use crate::s3::error::Error;
|
||||
use crate::s3::types::Filter;
|
||||
use crate::s3::utils::to_iso8601utc;
|
||||
use xmltree::Element;
|
||||
|
||||
#[derive(PartialEq, Clone, Debug, Default)]
|
||||
/// Lifecycle configuration
|
||||
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() };
|
||||
|
||||
// Process all Rule elements in the XML
|
||||
for rule_elem in root.children.iter().filter_map(|c| c.as_element()) {
|
||||
if rule_elem.name == "Rule" {
|
||||
config.rules.push(LifecycleRule::from_xml(rule_elem)?);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(config)
|
||||
}
|
||||
|
||||
pub fn validate(&self) -> Result<(), Error> {
|
||||
// Skip validation if empty
|
||||
if self.rules.is_empty() {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
for rule in &self.rules {
|
||||
rule.validate()?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn empty(&self) -> bool {
|
||||
self.rules.is_empty()
|
||||
}
|
||||
|
||||
pub fn to_xml(&self) -> String {
|
||||
let mut data = String::from("<LifecycleConfiguration>");
|
||||
|
||||
for rule in &self.rules {
|
||||
data.push_str("<Rule>");
|
||||
|
||||
// ID should come earlier in XML based on Go ordering
|
||||
if !rule.id.is_empty() {
|
||||
data.push_str("<ID>");
|
||||
data.push_str(&rule.id);
|
||||
data.push_str("</ID>");
|
||||
}
|
||||
|
||||
// Status comes next
|
||||
data.push_str("<Status>");
|
||||
if rule.status {
|
||||
data.push_str("Enabled");
|
||||
} else {
|
||||
data.push_str("Disabled");
|
||||
}
|
||||
data.push_str("</Status>");
|
||||
|
||||
// Filter
|
||||
data.push_str(&rule.filter.to_xml());
|
||||
|
||||
// AbortIncompleteMultipartUpload
|
||||
if let Some(days) = rule.abort_incomplete_multipart_upload_days_after_initiation {
|
||||
data.push_str("<AbortIncompleteMultipartUpload><DaysAfterInitiation>");
|
||||
data.push_str(&days.to_string());
|
||||
data.push_str("</DaysAfterInitiation></AbortIncompleteMultipartUpload>");
|
||||
}
|
||||
|
||||
// Expiration
|
||||
let has_expiration = rule.expiration_date.is_some()
|
||||
|| rule.expiration_days.is_some()
|
||||
|| rule.expiration_expired_object_delete_marker.is_some()
|
||||
|| rule.expiration_expired_object_all_versions.is_some();
|
||||
|
||||
if has_expiration {
|
||||
data.push_str("<Expiration>");
|
||||
if let Some(date) = rule.expiration_date {
|
||||
data.push_str("<Date>");
|
||||
data.push_str(&to_iso8601utc(date));
|
||||
data.push_str("</Date>");
|
||||
}
|
||||
if let Some(days) = rule.expiration_days {
|
||||
data.push_str("<Days>");
|
||||
data.push_str(&days.to_string());
|
||||
data.push_str("</Days>");
|
||||
}
|
||||
if let Some(delete_marker) = rule.expiration_expired_object_delete_marker {
|
||||
if delete_marker {
|
||||
data.push_str(
|
||||
"<ExpiredObjectDeleteMarker>true</ExpiredObjectDeleteMarker>",
|
||||
);
|
||||
}
|
||||
}
|
||||
if let Some(delete_all) = rule.expiration_expired_object_all_versions {
|
||||
if delete_all {
|
||||
data.push_str("<ExpiredObjectAllVersions>true</ExpiredObjectAllVersions>");
|
||||
}
|
||||
}
|
||||
data.push_str("</Expiration>");
|
||||
}
|
||||
|
||||
// DelMarkerExpiration
|
||||
if let Some(days) = rule.del_marker_expiration_days {
|
||||
data.push_str("<DelMarkerExpiration><Days>");
|
||||
data.push_str(&days.to_string());
|
||||
data.push_str("</Days></DelMarkerExpiration>");
|
||||
}
|
||||
|
||||
// AllVersionsExpiration
|
||||
if let Some(days) = rule.all_versions_expiration_days {
|
||||
data.push_str("<AllVersionsExpiration><Days>");
|
||||
data.push_str(&days.to_string());
|
||||
data.push_str("</Days>");
|
||||
|
||||
if let Some(delete_marker) = rule.all_versions_expiration_delete_marker {
|
||||
if delete_marker {
|
||||
data.push_str("<DeleteMarker>true</DeleteMarker>");
|
||||
}
|
||||
}
|
||||
|
||||
data.push_str("</AllVersionsExpiration>");
|
||||
}
|
||||
|
||||
// NoncurrentVersionExpiration
|
||||
if let Some(days) = rule.noncurrent_version_expiration_noncurrent_days {
|
||||
data.push_str("<NoncurrentVersionExpiration><NoncurrentDays>");
|
||||
data.push_str(&days.to_string());
|
||||
data.push_str("</NoncurrentDays>");
|
||||
|
||||
if let Some(versions) = rule.noncurrent_version_expiration_newer_versions {
|
||||
data.push_str("<NewerNoncurrentVersions>");
|
||||
data.push_str(&versions.to_string());
|
||||
data.push_str("</NewerNoncurrentVersions>");
|
||||
}
|
||||
|
||||
data.push_str("</NoncurrentVersionExpiration>");
|
||||
}
|
||||
|
||||
// NoncurrentVersionTransition
|
||||
let has_noncurrent_transition =
|
||||
rule.noncurrent_version_transition_noncurrent_days.is_some()
|
||||
|| rule.noncurrent_version_transition_storage_class.is_some()
|
||||
|| rule.noncurrent_version_transition_newer_versions.is_some();
|
||||
|
||||
if has_noncurrent_transition {
|
||||
data.push_str("<NoncurrentVersionTransition>");
|
||||
|
||||
if let Some(days) = rule.noncurrent_version_transition_noncurrent_days {
|
||||
data.push_str("<NoncurrentDays>");
|
||||
data.push_str(&days.to_string());
|
||||
data.push_str("</NoncurrentDays>");
|
||||
}
|
||||
|
||||
if let Some(storage_class) = &rule.noncurrent_version_transition_storage_class {
|
||||
data.push_str("<StorageClass>");
|
||||
data.push_str(storage_class);
|
||||
data.push_str("</StorageClass>");
|
||||
}
|
||||
|
||||
if let Some(versions) = rule.noncurrent_version_transition_newer_versions {
|
||||
data.push_str("<NewerNoncurrentVersions>");
|
||||
data.push_str(&versions.to_string());
|
||||
data.push_str("</NewerNoncurrentVersions>");
|
||||
}
|
||||
|
||||
data.push_str("</NoncurrentVersionTransition>");
|
||||
}
|
||||
|
||||
// Transition
|
||||
let has_transition = rule.transition_date.is_some()
|
||||
|| rule.transition_days.is_some()
|
||||
|| rule.transition_storage_class.is_some();
|
||||
|
||||
if has_transition {
|
||||
data.push_str("<Transition>");
|
||||
|
||||
if let Some(date) = rule.transition_date {
|
||||
data.push_str("<Date>");
|
||||
data.push_str(&to_iso8601utc(date));
|
||||
data.push_str("</Date>");
|
||||
}
|
||||
|
||||
if let Some(days) = rule.transition_days {
|
||||
data.push_str("<Days>");
|
||||
data.push_str(&days.to_string());
|
||||
data.push_str("</Days>");
|
||||
}
|
||||
|
||||
if let Some(storage_class) = &rule.transition_storage_class {
|
||||
data.push_str("<StorageClass>");
|
||||
data.push_str(storage_class);
|
||||
data.push_str("</StorageClass>");
|
||||
}
|
||||
|
||||
data.push_str("</Transition>");
|
||||
}
|
||||
|
||||
data.push_str("</Rule>");
|
||||
}
|
||||
|
||||
data.push_str("</LifecycleConfiguration>");
|
||||
data
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Clone, Debug, Default)]
|
||||
pub struct LifecycleRule {
|
||||
// Common
|
||||
pub id: String,
|
||||
pub status: bool,
|
||||
pub filter: Filter,
|
||||
|
||||
// Expiration
|
||||
pub expiration_days: Option<u32>,
|
||||
pub expiration_date: Option<chrono::DateTime<chrono::Utc>>,
|
||||
pub expiration_expired_object_delete_marker: Option<bool>,
|
||||
pub expiration_expired_object_all_versions: Option<bool>,
|
||||
|
||||
// DelMarkerExpiration
|
||||
pub del_marker_expiration_days: Option<u32>,
|
||||
|
||||
// AllVersionsExpiration
|
||||
pub all_versions_expiration_days: Option<u32>,
|
||||
pub all_versions_expiration_delete_marker: Option<bool>,
|
||||
|
||||
// Transition
|
||||
pub transition_days: Option<u32>,
|
||||
pub transition_date: Option<chrono::DateTime<chrono::Utc>>,
|
||||
pub transition_storage_class: Option<String>,
|
||||
|
||||
// NoncurrentVersionExpiration
|
||||
pub noncurrent_version_expiration_noncurrent_days: Option<u32>,
|
||||
pub noncurrent_version_expiration_newer_versions: Option<u32>,
|
||||
|
||||
// NoncurrentVersionTransition
|
||||
pub noncurrent_version_transition_noncurrent_days: Option<u32>,
|
||||
pub noncurrent_version_transition_storage_class: Option<String>,
|
||||
pub noncurrent_version_transition_newer_versions: Option<u32>,
|
||||
|
||||
// AbortIncompleteMultipartUpload
|
||||
pub abort_incomplete_multipart_upload_days_after_initiation: Option<u32>,
|
||||
}
|
||||
|
||||
impl LifecycleRule {
|
||||
pub fn from_xml(rule_elem: &Element) -> Result<Self, Error> {
|
||||
let mut rule = LifecycleRule::default();
|
||||
|
||||
// Parse ID
|
||||
if let Some(id_elem) = rule_elem.get_child("ID") {
|
||||
if let Some(id_text) = id_elem.get_text() {
|
||||
rule.id = id_text.to_string();
|
||||
}
|
||||
}
|
||||
|
||||
// Parse Status
|
||||
if let Some(status_elem) = rule_elem.get_child("Status") {
|
||||
if let Some(status_text) = status_elem.get_text() {
|
||||
rule.status = status_text == "Enabled";
|
||||
}
|
||||
} else {
|
||||
return Err(Error::XmlError("Missing <Status> element".to_string()));
|
||||
}
|
||||
|
||||
// Parse Filter
|
||||
if let Some(filter_elem) = rule_elem.get_child("Filter") {
|
||||
rule.filter = Filter::from_xml(filter_elem)?;
|
||||
}
|
||||
|
||||
// Parse AbortIncompleteMultipartUpload
|
||||
if let Some(abort_elem) = rule_elem.get_child("AbortIncompleteMultipartUpload") {
|
||||
if let Some(days_elem) = abort_elem.get_child("DaysAfterInitiation") {
|
||||
if let Some(days_text) = days_elem.get_text() {
|
||||
rule.abort_incomplete_multipart_upload_days_after_initiation =
|
||||
Some(days_text.parse().map_err(|_| {
|
||||
Error::XmlError("Invalid DaysAfterInitiation value".to_string())
|
||||
})?);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Parse Expiration
|
||||
if let Some(expiration_elem) = rule_elem.get_child("Expiration") {
|
||||
// Date
|
||||
if let Some(date_elem) = expiration_elem.get_child("Date") {
|
||||
if let Some(date_text) = date_elem.get_text() {
|
||||
// Assume a function that parses ISO8601 to DateTime<Utc>
|
||||
rule.expiration_date = Some(parse_iso8601(&date_text)?);
|
||||
}
|
||||
}
|
||||
|
||||
// Days
|
||||
if let Some(days_elem) = expiration_elem.get_child("Days") {
|
||||
if let Some(days_text) = days_elem.get_text() {
|
||||
rule.expiration_days = Some(days_text.parse().map_err(|_| {
|
||||
Error::XmlError("Invalid Expiration Days value".to_string())
|
||||
})?);
|
||||
}
|
||||
}
|
||||
|
||||
// ExpiredObjectDeleteMarker
|
||||
if let Some(delete_marker_elem) = expiration_elem.get_child("ExpiredObjectDeleteMarker")
|
||||
{
|
||||
if let Some(delete_marker_text) = delete_marker_elem.get_text() {
|
||||
rule.expiration_expired_object_delete_marker =
|
||||
Some(delete_marker_text == "true");
|
||||
}
|
||||
}
|
||||
|
||||
// ExpiredObjectAllVersions
|
||||
if let Some(all_versions_elem) = expiration_elem.get_child("ExpiredObjectAllVersions") {
|
||||
if let Some(all_versions_text) = all_versions_elem.get_text() {
|
||||
rule.expiration_expired_object_all_versions = Some(all_versions_text == "true");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Parse DelMarkerExpiration
|
||||
if let Some(del_marker_elem) = rule_elem.get_child("DelMarkerExpiration") {
|
||||
if let Some(days_elem) = del_marker_elem.get_child("Days") {
|
||||
if let Some(days_text) = days_elem.get_text() {
|
||||
rule.del_marker_expiration_days = Some(days_text.parse().map_err(|_| {
|
||||
Error::XmlError("Invalid DelMarkerExpiration Days value".to_string())
|
||||
})?);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Parse AllVersionsExpiration
|
||||
if let Some(all_versions_elem) = rule_elem.get_child("AllVersionsExpiration") {
|
||||
if let Some(days_elem) = all_versions_elem.get_child("Days") {
|
||||
if let Some(days_text) = days_elem.get_text() {
|
||||
rule.all_versions_expiration_days = Some(days_text.parse().map_err(|_| {
|
||||
Error::XmlError("Invalid AllVersionsExpiration Days value".to_string())
|
||||
})?);
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(delete_marker_elem) = all_versions_elem.get_child("DeleteMarker") {
|
||||
if let Some(delete_marker_text) = delete_marker_elem.get_text() {
|
||||
rule.all_versions_expiration_delete_marker = Some(delete_marker_text == "true");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Parse NoncurrentVersionExpiration
|
||||
if let Some(noncurrent_exp_elem) = rule_elem.get_child("NoncurrentVersionExpiration") {
|
||||
if let Some(days_elem) = noncurrent_exp_elem.get_child("NoncurrentDays") {
|
||||
if let Some(days_text) = days_elem.get_text() {
|
||||
rule.noncurrent_version_expiration_noncurrent_days =
|
||||
Some(days_text.parse().map_err(|_| {
|
||||
Error::XmlError(
|
||||
"Invalid NoncurrentVersionExpiration NoncurrentDays value"
|
||||
.to_string(),
|
||||
)
|
||||
})?);
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(versions_elem) = noncurrent_exp_elem.get_child("NewerNoncurrentVersions") {
|
||||
if let Some(versions_text) = versions_elem.get_text() {
|
||||
rule.noncurrent_version_expiration_newer_versions =
|
||||
Some(versions_text.parse().map_err(|_| {
|
||||
Error::XmlError("Invalid NewerNoncurrentVersions value".to_string())
|
||||
})?);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Parse NoncurrentVersionTransition
|
||||
if let Some(noncurrent_trans_elem) = rule_elem.get_child("NoncurrentVersionTransition") {
|
||||
if let Some(days_elem) = noncurrent_trans_elem.get_child("NoncurrentDays") {
|
||||
if let Some(days_text) = days_elem.get_text() {
|
||||
rule.noncurrent_version_transition_noncurrent_days =
|
||||
Some(days_text.parse().map_err(|_| {
|
||||
Error::XmlError(
|
||||
"Invalid NoncurrentVersionTransition NoncurrentDays value"
|
||||
.to_string(),
|
||||
)
|
||||
})?);
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(storage_elem) = noncurrent_trans_elem.get_child("StorageClass") {
|
||||
if let Some(storage_text) = storage_elem.get_text() {
|
||||
rule.noncurrent_version_transition_storage_class =
|
||||
Some(storage_text.to_string());
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(versions_elem) = noncurrent_trans_elem.get_child("NewerNoncurrentVersions")
|
||||
{
|
||||
if let Some(versions_text) = versions_elem.get_text() {
|
||||
rule.noncurrent_version_transition_newer_versions =
|
||||
Some(versions_text.parse().map_err(|_| {
|
||||
Error::XmlError("Invalid NewerNoncurrentVersions value".to_string())
|
||||
})?);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Parse Transition
|
||||
if let Some(transition_elem) = rule_elem.get_child("Transition") {
|
||||
// Date
|
||||
if let Some(date_elem) = transition_elem.get_child("Date") {
|
||||
if let Some(date_text) = date_elem.get_text() {
|
||||
rule.transition_date = Some(parse_iso8601(&date_text)?);
|
||||
}
|
||||
}
|
||||
|
||||
// Days
|
||||
if let Some(days_elem) = transition_elem.get_child("Days") {
|
||||
if let Some(days_text) = days_elem.get_text() {
|
||||
rule.transition_days = Some(days_text.parse().map_err(|_| {
|
||||
Error::XmlError("Invalid Transition Days value".to_string())
|
||||
})?);
|
||||
}
|
||||
}
|
||||
|
||||
// StorageClass
|
||||
if let Some(storage_elem) = transition_elem.get_child("StorageClass") {
|
||||
if let Some(storage_text) = storage_elem.get_text() {
|
||||
rule.transition_storage_class = Some(storage_text.to_string());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(rule)
|
||||
}
|
||||
|
||||
pub fn validate(&self) -> Result<(), Error> {
|
||||
// Basic validation requirements
|
||||
|
||||
// Ensure ID is present
|
||||
if self.id.is_empty() {
|
||||
return Err(Error::XmlError("Rule ID cannot be empty".to_string()));
|
||||
}
|
||||
|
||||
// Validate storage classes in transitions
|
||||
if let Some(storage_class) = &self.transition_storage_class {
|
||||
if storage_class.is_empty() {
|
||||
return Err(Error::XmlError(
|
||||
"Transition StorageClass cannot be empty".to_string(),
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(storage_class) = &self.noncurrent_version_transition_storage_class {
|
||||
if storage_class.is_empty() {
|
||||
return Err(Error::XmlError(
|
||||
"NoncurrentVersionTransition StorageClass cannot be empty".to_string(),
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
// Check that expiration has either days or date, not both
|
||||
if self.expiration_days.is_some() && self.expiration_date.is_some() {
|
||||
return Err(Error::XmlError(
|
||||
"Expiration cannot specify both Days and Date".to_string(),
|
||||
));
|
||||
}
|
||||
|
||||
// Check that transition has either days or date, not both
|
||||
if self.transition_days.is_some() && self.transition_date.is_some() {
|
||||
return Err(Error::XmlError(
|
||||
"Transition cannot specify both Days and Date".to_string(),
|
||||
));
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
// Helper function to parse ISO8601 dates
|
||||
fn parse_iso8601(date_str: &str) -> Result<chrono::DateTime<chrono::Utc>, Error> {
|
||||
chrono::DateTime::parse_from_rfc3339(date_str)
|
||||
.map(|dt| dt.with_timezone(&chrono::Utc))
|
||||
.map_err(|_| Error::XmlError(format!("Invalid date format: {}", date_str)))
|
||||
}
|
||||
@ -20,6 +20,7 @@ pub mod client;
|
||||
pub mod creds;
|
||||
pub mod error;
|
||||
pub mod http;
|
||||
pub mod lifecycle_config;
|
||||
pub mod multimap;
|
||||
mod object_content;
|
||||
pub mod response;
|
||||
|
||||
@ -14,10 +14,12 @@
|
||||
// limitations under the License.
|
||||
|
||||
use crate::s3::error::Error;
|
||||
use crate::s3::types::{FromS3Response, LifecycleConfig, S3Request};
|
||||
use crate::s3::utils::take_bucket;
|
||||
use crate::s3::lifecycle_config::LifecycleConfig;
|
||||
use crate::s3::types::{FromS3Response, S3Request};
|
||||
use crate::s3::utils::{UtcTime, take_bucket};
|
||||
use async_trait::async_trait;
|
||||
use bytes::Buf;
|
||||
use chrono::{DateTime, NaiveDateTime, Utc};
|
||||
use http::HeaderMap;
|
||||
use std::mem;
|
||||
use xmltree::Element;
|
||||
@ -47,6 +49,10 @@ pub struct GetBucketLifecycleResponse {
|
||||
///
|
||||
/// If the bucket has no lifecycle configuration, this field may contain an empty configuration.
|
||||
pub config: LifecycleConfig,
|
||||
|
||||
/// Optional value of `X-Minio-LifecycleConfig-UpdatedAt` header, indicating the last update
|
||||
/// time of the lifecycle configuration.
|
||||
pub updated_at: Option<UtcTime>,
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
@ -62,11 +68,21 @@ impl FromS3Response for GetBucketLifecycleResponse {
|
||||
let mut root = Element::parse(body.reader())?;
|
||||
LifecycleConfig::from_xml(&mut root)?
|
||||
};
|
||||
let updated_at: Option<DateTime<Utc>> = headers
|
||||
.get("x-minio-lifecycleconfig-updatedat")
|
||||
.and_then(|v| v.to_str().ok())
|
||||
.and_then(|v| {
|
||||
NaiveDateTime::parse_from_str(v, "%Y%m%dT%H%M%SZ")
|
||||
.ok()
|
||||
.map(|naive| DateTime::from_naive_utc_and_offset(naive, Utc))
|
||||
});
|
||||
|
||||
Ok(Self {
|
||||
headers,
|
||||
region: req.inner_region,
|
||||
bucket: take_bucket(req.bucket)?,
|
||||
config,
|
||||
updated_at,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
313
src/s3/types.rs
313
src/s3/types.rs
@ -17,9 +17,7 @@
|
||||
|
||||
use super::client::{Client, DEFAULT_REGION};
|
||||
use crate::s3::error::Error;
|
||||
use crate::s3::utils::{
|
||||
UtcTime, from_iso8601utc, get_default_text, get_option_text, get_text, to_iso8601utc,
|
||||
};
|
||||
use crate::s3::utils::{UtcTime, get_option_text, get_text};
|
||||
|
||||
use crate::s3::multimap::Multimap;
|
||||
use crate::s3::segmented_bytes::SegmentedBytes;
|
||||
@ -934,7 +932,7 @@ pub struct AndOperator {
|
||||
pub tags: Option<HashMap<String, String>>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
#[derive(Clone, Debug, PartialEq, Default)]
|
||||
/// Filter information
|
||||
pub struct Filter {
|
||||
pub and_operator: Option<AndOperator>,
|
||||
@ -1050,309 +1048,6 @@ impl Filter {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Clone, Debug)]
|
||||
/// Lifecycle rule information
|
||||
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::from_xml(
|
||||
element
|
||||
.get_child("Filter")
|
||||
.ok_or(Error::XmlError("<Filter> tag not found".to_string()))?,
|
||||
)?,
|
||||
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(PartialEq, Clone, Debug, Default)]
|
||||
/// Lifecycle configuration
|
||||
pub struct LifecycleConfig {
|
||||
pub rules: Vec<LifecycleRule>,
|
||||
}
|
||||
|
||||
impl LifecycleConfig {
|
||||
pub fn from_xml(root: &mut Element) -> Result<LifecycleConfig, Error> {
|
||||
let mut config = LifecycleConfig { rules: Vec::new() };
|
||||
|
||||
// TODO consider consuming root
|
||||
if let Some(v) = root.get_child("Rule") {
|
||||
for rule in &v.children {
|
||||
config
|
||||
.rules
|
||||
.push(LifecycleRule::from_xml(rule.as_element().ok_or(
|
||||
Error::XmlError("<Rule> tag not found".to_string()),
|
||||
)?)?);
|
||||
}
|
||||
}
|
||||
|
||||
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(&rule.filter.to_xml());
|
||||
|
||||
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>");
|
||||
|
||||
data
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(clippy::type_complexity)]
|
||||
fn parse_common_notification_config(
|
||||
element: &mut Element,
|
||||
@ -1768,7 +1463,7 @@ impl ReplicationTime {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Clone, Debug)]
|
||||
#[derive(PartialEq, Clone, Debug, Default)]
|
||||
/// Destination information
|
||||
pub struct Destination {
|
||||
pub bucket_arn: String,
|
||||
@ -1909,7 +1604,7 @@ pub struct SourceSelectionCriteria {
|
||||
pub sse_kms_encrypted_objects_status: Option<bool>,
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Clone, Debug)]
|
||||
#[derive(PartialEq, Clone, Debug, Default)]
|
||||
/// Replication rule information
|
||||
pub struct ReplicationRule {
|
||||
pub destination: Destination,
|
||||
|
||||
@ -14,10 +14,12 @@
|
||||
// limitations under the License.
|
||||
|
||||
use minio::s3::client::DEFAULT_REGION;
|
||||
use minio::s3::error::{Error, ErrorCode};
|
||||
use minio::s3::lifecycle_config::LifecycleConfig;
|
||||
use minio::s3::response::{
|
||||
DeleteBucketLifecycleResponse, GetBucketLifecycleResponse, PutBucketLifecycleResponse,
|
||||
};
|
||||
use minio::s3::types::{LifecycleConfig, S3Api};
|
||||
use minio::s3::types::S3Api;
|
||||
use minio_common::example::create_bucket_lifecycle_config_examples;
|
||||
use minio_common::test_context::TestContext;
|
||||
|
||||
@ -37,39 +39,47 @@ async fn bucket_lifecycle() {
|
||||
.unwrap();
|
||||
assert_eq!(resp.bucket, bucket_name);
|
||||
assert_eq!(resp.region, DEFAULT_REGION);
|
||||
//println!("response of setting lifecycle: resp={:?}", resp);
|
||||
|
||||
if false {
|
||||
// TODO panics with: called `Result::unwrap()` on an `Err` value: XmlError("<Filter> tag not found")
|
||||
let resp: GetBucketLifecycleResponse = ctx
|
||||
.client
|
||||
.get_bucket_lifecycle(&bucket_name)
|
||||
.send()
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(resp.config, config);
|
||||
assert_eq!(resp.bucket, bucket_name);
|
||||
assert_eq!(resp.region, DEFAULT_REGION);
|
||||
println!("response of getting lifecycle: resp={:?}", resp);
|
||||
}
|
||||
let resp: GetBucketLifecycleResponse = ctx
|
||||
.client
|
||||
.get_bucket_lifecycle(&bucket_name)
|
||||
.with_updated_at(false)
|
||||
.send()
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(resp.config, config);
|
||||
assert_eq!(resp.bucket, bucket_name);
|
||||
assert_eq!(resp.region, DEFAULT_REGION);
|
||||
assert!(resp.updated_at.is_none());
|
||||
|
||||
let _resp: DeleteBucketLifecycleResponse = ctx
|
||||
let resp: GetBucketLifecycleResponse = ctx
|
||||
.client
|
||||
.get_bucket_lifecycle(&bucket_name)
|
||||
.with_updated_at(true)
|
||||
.send()
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(resp.config, config);
|
||||
assert_eq!(resp.bucket, bucket_name);
|
||||
assert_eq!(resp.region, DEFAULT_REGION);
|
||||
assert!(resp.updated_at.is_some());
|
||||
|
||||
let resp: DeleteBucketLifecycleResponse = ctx
|
||||
.client
|
||||
.delete_bucket_lifecycle(&bucket_name)
|
||||
.send()
|
||||
.await
|
||||
.unwrap();
|
||||
//println!("response of deleting lifecycle: resp={:?}", resp);
|
||||
assert_eq!(resp.bucket, bucket_name);
|
||||
assert_eq!(resp.region, DEFAULT_REGION);
|
||||
|
||||
if false {
|
||||
// TODO panics with: called `Result::unwrap()` on an `Err` value: XmlError("<Filter> tag not found")
|
||||
let resp: GetBucketLifecycleResponse = ctx
|
||||
.client
|
||||
.get_bucket_lifecycle(&bucket_name)
|
||||
.send()
|
||||
.await
|
||||
.unwrap();
|
||||
println!("response of getting policy: resp={:?}", resp);
|
||||
//assert_eq!(resp.config, LifecycleConfig::default());
|
||||
let resp: Result<GetBucketLifecycleResponse, Error> =
|
||||
ctx.client.get_bucket_lifecycle(&bucket_name).send().await;
|
||||
match resp {
|
||||
Err(Error::S3Error(e)) => assert_eq!(e.code, ErrorCode::NoSuchLifecycleConfiguration),
|
||||
v => panic!(
|
||||
"Expected error S3Error(NoSuchLifecycleConfiguration): but got {:?}",
|
||||
v
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user