use crate::client::ObjectstoreClient;
use crate::response::get_content_text;
use anyhow::{bail, Context as _, Result};
use derive_builder::Builder;
use reqwest::header::{ACCEPT, AUTHORIZATION, CONTENT_TYPE};
use serde::{Deserialize, Serialize};
use serde_aux::field_attributes::{deserialize_bool_from_anything, deserialize_default_from_null};
#[derive(Clone, Default, Debug, Deserialize, Serialize)]
#[serde(rename_all(serialize = "snake_case", deserialize = "camelCase"))]
pub struct Link {
pub rel: String,
pub href: String,
}
#[derive(Clone, Default, Debug, Deserialize, Serialize)]
#[serde(rename_all(serialize = "snake_case", deserialize = "camelCase"))]
pub struct MinMaxGovernor {
pub enforce_retention: bool,
#[serde(deserialize_with = "deserialize_default_from_null")]
pub minimum_fixed_retention: i64,
#[serde(deserialize_with = "deserialize_default_from_null")]
pub maximum_fixed_retention: i64,
#[serde(deserialize_with = "deserialize_default_from_null")]
pub minimum_variable_retention: i64,
#[serde(deserialize_with = "deserialize_default_from_null")]
pub maximum_variable_retention: i64,
}
#[derive(Clone, Default, Debug, Deserialize, Serialize)]
#[serde(rename_all(serialize = "snake_case", deserialize = "camelCase"))]
pub struct MetaData {
pub key_data_type: String,
pub key_value: String,
pub metadata_type: String,
}
#[derive(Clone, Default, Debug, Deserialize, Serialize)]
#[serde(rename_all(serialize = "snake_case", deserialize = "camelCase"))]
pub struct SearchMetaData {
pub is_enabled: bool,
#[serde(deserialize_with = "deserialize_default_from_null")]
pub meta_data: Vec<MetaData>,
pub max_keys: i32,
pub md_tokens: bool,
}
#[derive(Clone, Debug, Deserialize, Serialize)]
#[serde(rename_all(serialize = "PascalCase", deserialize = "camelCase"))]
pub struct BucketTag {
pub key: String,
pub value: String,
}
#[derive(Builder, Clone, Debug, Default, Deserialize, Serialize)]
#[builder(setter(skip))]
#[serde(
rename_all(serialize = "snake_case", deserialize = "camelCase"),
rename(serialize = "object_bucket_create")
)]
pub struct Bucket {
#[builder(setter(into))]
pub name: String,
pub id: String,
pub link: Link,
#[builder(setter(into))]
pub namespace: String,
pub replication: String,
pub locked: bool,
pub fs_acess_enabled: bool,
pub soft_quota: String,
pub created: String,
pub is_stale_allowed: bool,
pub object_lock_with_ado_allowed: bool,
pub is_tso_read_only: bool,
#[serde(deserialize_with = "deserialize_default_from_null")]
pub default_object_lock_retention_mode: String,
#[serde(deserialize_with = "deserialize_default_from_null")]
pub default_object_lock_retention_years: i32,
#[serde(deserialize_with = "deserialize_default_from_null")]
pub default_object_lock_retention_days: i32,
pub default_retention: i64,
pub block_size_in_g_b: i64,
pub auto_commit_period: i64,
pub notification_size_in_g_b: i64,
pub block_size_in_count: i64,
pub notification_size_in_count: i64,
#[builder(setter(skip = false), default = "false")]
#[serde(deserialize_with = "deserialize_bool_from_anything")]
pub is_encryption_enabled: bool,
pub retention: i64,
#[serde(deserialize_with = "deserialize_default_from_null")]
pub default_group: String,
pub default_group_file_read_permission: bool,
pub default_group_file_write_permission: bool,
pub default_group_file_execute_permission: bool,
pub default_group_dir_read_permission: bool,
pub default_group_dir_write_permission: bool,
pub default_group_dir_execute_permission: bool,
pub min_max_governor: MinMaxGovernor,
#[builder(setter(skip = false), default = "-2")]
#[serde(rename(serialize = "audited_delete_expiration"))]
pub audit_delete_expiration: i64,
pub enable_advanced_metadata_search: bool,
#[serde(deserialize_with = "deserialize_default_from_null")]
pub advanced_metadata_search_target_name: String,
#[serde(deserialize_with = "deserialize_default_from_null")]
pub advanced_metadata_search_target_stream: String,
pub is_empty_bucket_in_progress: bool,
pub meta_data: SearchMetaData,
pub local_object_metadata_reads: bool,
pub apitype: String,
pub bucket_owner: String,
#[builder(setter(skip = false), default)]
#[serde(rename = "TagSet", deserialize_with = "deserialize_default_from_null")]
pub tags: Vec<BucketTag>,
}
#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
struct CreateBucketResponse {
pub name: String,
pub id: String,
}
#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
struct ListBucketsResponse {
pub object_bucket_list: Vec<Bucket>,
pub filter: String,
pub next_marker: Option<String>,
pub max_buckets: Option<u32>,
pub next_page_link: Option<String>,
}
impl Bucket {
pub(crate) fn create(client: &mut ObjectstoreClient, bucket: Bucket) -> Result<String> {
let request_url = format!("{}object/bucket.json", client.endpoint);
let body = quick_xml::se::to_string(&bucket)?;
let resp = client
.management_client
.http_client
.post(request_url)
.header(ACCEPT, "application/json")
.header(
AUTHORIZATION,
client.management_client.access_token.as_ref().unwrap(),
)
.header(CONTENT_TYPE, "application/xml")
.body(body)
.send()?;
let text = get_content_text(resp)?;
let resp: CreateBucketResponse = serde_json::from_str(&text).with_context(|| {
format!(
"Unable to deserialise CreateBucketResponse. Body was: \"{}\"",
text
)
})?;
Ok(resp.name)
}
pub(crate) fn tag(
client: &mut ObjectstoreClient,
bucket_name: &str,
namespace: &str,
tags: Vec<BucketTag>,
) -> Result<()> {
let tags = tags
.iter()
.map(|tag| quick_xml::se::to_string_with_root("Tag", tag).unwrap())
.collect::<Vec<String>>()
.join("");
let body = format!(
r#"<add_bucket_tags><TagSet>{}</TagSet><namespace>{}</namespace></add_bucket_tags>"#,
tags, namespace
);
let request_url = format!("{}object/bucket/{}/tags", client.endpoint, bucket_name);
let resp = client
.management_client
.http_client
.post(request_url)
.header(ACCEPT, "application/json")
.header(
AUTHORIZATION,
client.management_client.access_token.as_ref().unwrap(),
)
.header(CONTENT_TYPE, "application/xml")
.body(body)
.send()?;
if !resp.status().is_success() {
bail!("Request failed: {}", resp.text()?);
}
Ok(())
}
pub(crate) fn get(
client: &mut ObjectstoreClient,
name: &str,
namespace: &str,
) -> Result<Bucket> {
let request_url = format!(
"{}object/bucket/{}/info.json?namespace={}",
client.endpoint, name, namespace,
);
let resp = client
.management_client
.http_client
.get(request_url)
.header(ACCEPT, "application/json")
.header(
AUTHORIZATION,
client.management_client.access_token.as_ref().unwrap(),
)
.send()?;
let text = get_content_text(resp)?;
let resp: Bucket = serde_json::from_str(&text)
.with_context(|| format!("Unable to deserialise GetBucket. Body was: \"{}\"", text))?;
Ok(resp)
}
pub(crate) fn update(client: &mut ObjectstoreClient, bucket: Bucket) -> Result<()> {
let request_url = format!(
"{}object/bucket/{}/auditDeleteExpiration?expiration={}&namespace={}",
client.endpoint, bucket.name, bucket.audit_delete_expiration, bucket.namespace,
);
let body = quick_xml::se::to_string(&bucket)?;
let resp = client
.management_client
.http_client
.put(request_url)
.header(ACCEPT, "application/json")
.header(
AUTHORIZATION,
client.management_client.access_token.as_ref().unwrap(),
)
.body(body)
.send()?;
if !resp.status().is_success() {
bail!("Request failed: {}", resp.text()?);
}
Ok(())
}
pub(crate) fn delete(
client: &mut ObjectstoreClient,
name: &str,
namespace: &str,
empty_bucket: bool,
) -> Result<()> {
let request_url = format!(
"{}object/bucket/{}/deactivate.json?namespace={}&emptyBucket={}",
client.endpoint, name, namespace, empty_bucket,
);
let resp = client
.management_client
.http_client
.post(request_url)
.header(ACCEPT, "application/json")
.header(
AUTHORIZATION,
client.management_client.access_token.as_ref().unwrap(),
)
.send()?;
if !resp.status().is_success() {
bail!("Request failed: {}", resp.text()?);
} else if resp.status().as_u16() == 202 {
bail!("Deletion ongoing");
} else {
Ok(())
}
}
pub(crate) fn list(
client: &mut ObjectstoreClient,
namespace: &str,
name_prefix: &str,
) -> Result<Vec<Bucket>> {
let prefix = if name_prefix.is_empty() {
"".to_string()
} else {
format!("&name={}", name_prefix)
};
let request_url = format!(
"{}object/bucket.json?namespace={}{}",
client.endpoint, namespace, prefix,
);
let resp = client
.management_client
.http_client
.get(request_url)
.header(ACCEPT, "application/json")
.header(
AUTHORIZATION,
client.management_client.access_token.as_ref().unwrap(),
)
.send()?;
let text = get_content_text(resp)?;
let mut resp: ListBucketsResponse = serde_json::from_str(&text).with_context(|| {
format!(
"Unable to deserialise ListBuckestResponse. Body was: \"{}\"",
text
)
})?;
let mut buckets: Vec<Bucket> = vec![];
buckets.extend(resp.object_bucket_list);
while let Some(marker) = resp.next_marker {
let request_url = format!(
"{}object/bucket.json?namespace={}{}&marker={}",
client.endpoint, namespace, prefix, marker,
);
let response = client
.management_client
.http_client
.get(request_url)
.header(ACCEPT, "application/json")
.header(
AUTHORIZATION,
client.management_client.access_token.as_ref().unwrap(),
)
.send()?;
let text = get_content_text(response)?;
resp = serde_json::from_str(&text).with_context(|| {
format!(
"Unable to deserialise ListBucketsResponse. Body was: \"{}\"",
text
)
})?;
buckets.extend(resp.object_bucket_list);
}
Ok(buckets)
}
}