Compare commits
1 Commits
feat/opnse
...
feat/rustf
| Author | SHA1 | Date | |
|---|---|---|---|
| 3cf4fe4855 |
10
Cargo.lock
generated
10
Cargo.lock
generated
@@ -2953,6 +2953,16 @@ dependencies = [
|
|||||||
"url",
|
"url",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "example-rustfs"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"harmony",
|
||||||
|
"harmony_cli",
|
||||||
|
"harmony_types",
|
||||||
|
"tokio",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "example-tenant"
|
name = "example-tenant"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ If you're new to Harmony, start here:
|
|||||||
See how to use Harmony to solve real-world problems.
|
See how to use Harmony to solve real-world problems.
|
||||||
|
|
||||||
- [**PostgreSQL on Local K3D**](./use-cases/postgresql-on-local-k3d.md): Deploy a production-grade PostgreSQL cluster on a local K3D cluster. The fastest way to get started.
|
- [**PostgreSQL on Local K3D**](./use-cases/postgresql-on-local-k3d.md): Deploy a production-grade PostgreSQL cluster on a local K3D cluster. The fastest way to get started.
|
||||||
|
- [**RustFS on Local K3D**](./use-cases/rustfs-on-local-k3d.md): Deploy a RustFS S3-compatible object store on a local K3D cluster.
|
||||||
- [**OKD on Bare Metal**](./use-cases/okd-on-bare-metal.md): A detailed walkthrough of bootstrapping a high-availability OKD cluster from physical hardware.
|
- [**OKD on Bare Metal**](./use-cases/okd-on-bare-metal.md): A detailed walkthrough of bootstrapping a high-availability OKD cluster from physical hardware.
|
||||||
|
|
||||||
## 3. Component Catalogs
|
## 3. Component Catalogs
|
||||||
|
|||||||
151
docs/use-cases/rustfs-on-local-k3d.md
Normal file
151
docs/use-cases/rustfs-on-local-k3d.md
Normal file
@@ -0,0 +1,151 @@
|
|||||||
|
# Use Case: RustFS (S3-Compatible Store) on Local K3D
|
||||||
|
|
||||||
|
Deploy a RustFS object store on a local Kubernetes cluster (K3D) using Harmony. RustFS is a Rust-based S3-compatible storage server, a modern alternative to MinIO for local development.
|
||||||
|
|
||||||
|
## What you'll have at the end
|
||||||
|
|
||||||
|
A fully operational S3-compatible object store with:
|
||||||
|
- 1 standalone instance with 1 GiB of storage
|
||||||
|
- S3 API endpoint on port 9000
|
||||||
|
- Web console on port 9001
|
||||||
|
- Ingress-based access at `http://rustfs.local`
|
||||||
|
- Default credentials: `rustfsadmin` / `rustfsadmin`
|
||||||
|
|
||||||
|
## Prerequisites
|
||||||
|
|
||||||
|
- Rust 2024 edition
|
||||||
|
- Docker running locally
|
||||||
|
- ~5 minutes
|
||||||
|
|
||||||
|
## The Score
|
||||||
|
|
||||||
|
The entire deployment is expressed in ~20 lines of Rust:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
use harmony::{
|
||||||
|
inventory::Inventory,
|
||||||
|
modules::rustfs::{K8sRustFsScore, RustFsConfig},
|
||||||
|
topology::K8sAnywhereTopology,
|
||||||
|
};
|
||||||
|
|
||||||
|
#[tokio::main]
|
||||||
|
async fn main() {
|
||||||
|
let rustfs = K8sRustFsScore {
|
||||||
|
config: RustFsConfig {
|
||||||
|
release_name: "harmony-rustfs".to_string(),
|
||||||
|
namespace: "harmony-rustfs".to_string(),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
harmony_cli::run(
|
||||||
|
Inventory::autoload(),
|
||||||
|
K8sAnywhereTopology::from_env(),
|
||||||
|
vec![Box::new(rustfs)],
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## What Harmony does
|
||||||
|
|
||||||
|
When you run this, Harmony:
|
||||||
|
|
||||||
|
1. **Connects to K8sAnywhereTopology** — auto-provisions a K3D cluster if none exists
|
||||||
|
2. **Creates a namespace** — `harmony-rustfs` (or your custom namespace)
|
||||||
|
3. **Creates credentials secret** — stores the access/secret keys securely
|
||||||
|
4. **Deploys via Helm** — installs the RustFS chart in standalone mode
|
||||||
|
5. **Configures Ingress** — sets up routing at `rustfs.local`
|
||||||
|
|
||||||
|
## Running it
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cargo run -p example-rustfs
|
||||||
|
```
|
||||||
|
|
||||||
|
## Verifying the deployment
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Check pods
|
||||||
|
kubectl get pods -n harmony-rustfs
|
||||||
|
|
||||||
|
# Check ingress
|
||||||
|
kubectl get ingress -n harmony-rustfs
|
||||||
|
|
||||||
|
# Access the S3 API
|
||||||
|
# Add rustfs.local to your /etc/hosts
|
||||||
|
echo "127.0.0.1 rustfs.local" | sudo tee -a /etc/hosts
|
||||||
|
|
||||||
|
# Use the AWS CLI or any S3 client
|
||||||
|
AWS_ACCESS_KEY_ID=rustfsadmin \
|
||||||
|
AWS_SECRET_ACCESS_KEY=rustfsadmin \
|
||||||
|
aws s3 ls --endpoint-url http://rustfs.local:9000
|
||||||
|
|
||||||
|
# Or via the web console
|
||||||
|
open http://rustfs.local:9001
|
||||||
|
```
|
||||||
|
|
||||||
|
## Customizing the deployment
|
||||||
|
|
||||||
|
The `RustFsConfig` struct supports:
|
||||||
|
|
||||||
|
| Field | Default | Description |
|
||||||
|
|-------|---------|-------------|
|
||||||
|
| `release_name` | `rustfs` | Helm release name |
|
||||||
|
| `namespace` | `harmony-rustfs` | Kubernetes namespace |
|
||||||
|
| `storage_size` | `1Gi` | Data storage size |
|
||||||
|
| `mode` | `Standalone` | Deployment mode (standalone only for now) |
|
||||||
|
| `access_key` | `None` | S3 access key (default: `rustfsadmin`) |
|
||||||
|
| `secret_key` | `None` | S3 secret key (default: `rustfsadmin`) |
|
||||||
|
| `ingress_class` | `None` | Ingress class to use (default: `nginx`) |
|
||||||
|
|
||||||
|
Example with custom credentials:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
let rustfs = K8sRustFsScore {
|
||||||
|
config: RustFsConfig {
|
||||||
|
release_name: "my-rustfs".to_string(),
|
||||||
|
namespace: "storage".to_string(),
|
||||||
|
access_key: Some("myaccess".to_string()),
|
||||||
|
secret_key: Some("mysecret".to_string()),
|
||||||
|
ingress_class: Some("traefik".to_string()),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
## Architecture
|
||||||
|
|
||||||
|
The RustFS module follows the same pattern as PostgreSQL:
|
||||||
|
|
||||||
|
```
|
||||||
|
┌─────────────────────────────────────────────────────────────┐
|
||||||
|
│ K8sRustFsScore (user-facing) │
|
||||||
|
│ └── K8sRustFsInterpret │
|
||||||
|
│ ├── ensure_namespace() │
|
||||||
|
│ ├── ensure_secret() → K8sResourceScore │
|
||||||
|
│ └── HelmChartScore → HelmChartInterpret │
|
||||||
|
│ └── Installs rustfs/rustfs chart │
|
||||||
|
└─────────────────────────────────────────────────────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
## Future: Unified S3 Capability
|
||||||
|
|
||||||
|
This is the first step toward a unified S3 capability that will work with:
|
||||||
|
- **RustFS** — local development (this example)
|
||||||
|
- **Ceph RGW** — production S3 via Rook/Ceph
|
||||||
|
- **AWS S3** — cloud-native S3
|
||||||
|
|
||||||
|
The pattern will be:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
// Future: unified S3 interface
|
||||||
|
trait S3Store: Send + Sync {
|
||||||
|
async fn deploy_bucket(&self, config: &BucketConfig) -> Result<(), String>;
|
||||||
|
async fn get_endpoint(&self) -> Result<S3Endpoint, String>;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
See the [Scores Catalog](../catalogs/scores.md) for related components.
|
||||||
@@ -7,6 +7,7 @@ This directory contains runnable examples demonstrating Harmony's capabilities.
|
|||||||
| Example | Description | Local K3D | Existing Cluster | Hardware Needed |
|
| Example | Description | Local K3D | Existing Cluster | Hardware Needed |
|
||||||
|---------|-------------|:---------:|:----------------:|:---------------:|
|
|---------|-------------|:---------:|:----------------:|:---------------:|
|
||||||
| `postgresql` | Deploy a PostgreSQL cluster | ✅ | ✅ | — |
|
| `postgresql` | Deploy a PostgreSQL cluster | ✅ | ✅ | — |
|
||||||
|
| `rustfs` | Deploy a RustFS S3-compatible store | ✅ | ✅ | — |
|
||||||
| `ntfy` | Deploy ntfy notification server | ✅ | ✅ | — |
|
| `ntfy` | Deploy ntfy notification server | ✅ | ✅ | — |
|
||||||
| `tenant` | Create a multi-tenant namespace | ✅ | ✅ | — |
|
| `tenant` | Create a multi-tenant namespace | ✅ | ✅ | — |
|
||||||
| `cert_manager` | Provision TLS certificates | ✅ | ✅ | — |
|
| `cert_manager` | Provision TLS certificates | ✅ | ✅ | — |
|
||||||
@@ -52,6 +53,7 @@ This directory contains runnable examples demonstrating Harmony's capabilities.
|
|||||||
- **`postgresql`** — Deploy a PostgreSQL cluster via CloudNativePG
|
- **`postgresql`** — Deploy a PostgreSQL cluster via CloudNativePG
|
||||||
- **`multisite_postgres`** — Multi-site PostgreSQL with failover
|
- **`multisite_postgres`** — Multi-site PostgreSQL with failover
|
||||||
- **`public_postgres`** — Public-facing PostgreSQL (⚠️ uses NationTech DNS)
|
- **`public_postgres`** — Public-facing PostgreSQL (⚠️ uses NationTech DNS)
|
||||||
|
- **`rustfs`** — Deploy a RustFS S3-compatible object store
|
||||||
|
|
||||||
### Kubernetes Utilities
|
### Kubernetes Utilities
|
||||||
- **`node_health`** — Check node health in a cluster
|
- **`node_health`** — Check node health in a cluster
|
||||||
|
|||||||
13
examples/rustfs/Cargo.toml
Normal file
13
examples/rustfs/Cargo.toml
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
[package]
|
||||||
|
name = "example-rustfs"
|
||||||
|
edition = "2024"
|
||||||
|
version.workspace = true
|
||||||
|
readme.workspace = true
|
||||||
|
license.workspace = true
|
||||||
|
publish = false
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
harmony = { path = "../../harmony" }
|
||||||
|
harmony_cli = { path = "../../harmony_cli" }
|
||||||
|
harmony_types = { path = "../../harmony_types" }
|
||||||
|
tokio = { workspace = true }
|
||||||
25
examples/rustfs/src/main.rs
Normal file
25
examples/rustfs/src/main.rs
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
use harmony::{
|
||||||
|
inventory::Inventory,
|
||||||
|
modules::rustfs::{K8sRustFsScore, RustFsConfig},
|
||||||
|
topology::K8sAnywhereTopology,
|
||||||
|
};
|
||||||
|
|
||||||
|
#[tokio::main]
|
||||||
|
async fn main() {
|
||||||
|
let rustfs = K8sRustFsScore {
|
||||||
|
config: RustFsConfig {
|
||||||
|
release_name: "harmony-rustfs".to_string(),
|
||||||
|
namespace: "harmony-rustfs".to_string(),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
harmony_cli::run(
|
||||||
|
Inventory::autoload(),
|
||||||
|
K8sAnywhereTopology::from_env(),
|
||||||
|
vec![Box::new(rustfs)],
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
@@ -21,6 +21,7 @@ pub mod openbao;
|
|||||||
pub mod opnsense;
|
pub mod opnsense;
|
||||||
pub mod postgresql;
|
pub mod postgresql;
|
||||||
pub mod prometheus;
|
pub mod prometheus;
|
||||||
|
pub mod rustfs;
|
||||||
pub mod storage;
|
pub mod storage;
|
||||||
pub mod tenant;
|
pub mod tenant;
|
||||||
pub mod tftp;
|
pub mod tftp;
|
||||||
|
|||||||
47
harmony/src/modules/rustfs/capability.rs
Normal file
47
harmony/src/modules/rustfs/capability.rs
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
use async_trait::async_trait;
|
||||||
|
use harmony_types::storage::StorageSize;
|
||||||
|
use serde::Serialize;
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Serialize)]
|
||||||
|
pub struct RustFsConfig {
|
||||||
|
pub release_name: String,
|
||||||
|
pub namespace: String,
|
||||||
|
pub storage_size: StorageSize,
|
||||||
|
pub mode: RustFsMode,
|
||||||
|
pub access_key: Option<String>,
|
||||||
|
pub secret_key: Option<String>,
|
||||||
|
pub ingress_class: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for RustFsConfig {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
release_name: "rustfs".to_string(),
|
||||||
|
namespace: "harmony-rustfs".to_string(),
|
||||||
|
storage_size: StorageSize::gi(1),
|
||||||
|
mode: RustFsMode::Standalone,
|
||||||
|
access_key: None,
|
||||||
|
secret_key: None,
|
||||||
|
ingress_class: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Serialize)]
|
||||||
|
pub enum RustFsMode {
|
||||||
|
Standalone,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
|
pub trait RustFs: Send + Sync {
|
||||||
|
async fn deploy(&self, config: &RustFsConfig) -> Result<String, String>;
|
||||||
|
async fn get_endpoint(&self, config: &RustFsConfig) -> Result<RustFsEndpoint, String>;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub struct RustFsEndpoint {
|
||||||
|
pub s3_endpoint: String,
|
||||||
|
pub console_endpoint: String,
|
||||||
|
pub access_key: String,
|
||||||
|
pub secret_key: String,
|
||||||
|
}
|
||||||
6
harmony/src/modules/rustfs/mod.rs
Normal file
6
harmony/src/modules/rustfs/mod.rs
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
pub mod capability;
|
||||||
|
mod score;
|
||||||
|
mod score_k8s;
|
||||||
|
pub use capability::*;
|
||||||
|
pub use score::*;
|
||||||
|
pub use score_k8s::*;
|
||||||
85
harmony/src/modules/rustfs/score.rs
Normal file
85
harmony/src/modules/rustfs/score.rs
Normal file
@@ -0,0 +1,85 @@
|
|||||||
|
use async_trait::async_trait;
|
||||||
|
use harmony_types::id::Id;
|
||||||
|
use serde::Serialize;
|
||||||
|
|
||||||
|
use crate::data::Version;
|
||||||
|
use crate::interpret::{Interpret, InterpretError, InterpretName, InterpretStatus, Outcome};
|
||||||
|
use crate::inventory::Inventory;
|
||||||
|
use crate::modules::rustfs::capability::{RustFs, RustFsConfig};
|
||||||
|
use crate::score::Score;
|
||||||
|
use crate::topology::Topology;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Serialize)]
|
||||||
|
pub struct RustFsScore {
|
||||||
|
pub config: RustFsConfig,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for RustFsScore {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
config: RustFsConfig::default(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RustFsScore {
|
||||||
|
pub fn new(namespace: &str) -> Self {
|
||||||
|
Self {
|
||||||
|
config: RustFsConfig {
|
||||||
|
namespace: namespace.to_string(),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: Topology + RustFs + Send + Sync> Score<T> for RustFsScore {
|
||||||
|
fn create_interpret(&self) -> Box<dyn Interpret<T>> {
|
||||||
|
Box::new(RustFsInterpret {
|
||||||
|
config: self.config.clone(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn name(&self) -> String {
|
||||||
|
format!(
|
||||||
|
"RustFsScore({}:{})",
|
||||||
|
self.config.namespace, self.config.release_name
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
struct RustFsInterpret {
|
||||||
|
config: RustFsConfig,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
|
impl<T: Topology + RustFs + Send + Sync> Interpret<T> for RustFsInterpret {
|
||||||
|
fn get_name(&self) -> InterpretName {
|
||||||
|
InterpretName::Custom("RustFsInterpret")
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_version(&self) -> Version {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_status(&self) -> InterpretStatus {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_children(&self) -> Vec<Id> {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn execute(&self, _inventory: &Inventory, topo: &T) -> Result<Outcome, InterpretError> {
|
||||||
|
let release_name = topo
|
||||||
|
.deploy(&self.config)
|
||||||
|
.await
|
||||||
|
.map_err(|e| InterpretError::new(e))?;
|
||||||
|
|
||||||
|
Ok(Outcome::success(format!(
|
||||||
|
"RustFS '{}' deployed in namespace '{}'",
|
||||||
|
release_name, self.config.namespace
|
||||||
|
)))
|
||||||
|
}
|
||||||
|
}
|
||||||
285
harmony/src/modules/rustfs/score_k8s.rs
Normal file
285
harmony/src/modules/rustfs/score_k8s.rs
Normal file
@@ -0,0 +1,285 @@
|
|||||||
|
use std::str::FromStr;
|
||||||
|
|
||||||
|
use crate::data::Version;
|
||||||
|
use crate::interpret::{Interpret, InterpretError, InterpretName, InterpretStatus, Outcome};
|
||||||
|
use crate::inventory::Inventory;
|
||||||
|
use crate::modules::helm::chart::{HelmChartScore, HelmRepository};
|
||||||
|
use crate::modules::k8s::resource::K8sResourceScore;
|
||||||
|
use crate::modules::rustfs::capability::{RustFs, RustFsConfig, RustFsEndpoint, RustFsMode};
|
||||||
|
use crate::score::Score;
|
||||||
|
use crate::topology::{HelmCommand, K8sclient, Topology};
|
||||||
|
use async_trait::async_trait;
|
||||||
|
use harmony_types::id::Id;
|
||||||
|
use harmony_types::net::Url;
|
||||||
|
use k8s_openapi::api::core::v1::Secret;
|
||||||
|
use k8s_openapi::apimachinery::pkg::apis::meta::v1::ObjectMeta;
|
||||||
|
use k8s_openapi::ByteString;
|
||||||
|
use log::info;
|
||||||
|
use non_blank_string_rs::NonBlankString;
|
||||||
|
use serde::Serialize;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Serialize)]
|
||||||
|
pub struct K8sRustFsScore {
|
||||||
|
pub config: RustFsConfig,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for K8sRustFsScore {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
config: RustFsConfig::default(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl K8sRustFsScore {
|
||||||
|
pub fn new(namespace: &str) -> Self {
|
||||||
|
Self {
|
||||||
|
config: RustFsConfig {
|
||||||
|
namespace: namespace.to_string(),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: Topology + K8sclient + HelmCommand + 'static> Score<T> for K8sRustFsScore {
|
||||||
|
fn create_interpret(&self) -> Box<dyn Interpret<T>> {
|
||||||
|
Box::new(K8sRustFsInterpret {
|
||||||
|
config: self.config.clone(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn name(&self) -> String {
|
||||||
|
format!("K8sRustFsScore({})", self.config.namespace)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct K8sRustFsInterpret {
|
||||||
|
config: RustFsConfig,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl K8sRustFsInterpret {
|
||||||
|
async fn ensure_namespace<T: Topology + K8sclient>(
|
||||||
|
&self,
|
||||||
|
topology: &T,
|
||||||
|
) -> Result<(), InterpretError> {
|
||||||
|
let k8s_client = topology
|
||||||
|
.k8s_client()
|
||||||
|
.await
|
||||||
|
.map_err(|e| InterpretError::new(format!("Failed to get k8s client: {}", e)))?;
|
||||||
|
|
||||||
|
let namespace_name = &self.config.namespace;
|
||||||
|
|
||||||
|
if k8s_client
|
||||||
|
.namespace_exists(namespace_name)
|
||||||
|
.await
|
||||||
|
.map_err(|e| {
|
||||||
|
InterpretError::new(format!(
|
||||||
|
"Failed to check namespace '{}': {}",
|
||||||
|
namespace_name, e
|
||||||
|
))
|
||||||
|
})?
|
||||||
|
{
|
||||||
|
info!("Namespace '{}' already exists", namespace_name);
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
info!("Creating namespace '{}'", namespace_name);
|
||||||
|
k8s_client
|
||||||
|
.create_namespace(namespace_name)
|
||||||
|
.await
|
||||||
|
.map_err(|e| {
|
||||||
|
InterpretError::new(format!(
|
||||||
|
"Failed to create namespace '{}': {}",
|
||||||
|
namespace_name, e
|
||||||
|
))
|
||||||
|
})?;
|
||||||
|
|
||||||
|
k8s_client
|
||||||
|
.wait_for_namespace(namespace_name, Some(std::time::Duration::from_secs(30)))
|
||||||
|
.await
|
||||||
|
.map_err(|e| {
|
||||||
|
InterpretError::new(format!("Namespace '{}' not ready: {}", namespace_name, e))
|
||||||
|
})?;
|
||||||
|
|
||||||
|
info!("Namespace '{}' is ready", namespace_name);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn ensure_secret<T: Topology + K8sclient>(
|
||||||
|
&self,
|
||||||
|
topology: &T,
|
||||||
|
) -> Result<(), InterpretError> {
|
||||||
|
let access_key = self.config.access_key.as_deref().unwrap_or("rustfsadmin");
|
||||||
|
let secret_key = self.config.secret_key.as_deref().unwrap_or("rustfsadmin");
|
||||||
|
|
||||||
|
let k8s_client = topology
|
||||||
|
.k8s_client()
|
||||||
|
.await
|
||||||
|
.map_err(|e| InterpretError::new(format!("Failed to get k8s client: {}", e)))?;
|
||||||
|
|
||||||
|
let namespace_name = &self.config.namespace;
|
||||||
|
let secret_name = format!("{}-credentials", self.config.release_name);
|
||||||
|
|
||||||
|
let secret_exists = k8s_client
|
||||||
|
.get_secret_json_value(&secret_name, Some(namespace_name))
|
||||||
|
.await
|
||||||
|
.is_ok();
|
||||||
|
|
||||||
|
if secret_exists {
|
||||||
|
info!(
|
||||||
|
"Secret '{}' already exists in namespace '{}'",
|
||||||
|
secret_name, namespace_name
|
||||||
|
);
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
info!("Creating secret '{}' in namespace '{}'", secret_name, namespace_name);
|
||||||
|
|
||||||
|
let mut data = std::collections::BTreeMap::new();
|
||||||
|
data.insert(
|
||||||
|
"access_key".to_string(),
|
||||||
|
ByteString(access_key.as_bytes().to_vec()),
|
||||||
|
);
|
||||||
|
data.insert(
|
||||||
|
"secret_key".to_string(),
|
||||||
|
ByteString(secret_key.as_bytes().to_vec()),
|
||||||
|
);
|
||||||
|
|
||||||
|
let secret = Secret {
|
||||||
|
metadata: ObjectMeta {
|
||||||
|
name: Some(secret_name.clone()),
|
||||||
|
namespace: Some(namespace_name.clone()),
|
||||||
|
..ObjectMeta::default()
|
||||||
|
},
|
||||||
|
data: Some(data),
|
||||||
|
string_data: None,
|
||||||
|
type_: Some("Opaque".to_string()),
|
||||||
|
..Secret::default()
|
||||||
|
};
|
||||||
|
|
||||||
|
K8sResourceScore::single(secret, Some(namespace_name.clone()))
|
||||||
|
.create_interpret()
|
||||||
|
.execute(&Inventory::empty(), topology)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn to_values_yaml(&self) -> String {
|
||||||
|
let storage_size = self.config.storage_size.to_string();
|
||||||
|
let ingress_class = self.config.ingress_class.as_deref().unwrap_or("nginx");
|
||||||
|
|
||||||
|
let mode_yaml = match self.config.mode {
|
||||||
|
RustFsMode::Standalone => {
|
||||||
|
"mode:\n standalone:\n enabled: true\n distributed:\n enabled: false"
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
format!(
|
||||||
|
r#"{mode_yaml}
|
||||||
|
storageclass:
|
||||||
|
name: local-path
|
||||||
|
dataStorageSize: {storage_size}
|
||||||
|
logStorageSize: 256Mi
|
||||||
|
ingress:
|
||||||
|
enabled: true
|
||||||
|
className: {ingress_class}
|
||||||
|
hosts:
|
||||||
|
- host: rustfs.local
|
||||||
|
paths:
|
||||||
|
- path: /
|
||||||
|
pathType: Prefix
|
||||||
|
secret:
|
||||||
|
existingSecret: {release_name}-credentials
|
||||||
|
"#,
|
||||||
|
release_name = self.config.release_name
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
|
impl<T: Topology + K8sclient + HelmCommand + 'static> Interpret<T> for K8sRustFsInterpret {
|
||||||
|
async fn execute(
|
||||||
|
&self,
|
||||||
|
_inventory: &Inventory,
|
||||||
|
topology: &T,
|
||||||
|
) -> Result<Outcome, InterpretError> {
|
||||||
|
self.ensure_namespace(topology).await?;
|
||||||
|
self.ensure_secret(topology).await?;
|
||||||
|
|
||||||
|
let helm_score = HelmChartScore {
|
||||||
|
namespace: Some(NonBlankString::from_str(&self.config.namespace).unwrap()),
|
||||||
|
release_name: NonBlankString::from_str(&self.config.release_name).unwrap(),
|
||||||
|
chart_name: NonBlankString::from_str("rustfs/rustfs").unwrap(),
|
||||||
|
chart_version: None,
|
||||||
|
values_overrides: None,
|
||||||
|
values_yaml: Some(self.to_values_yaml()),
|
||||||
|
create_namespace: false,
|
||||||
|
install_only: false,
|
||||||
|
repository: Some(HelmRepository::new(
|
||||||
|
"rustfs".to_string(),
|
||||||
|
Url::Url(url::Url::parse("https://charts.rustfs.com").unwrap()),
|
||||||
|
true,
|
||||||
|
)),
|
||||||
|
};
|
||||||
|
|
||||||
|
helm_score
|
||||||
|
.create_interpret()
|
||||||
|
.execute(&Inventory::empty(), topology)
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_name(&self) -> InterpretName {
|
||||||
|
InterpretName::Custom("K8sRustFsInterpret")
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_version(&self) -> Version {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_status(&self) -> InterpretStatus {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_children(&self) -> Vec<Id> {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct K8sAnywhereRustFs;
|
||||||
|
|
||||||
|
impl K8sAnywhereRustFs {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for K8sAnywhereRustFs {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self::new()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
|
impl RustFs for K8sAnywhereRustFs {
|
||||||
|
async fn deploy(&self, config: &RustFsConfig) -> Result<String, String> {
|
||||||
|
Ok(config.release_name.clone())
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn get_endpoint(&self, config: &RustFsConfig) -> Result<RustFsEndpoint, String> {
|
||||||
|
Ok(RustFsEndpoint {
|
||||||
|
s3_endpoint: "http://rustfs.local:9000".to_string(),
|
||||||
|
console_endpoint: "http://rustfs.local:9001".to_string(),
|
||||||
|
access_key: config
|
||||||
|
.access_key
|
||||||
|
.clone()
|
||||||
|
.unwrap_or_else(|| "rustfsadmin".to_string()),
|
||||||
|
secret_key: config
|
||||||
|
.secret_key
|
||||||
|
.clone()
|
||||||
|
.unwrap_or_else(|| "rustfsadmin".to_string()),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user