diff --git a/src/minio.rs b/src/minio.rs index 9950a6d..b9dfc55 100644 --- a/src/minio.rs +++ b/src/minio.rs @@ -1,9 +1,11 @@ +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::{MinioBucketSpec, RetentionType}; use crate::utils::rand_str; -use serde::de::DeserializeOwned; -use serde::Deserialize; -use std::process::Command; const MC_ALIAS_NAME: &str = "managedminioinst"; @@ -62,6 +64,16 @@ struct BasicMinioResult { pub status: String, } +#[derive(Debug, Clone, Deserialize)] +struct MinioGetVersioningResult { + pub versioning: Option, +} + +#[derive(Debug, Clone, Deserialize)] +struct MinioVersioning { + pub status: String, +} + impl BasicMinioResult { pub fn success(&self) -> bool { self.status == "success" @@ -89,6 +101,11 @@ impl MinioService { 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(&self, args: &[&str]) -> anyhow::Result> where @@ -179,10 +196,6 @@ impl MinioService { let bucket_name = format!("{}/{}", MC_ALIAS_NAME, b.name); let mut args = ["mb", bucket_name.as_str()].to_vec(); - if b.versioning { - args.push("--with-versioning"); - } - if b.lock || b.retention.is_some() { args.push("--with-lock"); } @@ -192,6 +205,8 @@ impl MinioService { return Err(MinioError::MakeBucketFailed.into()); } + self.bucket_set_versioning(&b.name, b.versioning).await?; + // Enable anonymous access, eg. public hosting if b.anonymous_read_access { let target = format!("{}/*", bucket_name); @@ -249,6 +264,38 @@ impl MinioService { 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::(&[ + "version", + match enable { + true => "enable", + false => "suspend", + }, + bucket_name.as_str(), + ]) + .await?; + + if res.get(0).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 { + let bucket_name = self.absolute_bucket_name(bucket_name); + Ok(self.exec_mc_cmd::(&["version", "info", bucket_name.as_str()]) + .await? + .remove(0) + .versioning + .map(|v| v.status.to_lowercase().eq("enabled")) + .unwrap_or_default()) + } } #[cfg(test)] @@ -256,6 +303,8 @@ mod test { use crate::crd::MinioBucketSpec; use crate::minio_test_server::MinioTestServer; + const TEST_BUCKET_NAME: &str = "mybucket"; + #[tokio::test] async fn list_buckets_empty_instance() { let srv = MinioTestServer::start().await.unwrap(); @@ -278,7 +327,7 @@ mod test { service .create_bucket(&MinioBucketSpec { instance: "".to_string(), - name: "mybucket".to_string(), + name: TEST_BUCKET_NAME.to_string(), secret: "".to_string(), anonymous_read_access: false, versioning: false, @@ -288,13 +337,148 @@ mod test { }) .await .unwrap(); - assert!(service.bucket_exists("mybucket").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 + .create_bucket(&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_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 + .create_bucket(&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 + ); + } + + // With versioning + #[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 + .create_bucket(&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 + .create_bucket(&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 + .create_bucket(&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()); } - // TODO : with anonymous access - // TODO : without anonymous access - // TODO : with versioning - // TODO : without versioning // TODO : with quota // TODO : without quota // TODO : with lock