Compare commits
10 Commits
master
...
e3860d9f93
| Author | SHA1 | Date | |
|---|---|---|---|
| e3860d9f93 | |||
| fb2a9f39fb | |||
| f779715d65 | |||
| a3f523410f | |||
| 11d2ba5312 | |||
| 76c22150c0 | |||
| 36aaf5fb4d | |||
| 547cc02800 | |||
| 4b7748bfbf | |||
| 68c27a310d |
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
/target
|
||||||
|
.idea
|
||||||
2121
Cargo.lock
generated
Normal file
2121
Cargo.lock
generated
Normal file
File diff suppressed because it is too large
Load Diff
22
Cargo.toml
Normal file
22
Cargo.toml
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
[package]
|
||||||
|
name = "minio-operator"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
log = "0.4.17"
|
||||||
|
env_logger = "0.10.0"
|
||||||
|
anyhow = "1.0.71"
|
||||||
|
serde = { version = "1.0.162", features = ["derive"] }
|
||||||
|
serde_json = "1.0.96"
|
||||||
|
schemars = "0.8.12"
|
||||||
|
tokio = { version = "1.28.0", features = ["full"] }
|
||||||
|
kube = { version = "0.82.2", features = ["runtime", "derive"] }
|
||||||
|
k8s-openapi = { version = "0.18.0", features = ["v1_26"] } # TODO : switch to v1_27
|
||||||
|
futures = "0.3.28"
|
||||||
|
thiserror = "1.0.40"
|
||||||
|
rand = "0.8.5"
|
||||||
|
mktemp = "0.5.0"
|
||||||
|
reqwest = "0.11.17"
|
||||||
10
README.md
10
README.md
@@ -2,4 +2,12 @@
|
|||||||
|
|
||||||
Automatically create Minio buckets based on K8S CRD.
|
Automatically create Minio buckets based on K8S CRD.
|
||||||
|
|
||||||
WIP, early project
|
WIP, early project
|
||||||
|
|
||||||
|
Apply all K8s config files manually:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cat yaml/*.yaml | kubectl apply -f -
|
||||||
|
```
|
||||||
|
|
||||||
|
Note : [mc tool](https://min.io/download) is required
|
||||||
11
src/constants.rs
Normal file
11
src/constants.rs
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
//! # Application constants
|
||||||
|
pub const SECRET_MINIO_INSTANCE_ACCESS_KEY: &str = "accessKey";
|
||||||
|
pub const SECRET_MINIO_INSTANCE_SECRET_KEY: &str = "secretKey";
|
||||||
|
|
||||||
|
pub const SECRET_MINIO_BUCKET_ACCESS_KEY: &str = "accessKey";
|
||||||
|
pub const SECRET_MINIO_BUCKET_SECRET_KEY: &str = "secretKey";
|
||||||
|
|
||||||
|
pub const SECRET_MINIO_BUCKET_ACCESS_LEN: usize = 20;
|
||||||
|
pub const SECRET_MINIO_BUCKET_SECRET_LEN: usize = 35;
|
||||||
|
|
||||||
|
pub const MC_EXE: &str = "mc";
|
||||||
51
src/crd.rs
Normal file
51
src/crd.rs
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
use kube::CustomResource;
|
||||||
|
use schemars::JsonSchema;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
#[derive(CustomResource, Debug, Serialize, Deserialize, Default, Clone, JsonSchema)]
|
||||||
|
#[kube(
|
||||||
|
group = "communiquons.org",
|
||||||
|
version = "v1",
|
||||||
|
kind = "MinioInstance",
|
||||||
|
namespaced
|
||||||
|
)]
|
||||||
|
pub struct MinioInstanceSpec {
|
||||||
|
pub endpoint: String,
|
||||||
|
pub credentials: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize, Deserialize, Default, Clone, JsonSchema)]
|
||||||
|
pub enum RetentionType {
|
||||||
|
#[default]
|
||||||
|
#[serde(rename_all = "lowercase")]
|
||||||
|
Compliance,
|
||||||
|
#[serde(rename_all = "lowercase")]
|
||||||
|
Governance,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize, Deserialize, Default, Clone, JsonSchema)]
|
||||||
|
pub struct BucketRetention {
|
||||||
|
pub validity: usize,
|
||||||
|
pub r#type: RetentionType,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(CustomResource, Debug, Serialize, Deserialize, Default, Clone, JsonSchema)]
|
||||||
|
#[kube(
|
||||||
|
group = "communiquons.org",
|
||||||
|
version = "v1",
|
||||||
|
kind = "MinioBucket",
|
||||||
|
namespaced
|
||||||
|
)]
|
||||||
|
pub struct MinioBucketSpec {
|
||||||
|
pub instance: String,
|
||||||
|
pub name: String,
|
||||||
|
pub secret: String,
|
||||||
|
#[serde(default)]
|
||||||
|
pub anonymous_read_access: bool,
|
||||||
|
#[serde(default)]
|
||||||
|
pub versioning: bool,
|
||||||
|
pub quota: Option<usize>,
|
||||||
|
#[serde(default)]
|
||||||
|
pub lock: bool,
|
||||||
|
pub retention: Option<BucketRetention>,
|
||||||
|
}
|
||||||
7
src/lib.rs
Normal file
7
src/lib.rs
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
pub mod constants;
|
||||||
|
pub mod crd;
|
||||||
|
pub mod minio;
|
||||||
|
#[cfg(test)]
|
||||||
|
pub mod minio_test_server;
|
||||||
|
pub mod secrets;
|
||||||
|
pub mod utils;
|
||||||
95
src/main.rs
Normal file
95
src/main.rs
Normal file
@@ -0,0 +1,95 @@
|
|||||||
|
use futures::TryStreamExt;
|
||||||
|
use k8s_openapi::api::core::v1::Secret;
|
||||||
|
use kube::runtime::{watcher, WatchStreamExt};
|
||||||
|
use kube::{Api, Client};
|
||||||
|
use minio_operator::constants::{
|
||||||
|
SECRET_MINIO_BUCKET_ACCESS_KEY, SECRET_MINIO_BUCKET_SECRET_KEY,
|
||||||
|
SECRET_MINIO_INSTANCE_ACCESS_KEY, SECRET_MINIO_INSTANCE_SECRET_KEY,
|
||||||
|
};
|
||||||
|
use minio_operator::crd::{MinioBucket, MinioInstance};
|
||||||
|
use minio_operator::minio::{MinioService, MinioUser};
|
||||||
|
use minio_operator::secrets::{create_secret, read_secret_str};
|
||||||
|
use std::collections::BTreeMap;
|
||||||
|
|
||||||
|
#[tokio::main]
|
||||||
|
async fn main() -> anyhow::Result<()> {
|
||||||
|
env_logger::init_from_env(env_logger::Env::new().default_filter_or("info"));
|
||||||
|
|
||||||
|
let client = Client::try_default().await?;
|
||||||
|
|
||||||
|
let buckets: Api<MinioBucket> = Api::default_namespaced(client.clone());
|
||||||
|
|
||||||
|
// Listen for events / buckets creation or update (deletion is not supported)
|
||||||
|
let wc = watcher::Config::default();
|
||||||
|
let bw = watcher(buckets, wc).applied_objects();
|
||||||
|
futures::pin_mut!(bw);
|
||||||
|
|
||||||
|
while let Some(b) = bw.try_next().await? {
|
||||||
|
if let Err(e) = apply_bucket(&b, &client).await {
|
||||||
|
log::error!(
|
||||||
|
"Failed to apply desired configuration for applied bucket {} : {}",
|
||||||
|
b.spec.name,
|
||||||
|
e
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Make sure a bucket is compliant with a desired configuration
|
||||||
|
async fn apply_bucket(b: &MinioBucket, client: &Client) -> anyhow::Result<()> {
|
||||||
|
log::info!("Apply configuration for bucket {}", b.spec.name);
|
||||||
|
|
||||||
|
// Get instance information
|
||||||
|
let instances: Api<MinioInstance> = Api::default_namespaced(client.clone());
|
||||||
|
let instance = instances.get(&b.spec.instance).await?;
|
||||||
|
|
||||||
|
// Get instance configuration
|
||||||
|
let secrets: Api<Secret> = Api::default_namespaced(client.clone());
|
||||||
|
let instance_secret = secrets.get(&instance.spec.credentials).await?;
|
||||||
|
let service = MinioService {
|
||||||
|
hostname: instance.spec.endpoint,
|
||||||
|
access_key: read_secret_str(&instance_secret, SECRET_MINIO_INSTANCE_ACCESS_KEY)?,
|
||||||
|
secret_key: read_secret_str(&instance_secret, SECRET_MINIO_INSTANCE_SECRET_KEY)?,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Get user key & password
|
||||||
|
let user_secret = match secrets.get_opt(&b.spec.secret).await? {
|
||||||
|
Some(s) => s,
|
||||||
|
None => {
|
||||||
|
log::info!(
|
||||||
|
"Needs to create the secret {} for the bucket {}",
|
||||||
|
b.spec.secret,
|
||||||
|
b.spec.name
|
||||||
|
);
|
||||||
|
|
||||||
|
// The secret needs to be created
|
||||||
|
let new_user = MinioUser::gen_random();
|
||||||
|
create_secret(
|
||||||
|
&secrets,
|
||||||
|
&b.spec.secret,
|
||||||
|
BTreeMap::from([
|
||||||
|
(
|
||||||
|
SECRET_MINIO_BUCKET_ACCESS_KEY.to_string(),
|
||||||
|
new_user.username,
|
||||||
|
),
|
||||||
|
(
|
||||||
|
SECRET_MINIO_BUCKET_SECRET_KEY.to_string(),
|
||||||
|
new_user.password,
|
||||||
|
),
|
||||||
|
]),
|
||||||
|
)
|
||||||
|
.await?
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let user = MinioUser {
|
||||||
|
username: read_secret_str(&user_secret, SECRET_MINIO_BUCKET_ACCESS_KEY)?,
|
||||||
|
password: read_secret_str(&user_secret, SECRET_MINIO_BUCKET_SECRET_KEY)?,
|
||||||
|
};
|
||||||
|
|
||||||
|
println!("{:?}", service);
|
||||||
|
println!("{:?}", user);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
690
src/minio.rs
Normal file
690
src/minio.rs
Normal file
@@ -0,0 +1,690 @@
|
|||||||
|
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;
|
||||||
|
|
||||||
|
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,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[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>,
|
||||||
|
}
|
||||||
|
|
||||||
|
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 = mktemp::Temp::new_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)))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create a bucket
|
||||||
|
pub async fn create_bucket(&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()].to_vec();
|
||||||
|
|
||||||
|
if b.lock || b.retention.is_some() {
|
||||||
|
args.push("--with-lock");
|
||||||
|
}
|
||||||
|
|
||||||
|
let res = self.exec_mc_cmd::<BasicMinioResult>(&args).await?;
|
||||||
|
if res.get(0).map(|r| r.success()) != Some(true) {
|
||||||
|
return Err(MinioError::MakeBucketFailed.into());
|
||||||
|
}
|
||||||
|
|
||||||
|
self.bucket_set_versioning(&b.name, b.versioning).await?;
|
||||||
|
self.bucket_set_anonymous_access(&b.name, b.anonymous_read_access)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
// Set quota, if requested
|
||||||
|
self.bucket_set_quota(&b.name, b.quota).await?;
|
||||||
|
|
||||||
|
// Set retention, if requested
|
||||||
|
if let Some(retention) = &b.retention {
|
||||||
|
let days = format!("{}d", retention.validity);
|
||||||
|
|
||||||
|
let res = 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?;
|
||||||
|
|
||||||
|
if res.get(0).map(|r| r.success()) != Some(true) {
|
||||||
|
return Err(MinioError::SetRetentionFailed.into());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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.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<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 = 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.get(0).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 = 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) = "a {
|
||||||
|
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.get(0).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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
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();
|
||||||
|
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
|
||||||
|
.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());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[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!(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
|
||||||
|
.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
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[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
|
||||||
|
.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());
|
||||||
|
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
|
||||||
|
.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());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn bucket_without_qutoa() {
|
||||||
|
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!(
|
||||||
|
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_qutoa() {
|
||||||
|
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: 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)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO : with retention
|
||||||
|
// TODO : without retention
|
||||||
|
}
|
||||||
113
src/minio_test_server.rs
Normal file
113
src/minio_test_server.rs
Normal file
@@ -0,0 +1,113 @@
|
|||||||
|
//! # Minio server controller
|
||||||
|
//!
|
||||||
|
//! Used for testing only
|
||||||
|
|
||||||
|
use crate::minio::MinioService;
|
||||||
|
use crate::utils::rand_str;
|
||||||
|
use rand::RngCore;
|
||||||
|
use std::io::ErrorKind;
|
||||||
|
use std::process::{Child, Command};
|
||||||
|
|
||||||
|
pub struct MinioTestServer {
|
||||||
|
#[allow(dead_code)]
|
||||||
|
storage_base_dir: mktemp::Temp,
|
||||||
|
child: Child,
|
||||||
|
pub api_port: u16,
|
||||||
|
pub root_user: String,
|
||||||
|
pub root_password: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl MinioTestServer {
|
||||||
|
pub async fn start() -> anyhow::Result<Self> {
|
||||||
|
let storage_dir = mktemp::Temp::new_dir()?;
|
||||||
|
|
||||||
|
let root_user = rand_str(30);
|
||||||
|
let root_password = rand_str(30);
|
||||||
|
let api_port = (2000 + rand::thread_rng().next_u64() % 5000) as u16;
|
||||||
|
log::info!(
|
||||||
|
"Spwan a new Minio server on port {} with root credentials {}:{}",
|
||||||
|
api_port,
|
||||||
|
root_user,
|
||||||
|
root_password
|
||||||
|
);
|
||||||
|
|
||||||
|
let child = Command::new("minio")
|
||||||
|
.current_dir(storage_dir.clone())
|
||||||
|
.arg("server")
|
||||||
|
.arg("--address")
|
||||||
|
.arg(format!(":{api_port}"))
|
||||||
|
.arg(storage_dir.to_str().unwrap())
|
||||||
|
.env("MINIO_ROOT_USER", &root_user)
|
||||||
|
.env("MINIO_ROOT_PASSWORD", &root_password)
|
||||||
|
.spawn()?;
|
||||||
|
|
||||||
|
let instance = Self {
|
||||||
|
storage_base_dir: storage_dir,
|
||||||
|
child,
|
||||||
|
api_port,
|
||||||
|
root_user,
|
||||||
|
root_password,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Wait for Minio to become ready
|
||||||
|
let mut check_count = 0;
|
||||||
|
loop {
|
||||||
|
if check_count >= 100 {
|
||||||
|
log::error!("Minio failed to respond properly in time!");
|
||||||
|
return Err(std::io::Error::new(
|
||||||
|
ErrorKind::Other,
|
||||||
|
"Minio failed to respond in time!",
|
||||||
|
)
|
||||||
|
.into());
|
||||||
|
}
|
||||||
|
check_count += 1;
|
||||||
|
|
||||||
|
if instance.as_service().is_ready().await {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(instance)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn base_url(&self) -> String {
|
||||||
|
format!("http://127.0.0.1:{}", self.api_port)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get a MinioService instance of this temporary server
|
||||||
|
pub fn as_service(&self) -> MinioService {
|
||||||
|
MinioService {
|
||||||
|
hostname: self.base_url(),
|
||||||
|
access_key: self.root_user.clone(),
|
||||||
|
secret_key: self.root_password.clone(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Drop for MinioTestServer {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
if let Err(e) = self.child.kill() {
|
||||||
|
log::error!("Failed to kill child server! {}", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod test {
|
||||||
|
use crate::minio_test_server::MinioTestServer;
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn start_minio() {
|
||||||
|
let _ = env_logger::builder().is_test(true).try_init();
|
||||||
|
|
||||||
|
let server = MinioTestServer::start().await.unwrap();
|
||||||
|
let service = server.as_service();
|
||||||
|
println!("{:?}", service);
|
||||||
|
|
||||||
|
assert!(service.is_ready().await);
|
||||||
|
|
||||||
|
// Check if minio properly exit
|
||||||
|
drop(server);
|
||||||
|
assert!(!service.is_ready().await);
|
||||||
|
}
|
||||||
|
}
|
||||||
64
src/secrets.rs
Normal file
64
src/secrets.rs
Normal file
@@ -0,0 +1,64 @@
|
|||||||
|
use k8s_openapi::api::core::v1::Secret;
|
||||||
|
use k8s_openapi::apimachinery::pkg::apis::meta::v1::ObjectMeta;
|
||||||
|
use kube::api::PostParams;
|
||||||
|
use kube::Api;
|
||||||
|
use std::collections::BTreeMap;
|
||||||
|
|
||||||
|
#[derive(thiserror::Error, Debug)]
|
||||||
|
enum SecretError {
|
||||||
|
#[error("Secret has no data!")]
|
||||||
|
MissingData,
|
||||||
|
#[error("The key '{0}' is not present in the secret!")]
|
||||||
|
MissingKey(String),
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Attempt to read a value contained in a secret. Returns an error in case
|
||||||
|
/// of failure
|
||||||
|
pub fn read_secret_str(s: &Secret, key: &str) -> anyhow::Result<String> {
|
||||||
|
let data = s.data.as_ref().ok_or(SecretError::MissingData)?;
|
||||||
|
|
||||||
|
let value = data
|
||||||
|
.get(key)
|
||||||
|
.ok_or(SecretError::MissingKey(key.to_string()))?;
|
||||||
|
|
||||||
|
Ok(String::from_utf8(value.0.clone())?)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create a secret consisting only of string key / value pairs
|
||||||
|
pub async fn create_secret(
|
||||||
|
secrets: &Api<Secret>,
|
||||||
|
name: &str,
|
||||||
|
values: BTreeMap<String, String>,
|
||||||
|
) -> anyhow::Result<Secret> {
|
||||||
|
Ok(secrets
|
||||||
|
.create(
|
||||||
|
&PostParams::default(),
|
||||||
|
&Secret {
|
||||||
|
data: None,
|
||||||
|
immutable: None,
|
||||||
|
metadata: ObjectMeta {
|
||||||
|
annotations: None,
|
||||||
|
creation_timestamp: None,
|
||||||
|
deletion_grace_period_seconds: None,
|
||||||
|
deletion_timestamp: None,
|
||||||
|
finalizers: None,
|
||||||
|
generate_name: None,
|
||||||
|
generation: None,
|
||||||
|
labels: Some(BTreeMap::from([(
|
||||||
|
"created-by".to_string(),
|
||||||
|
"miniok8sbuckets".to_string(),
|
||||||
|
)])),
|
||||||
|
managed_fields: None,
|
||||||
|
name: Some(name.to_string()),
|
||||||
|
namespace: None,
|
||||||
|
owner_references: None,
|
||||||
|
resource_version: None,
|
||||||
|
self_link: None,
|
||||||
|
uid: None,
|
||||||
|
},
|
||||||
|
string_data: Some(values),
|
||||||
|
type_: None,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.await?)
|
||||||
|
}
|
||||||
11
src/utils.rs
Normal file
11
src/utils.rs
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
use rand::distributions::Alphanumeric;
|
||||||
|
use rand::Rng;
|
||||||
|
|
||||||
|
/// Generate a random string of a given size
|
||||||
|
pub fn rand_str(len: usize) -> String {
|
||||||
|
rand::thread_rng()
|
||||||
|
.sample_iter(&Alphanumeric)
|
||||||
|
.take(len)
|
||||||
|
.map(char::from)
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
28
test/bucket-policy.yaml
Normal file
28
test/bucket-policy.yaml
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
{
|
||||||
|
"Version": "2012-10-17",
|
||||||
|
"Statement": [
|
||||||
|
{
|
||||||
|
"Sid": "ListObjectsInBucket",
|
||||||
|
"Effect": "Allow",
|
||||||
|
"Action": [
|
||||||
|
"s3:ListBucket"
|
||||||
|
],
|
||||||
|
"Resource": [
|
||||||
|
"arn:aws:s3:::bucket"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Sid": "AllObjectActions",
|
||||||
|
"Effect": "Allow",
|
||||||
|
"Action": [
|
||||||
|
"s3:DeleteObject",
|
||||||
|
"s3:Get*",
|
||||||
|
"s3:PutObject",
|
||||||
|
"s3:*Object"
|
||||||
|
],
|
||||||
|
"Resource": [
|
||||||
|
"arn:aws:s3:::bucket/*"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
25
test/first-test.yaml
Normal file
25
test/first-test.yaml
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
apiVersion: v1
|
||||||
|
kind: Secret
|
||||||
|
metadata:
|
||||||
|
name: minio-root
|
||||||
|
type: Opaque
|
||||||
|
data:
|
||||||
|
accessKey: bWluaW9hZG1pbg==
|
||||||
|
secretKey: bWluaW9hZG1pbg==
|
||||||
|
---
|
||||||
|
apiVersion: "communiquons.org/v1"
|
||||||
|
kind: MinioInstance
|
||||||
|
metadata:
|
||||||
|
name: my-minio-instance
|
||||||
|
spec:
|
||||||
|
endpoint: http://minio:9000/
|
||||||
|
credentials: minio-root
|
||||||
|
---
|
||||||
|
apiVersion: "communiquons.org/v1"
|
||||||
|
kind: MinioBucket
|
||||||
|
metadata:
|
||||||
|
name: first-bucket
|
||||||
|
spec:
|
||||||
|
instance: my-minio-instance
|
||||||
|
name: first-bucket
|
||||||
|
secret: first-bucket-secret
|
||||||
9
test/second-bucket.yaml
Normal file
9
test/second-bucket.yaml
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
---
|
||||||
|
apiVersion: "communiquons.org/v1"
|
||||||
|
kind: MinioBucket
|
||||||
|
metadata:
|
||||||
|
name: second-bucket
|
||||||
|
spec:
|
||||||
|
instance: my-minio-instance
|
||||||
|
name: second-bucket
|
||||||
|
secret: second-bucket-secret
|
||||||
@@ -34,7 +34,7 @@ spec:
|
|||||||
type: string
|
type: string
|
||||||
example: mybucket
|
example: mybucket
|
||||||
secret:
|
secret:
|
||||||
description: The name of the secret that will receive an access key & token with write access on the bucket
|
description: The name of the secret that will receive an access key & a secret key with write access on the bucket
|
||||||
type: string
|
type: string
|
||||||
example: secret-name
|
example: secret-name
|
||||||
anonymous_read_access:
|
anonymous_read_access:
|
||||||
@@ -47,8 +47,12 @@ spec:
|
|||||||
default: false
|
default: false
|
||||||
quota:
|
quota:
|
||||||
type: integer
|
type: integer
|
||||||
description: Limits the amount of data in the bucket, in Megabytes. By default it is unlimited
|
description: Limits the amount of data in the bucket, in bytes. By default it is unlimited
|
||||||
example: 100
|
example: 1000000000
|
||||||
|
lock:
|
||||||
|
description: Object locking prevent objects from being deleted. Will be considered as set to true when retention is defined.
|
||||||
|
type: boolean
|
||||||
|
default: false
|
||||||
retention:
|
retention:
|
||||||
type: object
|
type: object
|
||||||
description: Impose rules to prevent object deletion for a period of time. It requires versioning to be enabled/disabled
|
description: Impose rules to prevent object deletion for a period of time. It requires versioning to be enabled/disabled
|
||||||
@@ -60,15 +64,12 @@ spec:
|
|||||||
type: integer
|
type: integer
|
||||||
description: The number of days the data shall be kept
|
description: The number of days the data shall be kept
|
||||||
example: 180
|
example: 180
|
||||||
mode:
|
type:
|
||||||
type: string
|
type: string
|
||||||
description: Retention type. In governance mode, some privileged user can bypass retention policy, while in governance policy, no one, including root user, can delete the data
|
description: Retention type. In governance mode, some privileged user can bypass retention policy, while in governance policy, no one, including root user, can delete the data
|
||||||
enum:
|
enum:
|
||||||
- compliance
|
- compliance
|
||||||
- governance
|
- governance
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# either Namespaced or Cluster
|
# either Namespaced or Cluster
|
||||||
scope: Namespaced
|
scope: Namespaced
|
||||||
names:
|
names:
|
||||||
@@ -81,4 +82,5 @@ spec:
|
|||||||
# shortNames allow shorter string to match your resource on the CLI
|
# shortNames allow shorter string to match your resource on the CLI
|
||||||
shortNames:
|
shortNames:
|
||||||
- mbs
|
- mbs
|
||||||
- buckets
|
- buckets
|
||||||
|
---
|
||||||
|
|||||||
@@ -45,4 +45,5 @@ spec:
|
|||||||
kind: MinioInstance
|
kind: MinioInstance
|
||||||
# shortNames allow shorter string to match your resource on the CLI
|
# shortNames allow shorter string to match your resource on the CLI
|
||||||
shortNames:
|
shortNames:
|
||||||
- mis
|
- mis
|
||||||
|
---
|
||||||
|
|||||||
@@ -30,3 +30,4 @@ roleRef:
|
|||||||
apiGroup: rbac.authorization.k8s.io
|
apiGroup: rbac.authorization.k8s.io
|
||||||
kind: Role
|
kind: Role
|
||||||
name: minio-buckets
|
name: minio-buckets
|
||||||
|
---
|
||||||
|
|||||||
Reference in New Issue
Block a user