Compare commits

..

16 Commits

Author SHA1 Message Date
073c91fe0d Can attach policies to users
Some checks failed
continuous-integration/drone/push Build is failing
2023-05-08 17:08:59 +02:00
c90e46f038 Can create users
All checks were successful
continuous-integration/drone/push Build is passing
2023-05-08 16:18:15 +02:00
328036b7b3 Add basic drone configuration
All checks were successful
continuous-integration/drone Build is passing
2023-05-08 16:04:23 +02:00
1ae2cf7282 Can create policies 2023-05-08 16:00:53 +02:00
42e2ea5539 Improve terminology 2023-05-08 15:28:41 +02:00
239b58d8db Properly test retention 2023-05-08 15:23:19 +02:00
e3860d9f93 Test properly quota definition 2023-05-08 14:38:54 +02:00
fb2a9f39fb Test properly anonymous access 2023-05-08 14:23:30 +02:00
f779715d65 Test properly versioning 2023-05-08 14:10:15 +02:00
a3f523410f Add buckets creation 2023-05-06 16:41:36 +02:00
11d2ba5312 Spwan test minio instance 2023-05-06 13:15:17 +02:00
76c22150c0 Automatically create secret for bucket if missing 2023-05-06 11:47:18 +02:00
36aaf5fb4d Read minio instance secret key 2023-05-06 10:58:18 +02:00
547cc02800 Can listen to Minio buckets creation / update 2023-05-06 09:35:18 +02:00
4b7748bfbf Create first test bucket 2023-05-05 20:05:22 +02:00
68c27a310d Create base Rust project 2023-05-05 19:53:17 +02:00
26 changed files with 966 additions and 2068 deletions

View File

@@ -4,64 +4,13 @@ type: docker
name: default
steps:
- name: fetch_dependencies
- name: cargo_check
image: rust
volumes:
- name: rust_registry
path: /usr/local/cargo/registry
commands:
- cargo fetch
- name: code_quality
image: rust
volumes:
- name: rust_registry
path: /usr/local/cargo/registry
depends_on:
- fetch_dependencies
commands:
- rustup component add clippy
- cargo clippy -- -D warnings
- name: test
image: rust
depends_on:
- code_quality
volumes:
- name: rust_registry
path: /usr/local/cargo/registry
commands:
- wget -O /usr/bin/minio https://dl.min.io/server/minio/release/linux-amd64/minio
- wget -O /usr/bin/mc https://dl.min.io/client/mc/release/linux-amd64/mc
- chmod +x /usr/bin/minio /usr/bin/mc
- rustup component add clippy
- cargo clippy -- -D warnings
- cargo test
- name: build_doc
image: python
environment:
AWS_ACCESS_KEY_ID:
from_secret: AWS_ACCESS_KEY_ID
AWS_SECRET_ACCESS_KEY:
from_secret: AWS_SECRET_ACCESS_KEY
AWS_DEFAULT_REGION: us-east-1
when:
branch:
- master
event:
exclude:
- pull_request
commands:
# Build website
- pip install mkdocs-material
- mkdocs build --site-dir public
# Install AWS
- curl "https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip" -o "awscliv2.zip"
- unzip awscliv2.zip
- ./aws/install
- aws configure set default.s3.signature_version s3v4
# Upload to bucket
- cd public && aws --endpoint-url https://s3.communiquons.org s3 sync . s3://miniok8sbucketsoperator-website
volumes:
- name: rust_registry
temp: {}

2115
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -6,17 +6,17 @@ edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
log = "0.4.28"
env_logger = "0.11.8"
anyhow = "1.0.100"
serde = { version = "1.0.228", features = ["derive"] }
serde_json = "1.0.145"
schemars = "1.1.0"
tokio = { version = "1.48.0", features = ["full"] }
kube = { version = "2.0.1", features = ["runtime", "derive"] }
k8s-openapi = { version = "0.26.0", features = ["v1_31"] }
futures = "0.3.31"
thiserror = "2.0.17"
rand = "0.9.2"
mktemp = "0.5.1"
reqwest = "0.12.24"
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"

View File

