forked from NationTech/harmony
Compare commits
4 Commits
feat/init_
...
feat/kube-
| Author | SHA1 | Date | |
|---|---|---|---|
| de3e7869f7 | |||
| 57eabc9834 | |||
| cd40660350 | |||
| 2ca732cecd |
24
Cargo.lock
generated
24
Cargo.lock
generated
@@ -1070,21 +1070,6 @@ dependencies = [
|
|||||||
"url",
|
"url",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "example-tenant"
|
|
||||||
version = "0.1.0"
|
|
||||||
dependencies = [
|
|
||||||
"cidr",
|
|
||||||
"env_logger",
|
|
||||||
"harmony",
|
|
||||||
"harmony_cli",
|
|
||||||
"harmony_macros",
|
|
||||||
"harmony_types",
|
|
||||||
"log",
|
|
||||||
"tokio",
|
|
||||||
"url",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "example-tui"
|
name = "example-tui"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
@@ -1424,14 +1409,12 @@ dependencies = [
|
|||||||
"derive-new",
|
"derive-new",
|
||||||
"directories",
|
"directories",
|
||||||
"dockerfile_builder",
|
"dockerfile_builder",
|
||||||
"dyn-clone",
|
|
||||||
"email_address",
|
"email_address",
|
||||||
"env_logger",
|
"env_logger",
|
||||||
"fqdn",
|
"fqdn",
|
||||||
"harmony_macros",
|
"harmony_macros",
|
||||||
"harmony_types",
|
"harmony_types",
|
||||||
"helm-wrapper-rs",
|
"helm-wrapper-rs",
|
||||||
"hex",
|
|
||||||
"http 1.3.1",
|
"http 1.3.1",
|
||||||
"inquire",
|
"inquire",
|
||||||
"k3d-rs",
|
"k3d-rs",
|
||||||
@@ -1443,7 +1426,6 @@ dependencies = [
|
|||||||
"non-blank-string-rs",
|
"non-blank-string-rs",
|
||||||
"opnsense-config",
|
"opnsense-config",
|
||||||
"opnsense-config-xml",
|
"opnsense-config-xml",
|
||||||
"rand 0.9.1",
|
|
||||||
"reqwest 0.11.27",
|
"reqwest 0.11.27",
|
||||||
"russh",
|
"russh",
|
||||||
"rust-ipmi",
|
"rust-ipmi",
|
||||||
@@ -1568,12 +1550,6 @@ version = "0.3.9"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024"
|
checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "hex"
|
|
||||||
version = "0.4.3"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "hex-literal"
|
name = "hex-literal"
|
||||||
version = "0.4.1"
|
version = "0.4.1"
|
||||||
|
|||||||
@@ -137,9 +137,8 @@ Our approach addresses both customer and team multi-tenancy requirements:
|
|||||||
### Implementation Roadmap
|
### Implementation Roadmap
|
||||||
1. **Phase 1**: Implement VPN access and manual tenant provisioning
|
1. **Phase 1**: Implement VPN access and manual tenant provisioning
|
||||||
2. **Phase 2**: Deploy TenantScore automation for namespace, RBAC, and NetworkPolicy management
|
2. **Phase 2**: Deploy TenantScore automation for namespace, RBAC, and NetworkPolicy management
|
||||||
4. **Phase 3**: Work on privilege escalation from pods, audit for weaknesses, enforce security policies on pod runtimes
|
3. **Phase 3**: Integrate Keycloak for centralized identity management
|
||||||
3. **Phase 4**: Integrate Keycloak for centralized identity management
|
4. **Phase 4**: Add advanced monitoring and per-tenant observability
|
||||||
4. **Phase 5**: Add advanced monitoring and per-tenant observability
|
|
||||||
|
|
||||||
### TenantScore Structure Preview
|
### TenantScore Structure Preview
|
||||||
```rust
|
```rust
|
||||||
|
|||||||
@@ -1,41 +0,0 @@
|
|||||||
apiVersion: networking.k8s.io/v1
|
|
||||||
kind: NetworkPolicy
|
|
||||||
metadata:
|
|
||||||
name: tenant-isolation-policy
|
|
||||||
namespace: testtenant
|
|
||||||
spec:
|
|
||||||
podSelector: {} # Selects all pods in the namespace
|
|
||||||
policyTypes:
|
|
||||||
- Ingress
|
|
||||||
- Egress
|
|
||||||
ingress:
|
|
||||||
- from:
|
|
||||||
- podSelector: {} # Allow from all pods in the same namespace
|
|
||||||
egress:
|
|
||||||
- to:
|
|
||||||
- podSelector: {} # Allow to all pods in the same namespace
|
|
||||||
- to:
|
|
||||||
- podSelector: {}
|
|
||||||
namespaceSelector:
|
|
||||||
matchLabels:
|
|
||||||
kubernetes.io/metadata.name: openshift-dns # Target the openshift-dns namespace
|
|
||||||
# Note, only opening port 53 is not enough, will have to dig deeper into this one eventually
|
|
||||||
# ports:
|
|
||||||
# - protocol: UDP
|
|
||||||
# port: 53
|
|
||||||
# - protocol: TCP
|
|
||||||
# port: 53
|
|
||||||
# Allow egress to public internet only
|
|
||||||
- to:
|
|
||||||
- ipBlock:
|
|
||||||
cidr: 0.0.0.0/0
|
|
||||||
except:
|
|
||||||
- 10.0.0.0/8 # RFC1918
|
|
||||||
- 172.16.0.0/12 # RFC1918
|
|
||||||
- 192.168.0.0/16 # RFC1918
|
|
||||||
- 169.254.0.0/16 # Link-local
|
|
||||||
- 127.0.0.0/8 # Loopback
|
|
||||||
- 224.0.0.0/4 # Multicast
|
|
||||||
- 240.0.0.0/4 # Reserved
|
|
||||||
- 100.64.0.0/10 # Carrier-grade NAT
|
|
||||||
- 0.0.0.0/8 # Reserved
|
|
||||||
@@ -1,95 +0,0 @@
|
|||||||
apiVersion: v1
|
|
||||||
kind: Namespace
|
|
||||||
metadata:
|
|
||||||
name: testtenant
|
|
||||||
---
|
|
||||||
apiVersion: v1
|
|
||||||
kind: Namespace
|
|
||||||
metadata:
|
|
||||||
name: testtenant2
|
|
||||||
---
|
|
||||||
apiVersion: apps/v1
|
|
||||||
kind: Deployment
|
|
||||||
metadata:
|
|
||||||
name: test-web
|
|
||||||
namespace: testtenant
|
|
||||||
spec:
|
|
||||||
replicas: 1
|
|
||||||
selector:
|
|
||||||
matchLabels:
|
|
||||||
app: test-web
|
|
||||||
template:
|
|
||||||
metadata:
|
|
||||||
labels:
|
|
||||||
app: test-web
|
|
||||||
spec:
|
|
||||||
containers:
|
|
||||||
- name: nginx
|
|
||||||
image: nginxinc/nginx-unprivileged
|
|
||||||
ports:
|
|
||||||
- containerPort: 80
|
|
||||||
---
|
|
||||||
apiVersion: v1
|
|
||||||
kind: Service
|
|
||||||
metadata:
|
|
||||||
name: test-web
|
|
||||||
namespace: testtenant
|
|
||||||
spec:
|
|
||||||
selector:
|
|
||||||
app: test-web
|
|
||||||
ports:
|
|
||||||
- port: 80
|
|
||||||
targetPort: 8080
|
|
||||||
---
|
|
||||||
apiVersion: apps/v1
|
|
||||||
kind: Deployment
|
|
||||||
metadata:
|
|
||||||
name: test-client
|
|
||||||
namespace: testtenant
|
|
||||||
spec:
|
|
||||||
replicas: 1
|
|
||||||
selector:
|
|
||||||
matchLabels:
|
|
||||||
app: test-client
|
|
||||||
template:
|
|
||||||
metadata:
|
|
||||||
labels:
|
|
||||||
app: test-client
|
|
||||||
spec:
|
|
||||||
containers:
|
|
||||||
- name: curl
|
|
||||||
image: curlimages/curl:latest
|
|
||||||
command: ["/bin/sh", "-c", "sleep 3600"]
|
|
||||||
---
|
|
||||||
apiVersion: apps/v1
|
|
||||||
kind: Deployment
|
|
||||||
metadata:
|
|
||||||
name: test-web
|
|
||||||
namespace: testtenant2
|
|
||||||
spec:
|
|
||||||
replicas: 1
|
|
||||||
selector:
|
|
||||||
matchLabels:
|
|
||||||
app: test-web
|
|
||||||
template:
|
|
||||||
metadata:
|
|
||||||
labels:
|
|
||||||
app: test-web
|
|
||||||
spec:
|
|
||||||
containers:
|
|
||||||
- name: nginx
|
|
||||||
image: nginxinc/nginx-unprivileged
|
|
||||||
ports:
|
|
||||||
- containerPort: 80
|
|
||||||
---
|
|
||||||
apiVersion: v1
|
|
||||||
kind: Service
|
|
||||||
metadata:
|
|
||||||
name: test-web
|
|
||||||
namespace: testtenant2
|
|
||||||
spec:
|
|
||||||
selector:
|
|
||||||
app: test-web
|
|
||||||
ports:
|
|
||||||
- port: 80
|
|
||||||
targetPort: 8080
|
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
[package]
|
|
||||||
name = "example-tenant"
|
|
||||||
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" }
|
|
||||||
cidr = { workspace = true }
|
|
||||||
tokio = { workspace = true }
|
|
||||||
harmony_macros = { path = "../../harmony_macros" }
|
|
||||||
log = { workspace = true }
|
|
||||||
env_logger = { workspace = true }
|
|
||||||
url = { workspace = true }
|
|
||||||
@@ -1,41 +0,0 @@
|
|||||||
use harmony::{
|
|
||||||
data::Id,
|
|
||||||
inventory::Inventory,
|
|
||||||
maestro::Maestro,
|
|
||||||
modules::tenant::TenantScore,
|
|
||||||
topology::{K8sAnywhereTopology, tenant::TenantConfig},
|
|
||||||
};
|
|
||||||
|
|
||||||
#[tokio::main]
|
|
||||||
async fn main() {
|
|
||||||
let tenant = TenantScore {
|
|
||||||
config: TenantConfig {
|
|
||||||
id: Id::default(),
|
|
||||||
name: "TestTenant".to_string(),
|
|
||||||
..Default::default()
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
let mut maestro = Maestro::<K8sAnywhereTopology>::initialize(
|
|
||||||
Inventory::autoload(),
|
|
||||||
K8sAnywhereTopology::new(),
|
|
||||||
)
|
|
||||||
.await
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
maestro.register_all(vec![Box::new(tenant)]);
|
|
||||||
harmony_cli::init(maestro, None).await.unwrap();
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO write tests
|
|
||||||
// - Create Tenant with default config mostly, make sure namespace is created
|
|
||||||
// - deploy sample client/server app with nginx unprivileged and a service
|
|
||||||
// - exec in the client pod and validate the following
|
|
||||||
// - can reach internet
|
|
||||||
// - can reach server pod
|
|
||||||
// - can resolve dns queries to internet
|
|
||||||
// - can resolve dns queries to services
|
|
||||||
// - cannot reach services and pods in other namespaces
|
|
||||||
// - Create Tenant with specific cpu/ram/storage requests / limits and make sure they are enforced by trying to
|
|
||||||
// deploy a pod with lower requests/limits (accepted) and higher requests/limits (rejected)
|
|
||||||
// - Create TenantCredentials and make sure they give only access to the correct tenant
|
|
||||||
@@ -6,8 +6,6 @@ readme.workspace = true
|
|||||||
license.workspace = true
|
license.workspace = true
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
rand = "0.9"
|
|
||||||
hex = "0.4"
|
|
||||||
libredfish = "0.1.1"
|
libredfish = "0.1.1"
|
||||||
reqwest = { version = "0.11", features = ["blocking", "json"] }
|
reqwest = { version = "0.11", features = ["blocking", "json"] }
|
||||||
russh = "0.45.0"
|
russh = "0.45.0"
|
||||||
|
|||||||
@@ -1,23 +1,5 @@
|
|||||||
use rand::distr::Alphanumeric;
|
|
||||||
use rand::distr::SampleString;
|
|
||||||
use std::time::SystemTime;
|
|
||||||
use std::time::UNIX_EPOCH;
|
|
||||||
|
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
/// A unique identifier designed for ease of use.
|
|
||||||
///
|
|
||||||
/// You can pass it any String to use and Id, or you can use the default format with `Id::default()`
|
|
||||||
///
|
|
||||||
/// The default format looks like this
|
|
||||||
///
|
|
||||||
/// `462d4c_g2COgai`
|
|
||||||
///
|
|
||||||
/// The first part is the unix timesamp in hexadecimal which makes Id easily sorted by creation time.
|
|
||||||
/// Second part is a serie of 7 random characters.
|
|
||||||
///
|
|
||||||
/// **It is not meant to be very secure or unique**, it is suitable to generate up to 10 000 items per
|
|
||||||
/// second with a reasonable collision rate of 0,000014 % as calculated by this calculator : https://kevingal.com/apps/collision.html
|
|
||||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||||
pub struct Id {
|
pub struct Id {
|
||||||
value: String,
|
value: String,
|
||||||
@@ -28,26 +10,3 @@ impl Id {
|
|||||||
Self { value }
|
Self { value }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl std::fmt::Display for Id {
|
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
||||||
f.write_str(&self.value)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Default for Id {
|
|
||||||
fn default() -> Self {
|
|
||||||
let start = SystemTime::now();
|
|
||||||
let since_the_epoch = start
|
|
||||||
.duration_since(UNIX_EPOCH)
|
|
||||||
.expect("Time went backwards");
|
|
||||||
let timestamp = since_the_epoch.as_secs();
|
|
||||||
|
|
||||||
let hex_timestamp = format!("{:x}", timestamp & 0xffffff);
|
|
||||||
|
|
||||||
let random_part: String = Alphanumeric.sample_string(&mut rand::rng(), 7);
|
|
||||||
|
|
||||||
let value = format!("{}_{}", hex_timestamp, random_part);
|
|
||||||
Self { value }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -20,7 +20,6 @@ pub enum InterpretName {
|
|||||||
Panic,
|
Panic,
|
||||||
OPNSense,
|
OPNSense,
|
||||||
K3dInstallation,
|
K3dInstallation,
|
||||||
TenantInterpret,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl std::fmt::Display for InterpretName {
|
impl std::fmt::Display for InterpretName {
|
||||||
@@ -36,7 +35,6 @@ impl std::fmt::Display for InterpretName {
|
|||||||
InterpretName::Panic => f.write_str("Panic"),
|
InterpretName::Panic => f.write_str("Panic"),
|
||||||
InterpretName::OPNSense => f.write_str("OPNSense"),
|
InterpretName::OPNSense => f.write_str("OPNSense"),
|
||||||
InterpretName::K3dInstallation => f.write_str("K3dInstallation"),
|
InterpretName::K3dInstallation => f.write_str("K3dInstallation"),
|
||||||
InterpretName::TenantInterpret => f.write_str("Tenant"),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
use std::{io::Error, process::Command, sync::Arc};
|
use std::{collections::HashMap, process::Command, sync::Arc};
|
||||||
|
|
||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
use inquire::Confirm;
|
use inquire::Confirm;
|
||||||
use log::{info, warn};
|
use log::{info, warn};
|
||||||
use tokio::sync::OnceCell;
|
use tokio::sync::{Mutex, OnceCell};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
executors::ExecutorError,
|
executors::ExecutorError,
|
||||||
@@ -17,6 +17,7 @@ use crate::{
|
|||||||
use super::{
|
use super::{
|
||||||
HelmCommand, K8sclient, Topology,
|
HelmCommand, K8sclient, Topology,
|
||||||
k8s::K8sClient,
|
k8s::K8sClient,
|
||||||
|
oberservability::monitoring::AlertReceiver,
|
||||||
tenant::{
|
tenant::{
|
||||||
ResourceLimits, TenantConfig, TenantManager, TenantNetworkPolicy, k8s::K8sTenantManager,
|
ResourceLimits, TenantConfig, TenantManager, TenantNetworkPolicy, k8s::K8sTenantManager,
|
||||||
},
|
},
|
||||||
@@ -37,6 +38,7 @@ enum K8sSource {
|
|||||||
pub struct K8sAnywhereTopology {
|
pub struct K8sAnywhereTopology {
|
||||||
k8s_state: OnceCell<Option<K8sState>>,
|
k8s_state: OnceCell<Option<K8sState>>,
|
||||||
tenant_manager: OnceCell<K8sTenantManager>,
|
tenant_manager: OnceCell<K8sTenantManager>,
|
||||||
|
pub alert_receivers: Mutex<HashMap<String, OnceCell<AlertReceiver>>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
@@ -61,6 +63,7 @@ impl K8sAnywhereTopology {
|
|||||||
Self {
|
Self {
|
||||||
k8s_state: OnceCell::new(),
|
k8s_state: OnceCell::new(),
|
||||||
tenant_manager: OnceCell::new(),
|
tenant_manager: OnceCell::new(),
|
||||||
|
alert_receivers: Mutex::new(HashMap::new()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -170,22 +173,6 @@ impl K8sAnywhereTopology {
|
|||||||
Ok(Some(state))
|
Ok(Some(state))
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn ensure_k8s_tenant_manager(&self) -> Result<(), String> {
|
|
||||||
if let Some(_) = self.tenant_manager.get() {
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
|
|
||||||
self.tenant_manager
|
|
||||||
.get_or_try_init(async || -> Result<K8sTenantManager, String> {
|
|
||||||
let k8s_client = self.k8s_client().await?;
|
|
||||||
Ok(K8sTenantManager::new(k8s_client))
|
|
||||||
})
|
|
||||||
.await
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_k8s_tenant_manager(&self) -> Result<&K8sTenantManager, ExecutorError> {
|
fn get_k8s_tenant_manager(&self) -> Result<&K8sTenantManager, ExecutorError> {
|
||||||
match self.tenant_manager.get() {
|
match self.tenant_manager.get() {
|
||||||
Some(t) => Ok(t),
|
Some(t) => Ok(t),
|
||||||
@@ -233,10 +220,6 @@ impl Topology for K8sAnywhereTopology {
|
|||||||
"No K8s client could be found or installed".to_string(),
|
"No K8s client could be found or installed".to_string(),
|
||||||
))?;
|
))?;
|
||||||
|
|
||||||
self.ensure_k8s_tenant_manager()
|
|
||||||
.await
|
|
||||||
.map_err(|e| InterpretError::new(e))?;
|
|
||||||
|
|
||||||
match self.is_helm_available() {
|
match self.is_helm_available() {
|
||||||
Ok(()) => Ok(Outcome::success(format!(
|
Ok(()) => Ok(Outcome::success(format!(
|
||||||
"{} + helm available",
|
"{} + helm available",
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
|
use dyn_clone::DynClone;
|
||||||
|
use serde::Serialize;
|
||||||
|
|
||||||
use std::fmt::Debug;
|
use std::fmt::Debug;
|
||||||
use url::Url;
|
|
||||||
|
|
||||||
use crate::interpret::InterpretError;
|
use crate::interpret::InterpretError;
|
||||||
|
|
||||||
@@ -13,19 +14,20 @@ use crate::{interpret::Outcome, topology::Topology};
|
|||||||
/// monitoring data, enabling consistent processing regardless of the underlying data source.
|
/// monitoring data, enabling consistent processing regardless of the underlying data source.
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
pub trait Monitor<T: Topology>: Debug + Send + Sync {
|
pub trait Monitor<T: Topology>: Debug + Send + Sync {
|
||||||
async fn deploy_monitor(
|
async fn deploy_monitor(&self, topology: &T) -> Result<Outcome, InterpretError>;
|
||||||
&self,
|
|
||||||
topology: &T,
|
|
||||||
alert_receivers: Vec<AlertReceiver>,
|
|
||||||
) -> Result<Outcome, InterpretError>;
|
|
||||||
|
|
||||||
async fn delete_monitor(
|
async fn delete_monitor(&self, topolgy: &T) -> Result<Outcome, InterpretError>;
|
||||||
&self,
|
|
||||||
topolgy: &T,
|
|
||||||
alert_receivers: Vec<AlertReceiver>,
|
|
||||||
) -> Result<Outcome, InterpretError>;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
|
pub trait AlertReceiverDeployment<T: Topology>: Debug + DynClone + Send + Sync {
|
||||||
|
async fn deploy_alert_receiver(&self, topology: &T) -> Result<Outcome, InterpretError>;
|
||||||
|
}
|
||||||
|
|
||||||
|
dyn_clone::clone_trait_object!(<T> AlertReceiverDeployment<T>);
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Serialize)]
|
||||||
pub struct AlertReceiver {
|
pub struct AlertReceiver {
|
||||||
pub receiver_id: String,
|
pub receiver_id: String,
|
||||||
|
pub receiver_installed: bool,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -71,21 +71,6 @@ impl TenantManager for K8sTenantManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
);
|
);
|
||||||
|
|
||||||
let network_policy = json!({
|
|
||||||
"apiVersion": "networking.k8s.io/v1",
|
|
||||||
"kind": "NetworkPolicy",
|
|
||||||
"metadata": {
|
|
||||||
"name": format!("{}-network-policy", config.name),
|
|
||||||
},
|
|
||||||
"spec": {
|
|
||||||
"podSelector": {},
|
|
||||||
"egress": [],
|
|
||||||
"ingress": [],
|
|
||||||
"policyTypes": [
|
|
||||||
]
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn update_tenant_resource_limits(
|
async fn update_tenant_resource_limits(
|
||||||
|
|||||||
@@ -27,28 +27,6 @@ pub struct TenantConfig {
|
|||||||
pub labels_or_tags: HashMap<String, String>,
|
pub labels_or_tags: HashMap<String, String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for TenantConfig {
|
|
||||||
fn default() -> Self {
|
|
||||||
let id = Id::default();
|
|
||||||
Self {
|
|
||||||
name: format!("tenant_{id}"),
|
|
||||||
id,
|
|
||||||
resource_limits: ResourceLimits {
|
|
||||||
cpu_request_cores: 4.0,
|
|
||||||
cpu_limit_cores: 4.0,
|
|
||||||
memory_request_gb: 4.0,
|
|
||||||
memory_limit_gb: 4.0,
|
|
||||||
storage_total_gb: 20.0,
|
|
||||||
},
|
|
||||||
network_policy: TenantNetworkPolicy {
|
|
||||||
default_inter_tenant_ingress: InterTenantIngressPolicy::DenyAll,
|
|
||||||
default_internet_egress: InternetEgressPolicy::AllowAll,
|
|
||||||
},
|
|
||||||
labels_or_tags: HashMap::new(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Default)]
|
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Default)]
|
||||||
pub struct ResourceLimits {
|
pub struct ResourceLimits {
|
||||||
/// Requested/guaranteed CPU cores (e.g., 2.0).
|
/// Requested/guaranteed CPU cores (e.g., 2.0).
|
||||||
|
|||||||
@@ -12,5 +12,4 @@ pub mod load_balancer;
|
|||||||
pub mod monitoring;
|
pub mod monitoring;
|
||||||
pub mod okd;
|
pub mod okd;
|
||||||
pub mod opnsense;
|
pub mod opnsense;
|
||||||
pub mod tenant;
|
|
||||||
pub mod tftp;
|
pub mod tftp;
|
||||||
|
|||||||
102
harmony/src/modules/monitoring/alertmanager_types.rs
Normal file
102
harmony/src/modules/monitoring/alertmanager_types.rs
Normal file
@@ -0,0 +1,102 @@
|
|||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use url::Url;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
|
pub struct AlertManagerValues {
|
||||||
|
pub alertmanager: AlertManager,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
|
pub struct AlertManager {
|
||||||
|
pub enabled: bool,
|
||||||
|
pub config: AlertManagerConfig,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||||
|
pub struct AlertChannelConfig {
|
||||||
|
pub receiver: AlertChannelReceiver,
|
||||||
|
pub route: AlertChannelRoute,
|
||||||
|
pub global_config: Option<AlertChannelGlobalConfig>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
|
pub struct AlertChannelReceiver {
|
||||||
|
pub name: String,
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
pub slack_configs: Option<Vec<SlackConfig>>,
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
pub webhook_configs: Option<Vec<WebhookConfig>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
|
pub struct AlertManagerRoute {
|
||||||
|
pub group_by: Vec<String>,
|
||||||
|
pub group_wait: String,
|
||||||
|
pub group_interval: String,
|
||||||
|
pub repeat_interval: String,
|
||||||
|
pub routes: Vec<AlertChannelRoute>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
|
pub struct AlertChannelGlobalConfig {
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
pub slack_api_url: Option<Url>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
|
pub struct SlackConfig {
|
||||||
|
pub channel: String,
|
||||||
|
pub send_resolved: bool,
|
||||||
|
pub title: String,
|
||||||
|
pub text: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
|
pub struct WebhookConfig {
|
||||||
|
pub url: Url,
|
||||||
|
pub send_resolved: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
|
pub struct AlertChannelRoute {
|
||||||
|
pub receiver: String,
|
||||||
|
pub matchers: Vec<String>,
|
||||||
|
#[serde(default)]
|
||||||
|
pub r#continue: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
|
pub struct AlertManagerConfig {
|
||||||
|
pub global: Option<AlertChannelGlobalConfig>,
|
||||||
|
pub route: AlertManagerRoute,
|
||||||
|
pub receivers: Vec<AlertChannelReceiver>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AlertManagerValues {
|
||||||
|
pub fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
alertmanager: AlertManager {
|
||||||
|
enabled: true,
|
||||||
|
config: AlertManagerConfig {
|
||||||
|
global: None,
|
||||||
|
route: AlertManagerRoute {
|
||||||
|
group_by: vec!["job".to_string()],
|
||||||
|
group_wait: "30s".to_string(),
|
||||||
|
group_interval: "5m".to_string(),
|
||||||
|
repeat_interval: "12h".to_string(),
|
||||||
|
routes: vec![AlertChannelRoute {
|
||||||
|
receiver: "null".to_string(),
|
||||||
|
matchers: vec!["alertname=Watchdog".to_string()],
|
||||||
|
r#continue: false,
|
||||||
|
}],
|
||||||
|
},
|
||||||
|
receivers: vec![AlertChannelReceiver {
|
||||||
|
name: "null".to_string(),
|
||||||
|
slack_configs: None,
|
||||||
|
webhook_configs: None,
|
||||||
|
}],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,55 +1,168 @@
|
|||||||
|
use super::{
|
||||||
|
discord_alert_manager::discord_alert_manager_score, kube_prometheus_monitor::AlertManagerConfig,
|
||||||
|
};
|
||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
use serde_json::Value;
|
use serde::Serialize;
|
||||||
|
use serde_yaml::Value;
|
||||||
|
use tokio::sync::OnceCell;
|
||||||
use url::Url;
|
use url::Url;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
interpret::{InterpretError, Outcome},
|
data::{Id, Version},
|
||||||
topology::K8sAnywhereTopology,
|
interpret::{Interpret, InterpretError, InterpretName, InterpretStatus, Outcome},
|
||||||
|
inventory::Inventory,
|
||||||
|
score::Score,
|
||||||
|
topology::{
|
||||||
|
HelmCommand, K8sAnywhereTopology, Topology,
|
||||||
|
oberservability::monitoring::{AlertReceiver, AlertReceiverDeployment},
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[async_trait]
|
||||||
|
impl<T: Topology + DiscordWebhookReceiver> AlertReceiverDeployment<T> for DiscordWebhookConfig {
|
||||||
|
async fn deploy_alert_receiver(&self, topology: &T) -> Result<Outcome, InterpretError> {
|
||||||
|
topology.deploy_discord_webhook_receiver(self.clone()).await
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Serialize)]
|
||||||
pub struct DiscordWebhookConfig {
|
pub struct DiscordWebhookConfig {
|
||||||
pub webhook_url: Url,
|
pub webhook_url: Url,
|
||||||
pub name: String,
|
pub name: String,
|
||||||
pub send_resolved_notifications: bool,
|
pub send_resolved_notifications: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
pub trait DiscordWebhookReceiver {
|
pub trait DiscordWebhookReceiver {
|
||||||
fn deploy_discord_webhook_receiver(
|
async fn deploy_discord_webhook_receiver(
|
||||||
&self,
|
&self,
|
||||||
_notification_adapter_id: &str,
|
config: DiscordWebhookConfig,
|
||||||
) -> Result<Outcome, InterpretError>;
|
) -> Result<Outcome, InterpretError>;
|
||||||
|
|
||||||
fn delete_discord_webhook_receiver(
|
fn delete_discord_webhook_receiver(
|
||||||
&self,
|
&self,
|
||||||
_notification_adapter_id: &str,
|
config: DiscordWebhookConfig,
|
||||||
) -> Result<Outcome, InterpretError>;
|
) -> Result<Outcome, InterpretError>;
|
||||||
}
|
}
|
||||||
|
|
||||||
// trait used to generate alert manager config values impl<T: Topology + AlertManagerConfig> Monitor for KubePrometheus
|
|
||||||
pub trait AlertManagerConfig<T> {
|
|
||||||
fn get_alert_manager_config(&self) -> Result<Value, InterpretError>;
|
|
||||||
}
|
|
||||||
|
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
impl<T: DiscordWebhookReceiver> AlertManagerConfig<T> for DiscordWebhookConfig {
|
impl<T: DiscordWebhookReceiver> AlertManagerConfig<T> for DiscordWebhookConfig {
|
||||||
fn get_alert_manager_config(&self) -> Result<Value, InterpretError> {
|
async fn get_alert_manager_config(&self) -> Result<Value, InterpretError> {
|
||||||
todo!()
|
todo!()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
impl DiscordWebhookReceiver for K8sAnywhereTopology {
|
impl DiscordWebhookReceiver for K8sAnywhereTopology {
|
||||||
fn deploy_discord_webhook_receiver(
|
async fn deploy_discord_webhook_receiver(
|
||||||
&self,
|
&self,
|
||||||
_notification_adapter_id: &str,
|
config: DiscordWebhookConfig,
|
||||||
) -> Result<Outcome, InterpretError> {
|
) -> Result<Outcome, InterpretError> {
|
||||||
todo!()
|
let receiver_key = config.name.clone();
|
||||||
|
let mut adapters_map_guard = self.alert_receivers.lock().await;
|
||||||
|
|
||||||
|
let cell = adapters_map_guard
|
||||||
|
.entry(receiver_key.clone())
|
||||||
|
.or_insert_with(OnceCell::new);
|
||||||
|
|
||||||
|
if let Some(initialized_receiver) = cell.get() {
|
||||||
|
return Ok(Outcome::success(format!(
|
||||||
|
"Discord Webhook adapter for '{}' already initialized.",
|
||||||
|
initialized_receiver.receiver_id
|
||||||
|
)));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let final_state = cell
|
||||||
|
.get_or_try_init(|| async {
|
||||||
|
initialize_discord_webhook_receiver(config.clone(), self).await
|
||||||
|
})
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
Ok(Outcome::success(format!(
|
||||||
|
"Discord Webhook Receiver for '{}' ensured/initialized.",
|
||||||
|
final_state.receiver_id
|
||||||
|
)))
|
||||||
|
}
|
||||||
|
|
||||||
fn delete_discord_webhook_receiver(
|
fn delete_discord_webhook_receiver(
|
||||||
&self,
|
&self,
|
||||||
_notification_adapter_id: &str,
|
_config: DiscordWebhookConfig,
|
||||||
) -> Result<Outcome, InterpretError> {
|
) -> Result<Outcome, InterpretError> {
|
||||||
todo!()
|
todo!()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn initialize_discord_webhook_receiver(
|
||||||
|
conf: DiscordWebhookConfig,
|
||||||
|
topology: &K8sAnywhereTopology,
|
||||||
|
) -> Result<AlertReceiver, InterpretError> {
|
||||||
|
println!(
|
||||||
|
"Attempting to initialize Discord adapter for: {}",
|
||||||
|
conf.name
|
||||||
|
);
|
||||||
|
let score = DiscordWebhookReceiverScore {
|
||||||
|
config: conf.clone(),
|
||||||
|
};
|
||||||
|
let inventory = Inventory::autoload();
|
||||||
|
let interpret = score.create_interpret();
|
||||||
|
|
||||||
|
interpret.execute(&inventory, topology).await?;
|
||||||
|
|
||||||
|
Ok(AlertReceiver {
|
||||||
|
receiver_id: conf.name,
|
||||||
|
receiver_installed: true,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
#[derive(Debug, Clone, Serialize)]
|
||||||
|
struct DiscordWebhookReceiverScore {
|
||||||
|
config: DiscordWebhookConfig,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: Topology + HelmCommand> Score<T> for DiscordWebhookReceiverScore {
|
||||||
|
fn create_interpret(&self) -> Box<dyn Interpret<T>> {
|
||||||
|
Box::new(DiscordWebhookReceiverScoreInterpret {
|
||||||
|
config: self.config.clone(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn name(&self) -> String {
|
||||||
|
"DiscordWebhookReceiverScore".to_string()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#[derive(Debug)]
|
||||||
|
struct DiscordWebhookReceiverScoreInterpret {
|
||||||
|
config: DiscordWebhookConfig,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
|
impl<T: Topology + HelmCommand> Interpret<T> for DiscordWebhookReceiverScoreInterpret {
|
||||||
|
async fn execute(
|
||||||
|
&self,
|
||||||
|
inventory: &Inventory,
|
||||||
|
topology: &T,
|
||||||
|
) -> Result<Outcome, InterpretError> {
|
||||||
|
discord_alert_manager_score(
|
||||||
|
self.config.webhook_url.clone(),
|
||||||
|
self.config.name.clone(),
|
||||||
|
self.config.name.clone(),
|
||||||
|
)
|
||||||
|
.create_interpret()
|
||||||
|
.execute(inventory, topology)
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_name(&self) -> InterpretName {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_version(&self) -> Version {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_status(&self) -> InterpretStatus {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_children(&self) -> Vec<Id> {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
108
harmony/src/modules/monitoring/kube_prometheus_monitor.rs
Normal file
108
harmony/src/modules/monitoring/kube_prometheus_monitor.rs
Normal file
@@ -0,0 +1,108 @@
|
|||||||
|
use async_trait::async_trait;
|
||||||
|
use serde::Serialize;
|
||||||
|
use serde_yaml::Value;
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
data::{Id, Version},
|
||||||
|
interpret::{Interpret, InterpretError, InterpretName, InterpretStatus, Outcome},
|
||||||
|
inventory::Inventory,
|
||||||
|
score::Score,
|
||||||
|
topology::{
|
||||||
|
HelmCommand, Topology,
|
||||||
|
oberservability::monitoring::{AlertReceiverDeployment, Monitor},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
use super::{
|
||||||
|
config::KubePrometheusConfig, kube_prometheus_helm_chart::kube_prometheus_helm_chart_score,
|
||||||
|
};
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct KubePrometheus<T> {
|
||||||
|
alert_receivers: Vec<Box<dyn AlertReceiverDeployment<T>>>,
|
||||||
|
config: KubePrometheusConfig,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
|
pub trait AlertManagerConfig<T> {
|
||||||
|
async fn get_alert_manager_config(&self) -> Result<Value, InterpretError>;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: Topology> KubePrometheus<T> {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self {
|
||||||
|
alert_receivers: Vec::new(),
|
||||||
|
config: KubePrometheusConfig::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
|
impl<T: Topology + HelmCommand + std::fmt::Debug> Monitor<T> for KubePrometheus<T> {
|
||||||
|
async fn deploy_monitor(&self, topology: &T) -> Result<Outcome, InterpretError> {
|
||||||
|
for alert_receiver in &self.alert_receivers {
|
||||||
|
alert_receiver.deploy_alert_receiver(topology).await?;
|
||||||
|
}
|
||||||
|
let score = KubePrometheusScore {
|
||||||
|
config: self.config.clone(),
|
||||||
|
};
|
||||||
|
let inventory = Inventory::autoload();
|
||||||
|
score.create_interpret().execute(&inventory, topology).await
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn delete_monitor(&self, _topolgy: &T) -> Result<Outcome, InterpretError> {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Serialize)]
|
||||||
|
struct KubePrometheusScore {
|
||||||
|
config: KubePrometheusConfig,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: Topology + HelmCommand> Score<T> for KubePrometheusScore {
|
||||||
|
fn create_interpret(&self) -> Box<dyn Interpret<T>> {
|
||||||
|
Box::new(KubePromethusScoreInterpret {
|
||||||
|
score: self.clone(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn name(&self) -> String {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Serialize)]
|
||||||
|
struct KubePromethusScoreInterpret {
|
||||||
|
score: KubePrometheusScore,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
|
impl<T: Topology + HelmCommand> Interpret<T> for KubePromethusScoreInterpret {
|
||||||
|
async fn execute(
|
||||||
|
&self,
|
||||||
|
inventory: &Inventory,
|
||||||
|
topology: &T,
|
||||||
|
) -> Result<Outcome, InterpretError> {
|
||||||
|
kube_prometheus_helm_chart_score(&self.score.config)
|
||||||
|
.create_interpret()
|
||||||
|
.execute(inventory, topology)
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_name(&self) -> InterpretName {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_version(&self) -> Version {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_status(&self) -> InterpretStatus {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_children(&self) -> Vec<Id> {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,5 +1,7 @@
|
|||||||
|
pub mod alertmanager_types;
|
||||||
mod config;
|
mod config;
|
||||||
mod discord_alert_manager;
|
mod discord_alert_manager;
|
||||||
pub mod discord_webhook_sender;
|
pub mod discord_webhook_sender;
|
||||||
mod kube_prometheus;
|
mod kube_prometheus_helm_chart;
|
||||||
|
pub mod kube_prometheus_monitor;
|
||||||
pub mod monitoring_alerting;
|
pub mod monitoring_alerting;
|
||||||
|
|||||||
@@ -14,8 +14,7 @@ use crate::{
|
|||||||
};
|
};
|
||||||
|
|
||||||
use super::{
|
use super::{
|
||||||
config::KubePrometheusConfig, discord_alert_manager::discord_alert_manager_score,
|
config::KubePrometheusConfig, kube_prometheus_helm_chart::kube_prometheus_helm_chart_score,
|
||||||
kube_prometheus::kube_prometheus_helm_chart_score,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Debug, Clone, Serialize)]
|
#[derive(Debug, Clone, Serialize)]
|
||||||
|
|||||||
@@ -1,67 +0,0 @@
|
|||||||
use async_trait::async_trait;
|
|
||||||
use serde::Serialize;
|
|
||||||
|
|
||||||
use crate::{
|
|
||||||
data::{Id, Version},
|
|
||||||
interpret::{Interpret, InterpretError, InterpretName, InterpretStatus, Outcome},
|
|
||||||
inventory::Inventory,
|
|
||||||
score::Score,
|
|
||||||
topology::{
|
|
||||||
Topology,
|
|
||||||
tenant::{TenantConfig, TenantManager},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Clone)]
|
|
||||||
pub struct TenantScore {
|
|
||||||
pub config: TenantConfig,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T: Topology + TenantManager> Score<T> for TenantScore {
|
|
||||||
fn create_interpret(&self) -> Box<dyn crate::interpret::Interpret<T>> {
|
|
||||||
Box::new(TenantInterpret {
|
|
||||||
tenant_config: self.config.clone(),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
fn name(&self) -> String {
|
|
||||||
format!("{} TenantScore", self.config.name)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct TenantInterpret {
|
|
||||||
tenant_config: TenantConfig,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[async_trait]
|
|
||||||
impl<T: Topology + TenantManager> Interpret<T> for TenantInterpret {
|
|
||||||
async fn execute(
|
|
||||||
&self,
|
|
||||||
_inventory: &Inventory,
|
|
||||||
topology: &T,
|
|
||||||
) -> Result<Outcome, InterpretError> {
|
|
||||||
topology.provision_tenant(&self.tenant_config).await?;
|
|
||||||
|
|
||||||
Ok(Outcome::success(format!(
|
|
||||||
"Successfully provisioned tenant {} with id {}",
|
|
||||||
self.tenant_config.name, self.tenant_config.id
|
|
||||||
)))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_name(&self) -> InterpretName {
|
|
||||||
InterpretName::TenantInterpret
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_version(&self) -> Version {
|
|
||||||
todo!()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_status(&self) -> InterpretStatus {
|
|
||||||
todo!()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_children(&self) -> Vec<Id> {
|
|
||||||
todo!()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user