Compare commits
14 Commits
feat/kube-
...
feat/init_
| Author | SHA1 | Date | |
|---|---|---|---|
| 14fc4345c1 | |||
| 8e472e4c65 | |||
| ec17ccc246 | |||
| 5127f44ab3 | |||
| 2ff70db0b1 | |||
| e17ac1af83 | |||
| 31e59937dc | |||
| 60f2f31d6c | |||
| 045954f8d3 | |||
| 7c809bf18a | |||
| 6490e5e82a | |||
| 5e51f7490c | |||
| 97fba07f4e | |||
| 624e4330bb |
24
Cargo.lock
generated
24
Cargo.lock
generated
@@ -1070,6 +1070,21 @@ 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"
|
||||||
@@ -1409,12 +1424,14 @@ 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",
|
||||||
@@ -1426,6 +1443,7 @@ 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",
|
||||||
@@ -1550,6 +1568,12 @@ 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,8 +137,9 @@ 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
|
||||||
3. **Phase 3**: Integrate Keycloak for centralized identity management
|
4. **Phase 3**: Work on privilege escalation from pods, audit for weaknesses, enforce security policies on pod runtimes
|
||||||
4. **Phase 4**: Add advanced monitoring and per-tenant observability
|
3. **Phase 4**: Integrate Keycloak for centralized identity management
|
||||||
|
4. **Phase 5**: Add advanced monitoring and per-tenant observability
|
||||||
|
|
||||||
### TenantScore Structure Preview
|
### TenantScore Structure Preview
|
||||||
```rust
|
```rust
|
||||||
|
|||||||
41
adr/tenant/NetworkPolicy.yaml
Normal file
41
adr/tenant/NetworkPolicy.yaml
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
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
|
||||||
95
adr/tenant/TestDeployment.yaml
Normal file
95
adr/tenant/TestDeployment.yaml
Normal file
@@ -0,0 +1,95 @@
|
|||||||
|
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
|
||||||
18
examples/tenant/Cargo.toml
Normal file
18
examples/tenant/Cargo.toml
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
[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 }
|
||||||
41
examples/tenant/src/main.rs
Normal file
41
examples/tenant/src/main.rs
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
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,6 +6,8 @@ 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,5 +1,23 @@
|
|||||||
|
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,
|
||||||
@@ -10,3 +28,26 @@ 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,6 +20,7 @@ pub enum InterpretName {
|
|||||||
Panic,
|
Panic,
|
||||||
OPNSense,
|
OPNSense,
|
||||||
K3dInstallation,
|
K3dInstallation,
|
||||||
|
TenantInterpret,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl std::fmt::Display for InterpretName {
|
impl std::fmt::Display for InterpretName {
|
||||||
@@ -35,6 +36,7 @@ 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,4 +1,4 @@
|
|||||||
use std::{process::Command, sync::Arc};
|
use std::{io::Error, process::Command, sync::Arc};
|
||||||
|
|
||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
use inquire::Confirm;
|
use inquire::Confirm;
|
||||||
@@ -170,6 +170,22 @@ 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),
|
||||||
@@ -217,6 +233,10 @@ 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",
|
||||||
|
|||||||
@@ -71,6 +71,21 @@ 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,6 +27,28 @@ 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,4 +12,5 @@ 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;
|
||||||
|
|||||||
67
harmony/src/modules/tenant/mod.rs
Normal file
67
harmony/src/modules/tenant/mod.rs
Normal file
@@ -0,0 +1,67 @@
|
|||||||
|
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