Compare commits

..

4 Commits

Author SHA1 Message Date
de3e7869f7 feat: added impl for kube prometheus monitor 2025-06-04 11:50:36 -04:00
57eabc9834 wip: added alert manager types for use with kube-prometheus alert manager 2025-06-04 09:31:17 -04:00
cd40660350 fix: Ensure idempotent deployment of Discord adapters
Updates  to use  for managing
Discord webhook adapter deployments. This prevents redundant installations
by ensuring that the deployment interpret for a given adapter
configuration is executed only once.

The internal state now uses a  keyed by the alertreceiver ID to
track and initialize each receiver's .
2025-06-03 16:07:11 -04:00
2ca732cecd feat: added the steps to install discord-webhook-receiver for k8s anywhere topology if not already installed 2025-06-03 14:14:41 -04:00
21 changed files with 365 additions and 426 deletions

24
Cargo.lock generated
View File

@@ -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"

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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 }

View File

@@ -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

View File

@@ -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"

View File

@@ -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 }
}
}

View File

@@ -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"),
} }
} }
} }

View File

@@ -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",

View File

@@ -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,
} }

View File

@@ -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(

View File

@@ -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).

View File

@@ -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;

View 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,
}],
},
},
}
}
}

View File

@@ -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!()
}
}

View 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!()
}
}

View File

@@ -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;

View File

@@ -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)]

View File

@@ -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!()
}
}