use garage_table::crdt::*; use garage_table::*; use garage_util::data::*; use garage_util::time::*; use crate::permission::BucketKeyPerm; mod v08 { use crate::permission::BucketKeyPerm; use garage_util::crdt; use garage_util::data::Uuid; use serde::{Deserialize, Serialize}; /// A bucket is a collection of objects /// /// Its parameters are not directly accessible as: /// - It must be possible to merge paramaters, hence the use of a LWW CRDT. /// - A bucket has 2 states, Present or Deleted and parameters make sense only if present. #[derive(PartialEq, Eq, Clone, Debug, Serialize, Deserialize)] pub struct Bucket { /// ID of the bucket pub id: Uuid, /// State, and configuration if not deleted, of the bucket pub state: crdt::Deletable, } /// Configuration for a bucket #[derive(PartialEq, Eq, Clone, Debug, Serialize, Deserialize)] pub struct BucketParams { /// Bucket's creation date pub creation_date: u64, /// Map of key with access to the bucket, and what kind of access they give pub authorized_keys: crdt::Map, /// Map of aliases that are or have been given to this bucket /// in the global namespace /// (not authoritative: this is just used as an indication to /// map back to aliases when doing ListBuckets) pub aliases: crdt::LwwMap, /// Map of aliases that are or have been given to this bucket /// in namespaces local to keys /// key = (access key id, alias name) pub local_aliases: crdt::LwwMap<(String, String), bool>, /// Whether this bucket is allowed for website access /// (under all of its global alias names), /// and if so, the website configuration XML document pub website_config: crdt::Lww>, /// CORS rules pub cors_config: crdt::Lww>>, /// Lifecycle configuration #[serde(default)] pub lifecycle_config: crdt::Lww>>, /// Bucket quotas #[serde(default)] pub quotas: crdt::Lww, } #[derive(PartialEq, Eq, Clone, Debug, Serialize, Deserialize)] pub struct WebsiteConfig { pub index_document: String, pub error_document: Option, } #[derive(PartialEq, Eq, Clone, Debug, Serialize, Deserialize)] pub struct CorsRule { pub id: Option, pub max_age_seconds: Option, pub allow_origins: Vec, pub allow_methods: Vec, pub allow_headers: Vec, pub expose_headers: Vec, } /// Lifecycle configuration rule #[derive(PartialEq, Eq, Clone, Debug, Serialize, Deserialize)] pub struct LifecycleRule { /// The ID of the rule pub id: Option, /// Whether the rule is active pub enabled: bool, /// The filter to check whether rule applies to a given object pub filter: LifecycleFilter, /// Number of days after which incomplete multipart uploads are aborted pub abort_incomplete_mpu_days: Option, /// Expiration policy for stored objects pub expiration: Option, } /// 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), /// and if it is Some(x), then it is verified for value x #[derive(PartialEq, Eq, Clone, Debug, Serialize, Deserialize, Default)] pub struct LifecycleFilter { /// If Some(x), object key has to start with prefix x pub prefix: Option, /// If Some(x), object size has to be more than x pub size_gt: Option, /// If Some(x), object size has to be less than x pub size_lt: Option, } #[derive(PartialEq, Eq, Clone, Debug, Serialize, Deserialize)] pub enum LifecycleExpiration { /// Objects expire x days after they were created AfterDays(usize), /// Objects expire at date x (must be in yyyy-mm-dd format) AtDate(String), } #[derive(Default, PartialEq, Eq, PartialOrd, Ord, Clone, Debug, Serialize, Deserialize)] pub struct BucketQuotas { /// Maximum size in bytes (bucket size = sum of sizes of objects in the bucket) pub max_size: Option, /// Maximum number of non-deleted objects in the bucket pub max_objects: Option, } impl garage_util::migrate::InitialFormat for Bucket {} } pub use v08::*; impl AutoCrdt for BucketQuotas { const WARN_IF_DIFFERENT: bool = true; } impl BucketParams { /// Create an empty BucketParams with no authorized keys and no website accesss pub fn new() -> Self { BucketParams { creation_date: now_msec(), authorized_keys: crdt::Map::new(), aliases: crdt::LwwMap::new(), local_aliases: crdt::LwwMap::new(), website_config: crdt::Lww::new(None), cors_config: crdt::Lww::new(None), lifecycle_config: crdt::Lww::new(None), quotas: crdt::Lww::new(BucketQuotas::default()), } } } impl Crdt for BucketParams { fn merge(&mut self, o: &Self) { self.creation_date = std::cmp::min(self.creation_date, o.creation_date); self.authorized_keys.merge(&o.authorized_keys); self.aliases.merge(&o.aliases); self.local_aliases.merge(&o.local_aliases); self.website_config.merge(&o.website_config); self.cors_config.merge(&o.cors_config); self.lifecycle_config.merge(&o.lifecycle_config); self.quotas.merge(&o.quotas); } } pub fn parse_lifecycle_date(date: &str) -> Result { use chrono::prelude::*; if let Ok(datetime) = NaiveDateTime::parse_from_str(date, "%Y-%m-%dT%H:%M:%SZ") { if datetime.time() == NaiveTime::MIN { Ok(datetime.date()) } else { Err("date must be at midnight") } } else { NaiveDate::parse_from_str(date, "%Y-%m-%d").map_err(|_| "date has invalid format") } } impl Default for Bucket { fn default() -> Self { Self::new() } } impl Default for BucketParams { fn default() -> Self { Self::new() } } impl Bucket { /// Initializes a new instance of the Bucket struct pub fn new() -> Self { Bucket { id: gen_uuid(), state: crdt::Deletable::present(BucketParams::new()), } } /// Returns true if this represents a deleted bucket pub fn is_deleted(&self) -> bool { self.state.is_deleted() } /// Returns an option representing the parameters (None if in deleted state) pub fn params(&self) -> Option<&BucketParams> { self.state.as_option() } /// Mutable version of `.params()` pub fn params_mut(&mut self) -> Option<&mut BucketParams> { self.state.as_option_mut() } /// Return the list of authorized keys, when each was updated, and the permission associated to /// the key pub fn authorized_keys(&self) -> &[(String, BucketKeyPerm)] { self.params() .map(|s| s.authorized_keys.items()) .unwrap_or(&[]) } pub fn aliases(&self) -> &[(String, u64, bool)] { self.params().map(|s| s.aliases.items()).unwrap_or(&[]) } pub fn local_aliases(&self) -> &[((String, String), u64, bool)] { self.params() .map(|s| s.local_aliases.items()) .unwrap_or(&[]) } } impl Entry for Bucket { fn partition_key(&self) -> &EmptyKey { &EmptyKey } fn sort_key(&self) -> &Uuid { &self.id } } impl Crdt for Bucket { fn merge(&mut self, other: &Self) { self.state.merge(&other.state); } } pub struct BucketTable; impl TableSchema for BucketTable { const TABLE_NAME: &'static str = "bucket_v2"; type P = EmptyKey; type S = Uuid; type E = Bucket; type Filter = DeletedFilter; fn matches_filter(entry: &Self::E, filter: &Self::Filter) -> bool { filter.apply(entry.is_deleted()) } }