forked from Deuxfleurs/garage
lifecycle: implement validation into garage's internal data structure
This commit is contained in:
parent
8041d9a827
commit
abf011c290
2 changed files with 178 additions and 24 deletions
|
@ -22,13 +22,7 @@ pub async fn handle_get_lifecycle(bucket: &Bucket) -> Result<Response<Body>, Err
|
||||||
.ok_or_internal_error("Bucket should not be deleted at this point")?;
|
.ok_or_internal_error("Bucket should not be deleted at this point")?;
|
||||||
|
|
||||||
if let Some(lifecycle) = param.lifecycle_config.get() {
|
if let Some(lifecycle) = param.lifecycle_config.get() {
|
||||||
let wc = LifecycleConfiguration {
|
let wc = LifecycleConfiguration::from_garage_lifecycle_config(lifecycle);
|
||||||
xmlns: (),
|
|
||||||
lifecycle_rules: lifecycle
|
|
||||||
.iter()
|
|
||||||
.map(LifecycleRule::from_garage_lifecycle_rule)
|
|
||||||
.collect::<Vec<_>>(),
|
|
||||||
};
|
|
||||||
let xml = to_xml_with_header(&wc)?;
|
let xml = to_xml_with_header(&wc)?;
|
||||||
Ok(Response::builder()
|
Ok(Response::builder()
|
||||||
.status(StatusCode::OK)
|
.status(StatusCode::OK)
|
||||||
|
@ -81,9 +75,10 @@ pub async fn handle_put_lifecycle(
|
||||||
|
|
||||||
let conf: LifecycleConfiguration = from_reader(&body as &[u8])?;
|
let conf: LifecycleConfiguration = from_reader(&body as &[u8])?;
|
||||||
|
|
||||||
param
|
let config = conf
|
||||||
.lifecycle_config
|
.validate_into_garage_lifecycle_config()
|
||||||
.update(Some(conf.validate_into_garage_lifecycle_config()?));
|
.ok_or_bad_request("Invalid lifecycle configuration")?;
|
||||||
|
param.lifecycle_config.update(Some(config));
|
||||||
garage.bucket_table.insert(&bucket).await?;
|
garage.bucket_table.insert(&bucket).await?;
|
||||||
|
|
||||||
Ok(Response::builder()
|
Ok(Response::builder()
|
||||||
|
@ -109,7 +104,7 @@ pub struct LifecycleRule {
|
||||||
#[serde(rename = "Status")]
|
#[serde(rename = "Status")]
|
||||||
pub status: Value,
|
pub status: Value,
|
||||||
#[serde(rename = "Filter", default)]
|
#[serde(rename = "Filter", default)]
|
||||||
pub filter: Filter,
|
pub filter: Option<Filter>,
|
||||||
#[serde(rename = "Expiration", default)]
|
#[serde(rename = "Expiration", default)]
|
||||||
pub expiration: Option<Expiration>,
|
pub expiration: Option<Expiration>,
|
||||||
#[serde(rename = "AbortIncompleteMultipartUpload", default)]
|
#[serde(rename = "AbortIncompleteMultipartUpload", default)]
|
||||||
|
@ -139,11 +134,13 @@ pub struct Expiration {
|
||||||
#[derive(Debug, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord)]
|
#[derive(Debug, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord)]
|
||||||
pub struct AbortIncompleteMpu {
|
pub struct AbortIncompleteMpu {
|
||||||
#[serde(rename = "DaysAfterInitiation")]
|
#[serde(rename = "DaysAfterInitiation")]
|
||||||
pub days: Option<IntValue>,
|
pub days: IntValue,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl LifecycleConfiguration {
|
impl LifecycleConfiguration {
|
||||||
pub fn validate_into_garage_lifecycle_config(self) -> Result<Vec<GarageLifecycleRule>, Error> {
|
pub fn validate_into_garage_lifecycle_config(
|
||||||
|
self,
|
||||||
|
) -> Result<Vec<GarageLifecycleRule>, &'static str> {
|
||||||
let mut ret = vec![];
|
let mut ret = vec![];
|
||||||
for rule in self.lifecycle_rules {
|
for rule in self.lifecycle_rules {
|
||||||
ret.push(rule.validate_into_garage_lifecycle_rule()?);
|
ret.push(rule.validate_into_garage_lifecycle_rule()?);
|
||||||
|
@ -163,12 +160,136 @@ impl LifecycleConfiguration {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl LifecycleRule {
|
impl LifecycleRule {
|
||||||
pub fn validate_into_garage_lifecycle_rule(self) -> Result<GarageLifecycleRule, Error> {
|
pub fn validate_into_garage_lifecycle_rule(self) -> Result<GarageLifecycleRule, &'static str> {
|
||||||
todo!()
|
let enabled = match self.status.0.as_str() {
|
||||||
|
"Enabled" => true,
|
||||||
|
"Disabled" => false,
|
||||||
|
_ => return Err("invalid value for <Status>"),
|
||||||
|
};
|
||||||
|
|
||||||
|
let filter = self
|
||||||
|
.filter
|
||||||
|
.map(Filter::validate_into_garage_lifecycle_filter)
|
||||||
|
.transpose()?
|
||||||
|
.unwrap_or_default();
|
||||||
|
|
||||||
|
let abort_incomplete_mpu_days = self.abort_incomplete_mpu.map(|x| x.days.0 as usize);
|
||||||
|
|
||||||
|
let expiration = self
|
||||||
|
.expiration
|
||||||
|
.map(Expiration::validate_into_garage_lifecycle_expiration)
|
||||||
|
.transpose()?;
|
||||||
|
|
||||||
|
Ok(GarageLifecycleRule {
|
||||||
|
id: self.id.map(|x| x.0),
|
||||||
|
enabled,
|
||||||
|
filter,
|
||||||
|
abort_incomplete_mpu_days,
|
||||||
|
expiration,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn from_garage_lifecycle_rule(rule: &GarageLifecycleRule) -> Self {
|
pub fn from_garage_lifecycle_rule(rule: &GarageLifecycleRule) -> Self {
|
||||||
todo!()
|
Self {
|
||||||
|
id: rule.id.as_deref().map(Value::from),
|
||||||
|
status: if rule.enabled {
|
||||||
|
Value::from("Enabled")
|
||||||
|
} else {
|
||||||
|
Value::from("Disabled")
|
||||||
|
},
|
||||||
|
filter: Filter::from_garage_lifecycle_filter(&rule.filter),
|
||||||
|
abort_incomplete_mpu: rule
|
||||||
|
.abort_incomplete_mpu_days
|
||||||
|
.map(|days| AbortIncompleteMpu {
|
||||||
|
days: IntValue(days as i64),
|
||||||
|
}),
|
||||||
|
expiration: rule
|
||||||
|
.expiration
|
||||||
|
.as_ref()
|
||||||
|
.map(Expiration::from_garage_lifecycle_expiration),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Filter {
|
||||||
|
pub fn count(&self) -> i32 {
|
||||||
|
fn count<T>(x: &Option<T>) -> i32 {
|
||||||
|
x.as_ref().map(|_| 1).unwrap_or(0)
|
||||||
|
}
|
||||||
|
count(&self.prefix) + count(&self.size_gt) + count(&self.size_lt)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn validate_into_garage_lifecycle_filter(
|
||||||
|
self,
|
||||||
|
) -> Result<GarageLifecycleFilter, &'static str> {
|
||||||
|
if self.count() > 0 && self.and.is_some() {
|
||||||
|
Err("Filter tag cannot contain both <And> and another condition")
|
||||||
|
} else if let Some(and) = self.and {
|
||||||
|
if and.and.is_some() {
|
||||||
|
return Err("Nested <And> tags");
|
||||||
|
}
|
||||||
|
Ok(and.internal_into_garage_lifecycle_filter())
|
||||||
|
} else if self.count() > 1 {
|
||||||
|
Err("Multiple Filter conditions must be wrapped in an <And> tag")
|
||||||
|
} else {
|
||||||
|
Ok(self.internal_into_garage_lifecycle_filter())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn internal_into_garage_lifecycle_filter(self) -> GarageLifecycleFilter {
|
||||||
|
GarageLifecycleFilter {
|
||||||
|
prefix: self.prefix.map(|x| x.0),
|
||||||
|
size_gt: self.size_gt.map(|x| x.0 as usize),
|
||||||
|
size_lt: self.size_lt.map(|x| x.0 as usize),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn from_garage_lifecycle_filter(rule: &GarageLifecycleFilter) -> Option<Self> {
|
||||||
|
let filter = Filter {
|
||||||
|
and: None,
|
||||||
|
prefix: rule.prefix.as_deref().map(Value::from),
|
||||||
|
size_gt: rule.size_gt.map(|x| IntValue(x as i64)),
|
||||||
|
size_lt: rule.size_lt.map(|x| IntValue(x as i64)),
|
||||||
|
};
|
||||||
|
match filter.count() {
|
||||||
|
0 => None,
|
||||||
|
1 => Some(filter),
|
||||||
|
_ => Some(Filter {
|
||||||
|
and: Some(Box::new(filter)),
|
||||||
|
..Default::default()
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Expiration {
|
||||||
|
pub fn validate_into_garage_lifecycle_expiration(
|
||||||
|
self,
|
||||||
|
) -> Result<GarageLifecycleExpiration, &'static str> {
|
||||||
|
match (self.days, self.at_date) {
|
||||||
|
(Some(_), Some(_)) => Err("cannot have both <Days> and <Date> in <Expiration>"),
|
||||||
|
(None, None) => Err("<Expiration> must contain either <Days> or <Date>"),
|
||||||
|
(Some(days), None) => Ok(GarageLifecycleExpiration::AfterDays(days.0 as usize)),
|
||||||
|
(None, Some(date)) => {
|
||||||
|
if date.0.parse::<chrono::NaiveDate>().is_err() {
|
||||||
|
return Err("Invalid expiration <Date>");
|
||||||
|
}
|
||||||
|
Ok(GarageLifecycleExpiration::AtDate(date.0))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn from_garage_lifecycle_expiration(exp: &GarageLifecycleExpiration) -> Self {
|
||||||
|
match exp {
|
||||||
|
GarageLifecycleExpiration::AfterDays(days) => Expiration {
|
||||||
|
days: Some(IntValue(*days as i64)),
|
||||||
|
at_date: None,
|
||||||
|
},
|
||||||
|
GarageLifecycleExpiration::AtDate(days) => Expiration {
|
||||||
|
days: None,
|
||||||
|
at_date: Some(Value::from(days.as_str())),
|
||||||
|
},
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -213,26 +334,24 @@ mod tests {
|
||||||
LifecycleRule {
|
LifecycleRule {
|
||||||
id: Some("id1".into()),
|
id: Some("id1".into()),
|
||||||
status: "Enabled".into(),
|
status: "Enabled".into(),
|
||||||
filter: Filter {
|
filter: Some(Filter {
|
||||||
prefix: Some("documents/".into()),
|
prefix: Some("documents/".into()),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
|
||||||
expiration: None,
|
|
||||||
abort_incomplete_mpu: Some(AbortIncompleteMpu {
|
|
||||||
days: Some(IntValue(7)),
|
|
||||||
}),
|
}),
|
||||||
|
expiration: None,
|
||||||
|
abort_incomplete_mpu: Some(AbortIncompleteMpu { days: IntValue(7) }),
|
||||||
},
|
},
|
||||||
LifecycleRule {
|
LifecycleRule {
|
||||||
id: Some("id2".into()),
|
id: Some("id2".into()),
|
||||||
status: "Enabled".into(),
|
status: "Enabled".into(),
|
||||||
filter: Filter {
|
filter: Some(Filter {
|
||||||
and: Some(Box::new(Filter {
|
and: Some(Box::new(Filter {
|
||||||
prefix: Some("logs/".into()),
|
prefix: Some("logs/".into()),
|
||||||
size_gt: Some(IntValue(1000000)),
|
size_gt: Some(IntValue(1000000)),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
})),
|
})),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
}),
|
||||||
expiration: Some(Expiration {
|
expiration: Some(Expiration {
|
||||||
days: Some(IntValue(365)),
|
days: Some(IntValue(365)),
|
||||||
at_date: None,
|
at_date: None,
|
||||||
|
@ -251,6 +370,41 @@ mod tests {
|
||||||
let cleanup = |c: &str| c.replace(char::is_whitespace, "");
|
let cleanup = |c: &str| c.replace(char::is_whitespace, "");
|
||||||
assert_eq!(cleanup(message), cleanup(&message2));
|
assert_eq!(cleanup(message), cleanup(&message2));
|
||||||
|
|
||||||
|
// Check validation
|
||||||
|
let validated = ref_value
|
||||||
|
.validate_into_garage_lifecycle_config()
|
||||||
|
.ok_or_bad_request("invalid xml config")?;
|
||||||
|
|
||||||
|
let ref_config = vec![
|
||||||
|
GarageLifecycleRule {
|
||||||
|
id: Some("id1".into()),
|
||||||
|
enabled: true,
|
||||||
|
filter: GarageLifecycleFilter {
|
||||||
|
prefix: Some("documents/".into()),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
expiration: None,
|
||||||
|
abort_incomplete_mpu_days: Some(7),
|
||||||
|
},
|
||||||
|
GarageLifecycleRule {
|
||||||
|
id: Some("id2".into()),
|
||||||
|
enabled: true,
|
||||||
|
filter: GarageLifecycleFilter {
|
||||||
|
prefix: Some("logs/".into()),
|
||||||
|
size_gt: Some(1000000),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
expiration: Some(GarageLifecycleExpiration::AfterDays(365)),
|
||||||
|
abort_incomplete_mpu_days: None,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
assert_eq!(validated, ref_config);
|
||||||
|
|
||||||
|
let message3 = to_xml_with_header(&LifecycleConfiguration::from_garage_lifecycle_config(
|
||||||
|
&validated,
|
||||||
|
))?;
|
||||||
|
assert_eq!(cleanup(message), cleanup(&message3));
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -90,7 +90,7 @@ mod v08 {
|
||||||
/// A lifecycle filter is a set of conditions that must all be true.
|
/// A lifecycle filter is a set of conditions that must all be true.
|
||||||
/// For each condition, if it is None, it is not verified (always true),
|
/// For each condition, if it is None, it is not verified (always true),
|
||||||
/// and if it is Some(x), then it is verified for value x
|
/// and if it is Some(x), then it is verified for value x
|
||||||
#[derive(PartialEq, Eq, Clone, Debug, Serialize, Deserialize)]
|
#[derive(PartialEq, Eq, Clone, Debug, Serialize, Deserialize, Default)]
|
||||||
pub struct LifecycleFilter {
|
pub struct LifecycleFilter {
|
||||||
/// If Some(x), object key has to start with prefix x
|
/// If Some(x), object key has to start with prefix x
|
||||||
pub prefix: Option<String>,
|
pub prefix: Option<String>,
|
||||||
|
|
Loading…
Reference in a new issue