1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
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<BucketParams>,
	}

	/// 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<String, BucketKeyPerm>,

		/// 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<String, bool>,
		/// 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<Option<WebsiteConfig>>,
		/// CORS rules
		pub cors_config: crdt::Lww<Option<Vec<CorsRule>>>,
		/// Lifecycle configuration
		#[serde(default)]
		pub lifecycle_config: crdt::Lww<Option<Vec<LifecycleRule>>>,
		/// Bucket quotas
		#[serde(default)]
		pub quotas: crdt::Lww<BucketQuotas>,
	}

	#[derive(PartialEq, Eq, Clone, Debug, Serialize, Deserialize)]
	pub struct WebsiteConfig {
		pub index_document: String,
		pub error_document: Option<String>,
	}

	#[derive(PartialEq, Eq, Clone, Debug, Serialize, Deserialize)]
	pub struct CorsRule {
		pub id: Option<String>,
		pub max_age_seconds: Option<u64>,
		pub allow_origins: Vec<String>,
		pub allow_methods: Vec<String>,
		pub allow_headers: Vec<String>,
		pub expose_headers: Vec<String>,
	}

	/// Lifecycle configuration rule
	#[derive(PartialEq, Eq, Clone, Debug, Serialize, Deserialize)]
	pub struct LifecycleRule {
		/// The ID of the rule
		pub id: Option<String>,
		/// 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<usize>,
		/// Expiration policy for stored objects
		pub expiration: Option<LifecycleExpiration>,
	}

	/// 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<String>,
		/// If Some(x), object size has to be more than x
		pub size_gt: Option<u64>,
		/// If Some(x), object size has to be less than x
		pub size_lt: Option<u64>,
	}

	#[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<u64>,
		/// Maximum number of non-deleted objects in the bucket
		pub max_objects: Option<u64>,
	}

	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
	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<chrono::NaiveDate, &'static str> {
	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()),
		}
	}

	pub fn present(id: Uuid, params: BucketParams) -> Self {
		Bucket {
			id,
			state: crdt::Deletable::present(params),
		}
	}

	/// 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<EmptyKey, Uuid> 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())
	}
}