MinioK8sBuckets/src/minio.rs

1140 lines
33 KiB
Rust

use std::process::Command;
use serde::de::DeserializeOwned;
use serde::Deserialize;
use crate::constants::{MC_EXE, SECRET_MINIO_BUCKET_ACCESS_LEN, SECRET_MINIO_BUCKET_SECRET_LEN};
use crate::crd::{BucketRetention, MinioBucketSpec, RetentionType};
use crate::temp;
use crate::utils::rand_str;
const MC_ALIAS_NAME: &str = "managedminioinst";
#[derive(thiserror::Error, Debug)]
enum MinioError {
#[error("Failed to set 'mc' alias!")]
SetMcAlias,
#[error("Failed to execute 'mc' command!")]
ExecMc,
#[error("Failed to execute 'mc mb' command!")]
MakeBucketFailed,
#[error("Failed to set anonymous access!")]
SetAnonymousAcccessFailed,
#[error("Failed to set bucket quota!")]
SetQuotaFailed,
#[error("Failed to set bucket retention!")]
SetRetentionFailed,
#[error("Failed to set policy!")]
ApplyPolicyFailed,
#[error("Failed to create user!")]
CreateUserFailed,
}
#[derive(Debug, Clone)]
pub struct MinioService {
pub hostname: String,
pub access_key: String,
pub secret_key: String,
}
#[derive(Debug, Clone)]
pub struct MinioUser {
pub username: String,
pub password: String,
}
impl MinioUser {
pub fn gen_random() -> Self {
Self {
username: rand_str(SECRET_MINIO_BUCKET_ACCESS_LEN),
password: rand_str(SECRET_MINIO_BUCKET_SECRET_LEN),
}
}
}
#[derive(Debug, Clone, Deserialize)]
pub struct BucketEntry {
pub status: String,
key: String,
}
impl BucketEntry {
pub fn bucket_name(&self) -> &str {
&self.key[0..self.key.len() - 1]
}
}
#[derive(Debug, Clone, Deserialize)]
struct BasicMinioResult {
pub status: String,
}
#[derive(Debug, Clone, Deserialize)]
struct MinioGetVersioningResult {
pub versioning: Option<MinioVersioning>,
}
#[derive(Debug, Clone, Deserialize)]
struct MinioVersioning {
pub status: String,
}
#[derive(Debug, Clone, Deserialize)]
struct MinioAnonymousAccess {
pub permission: String,
}
#[derive(Debug, Clone, Deserialize)]
struct MinioQuota {
pub quota: Option<usize>,
}
#[derive(Debug, Clone, Deserialize)]
struct MinioRetentionResult {
pub enabled: Option<String>,
pub mode: Option<String>,
pub validity: Option<String>,
}
#[derive(Debug, Clone, Deserialize)]
struct MinioPolicy {
pub policy: String,
}
#[allow(non_snake_case)]
#[derive(Debug, Clone, Deserialize)]
struct MinioPolicyInfo {
pub policyInfo: PolicyInfo,
}
#[allow(non_snake_case)]
#[derive(Debug, Clone, Deserialize)]
struct PolicyInfo {
Policy: serde_json::Value,
}
#[allow(non_snake_case)]
#[derive(Debug, Clone, Deserialize)]
struct MinioUserListRes {
accessKey: String,
}
#[derive(Debug, Clone, Deserialize)]
struct MinioPoliciesUserEntities {
result: MinioPoliciesUserEntitiesInner,
}
#[allow(non_snake_case)]
#[derive(Debug, Clone, Deserialize)]
struct MinioPoliciesUserEntitiesInner {
userMappings: Option<Vec<MinioPoliciesUserEntitiesInnerUser>>,
}
#[derive(Debug, Clone, Deserialize)]
struct MinioPoliciesUserEntitiesInnerUser {
policies: Vec<String>,
}
impl BasicMinioResult {
pub fn success(&self) -> bool {
self.status == "success"
}
}
impl MinioService {
/// Check if the Minio Service is ready to respond to our requests
pub async fn is_ready(&self) -> bool {
match reqwest::get(format!("{}/minio/health/live", self.hostname)).await {
Ok(r) => {
if r.status() == 200 {
log::info!("Minio is ready!");
return true;
}
log::info!(
"Minio not ready yet, check failed with status code {}",
r.status()
);
}
Err(e) => log::info!("Minio not ready yet, check failed with error {e}"),
}
false
}
/// Get bucket name prefixed by mc alias name
fn absolute_bucket_name(&self, name: &str) -> String {
format!("{}/{name}", MC_ALIAS_NAME)
}
/// Execute a minio mc command
async fn exec_mc_cmd<A>(&self, args: &[&str]) -> anyhow::Result<Vec<A>>
where
A: DeserializeOwned,
{
log::debug!("exec_mc_cmd with args {:?}", args);
let conf_dir = temp::create_temp_dir()?;
let global_flags = ["--config-dir", conf_dir.to_str().unwrap(), "--json"];
// First, set our alias to mc in a temporary directory
let res = Command::new(MC_EXE)
.args(global_flags)
.args([
"alias",
"set",
MC_ALIAS_NAME,
self.hostname.as_str(),
self.access_key.as_str(),
self.secret_key.as_str(),
])
.output()?;
if res.status.code() != Some(0) {
log::error!(
"Failed to configure mc alias! (status code {:?}, stderr={}, stdout={})",
res.status,
String::from_utf8_lossy(&res.stderr),
String::from_utf8_lossy(&res.stdout)
);
return Err(MinioError::SetMcAlias.into());
}
// Execute requested command
let res = Command::new(MC_EXE)
.args(global_flags)
.args(args)
.output()?;
if res.status.code() != Some(0) {
log::error!(
"Failed execute command! (status code {:?}, stderr={}, stdout={})",
res.status,
String::from_utf8_lossy(&res.stderr),
String::from_utf8_lossy(&res.stdout)
);
return Err(MinioError::ExecMc.into());
}
let stdout = String::from_utf8_lossy(&res.stdout);
log::debug!(
"stdout='{}' stderr='{}'",
stdout,
String::from_utf8_lossy(&res.stderr)
);
if stdout.is_empty() {
log::info!("Command returned no result!");
return Ok(vec![]);
}
let mut out = vec![];
for l in stdout.split('\n') {
if !l.trim().is_empty() {
out.push(serde_json::from_str(l)?);
}
}
Ok(out)
}
/// Get the list of buckets
pub async fn buckets_list(&self) -> anyhow::Result<Vec<BucketEntry>> {
self.exec_mc_cmd::<BucketEntry>(&["ls", MC_ALIAS_NAME])
.await
}
/// Check if a bucket exists or not
pub async fn bucket_exists(&self, name: &str) -> anyhow::Result<bool> {
Ok(self
.buckets_list()
.await?
.iter()
.any(|b| b.bucket_name().eq(name)))
}
/// Apply bucket desired configuration. If bucket already exists, it is not dropped
pub async fn bucket_apply(&self, b: &MinioBucketSpec) -> anyhow::Result<()> {
// Set base parameters
let bucket_name = format!("{}/{}", MC_ALIAS_NAME, b.name);
let mut args = ["mb", bucket_name.as_str(), "-p"].to_vec();
if b.lock {
args.push("--with-lock");
}
let res = self.exec_mc_cmd::<BasicMinioResult>(&args).await?;
if res.first().map(|r| r.success()) != Some(true) {
return Err(MinioError::MakeBucketFailed.into());
}
self.bucket_set_versioning(&b.name, b.versioning || b.lock)
.await?;
self.bucket_set_anonymous_access(&b.name, b.anonymous_read_access)
.await?;
self.bucket_set_quota(&b.name, b.quota).await?;
if b.lock {
self.bucket_set_default_retention(&b.name, b.retention)
.await?;
}
Ok(())
}
/// Set bucket versioning
pub async fn bucket_set_versioning(&self, bucket: &str, enable: bool) -> anyhow::Result<()> {
let bucket_name = self.absolute_bucket_name(bucket);
let res = self
.exec_mc_cmd::<BasicMinioResult>(&[
"version",
match enable {
true => "enable",
false => "suspend",
},
bucket_name.as_str(),
])
.await?;
if res.first().map(|r| r.success()) != Some(true) {
return Err(MinioError::SetQuotaFailed.into());
}
Ok(())
}
/// Get current bucket versioning status
pub async fn bucket_get_versioning(&self, bucket_name: &str) -> anyhow::Result<bool> {
let bucket_name = self.absolute_bucket_name(bucket_name);
Ok(self
.exec_mc_cmd::<MinioGetVersioningResult>(&["version", "info", bucket_name.as_str()])
.await?
.remove(0)
.versioning
.map(|v| v.status.to_lowercase().eq("enabled"))
.unwrap_or_default())
}
/// Set bucket anonymous access
pub async fn bucket_set_anonymous_access(
&self,
bucket_name: &str,
access: bool,
) -> anyhow::Result<()> {
let target = format!("{}/*", self.absolute_bucket_name(bucket_name));
let res = self
.exec_mc_cmd::<BasicMinioResult>(&[
"anonymous",
"set",
match access {
true => "download",
false => "private",
},
target.as_str(),
])
.await?;
if res.first().map(|r| r.success()) != Some(true) {
return Err(MinioError::SetAnonymousAcccessFailed.into());
}
Ok(())
}
/// Get current bucket anonymous access status
pub async fn bucket_get_anonymous_access(&self, bucket_name: &str) -> anyhow::Result<bool> {
let bucket_name = format!("{}/*", self.absolute_bucket_name(bucket_name));
Ok(self
.exec_mc_cmd::<MinioAnonymousAccess>(&["anonymous", "get", bucket_name.as_str()])
.await?
.remove(0)
.permission
== "download")
}
/// Set bucket quota, in bytes
pub async fn bucket_set_quota(&self, bucket: &str, quota: Option<usize>) -> anyhow::Result<()> {
let bucket_name = self.absolute_bucket_name(bucket);
let res = if let Some(quota) = &quota {
let quota = format!("{}B", quota);
self.exec_mc_cmd::<BasicMinioResult>(&[
"quota",
"set",
bucket_name.as_str(),
"--size",
quota.as_str(),
])
.await?
} else {
self.exec_mc_cmd::<BasicMinioResult>(&["quota", "clear", bucket_name.as_str()])
.await?
};
if res.first().map(|r| r.success()) != Some(true) {
return Err(MinioError::SetQuotaFailed.into());
}
Ok(())
}
/// Get current bucket quota, in bytes
pub async fn bucket_get_quota(&self, bucket_name: &str) -> anyhow::Result<Option<usize>> {
let bucket_name = self.absolute_bucket_name(bucket_name);
Ok(self
.exec_mc_cmd::<MinioQuota>(&["quota", "info", bucket_name.as_str()])
.await?
.remove(0)
.quota)
}
/// Set bucket default retention policy
pub async fn bucket_set_default_retention(
&self,
bucket_name: &str,
retention: Option<BucketRetention>,
) -> anyhow::Result<()> {
let bucket_name = self.absolute_bucket_name(bucket_name);
let res = if let Some(retention) = &retention {
let days = format!("{}d", retention.validity);
self.exec_mc_cmd::<BasicMinioResult>(&[
"retention",
"set",
"--default",
match retention.r#type {
RetentionType::Compliance => "compliance",
RetentionType::Governance => "governance",
},
days.as_str(),
bucket_name.as_str(),
])
.await?
} else {
self.exec_mc_cmd::<BasicMinioResult>(&[
"retention",
"clear",
"--default",
bucket_name.as_str(),
])
.await?
};
if res.first().map(|r| r.success()) != Some(true) {
return Err(MinioError::SetRetentionFailed.into());
}
Ok(())
}
/// Get bucket default retention policy
pub async fn bucket_get_default_retention(
&self,
bucket: &str,
) -> anyhow::Result<Option<BucketRetention>> {
let bucket_name = self.absolute_bucket_name(bucket);
let res = self
.exec_mc_cmd::<MinioRetentionResult>(&[
"retention",
"info",
bucket_name.as_str(),
"--default",
])
.await?
.remove(0);
if let (Some(mode), Some(validity), Some(enabled)) = (res.mode, res.validity, res.enabled) {
if enabled.to_lowercase().eq("enabled") {
return Ok(Some(BucketRetention {
validity: validity.to_lowercase().replace("days", "").parse()?,
r#type: match mode.to_lowercase().as_str() {
"governance" => RetentionType::Governance,
"compliance" => RetentionType::Compliance,
o => {
log::error!("Unknown retention type: {}", o);
return Ok(None);
}
},
}));
}
}
Ok(None)
}
/// Apply a bucket policy
pub async fn policy_apply(&self, name: &str, content: &str) -> anyhow::Result<()> {
let tmp_file = temp::create_temp_file()?;
std::fs::write(&tmp_file, content)?;
let res = self
.exec_mc_cmd::<BasicMinioResult>(&[
"admin",
"policy",
"create",
MC_ALIAS_NAME,
name,
tmp_file.to_str().unwrap(),
])
.await?;
if res.first().map(|r| r.success()) != Some(true) {
return Err(MinioError::ApplyPolicyFailed.into());
}
Ok(())
}
/// Get the list of existing policies
pub async fn policy_list(&self) -> anyhow::Result<Vec<String>> {
Ok(self
.exec_mc_cmd::<MinioPolicy>(&["admin", "policy", "list", MC_ALIAS_NAME])
.await?
.iter()
.map(|p| p.policy.to_string())
.collect())
}
/// Get the content of a given policy
pub async fn policy_content(&self, name: &str) -> anyhow::Result<String> {
let policy = self
.exec_mc_cmd::<MinioPolicyInfo>(&["admin", "policy", "info", MC_ALIAS_NAME, name])
.await?
.remove(0);
Ok(serde_json::to_string(&policy.policyInfo.Policy)?)
}
/// Apply a user
pub async fn user_apply(&self, user: &MinioUser) -> anyhow::Result<()> {
let res = self
.exec_mc_cmd::<BasicMinioResult>(&[
"admin",
"user",
"add",
MC_ALIAS_NAME,
user.username.as_str(),
user.password.as_str(),
])
.await?;
if res.first().map(|r| r.success()) != Some(true) {
return Err(MinioError::CreateUserFailed.into());
}
Ok(())
}
/// Get the list of users
pub async fn user_list(&self) -> anyhow::Result<Vec<String>> {
Ok(self
.exec_mc_cmd::<MinioUserListRes>(&["admin", "user", "list", MC_ALIAS_NAME])
.await?
.iter()
.map(|p| p.accessKey.to_string())
.collect())
}
/// Attach a user to a policy
pub async fn policy_attach_user(&self, user: &MinioUser, policy: &str) -> anyhow::Result<()> {
// Check if the policy has already been attached to the user
if self
.policy_attach_get_user_list(user)
.await?
.contains(&policy.to_string())
{
return Ok(());
}
let res = self
.exec_mc_cmd::<BasicMinioResult>(&[
"admin",
"policy",
"attach",
MC_ALIAS_NAME,
policy,
"--user",
user.username.as_str(),
])
.await?;
if res.first().map(|r| r.success()) != Some(true) {
return Err(MinioError::CreateUserFailed.into());
}
Ok(())
}
/// Get the list of entities attached to a user
pub async fn policy_attach_get_user_list(
&self,
user: &MinioUser,
) -> anyhow::Result<Vec<String>> {
let res = self
.exec_mc_cmd::<MinioPoliciesUserEntities>(&[
"admin",
"policy",
"entities",
MC_ALIAS_NAME,
"--user",
user.username.as_str(),
])
.await?
.remove(0)
.result
.userMappings;
if let Some(mapping) = res {
if let Some(e) = mapping.first() {
return Ok(e.policies.clone());
}
}
Ok(vec![])
}
}
#[cfg(test)]
mod test {
use crate::crd::{BucketRetention, MinioBucketSpec, RetentionType};
use crate::minio::MinioUser;
use crate::minio_test_server::MinioTestServer;
const TEST_BUCKET_NAME: &str = "mybucket";
const TEST_POLICY_NAME: &str = "mypolicy";
#[tokio::test]
async fn list_buckets_empty_instance() {
let srv = MinioTestServer::start().await.unwrap();
let buckets = srv.as_service().buckets_list().await.unwrap();
assert!(buckets.is_empty());
}
#[tokio::test]
async fn bucket_exists_no_bucket() {
let srv = MinioTestServer::start().await.unwrap();
assert!(!srv.as_service().bucket_exists("mybucket").await.unwrap());
}
#[tokio::test]
async fn bucket_basic_creation() {
let _ = env_logger::builder().is_test(true).try_init();
let srv = MinioTestServer::start().await.unwrap();
let service = srv.as_service();
service
.bucket_apply(&MinioBucketSpec {
instance: "".to_string(),
name: TEST_BUCKET_NAME.to_string(),
secret: "".to_string(),
anonymous_read_access: false,
versioning: false,
quota: None,
lock: false,
retention: None,
})
.await
.unwrap();
assert!(service.bucket_exists(TEST_BUCKET_NAME).await.unwrap());
}
#[tokio::test]
async fn bucket_creation_with_anonymous_access() {
let _ = env_logger::builder().is_test(true).try_init();
let srv = MinioTestServer::start().await.unwrap();
let service = srv.as_service();
service
.bucket_apply(&MinioBucketSpec {
instance: "".to_string(),
name: TEST_BUCKET_NAME.to_string(),
secret: "".to_string(),
anonymous_read_access: true,
versioning: false,
quota: None,
lock: false,
retention: None,
})
.await
.unwrap();
assert!(service.bucket_exists(TEST_BUCKET_NAME).await.unwrap());
assert!(service
.bucket_get_anonymous_access(TEST_BUCKET_NAME)
.await
.unwrap());
assert_eq!(
reqwest::get(format!("{}/{}/test", service.hostname, TEST_BUCKET_NAME))
.await
.unwrap()
.status()
.as_u16(),
404
);
}
#[tokio::test]
async fn bucket_creation_without_anonymous_access() {
let _ = env_logger::builder().is_test(true).try_init();
let srv = MinioTestServer::start().await.unwrap();
let service = srv.as_service();
service
.bucket_apply(&MinioBucketSpec {
instance: "".to_string(),
name: TEST_BUCKET_NAME.to_string(),
secret: "".to_string(),
anonymous_read_access: false,
versioning: false,
quota: None,
lock: false,
retention: None,
})
.await
.unwrap();
assert!(service.bucket_exists(TEST_BUCKET_NAME).await.unwrap());
assert_eq!(
reqwest::get(format!("{}/{}/test", service.hostname, TEST_BUCKET_NAME))
.await
.unwrap()
.status()
.as_u16(),
403
);
}
#[tokio::test]
async fn bucket_creation_without_anonymous_access_updating_status() {
let _ = env_logger::builder().is_test(true).try_init();
let srv = MinioTestServer::start().await.unwrap();
let service = srv.as_service();
service
.bucket_apply(&MinioBucketSpec {
instance: "".to_string(),
name: TEST_BUCKET_NAME.to_string(),
secret: "".to_string(),
anonymous_read_access: false,
versioning: false,
quota: None,
lock: false,
retention: None,
})
.await
.unwrap();
assert!(service.bucket_exists(TEST_BUCKET_NAME).await.unwrap());
let test_url = format!("{}/{}/test", service.hostname, TEST_BUCKET_NAME);
assert_eq!(
reqwest::get(&test_url).await.unwrap().status().as_u16(),
403
);
service
.bucket_set_anonymous_access(TEST_BUCKET_NAME, true)
.await
.unwrap();
assert!(service
.bucket_get_anonymous_access(TEST_BUCKET_NAME)
.await
.unwrap());
assert_eq!(
reqwest::get(&test_url).await.unwrap().status().as_u16(),
404
);
service
.bucket_set_anonymous_access(TEST_BUCKET_NAME, false)
.await
.unwrap();
assert!(!service
.bucket_get_anonymous_access(TEST_BUCKET_NAME)
.await
.unwrap());
assert_eq!(
reqwest::get(&test_url).await.unwrap().status().as_u16(),
403
);
}
#[tokio::test]
async fn bucket_with_versioning() {
let _ = env_logger::builder().is_test(true).try_init();
let srv = MinioTestServer::start().await.unwrap();
let service = srv.as_service();
service
.bucket_apply(&MinioBucketSpec {
instance: "".to_string(),
name: TEST_BUCKET_NAME.to_string(),
secret: "".to_string(),
anonymous_read_access: false,
versioning: true,
quota: None,
lock: false,
retention: None,
})
.await
.unwrap();
assert!(service.bucket_exists(TEST_BUCKET_NAME).await.unwrap());
assert!(service
.bucket_get_versioning(TEST_BUCKET_NAME)
.await
.unwrap());
}
#[tokio::test]
async fn bucket_without_versioning() {
let _ = env_logger::builder().is_test(true).try_init();
let srv = MinioTestServer::start().await.unwrap();
let service = srv.as_service();
service
.bucket_apply(&MinioBucketSpec {
instance: "".to_string(),
name: TEST_BUCKET_NAME.to_string(),
secret: "".to_string(),
anonymous_read_access: false,
versioning: false,
quota: None,
lock: false,
retention: None,
})
.await
.unwrap();
assert!(service.bucket_exists(TEST_BUCKET_NAME).await.unwrap());
assert!(!service
.bucket_get_versioning(TEST_BUCKET_NAME)
.await
.unwrap());
}
#[tokio::test]
async fn change_versioning() {
let _ = env_logger::builder().is_test(true).try_init();
let srv = MinioTestServer::start().await.unwrap();
let service = srv.as_service();
service
.bucket_apply(&MinioBucketSpec {
instance: "".to_string(),
name: TEST_BUCKET_NAME.to_string(),
secret: "".to_string(),
anonymous_read_access: false,
versioning: false,
quota: None,
lock: false,
retention: None,
})
.await
.unwrap();
assert!(service.bucket_exists(TEST_BUCKET_NAME).await.unwrap());
assert!(!service
.bucket_get_versioning(TEST_BUCKET_NAME)
.await
.unwrap());
service
.bucket_set_versioning(TEST_BUCKET_NAME, true)
.await
.unwrap();
assert!(service
.bucket_get_versioning(TEST_BUCKET_NAME)
.await
.unwrap());
service
.bucket_set_versioning(TEST_BUCKET_NAME, false)
.await
.unwrap();
assert!(!service
.bucket_get_versioning(TEST_BUCKET_NAME)
.await
.unwrap());
}
#[tokio::test]
async fn bucket_without_quota() {
let _ = env_logger::builder().is_test(true).try_init();
let srv = MinioTestServer::start().await.unwrap();
let service = srv.as_service();
service
.bucket_apply(&MinioBucketSpec {
instance: "".to_string(),
name: TEST_BUCKET_NAME.to_string(),
secret: "".to_string(),
anonymous_read_access: false,
versioning: false,
quota: None,
lock: false,
retention: None,
})
.await
.unwrap();
assert!(service.bucket_exists(TEST_BUCKET_NAME).await.unwrap());
assert_eq!(
service.bucket_get_quota(TEST_BUCKET_NAME).await.unwrap(),
None
);
service
.bucket_set_quota(TEST_BUCKET_NAME, Some(5122600))
.await
.unwrap();
assert_eq!(
service.bucket_get_quota(TEST_BUCKET_NAME).await.unwrap(),
Some(5122600)
);
service
.bucket_set_quota(TEST_BUCKET_NAME, None)
.await
.unwrap();
assert_eq!(
service.bucket_get_quota(TEST_BUCKET_NAME).await.unwrap(),
None
);
}
#[tokio::test]
async fn bucket_with_quota() {
let _ = env_logger::builder().is_test(true).try_init();
let srv = MinioTestServer::start().await.unwrap();
let service = srv.as_service();
service
.bucket_apply(&MinioBucketSpec {
instance: "".to_string(),
name: TEST_BUCKET_NAME.to_string(),
secret: "".to_string(),
anonymous_read_access: false,
versioning: false,
quota: Some(42300),
lock: false,
retention: None,
})
.await
.unwrap();
assert!(service.bucket_exists(TEST_BUCKET_NAME).await.unwrap());
assert_eq!(
service.bucket_get_quota(TEST_BUCKET_NAME).await.unwrap(),
Some(42300)
);
}
#[tokio::test]
async fn bucket_with_retention() {
let _ = env_logger::builder().is_test(true).try_init();
let srv = MinioTestServer::start().await.unwrap();
let service = srv.as_service();
service
.bucket_apply(&MinioBucketSpec {
instance: "".to_string(),
name: TEST_BUCKET_NAME.to_string(),
secret: "".to_string(),
anonymous_read_access: false,
versioning: false,
quota: Some(42300),
lock: true,
retention: Some(BucketRetention {
validity: 10,
r#type: RetentionType::Governance,
}),
})
.await
.unwrap();
assert!(service.bucket_exists(TEST_BUCKET_NAME).await.unwrap());
assert_eq!(
service
.bucket_get_default_retention(TEST_BUCKET_NAME)
.await
.unwrap(),
Some(BucketRetention {
validity: 10,
r#type: RetentionType::Governance
})
);
service
.bucket_set_default_retention(TEST_BUCKET_NAME, None)
.await
.unwrap();
assert_eq!(
service
.bucket_get_default_retention(TEST_BUCKET_NAME)
.await
.unwrap(),
None
);
service
.bucket_set_default_retention(
TEST_BUCKET_NAME,
Some(BucketRetention {
validity: 42,
r#type: RetentionType::Compliance,
}),
)
.await
.unwrap();
assert_eq!(
service
.bucket_get_default_retention(TEST_BUCKET_NAME)
.await
.unwrap(),
Some(BucketRetention {
validity: 42,
r#type: RetentionType::Compliance
})
);
service
.bucket_set_default_retention(
TEST_BUCKET_NAME,
Some(BucketRetention {
validity: 21,
r#type: RetentionType::Governance,
}),
)
.await
.unwrap();
assert_eq!(
service
.bucket_get_default_retention(TEST_BUCKET_NAME)
.await
.unwrap(),
Some(BucketRetention {
validity: 21,
r#type: RetentionType::Governance
})
);
}
#[tokio::test]
async fn bucket_without_retention() {
let _ = env_logger::builder().is_test(true).try_init();
let srv = MinioTestServer::start().await.unwrap();
let service = srv.as_service();
service
.bucket_apply(&MinioBucketSpec {
instance: "".to_string(),
name: TEST_BUCKET_NAME.to_string(),
secret: "".to_string(),
anonymous_read_access: false,
versioning: false,
quota: Some(42300),
lock: true,
retention: None,
})
.await
.unwrap();
assert!(service.bucket_exists(TEST_BUCKET_NAME).await.unwrap());
assert_eq!(
service
.bucket_get_default_retention(TEST_BUCKET_NAME)
.await
.unwrap(),
None
);
}
fn unify_policy(p: &str) -> String {
serde_json::to_string(&serde_json::from_str::<serde_json::Value>(p).unwrap()).unwrap()
}
#[tokio::test]
async fn policy_apply() {
let _ = env_logger::builder().is_test(true).try_init();
let srv = MinioTestServer::start().await.unwrap();
let service = srv.as_service();
let policy_1 = unify_policy(include_str!("../test/test-policy1.json"));
let policy_2 = unify_policy(include_str!("../test/test-policy2.json"));
assert_ne!(policy_1, policy_2);
assert!(!service
.policy_list()
.await
.unwrap()
.contains(&TEST_POLICY_NAME.to_string()));
service
.policy_apply(TEST_POLICY_NAME, &policy_1)
.await
.unwrap();
assert!(service
.policy_list()
.await
.unwrap()
.contains(&TEST_POLICY_NAME.to_string()));
assert_eq!(
unify_policy(&service.policy_content(TEST_POLICY_NAME).await.unwrap()),
policy_1
);
service
.policy_apply(TEST_POLICY_NAME, &policy_2)
.await
.unwrap();
assert!(service
.policy_list()
.await
.unwrap()
.contains(&TEST_POLICY_NAME.to_string()));
assert_eq!(
unify_policy(&service.policy_content(TEST_POLICY_NAME).await.unwrap()),
policy_2
);
}
#[tokio::test]
async fn policy_user() {
let _ = env_logger::builder().is_test(true).try_init();
let srv = MinioTestServer::start().await.unwrap();
let service = srv.as_service();
let user = MinioUser::gen_random();
assert!(!service.user_list().await.unwrap().contains(&user.username));
service.user_apply(&user).await.unwrap();
assert!(service.user_list().await.unwrap().contains(&user.username));
}
#[tokio::test]
async fn attach_policy_user() {
let _ = env_logger::builder().is_test(true).try_init();
let srv = MinioTestServer::start().await.unwrap();
let service = srv.as_service();
let user = MinioUser::gen_random();
service.user_apply(&user).await.unwrap();
service
.policy_apply(TEST_POLICY_NAME, include_str!("../test/test-policy1.json"))
.await
.unwrap();
assert!(!service
.policy_attach_get_user_list(&user)
.await
.unwrap()
.contains(&TEST_POLICY_NAME.to_string()));
service
.policy_attach_user(&user, TEST_POLICY_NAME)
.await
.unwrap();
assert!(service
.policy_attach_get_user_list(&user)
.await
.unwrap()
.contains(&TEST_POLICY_NAME.to_string()));
}
}