@@ -1,10 +0,0 @@
FROM debian:bookworm-slim
RUN apt-get update && apt-get install -y --no-install-recommends \
libssl3 \
&& rm -rf /var/lib/apt/lists/*
COPY minio-operator /usr/local/bin/minio-operator
COPY mc /usr/local/bin/mc
ENTRYPOINT ["/usr/local/bin/minio-operator"]

View File

@@ -1,6 +1,13 @@
# MinioK8sBuckets
[![Build Status](https://drone.communiquons.org/api/badges/pierre/MinioK8sBuckets/status.svg)](https://drone.communiquons.org/pierre/MinioK8sBuckets)
Automatically create Minio buckets based on K8S Custom Resources.
Automatically create Minio buckets based on K8S CRD.
See the [docs](docs) to learn more.
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

View File

@@ -1,14 +0,0 @@
#!/bin/bash
cargo build --release
TEMP_DIR=$(mktemp -d)
cp target/release/minio-operator "$TEMP_DIR"
# Download mc
wget -O "$TEMP_DIR/mc" https://dl.min.io/client/mc/release/linux-amd64/mc
chmod +x "$TEMP_DIR/mc"
docker build -f Dockerfile "$TEMP_DIR" -t pierre42100/minio_operator
rm -r $TEMP_DIR

View File

@@ -1,109 +0,0 @@
# Setup for development
This guide will present you how to prepare your computer to update features of MinioK8SBucket
## Install Rust
As this project has been written using Rust, you will need to install it prior working on MinioK8SBucket. Please follow the official instructions: [https://www.rust-lang.org/tools/install](https://www.rust-lang.org/tools/install)
## Install Minikube
First, you need to install Minikube on your computer to have a K8S environment. In order to do this, please follow the official instructions: [https://minikube.sigs.k8s.io/docs/start](https://minikube.sigs.k8s.io/docs/start)
## Start Minikube
You will then need to start Minikube using the following command:
```bash
minikube start
```
You can then make sure that Minikube is working properly:
```
minikube kubectl get nodes
```
You should get a response similar to this one:
```
NAME STATUS ROLES AGE VERSION
minikube Ready control-plane 2m16s v1.32.0
```
## Clone repository
Clone this repository using:
```bash
https://gitea.communiquons.org/pierre/MinioK8sBuckets
```
!!! note "Gitea account request"
If you want to get a Gitea account to make pull request on this repository, you will need to contact me at: `pierre.git@communiquons.org`
## Deploy Minio
First, enable Minikube tunnel:
```bash
minikube tunnel --bind-address '127.0.0.1'
```
You will then need to deploy Minio in Minikube. Apply the Minio deployment located at the in MinioK8SBucket repository:
```bash
minikube kubectl -- apply -f yaml/minio-dev-deployment.yml
```
Wait for the pod to become ready:
```bash
minikube kubectl -- get pods -w
```
Check for the availability of the service that expose Minio to your host computer:
```bash
minikube kubectl -- get services
```
You should get a result similar to this one:
```
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 31m
minio LoadBalancer 10.103.82.87 127.0.0.1 9000:30656/TCP,9090:31369/TCP 6m40s
```
You should be able to access minio at the following address: [http://127.0.0.1:9090](http://127.0.0.1:9090/)
Minio API should be available at: [http://127.0.0.1:9000/](http://127.0.0.1:9000/)
## Deploy CRD
You will need then to deploy the Custom Resource Definitions of MinioK8SBucket using the following command:
```bash
minikube kubectl -- apply -f yaml/crd.yaml
```
## Run operator
You can then run the project using the following command:
```bash
cargo fmt && cargo clippy && RUST_LOG=debug cargo run --
```
## Create a first bucket
You should be able to create a first bucket using the following command:
```bash
minikube kubectl -- apply -f test/test-outside-cluster.yaml
```
The bucket should then appear in buckets list:
```bash
minikube kubectl -- get buckets
```
```
NAME AGE
first-bucket 8m43s
```
Have fun working for MinioK8SBucket!

View File

@@ -1,116 +0,0 @@
# Minio K8S bucket operator
An operator to automatically create and update S3 buckets on Minio, with their accounts.
One deployed, this tool will allow you to automatically create Minio accounts associated with buckets.
## Pre-requisites
You will need:
* `kubectl` access to the target cluster
* A running Minio instance, and especially:
* The URL where the API of the instance can be reached
* The root credentials
## Installation
The operator can be installed using the following commands:
```bash
kubectl apply -f https://raw.githubusercontent.com/pierre42100/MinioK8sBuckets/master/yaml/crd.yaml
kubectl apply -f https://raw.githubusercontent.com/pierre42100/MinioK8sBuckets/master/yaml/deployment.yaml
```
!!! warning "Known limitation"
The operator install a deployment on the `default` namespace. Currently, only this namespace is supported!
## Configure instance
In order to create buckets, the operator needs to know how to reach the Minio instance.
You first need to secret similar to that one:
```yaml
apiVersion: v1
kind: Secret
metadata:
name: minio-root
type: Opaque
dyringData:
accessKey: <MINIO_ROOT_ACCESS_KEY>
secretKey: <MINIO_ROOT_SECRET_KEY>
```
Replace `<MINIO_ROOT_ACCESS_KEY>` and `<MINIO_ROOT_SECRET_KEY>` with the appropriate values.
You can then declare a Minio instance simiarl to that one:
```yaml
apiVersion: "communiquons.org/v1"
kind: MinioInstance
metadata:
name: my-minio-instance
spec:
endpoint: https://minio.example.com/
credentials: minio-root
```
!!! note
Minio itself can be located outside of the Kubernetes cluster.
## Create a bucket
You are now ready to create your first bucket!
Here is a basic bucket example:
```yaml
apiVersion: "communiquons.org/v1"
kind: MinioBucket
metadata:
name: first-bucket
spec:
# The name of the minio instance
instance: my-minio-instance
# The name of the bucket to create
name: first-bucket
# The name of the secret that will be created
# by the operator which contains credentials to
# use to access the bucket
secret: first-bucket-secret
```
## More complete example
Here is a more complete example that makes use of all the available options:
```yaml
apiVersion: "communiquons.org/v1"
kind: MinioBucket
metadata:
name: my-bucket
spec:
instance: my-minio-instance
name: my-bucket
secret: my-bucket-secret
# This must be set to true to allow unauthenticated
# access to the bucket resources. Use this to host a
# static website for example
anonymous_read_access: true
# Enable versioning on the bucket => keep old versions
# of uploaded files
versioning: true
# If specified, a quota will be applied to the bucket, in bytes
quota: 1000000000
# Prevent files from being removed from the bucket. This parameter
# can not be changed, once the bucket has been created
lock: true
# Data retention policy. Versioning must be enabled to allow this
retention:
# The number of days data shall be kept
validity: 100
# compliance => nobody can bypass the policy
# governance => users with privileges might bypass policy restrictions
mode: compliance
```

View File

@@ -1,20 +0,0 @@
site_name: Minio K8S buckets operator
theme:
language: en
name: material
palette:
# Palette toggle for dark mode
- media: "(prefers-color-scheme: dark)"
scheme: slate
markdown_extensions:
- admonition
- pymdownx.details
- pymdownx.superfences
repo_url: https://gitea.communiquons.org/pierre/MinioK8sBuckets
edit_uri: src/branch/master/docs/
plugins:
- search

View File

@@ -1,3 +0,0 @@
{
"extends": ["local>renovate/presets"]
}

View File

@@ -4,5 +4,4 @@ pub mod minio;
#[cfg(test)]
pub mod minio_test_server;
pub mod secrets;
pub mod temp;
pub mod utils;

View File

@@ -10,7 +10,6 @@ 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;
use std::time::Duration;
#[tokio::main]
async fn main() -> anyhow::Result<()> {
@@ -55,17 +54,6 @@ async fn apply_bucket(b: &MinioBucket, client: &Client) -> anyhow::Result<()> {
secret_key: read_secret_str(&instance_secret, SECRET_MINIO_INSTANCE_SECRET_KEY)?,
};
// Check if Minio is responding
let mut ready_count = 0;
while !service.is_ready().await {
if ready_count > 10 {
panic!("Minio is unreachable!");
}
ready_count += 1;
tokio::time::sleep(Duration::from_millis(500)).await;
log::warn!("Minio is not responding yet, will try again to connect soon...");
}
// Get user key & password
let user_secret = match secrets.get_opt(&b.spec.secret).await? {
Some(s) => s,
@@ -77,7 +65,7 @@ async fn apply_bucket(b: &MinioBucket, client: &Client) -> anyhow::Result<()> {
);
// The secret needs to be created
let new_user = MinioUser::gen_random(&b.spec.name);
let new_user = MinioUser::gen_random();
create_secret(
&secrets,
&b.spec.secret,
@@ -100,22 +88,8 @@ async fn apply_bucket(b: &MinioBucket, client: &Client) -> anyhow::Result<()> {
password: read_secret_str(&user_secret, SECRET_MINIO_BUCKET_SECRET_KEY)?,
};
log::debug!("Create or update bucket...");
service.bucket_apply(&b.spec).await?;
let policy_name = format!("bucket-{}", b.spec.name);
log::debug!("Create or update policy '{policy_name}'...");
let policy_content =
include_str!("policy_template.json").replace("{{ bucket }}", b.spec.name.as_str());
service.policy_apply(&policy_name, &policy_content).await?;
log::debug!("Create or update user '{}'...", user.username);
service.user_apply(&user).await?;
log::debug!("Attach policy '{policy_name}' to user...");
service.policy_attach_user(&user, &policy_name).await?;
log::debug!("Successfully applied desired configuration!");
println!("{:?}", service);
println!("{:?}", user);
Ok(())
}

View File

@@ -5,7 +5,6 @@ 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";
@@ -44,9 +43,9 @@ pub struct MinioUser {
}
impl MinioUser {
pub fn gen_random(prefix: &str) -> Self {
pub fn gen_random() -> Self {
Self {
username: format!("{prefix}_{}", rand_str(SECRET_MINIO_BUCKET_ACCESS_LEN)),
username: rand_str(SECRET_MINIO_BUCKET_ACCESS_LEN),
password: rand_str(SECRET_MINIO_BUCKET_SECRET_LEN),
}
}
@@ -164,7 +163,7 @@ impl MinioService {
/// Get bucket name prefixed by mc alias name
fn absolute_bucket_name(&self, name: &str) -> String {
format!("{MC_ALIAS_NAME}/{name}")
format!("{}/{name}", MC_ALIAS_NAME)
}
/// Execute a minio mc command
@@ -172,9 +171,9 @@ impl MinioService {
where
A: DeserializeOwned,
{
log::debug!("exec_mc_cmd with args {args:?}");
log::debug!("exec_mc_cmd with args {:?}", args);
let conf_dir = temp::create_temp_dir()?;
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
@@ -252,7 +251,7 @@ impl MinioService {
}
/// Apply bucket desired configuration. If bucket already exists, it is not dropped
pub async fn bucket_apply(&self, b: &MinioBucketSpec) -> anyhow::Result<()> {
pub async fn apply_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(), "-p"].to_vec();
@@ -262,7 +261,7 @@ impl MinioService {
}
let res = self.exec_mc_cmd::<BasicMinioResult>(&args).await?;
if res.first().map(|r| r.success()) != Some(true) {
if res.get(0).map(|r| r.success()) != Some(true) {
return Err(MinioError::MakeBucketFailed.into());
}
@@ -293,7 +292,7 @@ impl MinioService {
])
.await?;
if res.first().map(|r| r.success()) != Some(true) {
if res.get(0).map(|r| r.success()) != Some(true) {
return Err(MinioError::SetQuotaFailed.into());
}
Ok(())
@@ -317,7 +316,7 @@ impl MinioService {
bucket_name: &str,
access: bool,
) -> anyhow::Result<()> {
let target = format!("{}/*", self.absolute_bucket_name(bucket_name));
let target = self.absolute_bucket_name(bucket_name);
let res = self
.exec_mc_cmd::<BasicMinioResult>(&[
@@ -331,7 +330,7 @@ impl MinioService {
])
.await?;
if res.first().map(|r| r.success()) != Some(true) {
if res.get(0).map(|r| r.success()) != Some(true) {
return Err(MinioError::SetAnonymousAcccessFailed.into());
}
@@ -340,7 +339,7 @@ impl MinioService {
/// 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));
let bucket_name = self.absolute_bucket_name(bucket_name);
Ok(self
.exec_mc_cmd::<MinioAnonymousAccess>(&["anonymous", "get", bucket_name.as_str()])
.await?
@@ -354,7 +353,7 @@ impl MinioService {
let bucket_name = self.absolute_bucket_name(bucket);
let res = if let Some(quota) = &quota {
let quota = format!("{quota}B");
let quota = format!("{}B", quota);
self.exec_mc_cmd::<BasicMinioResult>(&[
"quota",
"set",
@@ -368,7 +367,7 @@ impl MinioService {
.await?
};
if res.first().map(|r| r.success()) != Some(true) {
if res.get(0).map(|r| r.success()) != Some(true) {
return Err(MinioError::SetQuotaFailed.into());
}
Ok(())
@@ -416,7 +415,7 @@ impl MinioService {
.await?
};
if res.first().map(|r| r.success()) != Some(true) {
if res.get(0).map(|r| r.success()) != Some(true) {
return Err(MinioError::SetRetentionFailed.into());
}
@@ -447,7 +446,7 @@ impl MinioService {
"governance" => RetentionType::Governance,
"compliance" => RetentionType::Compliance,
o => {
log::error!("Unknown retention type: {o}");
log::error!("Unknown retention type: {}", o);
return Ok(None);
}
},
@@ -459,7 +458,7 @@ impl MinioService {
/// Apply a bucket policy
pub async fn policy_apply(&self, name: &str, content: &str) -> anyhow::Result<()> {
let tmp_file = temp::create_temp_file()?;
let tmp_file = mktemp::Temp::new_file()?;
std::fs::write(&tmp_file, content)?;
let res = self
@@ -473,7 +472,7 @@ impl MinioService {
])
.await?;
if res.first().map(|r| r.success()) != Some(true) {
if res.get(0).map(|r| r.success()) != Some(true) {
return Err(MinioError::ApplyPolicyFailed.into());
}
@@ -513,7 +512,7 @@ impl MinioService {
])
.await?;
if res.first().map(|r| r.success()) != Some(true) {
if res.get(0).map(|r| r.success()) != Some(true) {
return Err(MinioError::CreateUserFailed.into());
}
@@ -532,15 +531,6 @@ impl MinioService {
/// 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",
@@ -553,7 +543,7 @@ impl MinioService {
])
.await?;
if res.first().map(|r| r.success()) != Some(true) {
if res.get(0).map(|r| r.success()) != Some(true) {
return Err(MinioError::CreateUserFailed.into());
}
@@ -580,7 +570,7 @@ impl MinioService {
.userMappings;
if let Some(mapping) = res {
if let Some(e) = mapping.first() {
if let Some(e) = mapping.get(0) {
return Ok(e.policies.clone());
}
}
@@ -618,7 +608,7 @@ mod test {
let srv = MinioTestServer::start().await.unwrap();
let service = srv.as_service();
service
.bucket_apply(&MinioBucketSpec {
.apply_bucket(&MinioBucketSpec {
instance: "".to_string(),
name: TEST_BUCKET_NAME.to_string(),
secret: "".to_string(),
@@ -640,7 +630,7 @@ mod test {
let srv = MinioTestServer::start().await.unwrap();
let service = srv.as_service();
service
.bucket_apply(&MinioBucketSpec {
.apply_bucket(&MinioBucketSpec {
instance: "".to_string(),
name: TEST_BUCKET_NAME.to_string(),
secret: "".to_string(),
@@ -675,7 +665,7 @@ mod test {
let srv = MinioTestServer::start().await.unwrap();
let service = srv.as_service();
service
.bucket_apply(&MinioBucketSpec {
.apply_bucket(&MinioBucketSpec {
instance: "".to_string(),
name: TEST_BUCKET_NAME.to_string(),
secret: "".to_string(),
@@ -706,7 +696,7 @@ mod test {
let srv = MinioTestServer::start().await.unwrap();
let service = srv.as_service();
service
.bucket_apply(&MinioBucketSpec {
.apply_bucket(&MinioBucketSpec {
instance: "".to_string(),
name: TEST_BUCKET_NAME.to_string(),
secret: "".to_string(),
@@ -759,7 +749,7 @@ mod test {
let srv = MinioTestServer::start().await.unwrap();
let service = srv.as_service();
service
.bucket_apply(&MinioBucketSpec {
.apply_bucket(&MinioBucketSpec {
instance: "".to_string(),
name: TEST_BUCKET_NAME.to_string(),
secret: "".to_string(),
@@ -786,7 +776,7 @@ mod test {
let srv = MinioTestServer::start().await.unwrap();
let service = srv.as_service();
service
.bucket_apply(&MinioBucketSpec {
.apply_bucket(&MinioBucketSpec {
instance: "".to_string(),
name: TEST_BUCKET_NAME.to_string(),
secret: "".to_string(),
@@ -813,7 +803,7 @@ mod test {
let srv = MinioTestServer::start().await.unwrap();
let service = srv.as_service();
service
.bucket_apply(&MinioBucketSpec {
.apply_bucket(&MinioBucketSpec {
instance: "".to_string(),
name: TEST_BUCKET_NAME.to_string(),
secret: "".to_string(),
@@ -856,7 +846,7 @@ mod test {
let srv = MinioTestServer::start().await.unwrap();
let service = srv.as_service();
service
.bucket_apply(&MinioBucketSpec {
.apply_bucket(&MinioBucketSpec {
instance: "".to_string(),
name: TEST_BUCKET_NAME.to_string(),
secret: "".to_string(),
@@ -901,7 +891,7 @@ mod test {
let srv = MinioTestServer::start().await.unwrap();
let service = srv.as_service();
service
.bucket_apply(&MinioBucketSpec {
.apply_bucket(&MinioBucketSpec {
instance: "".to_string(),
name: TEST_BUCKET_NAME.to_string(),
secret: "".to_string(),
@@ -928,7 +918,7 @@ mod test {
let srv = MinioTestServer::start().await.unwrap();
let service = srv.as_service();
service
.bucket_apply(&MinioBucketSpec {
.apply_bucket(&MinioBucketSpec {
instance: "".to_string(),
name: TEST_BUCKET_NAME.to_string(),
secret: "".to_string(),
@@ -1018,7 +1008,7 @@ mod test {
let srv = MinioTestServer::start().await.unwrap();
let service = srv.as_service();
service
.bucket_apply(&MinioBucketSpec {
.apply_bucket(&MinioBucketSpec {
instance: "".to_string(),
name: TEST_BUCKET_NAME.to_string(),
secret: "".to_string(),
@@ -1099,7 +1089,7 @@ mod test {
let srv = MinioTestServer::start().await.unwrap();
let service = srv.as_service();
let user = MinioUser::gen_random("policy_user");
let user = MinioUser::gen_random();
assert!(!service.user_list().await.unwrap().contains(&user.username));
service.user_apply(&user).await.unwrap();
@@ -1113,7 +1103,7 @@ mod test {
let srv = MinioTestServer::start().await.unwrap();
let service = srv.as_service();
let user = MinioUser::gen_random("attach_policy_user");
let user = MinioUser::gen_random();
service.user_apply(&user).await.unwrap();
service

View File

@@ -3,7 +3,6 @@
//! Used for testing only
use crate::minio::MinioService;
use crate::temp;
use crate::utils::rand_str;
use rand::RngCore;
use std::io::ErrorKind;
@@ -21,11 +20,11 @@ pub struct MinioTestServer {
impl MinioTestServer {
pub async fn start() -> anyhow::Result<Self> {
let storage_dir = temp::create_temp_dir()?;
let storage_dir = mktemp::Temp::new_dir()?;
let root_user = rand_str(30);
let root_password = rand_str(30);
let api_port = (2000 + rand::rng().next_u64() % 5000) as u16;
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,
@@ -52,7 +51,6 @@ impl MinioTestServer {
};
// Wait for Minio to become ready
std::thread::sleep(Duration::from_millis(500));
let mut check_count = 0;
loop {
if check_count >= 100 {

View File

@@ -1,17 +0,0 @@
{
"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 }}/*"]
}
]
}

View File

@@ -1,26 +0,0 @@
use std::path::{Path, PathBuf};
/// Get the directory where temp files should be created
fn temp_path() -> Option<PathBuf> {
std::env::var("TEMP_DIR")
.as_deref()
.ok()
.map(Path::new)
.map(|p| p.to_path_buf())
}
/// Create a temporary directory
pub fn create_temp_dir() -> std::io::Result<mktemp::Temp> {
match temp_path() {
None => mktemp::Temp::new_dir(),
Some(p) => mktemp::Temp::new_dir_in(p),
}
}
/// Create a temporary file
pub fn create_temp_file() -> std::io::Result<mktemp::Temp> {
match temp_path() {
None => mktemp::Temp::new_file(),
Some(p) => mktemp::Temp::new_file_in(p),
}
}

View File

@@ -1,6 +1,11 @@
use rand::distr::{Alphanumeric, SampleString};
use rand::distributions::Alphanumeric;
use rand::Rng;
/// Generate a random string of a given size
pub fn rand_str(len: usize) -> String {
Alphanumeric.sample_string(&mut rand::rng(), len)
rand::thread_rng()
.sample_iter(&Alphanumeric)
.take(len)
.map(char::from)
.collect()
}

28
test/bucket-policy.json Normal file
View 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/*"
]
}
]
}

View File

@@ -7,4 +7,3 @@ spec:
instance: my-minio-instance
name: second-bucket
secret: second-bucket-secret
versioning: false

View File

@@ -1,25 +0,0 @@
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://192.168.2.103: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

View File

@@ -1,25 +0,0 @@
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://localhost: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

View File

@@ -1,81 +0,0 @@
apiVersion: v1
kind: ServiceAccount
automountServiceAccountToken: true
metadata:
name: minio-operator
namespace: default
labels:
app: minio-operator
---
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: minio-operator
namespace: default
rules:
- apiGroups: ["communiquons.org"]
resources: ["minioinstances", "miniobuckets"]
verbs: ["get", "list", "watch"]
- apiGroups: [""]
resources: ["secrets"]
verbs: ["get", "create"]
---
kind: RoleBinding
apiVersion: rbac.authorization.k8s.io/v1
metadata:
name: minio-operator
namespace: default
subjects:
- kind: ServiceAccount
name: minio-operator
namespace: default
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: Role
name: minio-operator
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: minio-operator
labels:
app: minio-operator
spec:
replicas: 1
strategy:
type: Recreate
selector:
matchLabels:
app: minio-operator
template:
metadata:
labels:
app: minio-operator
spec:
serviceAccountName: minio-operator
containers:
- name: minio-operator
image: pierre42100/minio_operator
resources:
limits:
memory: 300Mi
cpu: "0.1"
requests:
memory: 150Mi
cpu: "0.01"
volumeMounts:
- mountPath: /tmp
readOnly: false
name: tempdir
securityContext:
allowPrivilegeEscalation: false
readOnlyRootFilesystem: true
runAsUser: 1000
runAsGroup: 1000
capabilities:
drop:
- ALL
volumes:
- name: tempdir
emptyDir:
sizeLimit: 500Mi

View File

@@ -1,54 +1,5 @@
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
# name must match the spec fields below, and be in the form: <plural>.<group>
name: minioinstances.communiquons.org
spec:
# group name to use for REST API: /apis/<group>/<version>
group: communiquons.org
# list of versions supported by this CustomResourceDefinition
versions:
- name: v1
# Each version can be enabled/disabled by Served flag.
served: true
# One and only one version must be marked as the storage version.
storage: true
schema:
openAPIV3Schema:
type: object
properties:
spec:
type: object
description: Information about how to reach the Minio bucket
properties:
endpoint:
description: The URL where the Minio API can be reached
example: https://minio.communiquons.org
type: string
credentials:
description: |
The name of the secret containings privilegied / root credentials of Minio instance
The secret must contains two fields :
* An access key named `accessKey`
* A secret key named `secretKey`
type: string
example: minio-root
# either Namespaced or Cluster
scope: Namespaced
names:
# plural name to be used in the URL: /apis/<group>/<version>/<plural>
plural: minioinstances
# singular name to be used as an alias on the CLI and for display
singular: minioinstance
# kind is normally the CamelCased singular type. Your resource manifests use this.
kind: MinioInstance
# shortNames allow shorter string to match your resource on the CLI
shortNames:
- mis
---
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
# name must match the spec fields below, and be in the form: <plural>.<group>
name: miniobuckets.communiquons.org

View File

@@ -1,89 +0,0 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: minio
labels:
app: minio
spec:
replicas: 1
selector:
matchLabels:
app: minio
template:
metadata:
labels:
app: minio
spec:
volumes:
- name: data
persistentVolumeClaim:
claimName: minio
containers:
- name: minio
image: minio/minio
imagePullPolicy: Always
ports:
- containerPort: 9000
protocol: TCP
name: api
- containerPort: 9090
protocol: TCP
name: console
args:
- server
- /data
- --console-address
- ":9090"
env:
- name: MINIO_ROOT_USER
value: minioadmin
- name: MINIO_ROOT_PASSWORD
value: minioadmin
volumeMounts:
- mountPath: "/data"
name: data
---
apiVersion: v1
kind: PersistentVolume
metadata:
name: minio
spec:
storageClassName: manual
accessModes:
- ReadWriteOnce
capacity:
storage: 5Gi
hostPath:
path: /data/minio/
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: minio
spec:
storageClassName: manual
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 3Gi
---
apiVersion: v1
kind: Service
metadata:
name: minio
labels:
app: minio
spec:
type: LoadBalancer
selector:
app: minio
ports:
- name: api
port: 9000
targetPort: api
- name: console
port: 9090
targetPort: console
externalTrafficPolicy: Local

49
yaml/minio-instance.yaml Normal file
View File

@@ -0,0 +1,49 @@
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
# name must match the spec fields below, and be in the form: <plural>.<group>
name: minioinstances.communiquons.org
spec:
# group name to use for REST API: /apis/<group>/<version>
group: communiquons.org
# list of versions supported by this CustomResourceDefinition
versions:
- name: v1
# Each version can be enabled/disabled by Served flag.
served: true
# One and only one version must be marked as the storage version.
storage: true
schema:
openAPIV3Schema:
type: object
properties:
spec:
type: object
description: Information about how to reach the Minio bucket
properties:
endpoint:
description: The URL where the Minio API can be reached
example: https://minio.communiquons.org
type: string
credentials:
description: |
The name of the secret containings privilegied / root credentials of Minio instance
The secret must contains two fields :
* An access key named `accessKey`
* A secret key named `secretKey`
type: string
example: minio-root
# either Namespaced or Cluster
scope: Namespaced
names:
# plural name to be used in the URL: /apis/<group>/<version>/<plural>
plural: minioinstances
# singular name to be used as an alias on the CLI and for display
singular: minioinstance
# kind is normally the CamelCased singular type. Your resource manifests use this.
kind: MinioInstance
# shortNames allow shorter string to match your resource on the CLI
shortNames:
- mis
---

33
yaml/service_account.yaml Normal file
View File

@@ -0,0 +1,33 @@
apiVersion: v1
kind: ServiceAccount
automountServiceAccountToken: true
metadata:
name: minio-buckets
namespace: default
labels:
app: minio
---
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: minio-buckets
namespace: default
rules:
- apiGroups: ["communiquons.org"]
resources: ["minioinstances", "miniobuckets"]
verbs: ["get", "watch"]
---
kind: RoleBinding
apiVersion: rbac.authorization.k8s.io/v1
metadata:
name: minio-buckets
namespace: default
subjects:
- kind: ServiceAccount
name: minio-buckets
namespace: default
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: Role
name: minio-buckets
---