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",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "example-tenant"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"cidr",
|
||||
"env_logger",
|
||||
"harmony",
|
||||
"harmony_cli",
|
||||
"harmony_macros",
|
||||
"harmony_types",
|
||||
"log",
|
||||
"tokio",
|
||||
"url",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "example-tui"
|
||||
version = "0.1.0"
|
||||
@@ -1424,14 +1409,12 @@ dependencies = [
|
||||
"derive-new",
|
||||
"directories",
|
||||
"dockerfile_builder",
|
||||
"dyn-clone",
|
||||
"email_address",
|
||||
"env_logger",
|
||||
"fqdn",
|
||||
"harmony_macros",
|
||||
"harmony_types",
|
||||
"helm-wrapper-rs",
|
||||
"hex",
|
||||
"http 1.3.1",
|
||||
"inquire",
|
||||
"k3d-rs",
|
||||
@@ -1443,7 +1426,6 @@ dependencies = [
|
||||
"non-blank-string-rs",
|
||||
"opnsense-config",
|
||||
"opnsense-config-xml",
|
||||
"rand 0.9.1",
|
||||
"reqwest 0.11.27",
|
||||
"russh",
|
||||
"rust-ipmi",
|
||||
@@ -1568,12 +1550,6 @@ version = "0.3.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024"
|
||||
|
||||
[[package]]
|
||||
name = "hex"
|
||||
version = "0.4.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70"
|
||||
|
||||
[[package]]
|
||||
name = "hex-literal"
|
||||
version = "0.4.1"
|
||||
|
||||
@@ -137,9 +137,8 @@ Our approach addresses both customer and team multi-tenancy requirements:
|
||||
### Implementation Roadmap
|
||||
1. **Phase 1**: Implement VPN access and manual tenant provisioning
|
||||
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 4**: Integrate Keycloak for centralized identity management
|
||||
4. **Phase 5**: Add advanced monitoring and per-tenant observability
|
||||
3. **Phase 3**: Integrate Keycloak for centralized identity management
|
||||
4. **Phase 4**: Add advanced monitoring and per-tenant observability
|
||||
|
||||
### TenantScore Structure Preview
|
||||
```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
|
||||
|
||||
[dependencies]
|
||||
rand = "0.9"
|
||||
hex = "0.4"
|
||||
libredfish = "0.1.1"
|
||||
reqwest = { version = "0.11", features = ["blocking", "json"] }
|
||||
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};
|
||||
|
||||
/// 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)]
|
||||
pub struct Id {
|
||||
value: String,
|
||||
@@ -28,26 +10,3 @@ impl Id {
|
||||
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,
|
||||
OPNSense,
|
||||
K3dInstallation,
|
||||
TenantInterpret,
|
||||
}
|
||||
|
||||
impl std::fmt::Display for InterpretName {
|
||||
@@ -36,7 +35,6 @@ impl std::fmt::Display for InterpretName {
|
||||
InterpretName::Panic => f.write_str("Panic"),
|
||||
InterpretName::OPNSense => f.write_str("OPNSense"),
|
||||
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 inquire::Confirm;
|
||||
use log::{info, warn};
|
||||
use tokio::sync::OnceCell;
|
||||
use tokio::sync::{Mutex, OnceCell};
|
||||
|
||||
use crate::{
|
||||
executors::ExecutorError,
|
||||
@@ -17,6 +17,7 @@ use crate::{
|
||||
use super::{
|
||||
HelmCommand, K8sclient, Topology,
|
||||
k8s::K8sClient,
|
||||
oberservability::monitoring::AlertReceiver,
|
||||
tenant::{
|
||||
ResourceLimits, TenantConfig, TenantManager, TenantNetworkPolicy, k8s::K8sTenantManager,
|
||||
},
|
||||
@@ -37,6 +38,7 @@ enum K8sSource {
|
||||
pub struct K8sAnywhereTopology {
|
||||
k8s_state: OnceCell<Option<K8sState>>,
|
||||
tenant_manager: OnceCell<K8sTenantManager>,
|
||||
pub alert_receivers: Mutex<HashMap<String, OnceCell<AlertReceiver>>>,
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
@@ -61,6 +63,7 @@ impl K8sAnywhereTopology {
|
||||
Self {
|
||||
k8s_state: OnceCell::new(),
|
||||
tenant_manager: OnceCell::new(),
|
||||
alert_receivers: Mutex::new(HashMap::new()),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -170,22 +173,6 @@ impl K8sAnywhereTopology {
|
||||
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> {
|
||||
match self.tenant_manager.get() {
|
||||
Some(t) => Ok(t),
|
||||
@@ -233,10 +220,6 @@ impl Topology for K8sAnywhereTopology {
|
||||
"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() {
|
||||
Ok(()) => Ok(Outcome::success(format!(
|
||||
"{} + helm available",
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
use async_trait::async_trait;
|
||||
use dyn_clone::DynClone;
|
||||
use serde::Serialize;
|
||||
|
||||
use std::fmt::Debug;
|
||||
use url::Url;
|
||||
|
||||
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.
|
||||
#[async_trait]
|
||||
pub trait Monitor<T: Topology>: Debug + Send + Sync {
|
||||
async fn deploy_monitor(
|
||||
&self,
|
||||
topology: &T,
|
||||
alert_receivers: Vec<AlertReceiver>,
|
||||
) -> Result<Outcome, InterpretError>;
|
||||
async fn deploy_monitor(&self, topology: &T) -> Result<Outcome, InterpretError>;
|
||||
|
||||
async fn delete_monitor(
|
||||
&self,
|
||||
topolgy: &T,
|
||||
alert_receivers: Vec<AlertReceiver>,
|
||||
) -> Result<Outcome, InterpretError>;
|
||||
async fn delete_monitor(&self, topolgy: &T) -> 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 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(
|
||||
|
||||
@@ -27,28 +27,6 @@ pub struct TenantConfig {
|
||||
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)]
|
||||
pub struct ResourceLimits {
|
||||
/// Requested/guaranteed CPU cores (e.g., 2.0).
|
||||
|
||||
@@ -12,5 +12,4 @@ pub mod load_balancer;
|
||||
pub mod monitoring;
|
||||
pub mod okd;
|
||||
pub mod opnsense;
|
||||
pub mod tenant;
|
||||
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 serde_json::Value;
|
||||
use serde::Serialize;
|
||||
use serde_yaml::Value;
|
||||
use tokio::sync::OnceCell;
|
||||
use url::Url;
|
||||
|
||||
use crate::{
|
||||
interpret::{InterpretError, Outcome},
|
||||
topology::K8sAnywhereTopology,
|
||||
data::{Id, Version},
|
||||
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 webhook_url: Url,
|
||||
pub name: String,
|
||||
pub send_resolved_notifications: bool,
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
pub trait DiscordWebhookReceiver {
|
||||
fn deploy_discord_webhook_receiver(
|
||||
async fn deploy_discord_webhook_receiver(
|
||||
&self,
|
||||
_notification_adapter_id: &str,
|
||||
config: DiscordWebhookConfig,
|
||||
) -> Result<Outcome, InterpretError>;
|
||||
|
||||
fn delete_discord_webhook_receiver(
|
||||
&self,
|
||||
_notification_adapter_id: &str,
|
||||
config: DiscordWebhookConfig,
|
||||
) -> 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]
|
||||
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!()
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl DiscordWebhookReceiver for K8sAnywhereTopology {
|
||||
fn deploy_discord_webhook_receiver(
|
||||
async fn deploy_discord_webhook_receiver(
|
||||
&self,
|
||||
_notification_adapter_id: &str,
|
||||
config: DiscordWebhookConfig,
|
||||
) -> 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(
|
||||
&self,
|
||||
_notification_adapter_id: &str,
|
||||
_config: DiscordWebhookConfig,
|
||||
) -> Result<Outcome, InterpretError> {
|
||||
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 discord_alert_manager;
|
||||
pub mod discord_webhook_sender;
|
||||
mod kube_prometheus;
|
||||
mod kube_prometheus_helm_chart;
|
||||
pub mod kube_prometheus_monitor;
|
||||
pub mod monitoring_alerting;
|
||||
|
||||
@@ -14,8 +14,7 @@ use crate::{
|
||||
};
|
||||
|
||||
use super::{
|
||||
config::KubePrometheusConfig, discord_alert_manager::discord_alert_manager_score,
|
||||
kube_prometheus::kube_prometheus_helm_chart_score,
|
||||
config::KubePrometheusConfig, kube_prometheus_helm_chart::kube_prometheus_helm_chart_score,
|
||||
};
|
||||
|
||||
#[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