1use crate::client::ObjectstoreClient;
14use crate::response::get_content_text;
15use anyhow::{bail, Context as _, Result};
16use derive_builder::Builder;
17use reqwest::header::{ACCEPT, AUTHORIZATION, CONTENT_TYPE};
18use serde::{Deserialize, Serialize};
19use serde_aux::field_attributes::{deserialize_bool_from_anything, deserialize_default_from_null};
20
21#[derive(Clone, Default, Debug, Deserialize, Serialize)]
22#[serde(rename_all(serialize = "snake_case", deserialize = "camelCase"))]
23pub struct Link {
24 pub rel: String,
25 pub href: String,
26}
27
28#[derive(Clone, Default, Debug, Deserialize, Serialize)]
29#[serde(rename_all(serialize = "snake_case", deserialize = "camelCase"))]
30pub struct MinMaxGovernor {
31 pub enforce_retention: bool,
32 #[serde(deserialize_with = "deserialize_default_from_null")]
33 pub minimum_fixed_retention: i64,
34 #[serde(deserialize_with = "deserialize_default_from_null")]
36 pub maximum_fixed_retention: i64,
37 #[serde(deserialize_with = "deserialize_default_from_null")]
38 pub minimum_variable_retention: i64,
39 #[serde(deserialize_with = "deserialize_default_from_null")]
40 pub maximum_variable_retention: i64,
41}
42
43#[derive(Clone, Default, Debug, Deserialize, Serialize)]
44#[serde(rename_all(serialize = "snake_case", deserialize = "camelCase"))]
45pub struct MetaData {
46 pub key_data_type: String,
48 pub key_value: String,
50 pub metadata_type: String,
52}
53
54#[derive(Clone, Default, Debug, Deserialize, Serialize)]
55#[serde(rename_all(serialize = "snake_case", deserialize = "camelCase"))]
56pub struct SearchMetaData {
57 pub is_enabled: bool,
59 #[serde(deserialize_with = "deserialize_default_from_null")]
60 pub meta_data: Vec<MetaData>,
61 pub max_keys: i32,
63 pub md_tokens: bool,
65}
66
67#[derive(Clone, Debug, Deserialize, Serialize)]
69#[serde(rename_all(serialize = "PascalCase", deserialize = "camelCase"))]
70pub struct BucketTag {
71 pub key: String,
73 pub value: String,
75}
76
77#[derive(Builder, Clone, Debug, Default, Deserialize, Serialize)]
79#[builder(setter(skip))]
80#[serde(
81 rename_all(serialize = "snake_case", deserialize = "camelCase"),
82 rename(serialize = "object_bucket_create")
83)]
84pub struct Bucket {
85 #[builder(setter(into))]
87 pub name: String,
88 pub id: String,
90 pub link: Link,
92 #[builder(setter(into))]
93 pub namespace: String,
95 pub replication: String,
96 pub locked: bool,
98 pub fs_acess_enabled: bool,
100 pub soft_quota: String,
102 pub created: String,
104 pub is_stale_allowed: bool,
106 pub object_lock_with_ado_allowed: bool,
108 pub is_tso_read_only: bool,
110 #[serde(deserialize_with = "deserialize_default_from_null")]
112 pub default_object_lock_retention_mode: String,
113 #[serde(deserialize_with = "deserialize_default_from_null")]
115 pub default_object_lock_retention_years: i32,
116 #[serde(deserialize_with = "deserialize_default_from_null")]
118 pub default_object_lock_retention_days: i32,
119 pub default_retention: i64,
121 pub block_size_in_g_b: i64,
123 pub auto_commit_period: i64,
125 pub notification_size_in_g_b: i64,
127 pub block_size_in_count: i64,
128 pub notification_size_in_count: i64,
129 #[builder(setter(skip = false), default = "false")]
131 #[serde(deserialize_with = "deserialize_bool_from_anything")]
132 pub is_encryption_enabled: bool,
133 pub retention: i64,
135 #[serde(deserialize_with = "deserialize_default_from_null")]
137 pub default_group: String,
138 pub default_group_file_read_permission: bool,
140 pub default_group_file_write_permission: bool,
142 pub default_group_file_execute_permission: bool,
144 pub default_group_dir_read_permission: bool,
146 pub default_group_dir_write_permission: bool,
148 pub default_group_dir_execute_permission: bool,
150 pub min_max_governor: MinMaxGovernor,
151 #[builder(setter(skip = false), default = "-2")]
153 #[serde(rename(serialize = "audited_delete_expiration"))]
154 pub audit_delete_expiration: i64,
155 pub enable_advanced_metadata_search: bool,
156 #[serde(deserialize_with = "deserialize_default_from_null")]
157 pub advanced_metadata_search_target_name: String,
158 #[serde(deserialize_with = "deserialize_default_from_null")]
159 pub advanced_metadata_search_target_stream: String,
160 pub is_empty_bucket_in_progress: bool,
162 pub meta_data: SearchMetaData,
163 pub local_object_metadata_reads: bool,
165 pub apitype: String,
167 pub bucket_owner: String,
169 #[builder(setter(skip = false), default)]
171 #[serde(rename = "TagSet", deserialize_with = "deserialize_default_from_null")]
172 pub tags: Vec<BucketTag>,
173}
174
175#[derive(Debug, Deserialize)]
179#[serde(rename_all = "camelCase")]
180struct CreateBucketResponse {
181 pub name: String,
182 pub id: String,
183}
184
185#[derive(Debug, Deserialize)]
186#[serde(rename_all = "camelCase")]
187struct ListBucketsResponse {
188 pub object_bucket_list: Vec<Bucket>,
190 pub filter: String,
191 pub next_marker: Option<String>,
192 pub max_buckets: Option<u32>,
193 pub next_page_link: Option<String>,
194}
195
196impl Bucket {
197 pub(crate) fn create(client: &mut ObjectstoreClient, bucket: Bucket) -> Result<String> {
198 let request_url = format!("{}object/bucket.json", client.endpoint);
199 let body = quick_xml::se::to_string(&bucket)?;
200 let resp = client
201 .management_client
202 .http_client
203 .post(request_url)
204 .header(ACCEPT, "application/json")
205 .header(
206 AUTHORIZATION,
207 client.management_client.access_token.as_ref().unwrap(),
208 )
209 .header(CONTENT_TYPE, "application/xml")
210 .body(body)
211 .send()?;
212 let text = get_content_text(resp)?;
213 let resp: CreateBucketResponse = serde_json::from_str(&text).with_context(|| {
214 format!(
215 "Unable to deserialise CreateBucketResponse. Body was: \"{}\"",
216 text
217 )
218 })?;
219 Ok(resp.name)
220 }
221
222 pub(crate) fn tag(
223 client: &mut ObjectstoreClient,
224 bucket_name: &str,
225 namespace: &str,
226 tags: Vec<BucketTag>,
227 ) -> Result<()> {
228 let tags = tags
232 .iter()
233 .map(|tag| quick_xml::se::to_string_with_root("Tag", tag).unwrap())
234 .collect::<Vec<String>>()
235 .join("");
236 let body = format!(
237 r#"<add_bucket_tags><TagSet>{}</TagSet><namespace>{}</namespace></add_bucket_tags>"#,
238 tags, namespace
239 );
240 let request_url = format!("{}object/bucket/{}/tags", client.endpoint, bucket_name);
241 let resp = client
242 .management_client
243 .http_client
244 .post(request_url)
245 .header(ACCEPT, "application/json")
246 .header(
247 AUTHORIZATION,
248 client.management_client.access_token.as_ref().unwrap(),
249 )
250 .header(CONTENT_TYPE, "application/xml")
251 .body(body)
252 .send()?;
253 if !resp.status().is_success() {
254 bail!("Request failed: {}", resp.text()?);
255 }
256 Ok(())
257 }
258
259 pub(crate) fn get(
260 client: &mut ObjectstoreClient,
261 name: &str,
262 namespace: &str,
263 ) -> Result<Bucket> {
264 let request_url = format!(
265 "{}object/bucket/{}/info.json?namespace={}",
266 client.endpoint, name, namespace,
267 );
268 let resp = client
269 .management_client
270 .http_client
271 .get(request_url)
272 .header(ACCEPT, "application/json")
273 .header(
274 AUTHORIZATION,
275 client.management_client.access_token.as_ref().unwrap(),
276 )
277 .send()?;
278 let text = get_content_text(resp)?;
279 let resp: Bucket = serde_json::from_str(&text)
280 .with_context(|| format!("Unable to deserialise GetBucket. Body was: \"{}\"", text))?;
281 Ok(resp)
282 }
283
284 pub(crate) fn update(client: &mut ObjectstoreClient, bucket: Bucket) -> Result<()> {
285 let request_url = format!(
287 "{}object/bucket/{}/auditDeleteExpiration?expiration={}&namespace={}",
288 client.endpoint, bucket.name, bucket.audit_delete_expiration, bucket.namespace,
289 );
290 let body = quick_xml::se::to_string(&bucket)?;
291 let resp = client
292 .management_client
293 .http_client
294 .put(request_url)
295 .header(ACCEPT, "application/json")
296 .header(
297 AUTHORIZATION,
298 client.management_client.access_token.as_ref().unwrap(),
299 )
300 .body(body)
301 .send()?;
302 if !resp.status().is_success() {
303 bail!("Request failed: {}", resp.text()?);
304 }
305 Ok(())
306 }
307
308 pub(crate) fn delete(
309 client: &mut ObjectstoreClient,
310 name: &str,
311 namespace: &str,
312 empty_bucket: bool,
313 ) -> Result<()> {
314 let request_url = format!(
315 "{}object/bucket/{}/deactivate.json?namespace={}&emptyBucket={}",
316 client.endpoint, name, namespace, empty_bucket,
317 );
318 let resp = client
319 .management_client
320 .http_client
321 .post(request_url)
322 .header(ACCEPT, "application/json")
323 .header(
324 AUTHORIZATION,
325 client.management_client.access_token.as_ref().unwrap(),
326 )
327 .send()?;
328 if !resp.status().is_success() {
329 bail!("Request failed: {}", resp.text()?);
330 } else if resp.status().as_u16() == 202 {
331 bail!("Deletion ongoing");
334 } else {
335 Ok(())
336 }
337 }
338
339 pub(crate) fn list(
340 client: &mut ObjectstoreClient,
341 namespace: &str,
342 name_prefix: &str,
343 ) -> Result<Vec<Bucket>> {
344 let prefix = if name_prefix.is_empty() {
345 "".to_string()
346 } else {
347 format!("&name={}", name_prefix)
348 };
349 let request_url = format!(
350 "{}object/bucket.json?namespace={}{}",
351 client.endpoint, namespace, prefix,
352 );
353 let resp = client
354 .management_client
355 .http_client
356 .get(request_url)
357 .header(ACCEPT, "application/json")
358 .header(
359 AUTHORIZATION,
360 client.management_client.access_token.as_ref().unwrap(),
361 )
362 .send()?;
363 let text = get_content_text(resp)?;
364 let mut resp: ListBucketsResponse = serde_json::from_str(&text).with_context(|| {
365 format!(
366 "Unable to deserialise ListBuckestResponse. Body was: \"{}\"",
367 text
368 )
369 })?;
370 let mut buckets: Vec<Bucket> = vec![];
371 buckets.extend(resp.object_bucket_list);
372 while let Some(marker) = resp.next_marker {
373 let request_url = format!(
374 "{}object/bucket.json?namespace={}{}&marker={}",
375 client.endpoint, namespace, prefix, marker,
376 );
377 let response = client
378 .management_client
379 .http_client
380 .get(request_url)
381 .header(ACCEPT, "application/json")
382 .header(
383 AUTHORIZATION,
384 client.management_client.access_token.as_ref().unwrap(),
385 )
386 .send()?;
387 let text = get_content_text(response)?;
388 resp = serde_json::from_str(&text).with_context(|| {
389 format!(
390 "Unable to deserialise ListBucketsResponse. Body was: \"{}\"",
391 text
392 )
393 })?;
394 buckets.extend(resp.object_bucket_list);
395 }
396 Ok(buckets)
397 }
398}