Compare commits
3 Commits
master
...
feat/insta
| Author | SHA1 | Date | |
|---|---|---|---|
| 5ab58f0253 | |||
| 5af13800b7 | |||
| 8126b233d8 |
27
Cargo.lock
generated
27
Cargo.lock
generated
@ -1780,7 +1780,6 @@ dependencies = [
|
|||||||
name = "example-nanodc"
|
name = "example-nanodc"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"brocade",
|
|
||||||
"cidr",
|
"cidr",
|
||||||
"env_logger",
|
"env_logger",
|
||||||
"harmony",
|
"harmony",
|
||||||
@ -1789,7 +1788,6 @@ dependencies = [
|
|||||||
"harmony_tui",
|
"harmony_tui",
|
||||||
"harmony_types",
|
"harmony_types",
|
||||||
"log",
|
"log",
|
||||||
"serde",
|
|
||||||
"tokio",
|
"tokio",
|
||||||
"url",
|
"url",
|
||||||
]
|
]
|
||||||
@ -1808,7 +1806,6 @@ dependencies = [
|
|||||||
name = "example-okd-install"
|
name = "example-okd-install"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"brocade",
|
|
||||||
"cidr",
|
"cidr",
|
||||||
"env_logger",
|
"env_logger",
|
||||||
"harmony",
|
"harmony",
|
||||||
@ -1839,16 +1836,13 @@ dependencies = [
|
|||||||
name = "example-opnsense"
|
name = "example-opnsense"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"brocade",
|
|
||||||
"cidr",
|
"cidr",
|
||||||
"env_logger",
|
"env_logger",
|
||||||
"harmony",
|
"harmony",
|
||||||
"harmony_macros",
|
"harmony_macros",
|
||||||
"harmony_secret",
|
|
||||||
"harmony_tui",
|
"harmony_tui",
|
||||||
"harmony_types",
|
"harmony_types",
|
||||||
"log",
|
"log",
|
||||||
"serde",
|
|
||||||
"tokio",
|
"tokio",
|
||||||
"url",
|
"url",
|
||||||
]
|
]
|
||||||
@ -1857,7 +1851,6 @@ dependencies = [
|
|||||||
name = "example-pxe"
|
name = "example-pxe"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"brocade",
|
|
||||||
"cidr",
|
"cidr",
|
||||||
"env_logger",
|
"env_logger",
|
||||||
"harmony",
|
"harmony",
|
||||||
@ -1872,15 +1865,6 @@ dependencies = [
|
|||||||
"url",
|
"url",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "example-remove-rook-osd"
|
|
||||||
version = "0.1.0"
|
|
||||||
dependencies = [
|
|
||||||
"harmony",
|
|
||||||
"harmony_cli",
|
|
||||||
"tokio",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "example-rust"
|
name = "example-rust"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
@ -1934,6 +1918,8 @@ dependencies = [
|
|||||||
"env_logger",
|
"env_logger",
|
||||||
"harmony",
|
"harmony",
|
||||||
"harmony_macros",
|
"harmony_macros",
|
||||||
|
"harmony_secret",
|
||||||
|
"harmony_secret_derive",
|
||||||
"harmony_tui",
|
"harmony_tui",
|
||||||
"harmony_types",
|
"harmony_types",
|
||||||
"log",
|
"log",
|
||||||
@ -4627,6 +4613,15 @@ version = "0.8.6"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "caf4aa5b0f434c91fe5c7f1ecb6a5ece2130b02ad2a590589dda5146df959001"
|
checksum = "caf4aa5b0f434c91fe5c7f1ecb6a5ece2130b02ad2a590589dda5146df959001"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "remove_rook_osd"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"harmony",
|
||||||
|
"harmony_cli",
|
||||||
|
"tokio",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "reqwest"
|
name = "reqwest"
|
||||||
version = "0.11.27"
|
version = "0.11.27"
|
||||||
|
|||||||
@ -10,7 +10,6 @@ use log::{debug, info};
|
|||||||
use regex::Regex;
|
use regex::Regex;
|
||||||
use std::{collections::HashSet, str::FromStr};
|
use std::{collections::HashSet, str::FromStr};
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct FastIronClient {
|
pub struct FastIronClient {
|
||||||
shell: BrocadeShell,
|
shell: BrocadeShell,
|
||||||
version: BrocadeInfo,
|
version: BrocadeInfo,
|
||||||
|
|||||||
@ -162,7 +162,7 @@ pub async fn init(
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
pub trait BrocadeClient: std::fmt::Debug {
|
pub trait BrocadeClient {
|
||||||
/// Retrieves the operating system and version details from the connected Brocade switch.
|
/// Retrieves the operating system and version details from the connected Brocade switch.
|
||||||
///
|
///
|
||||||
/// This is typically the first call made after establishing a connection to determine
|
/// This is typically the first call made after establishing a connection to determine
|
||||||
|
|||||||
@ -10,7 +10,6 @@ use crate::{
|
|||||||
parse_brocade_mac_address, shell::BrocadeShell,
|
parse_brocade_mac_address, shell::BrocadeShell,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct NetworkOperatingSystemClient {
|
pub struct NetworkOperatingSystemClient {
|
||||||
shell: BrocadeShell,
|
shell: BrocadeShell,
|
||||||
version: BrocadeInfo,
|
version: BrocadeInfo,
|
||||||
|
|||||||
@ -13,7 +13,6 @@ use log::info;
|
|||||||
use russh::ChannelMsg;
|
use russh::ChannelMsg;
|
||||||
use tokio::time::timeout;
|
use tokio::time::timeout;
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct BrocadeShell {
|
pub struct BrocadeShell {
|
||||||
ip: IpAddr,
|
ip: IpAddr,
|
||||||
port: u16,
|
port: u16,
|
||||||
|
|||||||
@ -17,5 +17,3 @@ harmony_secret = { path = "../../harmony_secret" }
|
|||||||
log = { workspace = true }
|
log = { workspace = true }
|
||||||
env_logger = { workspace = true }
|
env_logger = { workspace = true }
|
||||||
url = { workspace = true }
|
url = { workspace = true }
|
||||||
serde = { workspace = true }
|
|
||||||
brocade = { path = "../../brocade" }
|
|
||||||
|
|||||||
@ -3,13 +3,12 @@ use std::{
|
|||||||
sync::Arc,
|
sync::Arc,
|
||||||
};
|
};
|
||||||
|
|
||||||
use brocade::BrocadeOptions;
|
|
||||||
use cidr::Ipv4Cidr;
|
use cidr::Ipv4Cidr;
|
||||||
use harmony::{
|
use harmony::{
|
||||||
config::secret::SshKeyPair,
|
config::secret::SshKeyPair,
|
||||||
data::{FileContent, FilePath},
|
data::{FileContent, FilePath},
|
||||||
hardware::{HostCategory, Location, PhysicalHost, SwitchGroup},
|
hardware::{HostCategory, Location, PhysicalHost, SwitchGroup},
|
||||||
infra::{brocade::BrocadeSwitchClient, opnsense::OPNSenseManagementInterface},
|
infra::opnsense::OPNSenseManagementInterface,
|
||||||
inventory::Inventory,
|
inventory::Inventory,
|
||||||
modules::{
|
modules::{
|
||||||
http::StaticFilesHttpScore,
|
http::StaticFilesHttpScore,
|
||||||
@ -23,9 +22,8 @@ use harmony::{
|
|||||||
topology::{LogicalHost, UnmanagedRouter},
|
topology::{LogicalHost, UnmanagedRouter},
|
||||||
};
|
};
|
||||||
use harmony_macros::{ip, mac_address};
|
use harmony_macros::{ip, mac_address};
|
||||||
use harmony_secret::{Secret, SecretManager};
|
use harmony_secret::SecretManager;
|
||||||
use harmony_types::net::Url;
|
use harmony_types::net::Url;
|
||||||
use serde::{Deserialize, Serialize};
|
|
||||||
|
|
||||||
#[tokio::main]
|
#[tokio::main]
|
||||||
async fn main() {
|
async fn main() {
|
||||||
@ -34,26 +32,6 @@ async fn main() {
|
|||||||
name: String::from("fw0"),
|
name: String::from("fw0"),
|
||||||
};
|
};
|
||||||
|
|
||||||
let switch_auth = SecretManager::get_or_prompt::<BrocadeSwitchAuth>()
|
|
||||||
.await
|
|
||||||
.expect("Failed to get credentials");
|
|
||||||
|
|
||||||
let switches: Vec<IpAddr> = vec![ip!("192.168.33.101")];
|
|
||||||
let brocade_options = Some(BrocadeOptions {
|
|
||||||
dry_run: *harmony::config::DRY_RUN,
|
|
||||||
..Default::default()
|
|
||||||
});
|
|
||||||
let switch_client = BrocadeSwitchClient::init(
|
|
||||||
&switches,
|
|
||||||
&switch_auth.username,
|
|
||||||
&switch_auth.password,
|
|
||||||
brocade_options,
|
|
||||||
)
|
|
||||||
.await
|
|
||||||
.expect("Failed to connect to switch");
|
|
||||||
|
|
||||||
let switch_client = Arc::new(switch_client);
|
|
||||||
|
|
||||||
let opnsense = Arc::new(
|
let opnsense = Arc::new(
|
||||||
harmony::infra::opnsense::OPNSenseFirewall::new(firewall, None, "root", "opnsense").await,
|
harmony::infra::opnsense::OPNSenseFirewall::new(firewall, None, "root", "opnsense").await,
|
||||||
);
|
);
|
||||||
@ -61,8 +39,7 @@ async fn main() {
|
|||||||
let gateway_ipv4 = Ipv4Addr::new(192, 168, 33, 1);
|
let gateway_ipv4 = Ipv4Addr::new(192, 168, 33, 1);
|
||||||
let gateway_ip = IpAddr::V4(gateway_ipv4);
|
let gateway_ip = IpAddr::V4(gateway_ipv4);
|
||||||
let topology = harmony::topology::HAClusterTopology {
|
let topology = harmony::topology::HAClusterTopology {
|
||||||
domain_name: "ncd0.harmony.mcd".to_string(), // TODO this must be set manually correctly
|
domain_name: "ncd0.harmony.mcd".to_string(),
|
||||||
// when setting up the opnsense firewall
|
|
||||||
router: Arc::new(UnmanagedRouter::new(
|
router: Arc::new(UnmanagedRouter::new(
|
||||||
gateway_ip,
|
gateway_ip,
|
||||||
Ipv4Cidr::new(lan_subnet, 24).unwrap(),
|
Ipv4Cidr::new(lan_subnet, 24).unwrap(),
|
||||||
@ -105,7 +82,8 @@ async fn main() {
|
|||||||
name: "wk2".to_string(),
|
name: "wk2".to_string(),
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
switch_client: switch_client.clone(),
|
switch: vec![],
|
||||||
|
node_exporter: opnsense.clone(),
|
||||||
};
|
};
|
||||||
|
|
||||||
let inventory = Inventory {
|
let inventory = Inventory {
|
||||||
@ -188,9 +166,3 @@ async fn main() {
|
|||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Secret, Serialize, Deserialize, Debug)]
|
|
||||||
pub struct BrocadeSwitchAuth {
|
|
||||||
pub username: String,
|
|
||||||
pub password: String,
|
|
||||||
}
|
|
||||||
|
|||||||
@ -19,4 +19,3 @@ log = { workspace = true }
|
|||||||
env_logger = { workspace = true }
|
env_logger = { workspace = true }
|
||||||
url = { workspace = true }
|
url = { workspace = true }
|
||||||
serde.workspace = true
|
serde.workspace = true
|
||||||
brocade = { path = "../../brocade" }
|
|
||||||
|
|||||||
@ -1,8 +1,7 @@
|
|||||||
use brocade::BrocadeOptions;
|
|
||||||
use cidr::Ipv4Cidr;
|
use cidr::Ipv4Cidr;
|
||||||
use harmony::{
|
use harmony::{
|
||||||
hardware::{Location, SwitchGroup},
|
hardware::{Location, SwitchGroup},
|
||||||
infra::{brocade::BrocadeSwitchClient, opnsense::OPNSenseManagementInterface},
|
infra::opnsense::OPNSenseManagementInterface,
|
||||||
inventory::Inventory,
|
inventory::Inventory,
|
||||||
topology::{HAClusterTopology, LogicalHost, UnmanagedRouter},
|
topology::{HAClusterTopology, LogicalHost, UnmanagedRouter},
|
||||||
};
|
};
|
||||||
@ -23,26 +22,6 @@ pub async fn get_topology() -> HAClusterTopology {
|
|||||||
name: String::from("opnsense-1"),
|
name: String::from("opnsense-1"),
|
||||||
};
|
};
|
||||||
|
|
||||||
let switch_auth = SecretManager::get_or_prompt::<BrocadeSwitchAuth>()
|
|
||||||
.await
|
|
||||||
.expect("Failed to get credentials");
|
|
||||||
|
|
||||||
let switches: Vec<IpAddr> = vec![ip!("192.168.1.101")]; // TODO: Adjust me
|
|
||||||
let brocade_options = Some(BrocadeOptions {
|
|
||||||
dry_run: *harmony::config::DRY_RUN,
|
|
||||||
..Default::default()
|
|
||||||
});
|
|
||||||
let switch_client = BrocadeSwitchClient::init(
|
|
||||||
&switches,
|
|
||||||
&switch_auth.username,
|
|
||||||
&switch_auth.password,
|
|
||||||
brocade_options,
|
|
||||||
)
|
|
||||||
.await
|
|
||||||
.expect("Failed to connect to switch");
|
|
||||||
|
|
||||||
let switch_client = Arc::new(switch_client);
|
|
||||||
|
|
||||||
let config = SecretManager::get_or_prompt::<OPNSenseFirewallConfig>().await;
|
let config = SecretManager::get_or_prompt::<OPNSenseFirewallConfig>().await;
|
||||||
let config = config.unwrap();
|
let config = config.unwrap();
|
||||||
|
|
||||||
@ -79,7 +58,8 @@ pub async fn get_topology() -> HAClusterTopology {
|
|||||||
name: "bootstrap".to_string(),
|
name: "bootstrap".to_string(),
|
||||||
},
|
},
|
||||||
workers: vec![],
|
workers: vec![],
|
||||||
switch_client: switch_client.clone(),
|
switch: vec![],
|
||||||
|
node_exporter: opnsense.clone(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -96,9 +76,3 @@ pub fn get_inventory() -> Inventory {
|
|||||||
control_plane_host: vec![],
|
control_plane_host: vec![],
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Secret, Serialize, Deserialize, Debug)]
|
|
||||||
pub struct BrocadeSwitchAuth {
|
|
||||||
pub username: String,
|
|
||||||
pub password: String,
|
|
||||||
}
|
|
||||||
|
|||||||
@ -19,4 +19,3 @@ log = { workspace = true }
|
|||||||
env_logger = { workspace = true }
|
env_logger = { workspace = true }
|
||||||
url = { workspace = true }
|
url = { workspace = true }
|
||||||
serde.workspace = true
|
serde.workspace = true
|
||||||
brocade = { path = "../../brocade" }
|
|
||||||
|
|||||||
@ -1,15 +1,13 @@
|
|||||||
use brocade::BrocadeOptions;
|
|
||||||
use cidr::Ipv4Cidr;
|
use cidr::Ipv4Cidr;
|
||||||
use harmony::{
|
use harmony::{
|
||||||
config::secret::OPNSenseFirewallCredentials,
|
config::secret::OPNSenseFirewallCredentials,
|
||||||
hardware::{Location, SwitchGroup},
|
hardware::{Location, SwitchGroup},
|
||||||
infra::{brocade::BrocadeSwitchClient, opnsense::OPNSenseManagementInterface},
|
infra::opnsense::OPNSenseManagementInterface,
|
||||||
inventory::Inventory,
|
inventory::Inventory,
|
||||||
topology::{HAClusterTopology, LogicalHost, UnmanagedRouter},
|
topology::{HAClusterTopology, LogicalHost, UnmanagedRouter},
|
||||||
};
|
};
|
||||||
use harmony_macros::{ip, ipv4};
|
use harmony_macros::{ip, ipv4};
|
||||||
use harmony_secret::{Secret, SecretManager};
|
use harmony_secret::SecretManager;
|
||||||
use serde::{Deserialize, Serialize};
|
|
||||||
use std::{net::IpAddr, sync::Arc};
|
use std::{net::IpAddr, sync::Arc};
|
||||||
|
|
||||||
pub async fn get_topology() -> HAClusterTopology {
|
pub async fn get_topology() -> HAClusterTopology {
|
||||||
@ -18,26 +16,6 @@ pub async fn get_topology() -> HAClusterTopology {
|
|||||||
name: String::from("opnsense-1"),
|
name: String::from("opnsense-1"),
|
||||||
};
|
};
|
||||||
|
|
||||||
let switch_auth = SecretManager::get_or_prompt::<BrocadeSwitchAuth>()
|
|
||||||
.await
|
|
||||||
.expect("Failed to get credentials");
|
|
||||||
|
|
||||||
let switches: Vec<IpAddr> = vec![ip!("192.168.1.101")]; // TODO: Adjust me
|
|
||||||
let brocade_options = Some(BrocadeOptions {
|
|
||||||
dry_run: *harmony::config::DRY_RUN,
|
|
||||||
..Default::default()
|
|
||||||
});
|
|
||||||
let switch_client = BrocadeSwitchClient::init(
|
|
||||||
&switches,
|
|
||||||
&switch_auth.username,
|
|
||||||
&switch_auth.password,
|
|
||||||
brocade_options,
|
|
||||||
)
|
|
||||||
.await
|
|
||||||
.expect("Failed to connect to switch");
|
|
||||||
|
|
||||||
let switch_client = Arc::new(switch_client);
|
|
||||||
|
|
||||||
let config = SecretManager::get_or_prompt::<OPNSenseFirewallCredentials>().await;
|
let config = SecretManager::get_or_prompt::<OPNSenseFirewallCredentials>().await;
|
||||||
let config = config.unwrap();
|
let config = config.unwrap();
|
||||||
|
|
||||||
@ -74,7 +52,8 @@ pub async fn get_topology() -> HAClusterTopology {
|
|||||||
name: "cp0".to_string(),
|
name: "cp0".to_string(),
|
||||||
},
|
},
|
||||||
workers: vec![],
|
workers: vec![],
|
||||||
switch_client: switch_client.clone(),
|
switch: vec![],
|
||||||
|
node_exporter: opnsense.clone(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -91,9 +70,3 @@ pub fn get_inventory() -> Inventory {
|
|||||||
control_plane_host: vec![],
|
control_plane_host: vec![],
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Secret, Serialize, Deserialize, Debug)]
|
|
||||||
pub struct BrocadeSwitchAuth {
|
|
||||||
pub username: String,
|
|
||||||
pub password: String,
|
|
||||||
}
|
|
||||||
|
|||||||
@ -16,6 +16,3 @@ harmony_macros = { path = "../../harmony_macros" }
|
|||||||
log = { workspace = true }
|
log = { workspace = true }
|
||||||
env_logger = { workspace = true }
|
env_logger = { workspace = true }
|
||||||
url = { workspace = true }
|
url = { workspace = true }
|
||||||
harmony_secret = { path = "../../harmony_secret" }
|
|
||||||
brocade = { path = "../../brocade" }
|
|
||||||
serde = { workspace = true }
|
|
||||||
|
|||||||
@ -3,11 +3,10 @@ use std::{
|
|||||||
sync::Arc,
|
sync::Arc,
|
||||||
};
|
};
|
||||||
|
|
||||||
use brocade::BrocadeOptions;
|
|
||||||
use cidr::Ipv4Cidr;
|
use cidr::Ipv4Cidr;
|
||||||
use harmony::{
|
use harmony::{
|
||||||
hardware::{HostCategory, Location, PhysicalHost, SwitchGroup},
|
hardware::{HostCategory, Location, PhysicalHost, SwitchGroup},
|
||||||
infra::{brocade::BrocadeSwitchClient, opnsense::OPNSenseManagementInterface},
|
infra::opnsense::OPNSenseManagementInterface,
|
||||||
inventory::Inventory,
|
inventory::Inventory,
|
||||||
modules::{
|
modules::{
|
||||||
dummy::{ErrorScore, PanicScore, SuccessScore},
|
dummy::{ErrorScore, PanicScore, SuccessScore},
|
||||||
@ -19,9 +18,7 @@ use harmony::{
|
|||||||
topology::{LogicalHost, UnmanagedRouter},
|
topology::{LogicalHost, UnmanagedRouter},
|
||||||
};
|
};
|
||||||
use harmony_macros::{ip, mac_address};
|
use harmony_macros::{ip, mac_address};
|
||||||
use harmony_secret::{Secret, SecretManager};
|
|
||||||
use harmony_types::net::Url;
|
use harmony_types::net::Url;
|
||||||
use serde::{Deserialize, Serialize};
|
|
||||||
|
|
||||||
#[tokio::main]
|
#[tokio::main]
|
||||||
async fn main() {
|
async fn main() {
|
||||||
@ -30,26 +27,6 @@ async fn main() {
|
|||||||
name: String::from("opnsense-1"),
|
name: String::from("opnsense-1"),
|
||||||
};
|
};
|
||||||
|
|
||||||
let switch_auth = SecretManager::get_or_prompt::<BrocadeSwitchAuth>()
|
|
||||||
.await
|
|
||||||
.expect("Failed to get credentials");
|
|
||||||
|
|
||||||
let switches: Vec<IpAddr> = vec![ip!("192.168.5.101")]; // TODO: Adjust me
|
|
||||||
let brocade_options = Some(BrocadeOptions {
|
|
||||||
dry_run: *harmony::config::DRY_RUN,
|
|
||||||
..Default::default()
|
|
||||||
});
|
|
||||||
let switch_client = BrocadeSwitchClient::init(
|
|
||||||
&switches,
|
|
||||||
&switch_auth.username,
|
|
||||||
&switch_auth.password,
|
|
||||||
brocade_options,
|
|
||||||
)
|
|
||||||
.await
|
|
||||||
.expect("Failed to connect to switch");
|
|
||||||
|
|
||||||
let switch_client = Arc::new(switch_client);
|
|
||||||
|
|
||||||
let opnsense = Arc::new(
|
let opnsense = Arc::new(
|
||||||
harmony::infra::opnsense::OPNSenseFirewall::new(firewall, None, "root", "opnsense").await,
|
harmony::infra::opnsense::OPNSenseFirewall::new(firewall, None, "root", "opnsense").await,
|
||||||
);
|
);
|
||||||
@ -77,7 +54,8 @@ async fn main() {
|
|||||||
name: "cp0".to_string(),
|
name: "cp0".to_string(),
|
||||||
},
|
},
|
||||||
workers: vec![],
|
workers: vec![],
|
||||||
switch_client: switch_client.clone(),
|
switch: vec![],
|
||||||
|
node_exporter: opnsense.clone(),
|
||||||
};
|
};
|
||||||
|
|
||||||
let inventory = Inventory {
|
let inventory = Inventory {
|
||||||
@ -132,9 +110,3 @@ async fn main() {
|
|||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Secret, Serialize, Deserialize, Debug)]
|
|
||||||
pub struct BrocadeSwitchAuth {
|
|
||||||
pub username: String,
|
|
||||||
pub password: String,
|
|
||||||
}
|
|
||||||
|
|||||||
20
examples/opnsense_node_exporter/Cargo.toml
Normal file
20
examples/opnsense_node_exporter/Cargo.toml
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
[package]
|
||||||
|
name = "example-opnsense-node-exporter"
|
||||||
|
edition = "2024"
|
||||||
|
version.workspace = true
|
||||||
|
readme.workspace = true
|
||||||
|
license.workspace = true
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
harmony = { path = "../../harmony" }
|
||||||
|
harmony_cli = { path = "../../harmony_cli" }
|
||||||
|
harmony_types = { path = "../../harmony_types" }
|
||||||
|
harmony_secret = { path = "../../harmony_secret" }
|
||||||
|
harmony_secret_derive = { path = "../../harmony_secret_derive" }
|
||||||
|
cidr = { workspace = true }
|
||||||
|
tokio = { workspace = true }
|
||||||
|
harmony_macros = { path = "../../harmony_macros" }
|
||||||
|
log = { workspace = true }
|
||||||
|
env_logger = { workspace = true }
|
||||||
|
url = { workspace = true }
|
||||||
|
serde.workspace = true
|
||||||
110
examples/opnsense_node_exporter/src/main.rs
Normal file
110
examples/opnsense_node_exporter/src/main.rs
Normal file
@ -0,0 +1,110 @@
|
|||||||
|
use std::{
|
||||||
|
net::{IpAddr, Ipv4Addr},
|
||||||
|
sync::Arc,
|
||||||
|
};
|
||||||
|
|
||||||
|
use cidr::Ipv4Cidr;
|
||||||
|
use harmony::{
|
||||||
|
hardware::{HostCategory, Location, PhysicalHost, SwitchGroup},
|
||||||
|
infra::opnsense::OPNSenseManagementInterface,
|
||||||
|
inventory::Inventory,
|
||||||
|
modules::opnsense::node_exporter::NodeExporterScore,
|
||||||
|
topology::{HAClusterTopology, LogicalHost, UnmanagedRouter},
|
||||||
|
};
|
||||||
|
use harmony_macros::{ip, ipv4, mac_address};
|
||||||
|
|
||||||
|
#[tokio::main]
|
||||||
|
async fn main() {
|
||||||
|
let firewall = harmony::topology::LogicalHost {
|
||||||
|
ip: ip!("192.168.33.1"),
|
||||||
|
name: String::from("fw0"),
|
||||||
|
};
|
||||||
|
|
||||||
|
let opnsense = Arc::new(
|
||||||
|
harmony::infra::opnsense::OPNSenseFirewall::new(firewall, None, "root", "opnsense").await,
|
||||||
|
);
|
||||||
|
let lan_subnet = Ipv4Addr::new(192, 168, 33, 0);
|
||||||
|
let gateway_ipv4 = Ipv4Addr::new(192, 168, 33, 1);
|
||||||
|
let gateway_ip = IpAddr::V4(gateway_ipv4);
|
||||||
|
let topology = harmony::topology::HAClusterTopology {
|
||||||
|
domain_name: "ncd0.harmony.mcd".to_string(),
|
||||||
|
router: Arc::new(UnmanagedRouter::new(
|
||||||
|
gateway_ip,
|
||||||
|
Ipv4Cidr::new(lan_subnet, 24).unwrap(),
|
||||||
|
)),
|
||||||
|
load_balancer: opnsense.clone(),
|
||||||
|
firewall: opnsense.clone(),
|
||||||
|
tftp_server: opnsense.clone(),
|
||||||
|
http_server: opnsense.clone(),
|
||||||
|
dhcp_server: opnsense.clone(),
|
||||||
|
dns_server: opnsense.clone(),
|
||||||
|
control_plane: vec![
|
||||||
|
LogicalHost {
|
||||||
|
ip: ip!("192.168.33.20"),
|
||||||
|
name: "cp0".to_string(),
|
||||||
|
},
|
||||||
|
LogicalHost {
|
||||||
|
ip: ip!("192.168.33.21"),
|
||||||
|
name: "cp1".to_string(),
|
||||||
|
},
|
||||||
|
LogicalHost {
|
||||||
|
ip: ip!("192.168.33.22"),
|
||||||
|
name: "cp2".to_string(),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
bootstrap_host: LogicalHost {
|
||||||
|
ip: ip!("192.168.33.66"),
|
||||||
|
name: "bootstrap".to_string(),
|
||||||
|
},
|
||||||
|
workers: vec![
|
||||||
|
LogicalHost {
|
||||||
|
ip: ip!("192.168.33.30"),
|
||||||
|
name: "wk0".to_string(),
|
||||||
|
},
|
||||||
|
LogicalHost {
|
||||||
|
ip: ip!("192.168.33.31"),
|
||||||
|
name: "wk1".to_string(),
|
||||||
|
},
|
||||||
|
LogicalHost {
|
||||||
|
ip: ip!("192.168.33.32"),
|
||||||
|
name: "wk2".to_string(),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
switch: vec![],
|
||||||
|
node_exporter: opnsense.clone(),
|
||||||
|
};
|
||||||
|
|
||||||
|
let inventory = Inventory {
|
||||||
|
location: Location::new("I am mobile".to_string(), "earth".to_string()),
|
||||||
|
switch: SwitchGroup::from([]),
|
||||||
|
firewall_mgmt: Box::new(OPNSenseManagementInterface::new()),
|
||||||
|
storage_host: vec![],
|
||||||
|
worker_host: vec![
|
||||||
|
PhysicalHost::empty(HostCategory::Server)
|
||||||
|
.mac_address(mac_address!("C4:62:37:02:61:0F")),
|
||||||
|
PhysicalHost::empty(HostCategory::Server)
|
||||||
|
.mac_address(mac_address!("C4:62:37:02:61:26")),
|
||||||
|
PhysicalHost::empty(HostCategory::Server)
|
||||||
|
.mac_address(mac_address!("C4:62:37:02:61:70")),
|
||||||
|
],
|
||||||
|
control_plane_host: vec![
|
||||||
|
PhysicalHost::empty(HostCategory::Server)
|
||||||
|
.mac_address(mac_address!("C4:62:37:02:60:FA")),
|
||||||
|
PhysicalHost::empty(HostCategory::Server)
|
||||||
|
.mac_address(mac_address!("C4:62:37:02:61:1A")),
|
||||||
|
PhysicalHost::empty(HostCategory::Server)
|
||||||
|
.mac_address(mac_address!("C4:62:37:01:BC:68")),
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
let node_exporter_score = NodeExporterScore {};
|
||||||
|
|
||||||
|
harmony_cli::run(
|
||||||
|
inventory,
|
||||||
|
topology,
|
||||||
|
vec![Box::new(node_exporter_score)],
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
@ -3,7 +3,7 @@ use harmony::{
|
|||||||
modules::{
|
modules::{
|
||||||
application::{
|
application::{
|
||||||
ApplicationScore, RustWebFramework, RustWebapp,
|
ApplicationScore, RustWebFramework, RustWebapp,
|
||||||
features::{Monitoring, PackagingDeployment},
|
features::{PackagingDeployment, rhob_monitoring::Monitoring},
|
||||||
},
|
},
|
||||||
monitoring::alert_channel::discord_alert_channel::DiscordWebhook,
|
monitoring::alert_channel::discord_alert_channel::DiscordWebhook,
|
||||||
},
|
},
|
||||||
|
|||||||
@ -1,5 +1,7 @@
|
|||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
|
use brocade::BrocadeOptions;
|
||||||
use harmony_macros::ip;
|
use harmony_macros::ip;
|
||||||
|
use harmony_secret::SecretManager;
|
||||||
use harmony_types::{
|
use harmony_types::{
|
||||||
net::{MacAddress, Url},
|
net::{MacAddress, Url},
|
||||||
switch::PortLocation,
|
switch::PortLocation,
|
||||||
@ -9,14 +11,16 @@ use kube::api::ObjectMeta;
|
|||||||
use log::debug;
|
use log::debug;
|
||||||
use log::info;
|
use log::info;
|
||||||
|
|
||||||
use crate::data::FileContent;
|
|
||||||
use crate::executors::ExecutorError;
|
use crate::executors::ExecutorError;
|
||||||
use crate::hardware::PhysicalHost;
|
use crate::hardware::PhysicalHost;
|
||||||
|
use crate::infra::brocade::BrocadeSwitchAuth;
|
||||||
|
use crate::infra::brocade::BrocadeSwitchClient;
|
||||||
use crate::modules::okd::crd::{
|
use crate::modules::okd::crd::{
|
||||||
InstallPlanApproval, OperatorGroup, OperatorGroupSpec, Subscription, SubscriptionSpec,
|
InstallPlanApproval, OperatorGroup, OperatorGroupSpec, Subscription, SubscriptionSpec,
|
||||||
nmstate::{self, NMState, NodeNetworkConfigurationPolicy, NodeNetworkConfigurationPolicySpec},
|
nmstate::{self, NMState, NodeNetworkConfigurationPolicy, NodeNetworkConfigurationPolicySpec},
|
||||||
};
|
};
|
||||||
use crate::topology::PxeOptions;
|
use crate::topology::PxeOptions;
|
||||||
|
use crate::{data::FileContent, topology::node_exporter::NodeExporter};
|
||||||
|
|
||||||
use super::{
|
use super::{
|
||||||
DHCPStaticEntry, DhcpServer, DnsRecord, DnsRecordType, DnsServer, Firewall, HostNetworkConfig,
|
DHCPStaticEntry, DhcpServer, DnsRecord, DnsRecordType, DnsServer, Firewall, HostNetworkConfig,
|
||||||
@ -26,6 +30,7 @@ use super::{
|
|||||||
};
|
};
|
||||||
|
|
||||||
use std::collections::BTreeMap;
|
use std::collections::BTreeMap;
|
||||||
|
use std::net::IpAddr;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
@ -38,10 +43,11 @@ pub struct HAClusterTopology {
|
|||||||
pub tftp_server: Arc<dyn TftpServer>,
|
pub tftp_server: Arc<dyn TftpServer>,
|
||||||
pub http_server: Arc<dyn HttpServer>,
|
pub http_server: Arc<dyn HttpServer>,
|
||||||
pub dns_server: Arc<dyn DnsServer>,
|
pub dns_server: Arc<dyn DnsServer>,
|
||||||
pub switch_client: Arc<dyn SwitchClient>,
|
pub node_exporter: Arc<dyn NodeExporter>,
|
||||||
pub bootstrap_host: LogicalHost,
|
pub bootstrap_host: LogicalHost,
|
||||||
pub control_plane: Vec<LogicalHost>,
|
pub control_plane: Vec<LogicalHost>,
|
||||||
pub workers: Vec<LogicalHost>,
|
pub workers: Vec<LogicalHost>,
|
||||||
|
pub switch: Vec<LogicalHost>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
@ -275,15 +281,36 @@ impl HAClusterTopology {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn get_switch_client(&self) -> Result<Box<dyn SwitchClient>, SwitchError> {
|
||||||
|
let auth = SecretManager::get_or_prompt::<BrocadeSwitchAuth>()
|
||||||
|
.await
|
||||||
|
.map_err(|e| SwitchError::new(format!("Failed to get credentials: {e}")))?;
|
||||||
|
|
||||||
|
// FIXME: We assume Brocade switches
|
||||||
|
let switches: Vec<IpAddr> = self.switch.iter().map(|s| s.ip).collect();
|
||||||
|
let brocade_options = Some(BrocadeOptions {
|
||||||
|
dry_run: *crate::config::DRY_RUN,
|
||||||
|
..Default::default()
|
||||||
|
});
|
||||||
|
let client =
|
||||||
|
BrocadeSwitchClient::init(&switches, &auth.username, &auth.password, brocade_options)
|
||||||
|
.await
|
||||||
|
.map_err(|e| SwitchError::new(format!("Failed to connect to switch: {e}")))?;
|
||||||
|
|
||||||
|
Ok(Box::new(client))
|
||||||
|
}
|
||||||
|
|
||||||
async fn configure_port_channel(
|
async fn configure_port_channel(
|
||||||
&self,
|
&self,
|
||||||
host: &PhysicalHost,
|
host: &PhysicalHost,
|
||||||
config: &HostNetworkConfig,
|
config: &HostNetworkConfig,
|
||||||
) -> Result<(), SwitchError> {
|
) -> Result<(), SwitchError> {
|
||||||
debug!("Configuring port channel: {config:#?}");
|
debug!("Configuring port channel: {config:#?}");
|
||||||
|
let client = self.get_switch_client().await?;
|
||||||
|
|
||||||
let switch_ports = config.switch_ports.iter().map(|s| s.port.clone()).collect();
|
let switch_ports = config.switch_ports.iter().map(|s| s.port.clone()).collect();
|
||||||
|
|
||||||
self.switch_client
|
client
|
||||||
.configure_port_channel(&format!("Harmony_{}", host.id), switch_ports)
|
.configure_port_channel(&format!("Harmony_{}", host.id), switch_ports)
|
||||||
.await
|
.await
|
||||||
.map_err(|e| SwitchError::new(format!("Failed to configure switch: {e}")))?;
|
.map_err(|e| SwitchError::new(format!("Failed to configure switch: {e}")))?;
|
||||||
@ -307,10 +334,11 @@ impl HAClusterTopology {
|
|||||||
tftp_server: dummy_infra.clone(),
|
tftp_server: dummy_infra.clone(),
|
||||||
http_server: dummy_infra.clone(),
|
http_server: dummy_infra.clone(),
|
||||||
dns_server: dummy_infra.clone(),
|
dns_server: dummy_infra.clone(),
|
||||||
switch_client: dummy_infra.clone(),
|
node_exporter: dummy_infra.clone(),
|
||||||
bootstrap_host: dummy_host,
|
bootstrap_host: dummy_host,
|
||||||
control_plane: vec![],
|
control_plane: vec![],
|
||||||
workers: vec![],
|
workers: vec![],
|
||||||
|
switch: vec![],
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -468,7 +496,8 @@ impl HttpServer for HAClusterTopology {
|
|||||||
#[async_trait]
|
#[async_trait]
|
||||||
impl Switch for HAClusterTopology {
|
impl Switch for HAClusterTopology {
|
||||||
async fn setup_switch(&self) -> Result<(), SwitchError> {
|
async fn setup_switch(&self) -> Result<(), SwitchError> {
|
||||||
self.switch_client.setup().await?;
|
let client = self.get_switch_client().await?;
|
||||||
|
client.setup().await?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -476,7 +505,8 @@ impl Switch for HAClusterTopology {
|
|||||||
&self,
|
&self,
|
||||||
mac_address: &MacAddress,
|
mac_address: &MacAddress,
|
||||||
) -> Result<Option<PortLocation>, SwitchError> {
|
) -> Result<Option<PortLocation>, SwitchError> {
|
||||||
let port = self.switch_client.find_port(mac_address).await?;
|
let client = self.get_switch_client().await?;
|
||||||
|
let port = client.find_port(mac_address).await?;
|
||||||
Ok(port)
|
Ok(port)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -488,6 +518,23 @@ impl Switch for HAClusterTopology {
|
|||||||
self.configure_bond(host, &config).await?;
|
self.configure_bond(host, &config).await?;
|
||||||
self.configure_port_channel(host, &config).await
|
self.configure_port_channel(host, &config).await
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//TODO add snmp here
|
||||||
|
}
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
|
impl NodeExporter for HAClusterTopology {
|
||||||
|
async fn ensure_initialized(&self) -> Result<(), ExecutorError> {
|
||||||
|
self.node_exporter.ensure_initialized().await
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn commit_config(&self) -> Result<(), ExecutorError> {
|
||||||
|
self.node_exporter.commit_config().await
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn reload_restart(&self) -> Result<(), ExecutorError> {
|
||||||
|
self.node_exporter.reload_restart().await
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
@ -678,23 +725,16 @@ impl DnsServer for DummyInfra {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
impl SwitchClient for DummyInfra {
|
impl NodeExporter for DummyInfra {
|
||||||
async fn setup(&self) -> Result<(), SwitchError> {
|
async fn ensure_initialized(&self) -> Result<(), ExecutorError> {
|
||||||
unimplemented!("{}", UNIMPLEMENTED_DUMMY_INFRA)
|
unimplemented!("{}", UNIMPLEMENTED_DUMMY_INFRA)
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn find_port(
|
async fn commit_config(&self) -> Result<(), ExecutorError> {
|
||||||
&self,
|
|
||||||
_mac_address: &MacAddress,
|
|
||||||
) -> Result<Option<PortLocation>, SwitchError> {
|
|
||||||
unimplemented!("{}", UNIMPLEMENTED_DUMMY_INFRA)
|
unimplemented!("{}", UNIMPLEMENTED_DUMMY_INFRA)
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn configure_port_channel(
|
async fn reload_restart(&self) -> Result<(), ExecutorError> {
|
||||||
&self,
|
|
||||||
_channel_name: &str,
|
|
||||||
_switch_ports: Vec<PortLocation>,
|
|
||||||
) -> Result<u8, SwitchError> {
|
|
||||||
unimplemented!("{}", UNIMPLEMENTED_DUMMY_INFRA)
|
unimplemented!("{}", UNIMPLEMENTED_DUMMY_INFRA)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -3,10 +3,7 @@ use std::time::Duration;
|
|||||||
use derive_new::new;
|
use derive_new::new;
|
||||||
use k8s_openapi::{
|
use k8s_openapi::{
|
||||||
ClusterResourceScope, NamespaceResourceScope,
|
ClusterResourceScope, NamespaceResourceScope,
|
||||||
api::{
|
api::{apps::v1::Deployment, core::v1::Pod},
|
||||||
apps::v1::Deployment,
|
|
||||||
core::v1::{Pod, ServiceAccount},
|
|
||||||
},
|
|
||||||
apimachinery::pkg::version::Info,
|
apimachinery::pkg::version::Info,
|
||||||
};
|
};
|
||||||
use kube::{
|
use kube::{
|
||||||
@ -24,7 +21,7 @@ use kube::{
|
|||||||
};
|
};
|
||||||
use log::{debug, error, info, trace};
|
use log::{debug, error, info, trace};
|
||||||
use serde::{Serialize, de::DeserializeOwned};
|
use serde::{Serialize, de::DeserializeOwned};
|
||||||
use serde_json::json;
|
use serde_json::{Value, json};
|
||||||
use similar::TextDiff;
|
use similar::TextDiff;
|
||||||
use tokio::{io::AsyncReadExt, time::sleep};
|
use tokio::{io::AsyncReadExt, time::sleep};
|
||||||
|
|
||||||
@ -60,11 +57,6 @@ impl K8sClient {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn service_account_api(&self, namespace: &str) -> Api<ServiceAccount> {
|
|
||||||
let api: Api<ServiceAccount> = Api::namespaced(self.client.clone(), namespace);
|
|
||||||
api
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn get_apiserver_version(&self) -> Result<Info, Error> {
|
pub async fn get_apiserver_version(&self) -> Result<Info, Error> {
|
||||||
let client: Client = self.client.clone();
|
let client: Client = self.client.clone();
|
||||||
let version_info: Info = client.apiserver_version().await?;
|
let version_info: Info = client.apiserver_version().await?;
|
||||||
|
|||||||
@ -1,12 +1,7 @@
|
|||||||
use std::{collections::BTreeMap, process::Command, sync::Arc};
|
use std::{process::Command, sync::Arc};
|
||||||
|
|
||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
use base64::{Engine, engine::general_purpose};
|
use kube::api::GroupVersionKind;
|
||||||
use k8s_openapi::api::{
|
|
||||||
core::v1::Secret,
|
|
||||||
rbac::v1::{ClusterRoleBinding, RoleRef, Subject},
|
|
||||||
};
|
|
||||||
use kube::api::{DynamicObject, GroupVersionKind, ObjectMeta};
|
|
||||||
use log::{debug, info, warn};
|
use log::{debug, info, warn};
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
use tokio::sync::OnceCell;
|
use tokio::sync::OnceCell;
|
||||||
@ -17,26 +12,14 @@ use crate::{
|
|||||||
inventory::Inventory,
|
inventory::Inventory,
|
||||||
modules::{
|
modules::{
|
||||||
k3d::K3DInstallationScore,
|
k3d::K3DInstallationScore,
|
||||||
k8s::ingress::{K8sIngressScore, PathType},
|
monitoring::kube_prometheus::crd::{
|
||||||
monitoring::{
|
crd_alertmanager_config::CRDPrometheus,
|
||||||
grafana::{grafana::Grafana, helm::helm_grafana::grafana_helm_chart_score},
|
prometheus_operator::prometheus_operator_helm_chart_score,
|
||||||
kube_prometheus::crd::{
|
rhob_alertmanager_config::RHOBObservability,
|
||||||
crd_alertmanager_config::CRDPrometheus,
|
|
||||||
crd_grafana::{
|
|
||||||
Grafana as GrafanaCRD, GrafanaCom, GrafanaDashboard,
|
|
||||||
GrafanaDashboardDatasource, GrafanaDashboardSpec, GrafanaDatasource,
|
|
||||||
GrafanaDatasourceConfig, GrafanaDatasourceJsonData,
|
|
||||||
GrafanaDatasourceSecureJsonData, GrafanaDatasourceSpec, GrafanaSpec,
|
|
||||||
},
|
|
||||||
crd_prometheuses::LabelSelector,
|
|
||||||
prometheus_operator::prometheus_operator_helm_chart_score,
|
|
||||||
rhob_alertmanager_config::RHOBObservability,
|
|
||||||
service_monitor::ServiceMonitor,
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
prometheus::{
|
prometheus::{
|
||||||
k8s_prometheus_alerting_score::K8sPrometheusCRDAlertingScore,
|
k8s_prometheus_alerting_score::K8sPrometheusCRDAlertingScore,
|
||||||
prometheus::PrometheusMonitoring, rhob_alerting_score::RHOBAlertingScore,
|
prometheus::PrometheusApplicationMonitoring, rhob_alerting_score::RHOBAlertingScore,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
score::Score,
|
score::Score,
|
||||||
@ -103,172 +86,41 @@ impl K8sclient for K8sAnywhereTopology {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
impl Grafana for K8sAnywhereTopology {
|
impl PrometheusApplicationMonitoring<CRDPrometheus> for K8sAnywhereTopology {
|
||||||
async fn ensure_grafana_operator(
|
|
||||||
&self,
|
|
||||||
inventory: &Inventory,
|
|
||||||
) -> Result<PreparationOutcome, PreparationError> {
|
|
||||||
debug!("ensure grafana operator");
|
|
||||||
let client = self.k8s_client().await.unwrap();
|
|
||||||
let grafana_gvk = GroupVersionKind {
|
|
||||||
group: "grafana.integreatly.org".to_string(),
|
|
||||||
version: "v1beta1".to_string(),
|
|
||||||
kind: "Grafana".to_string(),
|
|
||||||
};
|
|
||||||
let name = "grafanas.grafana.integreatly.org";
|
|
||||||
let ns = "grafana";
|
|
||||||
|
|
||||||
let grafana_crd = client
|
|
||||||
.get_resource_json_value(name, Some(ns), &grafana_gvk)
|
|
||||||
.await;
|
|
||||||
match grafana_crd {
|
|
||||||
Ok(_) => {
|
|
||||||
return Ok(PreparationOutcome::Success {
|
|
||||||
details: "Found grafana CRDs in cluster".to_string(),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
Err(_) => {
|
|
||||||
return self
|
|
||||||
.install_grafana_operator(inventory, Some("grafana"))
|
|
||||||
.await;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
async fn install_grafana(&self) -> Result<PreparationOutcome, PreparationError> {
|
|
||||||
let ns = "grafana";
|
|
||||||
|
|
||||||
let mut label = BTreeMap::new();
|
|
||||||
|
|
||||||
label.insert("dashboards".to_string(), "grafana".to_string());
|
|
||||||
|
|
||||||
let label_selector = LabelSelector {
|
|
||||||
match_labels: label.clone(),
|
|
||||||
match_expressions: vec![],
|
|
||||||
};
|
|
||||||
|
|
||||||
let client = self.k8s_client().await?;
|
|
||||||
|
|
||||||
let grafana = self.build_grafana(ns, &label);
|
|
||||||
|
|
||||||
client.apply(&grafana, Some(ns)).await?;
|
|
||||||
//TODO change this to a ensure ready or something better than just a timeout
|
|
||||||
client
|
|
||||||
.wait_until_deployment_ready(
|
|
||||||
"grafana-grafana-deployment".to_string(),
|
|
||||||
Some("grafana"),
|
|
||||||
Some(30),
|
|
||||||
)
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
let sa_name = "grafana-grafana-sa";
|
|
||||||
let token_secret_name = "grafana-sa-token-secret";
|
|
||||||
|
|
||||||
let sa_token_secret = self.build_sa_token_secret(token_secret_name, sa_name, ns);
|
|
||||||
|
|
||||||
client.apply(&sa_token_secret, Some(ns)).await?;
|
|
||||||
let secret_gvk = GroupVersionKind {
|
|
||||||
group: "".to_string(),
|
|
||||||
version: "v1".to_string(),
|
|
||||||
kind: "Secret".to_string(),
|
|
||||||
};
|
|
||||||
|
|
||||||
let secret = client
|
|
||||||
.get_resource_json_value(token_secret_name, Some(ns), &secret_gvk)
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
let token = format!(
|
|
||||||
"Bearer {}",
|
|
||||||
self.extract_and_normalize_token(&secret).unwrap()
|
|
||||||
);
|
|
||||||
|
|
||||||
debug!("creating grafana clusterrole binding");
|
|
||||||
|
|
||||||
let clusterrolebinding =
|
|
||||||
self.build_cluster_rolebinding(sa_name, "cluster-monitoring-view", ns);
|
|
||||||
|
|
||||||
client.apply(&clusterrolebinding, Some(ns)).await?;
|
|
||||||
|
|
||||||
debug!("creating grafana datasource crd");
|
|
||||||
|
|
||||||
let thanos_url = format!(
|
|
||||||
"https://{}",
|
|
||||||
self.get_domain("thanos-querier-openshift-monitoring")
|
|
||||||
.await
|
|
||||||
.unwrap()
|
|
||||||
);
|
|
||||||
|
|
||||||
let thanos_openshift_datasource = self.build_grafana_datasource(
|
|
||||||
"thanos-openshift-monitoring",
|
|
||||||
ns,
|
|
||||||
&label_selector,
|
|
||||||
&thanos_url,
|
|
||||||
&token,
|
|
||||||
);
|
|
||||||
|
|
||||||
client.apply(&thanos_openshift_datasource, Some(ns)).await?;
|
|
||||||
|
|
||||||
debug!("creating grafana dashboard crd");
|
|
||||||
let dashboard = self.build_grafana_dashboard(ns, &label_selector);
|
|
||||||
|
|
||||||
client.apply(&dashboard, Some(ns)).await?;
|
|
||||||
debug!("creating grafana ingress");
|
|
||||||
let grafana_ingress = self.build_grafana_ingress(ns).await;
|
|
||||||
|
|
||||||
grafana_ingress
|
|
||||||
.interpret(&Inventory::empty(), self)
|
|
||||||
.await
|
|
||||||
.map_err(|e| PreparationError::new(e.to_string()))?;
|
|
||||||
|
|
||||||
Ok(PreparationOutcome::Success {
|
|
||||||
details: "Installed grafana composants".to_string(),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[async_trait]
|
|
||||||
impl PrometheusMonitoring<CRDPrometheus> for K8sAnywhereTopology {
|
|
||||||
async fn install_prometheus(
|
async fn install_prometheus(
|
||||||
&self,
|
&self,
|
||||||
sender: &CRDPrometheus,
|
sender: &CRDPrometheus,
|
||||||
_inventory: &Inventory,
|
inventory: &Inventory,
|
||||||
_receivers: Option<Vec<Box<dyn AlertReceiver<CRDPrometheus>>>>,
|
receivers: Option<Vec<Box<dyn AlertReceiver<CRDPrometheus>>>>,
|
||||||
) -> Result<PreparationOutcome, PreparationError> {
|
|
||||||
let client = self.k8s_client().await?;
|
|
||||||
|
|
||||||
for monitor in sender.service_monitor.iter() {
|
|
||||||
client
|
|
||||||
.apply(monitor, Some(&sender.namespace))
|
|
||||||
.await
|
|
||||||
.map_err(|e| PreparationError::new(e.to_string()))?;
|
|
||||||
}
|
|
||||||
Ok(PreparationOutcome::Success {
|
|
||||||
details: "successfuly installed prometheus components".to_string(),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn ensure_prometheus_operator(
|
|
||||||
&self,
|
|
||||||
sender: &CRDPrometheus,
|
|
||||||
_inventory: &Inventory,
|
|
||||||
) -> Result<PreparationOutcome, PreparationError> {
|
) -> Result<PreparationOutcome, PreparationError> {
|
||||||
let po_result = self.ensure_prometheus_operator(sender).await?;
|
let po_result = self.ensure_prometheus_operator(sender).await?;
|
||||||
|
|
||||||
match po_result {
|
if po_result == PreparationOutcome::Noop {
|
||||||
PreparationOutcome::Success { details: _ } => {
|
debug!("Skipping Prometheus CR installation due to missing operator.");
|
||||||
debug!("Detected prometheus crds operator present in cluster.");
|
return Ok(po_result);
|
||||||
return Ok(po_result);
|
}
|
||||||
}
|
|
||||||
PreparationOutcome::Noop => {
|
let result = self
|
||||||
debug!("Skipping Prometheus CR installation due to missing operator.");
|
.get_k8s_prometheus_application_score(sender.clone(), receivers)
|
||||||
return Ok(po_result);
|
.await
|
||||||
}
|
.interpret(inventory, self)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
match result {
|
||||||
|
Ok(outcome) => match outcome.status {
|
||||||
|
InterpretStatus::SUCCESS => Ok(PreparationOutcome::Success {
|
||||||
|
details: outcome.message,
|
||||||
|
}),
|
||||||
|
InterpretStatus::NOOP => Ok(PreparationOutcome::Noop),
|
||||||
|
_ => Err(PreparationError::new(outcome.message)),
|
||||||
|
},
|
||||||
|
Err(err) => Err(PreparationError::new(err.to_string())),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
impl PrometheusMonitoring<RHOBObservability> for K8sAnywhereTopology {
|
impl PrometheusApplicationMonitoring<RHOBObservability> for K8sAnywhereTopology {
|
||||||
async fn install_prometheus(
|
async fn install_prometheus(
|
||||||
&self,
|
&self,
|
||||||
sender: &RHOBObservability,
|
sender: &RHOBObservability,
|
||||||
@ -302,14 +154,6 @@ impl PrometheusMonitoring<RHOBObservability> for K8sAnywhereTopology {
|
|||||||
Err(err) => Err(PreparationError::new(err.to_string())),
|
Err(err) => Err(PreparationError::new(err.to_string())),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn ensure_prometheus_operator(
|
|
||||||
&self,
|
|
||||||
sender: &RHOBObservability,
|
|
||||||
inventory: &Inventory,
|
|
||||||
) -> Result<PreparationOutcome, PreparationError> {
|
|
||||||
todo!()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Serialize for K8sAnywhereTopology {
|
impl Serialize for K8sAnywhereTopology {
|
||||||
@ -371,180 +215,6 @@ impl K8sAnywhereTopology {
|
|||||||
.await
|
.await
|
||||||
}
|
}
|
||||||
|
|
||||||
fn extract_and_normalize_token(&self, secret: &DynamicObject) -> Option<String> {
|
|
||||||
let token_b64 = secret
|
|
||||||
.data
|
|
||||||
.get("token")
|
|
||||||
.or_else(|| secret.data.get("data").and_then(|d| d.get("token")))
|
|
||||||
.and_then(|v| v.as_str())?;
|
|
||||||
|
|
||||||
let bytes = general_purpose::STANDARD.decode(token_b64).ok()?;
|
|
||||||
|
|
||||||
let s = String::from_utf8(bytes).ok()?;
|
|
||||||
|
|
||||||
let cleaned = s
|
|
||||||
.trim_matches(|c: char| c.is_whitespace() || c == '\0')
|
|
||||||
.to_string();
|
|
||||||
Some(cleaned)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn build_cluster_rolebinding(
|
|
||||||
&self,
|
|
||||||
service_account_name: &str,
|
|
||||||
clusterrole_name: &str,
|
|
||||||
ns: &str,
|
|
||||||
) -> ClusterRoleBinding {
|
|
||||||
ClusterRoleBinding {
|
|
||||||
metadata: ObjectMeta {
|
|
||||||
name: Some(format!("{}-view-binding", service_account_name)),
|
|
||||||
..Default::default()
|
|
||||||
},
|
|
||||||
role_ref: RoleRef {
|
|
||||||
api_group: "rbac.authorization.k8s.io".into(),
|
|
||||||
kind: "ClusterRole".into(),
|
|
||||||
name: clusterrole_name.into(),
|
|
||||||
},
|
|
||||||
subjects: Some(vec![Subject {
|
|
||||||
kind: "ServiceAccount".into(),
|
|
||||||
name: service_account_name.into(),
|
|
||||||
namespace: Some(ns.into()),
|
|
||||||
..Default::default()
|
|
||||||
}]),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn build_sa_token_secret(
|
|
||||||
&self,
|
|
||||||
secret_name: &str,
|
|
||||||
service_account_name: &str,
|
|
||||||
ns: &str,
|
|
||||||
) -> Secret {
|
|
||||||
let mut annotations = BTreeMap::new();
|
|
||||||
annotations.insert(
|
|
||||||
"kubernetes.io/service-account.name".to_string(),
|
|
||||||
service_account_name.to_string(),
|
|
||||||
);
|
|
||||||
|
|
||||||
Secret {
|
|
||||||
metadata: ObjectMeta {
|
|
||||||
name: Some(secret_name.into()),
|
|
||||||
namespace: Some(ns.into()),
|
|
||||||
annotations: Some(annotations),
|
|
||||||
..Default::default()
|
|
||||||
},
|
|
||||||
type_: Some("kubernetes.io/service-account-token".to_string()),
|
|
||||||
..Default::default()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn build_grafana_datasource(
|
|
||||||
&self,
|
|
||||||
name: &str,
|
|
||||||
ns: &str,
|
|
||||||
label_selector: &LabelSelector,
|
|
||||||
url: &str,
|
|
||||||
token: &str,
|
|
||||||
) -> GrafanaDatasource {
|
|
||||||
let mut json_data = BTreeMap::new();
|
|
||||||
json_data.insert("timeInterval".to_string(), "5s".to_string());
|
|
||||||
|
|
||||||
GrafanaDatasource {
|
|
||||||
metadata: ObjectMeta {
|
|
||||||
name: Some(name.to_string()),
|
|
||||||
namespace: Some(ns.to_string()),
|
|
||||||
..Default::default()
|
|
||||||
},
|
|
||||||
spec: GrafanaDatasourceSpec {
|
|
||||||
instance_selector: label_selector.clone(),
|
|
||||||
allow_cross_namespace_import: Some(true),
|
|
||||||
values_from: None,
|
|
||||||
datasource: GrafanaDatasourceConfig {
|
|
||||||
access: "proxy".to_string(),
|
|
||||||
name: name.to_string(),
|
|
||||||
r#type: "prometheus".to_string(),
|
|
||||||
url: url.to_string(),
|
|
||||||
database: None,
|
|
||||||
json_data: Some(GrafanaDatasourceJsonData {
|
|
||||||
time_interval: Some("60s".to_string()),
|
|
||||||
http_header_name1: Some("Authorization".to_string()),
|
|
||||||
tls_skip_verify: Some(true),
|
|
||||||
oauth_pass_thru: Some(true),
|
|
||||||
}),
|
|
||||||
secure_json_data: Some(GrafanaDatasourceSecureJsonData {
|
|
||||||
http_header_value1: Some(format!("Bearer {token}")),
|
|
||||||
}),
|
|
||||||
is_default: Some(false),
|
|
||||||
editable: Some(true),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn build_grafana_dashboard(
|
|
||||||
&self,
|
|
||||||
ns: &str,
|
|
||||||
label_selector: &LabelSelector,
|
|
||||||
) -> GrafanaDashboard {
|
|
||||||
let graf_dashboard = GrafanaDashboard {
|
|
||||||
metadata: ObjectMeta {
|
|
||||||
name: Some(format!("grafana-dashboard-{}", ns)),
|
|
||||||
namespace: Some(ns.to_string()),
|
|
||||||
..Default::default()
|
|
||||||
},
|
|
||||||
spec: GrafanaDashboardSpec {
|
|
||||||
resync_period: Some("30s".to_string()),
|
|
||||||
instance_selector: label_selector.clone(),
|
|
||||||
datasources: Some(vec![GrafanaDashboardDatasource {
|
|
||||||
input_name: "DS_PROMETHEUS".to_string(),
|
|
||||||
datasource_name: "thanos-openshift-monitoring".to_string(),
|
|
||||||
}]),
|
|
||||||
json: None,
|
|
||||||
grafana_com: Some(GrafanaCom {
|
|
||||||
id: 17406,
|
|
||||||
revision: None,
|
|
||||||
}),
|
|
||||||
},
|
|
||||||
};
|
|
||||||
graf_dashboard
|
|
||||||
}
|
|
||||||
|
|
||||||
fn build_grafana(&self, ns: &str, labels: &BTreeMap<String, String>) -> GrafanaCRD {
|
|
||||||
let grafana = GrafanaCRD {
|
|
||||||
metadata: ObjectMeta {
|
|
||||||
name: Some(format!("grafana-{}", ns)),
|
|
||||||
namespace: Some(ns.to_string()),
|
|
||||||
labels: Some(labels.clone()),
|
|
||||||
..Default::default()
|
|
||||||
},
|
|
||||||
spec: GrafanaSpec {
|
|
||||||
config: None,
|
|
||||||
admin_user: None,
|
|
||||||
admin_password: None,
|
|
||||||
ingress: None,
|
|
||||||
persistence: None,
|
|
||||||
resources: None,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
grafana
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn build_grafana_ingress(&self, ns: &str) -> K8sIngressScore {
|
|
||||||
let domain = self.get_domain(&format!("grafana-{}", ns)).await.unwrap();
|
|
||||||
let name = format!("{}-grafana", ns);
|
|
||||||
let backend_service = format!("grafana-{}-service", ns);
|
|
||||||
|
|
||||||
K8sIngressScore {
|
|
||||||
name: fqdn::fqdn!(&name),
|
|
||||||
host: fqdn::fqdn!(&domain),
|
|
||||||
backend_service: fqdn::fqdn!(&backend_service),
|
|
||||||
port: 3000,
|
|
||||||
path: Some("/".to_string()),
|
|
||||||
path_type: Some(PathType::Prefix),
|
|
||||||
namespace: Some(fqdn::fqdn!(&ns)),
|
|
||||||
ingress_class_name: Some("openshift-default".to_string()),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn get_cluster_observability_operator_prometheus_application_score(
|
async fn get_cluster_observability_operator_prometheus_application_score(
|
||||||
&self,
|
&self,
|
||||||
sender: RHOBObservability,
|
sender: RHOBObservability,
|
||||||
@ -562,14 +232,13 @@ impl K8sAnywhereTopology {
|
|||||||
&self,
|
&self,
|
||||||
sender: CRDPrometheus,
|
sender: CRDPrometheus,
|
||||||
receivers: Option<Vec<Box<dyn AlertReceiver<CRDPrometheus>>>>,
|
receivers: Option<Vec<Box<dyn AlertReceiver<CRDPrometheus>>>>,
|
||||||
service_monitors: Option<Vec<ServiceMonitor>>,
|
|
||||||
) -> K8sPrometheusCRDAlertingScore {
|
) -> K8sPrometheusCRDAlertingScore {
|
||||||
return K8sPrometheusCRDAlertingScore {
|
K8sPrometheusCRDAlertingScore {
|
||||||
sender,
|
sender,
|
||||||
receivers: receivers.unwrap_or_default(),
|
receivers: receivers.unwrap_or_default(),
|
||||||
service_monitors: service_monitors.unwrap_or_default(),
|
service_monitors: vec![],
|
||||||
prometheus_rules: vec![],
|
prometheus_rules: vec![],
|
||||||
};
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn openshift_ingress_operator_available(&self) -> Result<(), PreparationError> {
|
async fn openshift_ingress_operator_available(&self) -> Result<(), PreparationError> {
|
||||||
@ -837,30 +506,6 @@ impl K8sAnywhereTopology {
|
|||||||
details: "prometheus operator present in cluster".into(),
|
details: "prometheus operator present in cluster".into(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn install_grafana_operator(
|
|
||||||
&self,
|
|
||||||
inventory: &Inventory,
|
|
||||||
ns: Option<&str>,
|
|
||||||
) -> Result<PreparationOutcome, PreparationError> {
|
|
||||||
let namespace = ns.unwrap_or("grafana");
|
|
||||||
info!("installing grafana operator in ns {namespace}");
|
|
||||||
let tenant = self.get_k8s_tenant_manager()?.get_tenant_config().await;
|
|
||||||
let mut namespace_scope = false;
|
|
||||||
if tenant.is_some() {
|
|
||||||
namespace_scope = true;
|
|
||||||
}
|
|
||||||
let _grafana_operator_score = grafana_helm_chart_score(namespace, namespace_scope)
|
|
||||||
.interpret(inventory, self)
|
|
||||||
.await
|
|
||||||
.map_err(|e| PreparationError::new(e.to_string()));
|
|
||||||
Ok(PreparationOutcome::Success {
|
|
||||||
details: format!(
|
|
||||||
"Successfully installed grafana operator in ns {}",
|
|
||||||
ns.unwrap()
|
|
||||||
),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
mod ha_cluster;
|
mod ha_cluster;
|
||||||
pub mod ingress;
|
pub mod ingress;
|
||||||
|
pub mod node_exporter;
|
||||||
use harmony_types::net::IpAddress;
|
use harmony_types::net::IpAddress;
|
||||||
mod host_binding;
|
mod host_binding;
|
||||||
mod http;
|
mod http;
|
||||||
|
|||||||
@ -1,10 +1,4 @@
|
|||||||
use std::{
|
use std::{error::Error, net::Ipv4Addr, str::FromStr, sync::Arc};
|
||||||
error::Error,
|
|
||||||
fmt::{self, Debug},
|
|
||||||
net::Ipv4Addr,
|
|
||||||
str::FromStr,
|
|
||||||
sync::Arc,
|
|
||||||
};
|
|
||||||
|
|
||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
use derive_new::new;
|
use derive_new::new;
|
||||||
@ -25,8 +19,8 @@ pub struct DHCPStaticEntry {
|
|||||||
pub ip: Ipv4Addr,
|
pub ip: Ipv4Addr,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl fmt::Display for DHCPStaticEntry {
|
impl std::fmt::Display for DHCPStaticEntry {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
let mac = self
|
let mac = self
|
||||||
.mac
|
.mac
|
||||||
.iter()
|
.iter()
|
||||||
@ -48,8 +42,8 @@ pub trait Firewall: Send + Sync {
|
|||||||
fn get_host(&self) -> LogicalHost;
|
fn get_host(&self) -> LogicalHost;
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Debug for dyn Firewall {
|
impl std::fmt::Debug for dyn Firewall {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
f.write_fmt(format_args!("Firewall {}", self.get_ip()))
|
f.write_fmt(format_args!("Firewall {}", self.get_ip()))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -71,7 +65,7 @@ pub struct PxeOptions {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
pub trait DhcpServer: Send + Sync + Debug {
|
pub trait DhcpServer: Send + Sync + std::fmt::Debug {
|
||||||
async fn add_static_mapping(&self, entry: &DHCPStaticEntry) -> Result<(), ExecutorError>;
|
async fn add_static_mapping(&self, entry: &DHCPStaticEntry) -> Result<(), ExecutorError>;
|
||||||
async fn remove_static_mapping(&self, mac: &MacAddress) -> Result<(), ExecutorError>;
|
async fn remove_static_mapping(&self, mac: &MacAddress) -> Result<(), ExecutorError>;
|
||||||
async fn list_static_mappings(&self) -> Vec<(MacAddress, IpAddress)>;
|
async fn list_static_mappings(&self) -> Vec<(MacAddress, IpAddress)>;
|
||||||
@ -110,8 +104,8 @@ pub trait DnsServer: Send + Sync {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Debug for dyn DnsServer {
|
impl std::fmt::Debug for dyn DnsServer {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
f.write_fmt(format_args!("DnsServer {}", self.get_ip()))
|
f.write_fmt(format_args!("DnsServer {}", self.get_ip()))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -147,8 +141,8 @@ pub enum DnsRecordType {
|
|||||||
TXT,
|
TXT,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl fmt::Display for DnsRecordType {
|
impl std::fmt::Display for DnsRecordType {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
match self {
|
match self {
|
||||||
DnsRecordType::A => write!(f, "A"),
|
DnsRecordType::A => write!(f, "A"),
|
||||||
DnsRecordType::AAAA => write!(f, "AAAA"),
|
DnsRecordType::AAAA => write!(f, "AAAA"),
|
||||||
@ -222,8 +216,8 @@ pub struct SwitchError {
|
|||||||
msg: String,
|
msg: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl fmt::Display for SwitchError {
|
impl std::fmt::Display for SwitchError {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
f.write_str(&self.msg)
|
f.write_str(&self.msg)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -231,7 +225,7 @@ impl fmt::Display for SwitchError {
|
|||||||
impl Error for SwitchError {}
|
impl Error for SwitchError {}
|
||||||
|
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
pub trait SwitchClient: Debug + Send + Sync {
|
pub trait SwitchClient: Send + Sync {
|
||||||
/// Executes essential, idempotent, one-time initial configuration steps.
|
/// Executes essential, idempotent, one-time initial configuration steps.
|
||||||
///
|
///
|
||||||
/// This is an opiniated procedure that setups a switch to provide high availability
|
/// This is an opiniated procedure that setups a switch to provide high availability
|
||||||
|
|||||||
17
harmony/src/domain/topology/node_exporter.rs
Normal file
17
harmony/src/domain/topology/node_exporter.rs
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
use async_trait::async_trait;
|
||||||
|
|
||||||
|
use crate::executors::ExecutorError;
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
|
pub trait NodeExporter: Send + Sync {
|
||||||
|
async fn ensure_initialized(&self) -> Result<(), ExecutorError>;
|
||||||
|
async fn commit_config(&self) -> Result<(), ExecutorError>;
|
||||||
|
async fn reload_restart(&self) -> Result<(), ExecutorError>;
|
||||||
|
}
|
||||||
|
|
||||||
|
//TODO complete this impl
|
||||||
|
impl std::fmt::Debug for dyn NodeExporter {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
f.write_fmt(format_args!("NodeExporter ",))
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -21,7 +21,6 @@ pub struct AlertingInterpret<S: AlertSender> {
|
|||||||
pub sender: S,
|
pub sender: S,
|
||||||
pub receivers: Vec<Box<dyn AlertReceiver<S>>>,
|
pub receivers: Vec<Box<dyn AlertReceiver<S>>>,
|
||||||
pub rules: Vec<Box<dyn AlertRule<S>>>,
|
pub rules: Vec<Box<dyn AlertRule<S>>>,
|
||||||
pub scrape_targets: Option<Vec<Box<dyn ScrapeTarget<S>>>>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
@ -31,7 +30,6 @@ impl<S: AlertSender + Installable<T>, T: Topology> Interpret<T> for AlertingInte
|
|||||||
inventory: &Inventory,
|
inventory: &Inventory,
|
||||||
topology: &T,
|
topology: &T,
|
||||||
) -> Result<Outcome, InterpretError> {
|
) -> Result<Outcome, InterpretError> {
|
||||||
debug!("hit sender configure for AlertingInterpret");
|
|
||||||
self.sender.configure(inventory, topology).await?;
|
self.sender.configure(inventory, topology).await?;
|
||||||
for receiver in self.receivers.iter() {
|
for receiver in self.receivers.iter() {
|
||||||
receiver.install(&self.sender).await?;
|
receiver.install(&self.sender).await?;
|
||||||
@ -40,12 +38,6 @@ impl<S: AlertSender + Installable<T>, T: Topology> Interpret<T> for AlertingInte
|
|||||||
debug!("installing rule: {:#?}", rule);
|
debug!("installing rule: {:#?}", rule);
|
||||||
rule.install(&self.sender).await?;
|
rule.install(&self.sender).await?;
|
||||||
}
|
}
|
||||||
if let Some(targets) = &self.scrape_targets {
|
|
||||||
for target in targets.iter() {
|
|
||||||
debug!("installing scrape_target: {:#?}", target);
|
|
||||||
target.install(&self.sender).await?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
self.sender.ensure_installed(inventory, topology).await?;
|
self.sender.ensure_installed(inventory, topology).await?;
|
||||||
Ok(Outcome::success(format!(
|
Ok(Outcome::success(format!(
|
||||||
"successfully installed alert sender {}",
|
"successfully installed alert sender {}",
|
||||||
@ -85,7 +77,6 @@ pub trait AlertRule<S: AlertSender>: std::fmt::Debug + Send + Sync {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
pub trait ScrapeTarget<S: AlertSender>: std::fmt::Debug + Send + Sync {
|
pub trait ScrapeTarget<S: AlertSender> {
|
||||||
async fn install(&self, sender: &S) -> Result<Outcome, InterpretError>;
|
async fn install(&self, sender: &S) -> Result<(), InterpretError>;
|
||||||
fn clone_box(&self) -> Box<dyn ScrapeTarget<S>>;
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,14 +1,15 @@
|
|||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
use brocade::{BrocadeClient, BrocadeOptions, InterSwitchLink, InterfaceStatus, PortOperatingMode};
|
use brocade::{BrocadeClient, BrocadeOptions, InterSwitchLink, InterfaceStatus, PortOperatingMode};
|
||||||
|
use harmony_secret::Secret;
|
||||||
use harmony_types::{
|
use harmony_types::{
|
||||||
net::{IpAddress, MacAddress},
|
net::{IpAddress, MacAddress},
|
||||||
switch::{PortDeclaration, PortLocation},
|
switch::{PortDeclaration, PortLocation},
|
||||||
};
|
};
|
||||||
use option_ext::OptionExt;
|
use option_ext::OptionExt;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
use crate::topology::{SwitchClient, SwitchError};
|
use crate::topology::{SwitchClient, SwitchError};
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct BrocadeSwitchClient {
|
pub struct BrocadeSwitchClient {
|
||||||
brocade: Box<dyn BrocadeClient + Send + Sync>,
|
brocade: Box<dyn BrocadeClient + Send + Sync>,
|
||||||
}
|
}
|
||||||
@ -113,6 +114,12 @@ impl SwitchClient for BrocadeSwitchClient {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Secret, Serialize, Deserialize, Debug)]
|
||||||
|
pub struct BrocadeSwitchAuth {
|
||||||
|
pub username: String,
|
||||||
|
pub password: String,
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use std::sync::{Arc, Mutex};
|
use std::sync::{Arc, Mutex};
|
||||||
@ -228,7 +235,7 @@ mod tests {
|
|||||||
assert_that!(*configured_interfaces).is_empty();
|
assert_that!(*configured_interfaces).is_empty();
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Clone)]
|
||||||
struct FakeBrocadeClient {
|
struct FakeBrocadeClient {
|
||||||
stack_topology: Vec<InterSwitchLink>,
|
stack_topology: Vec<InterSwitchLink>,
|
||||||
interfaces: Vec<InterfaceInfo>,
|
interfaces: Vec<InterfaceInfo>,
|
||||||
|
|||||||
@ -4,6 +4,7 @@ mod firewall;
|
|||||||
mod http;
|
mod http;
|
||||||
mod load_balancer;
|
mod load_balancer;
|
||||||
mod management;
|
mod management;
|
||||||
|
pub mod node_exporter;
|
||||||
mod tftp;
|
mod tftp;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
|||||||
44
harmony/src/infra/opnsense/node_exporter.rs
Normal file
44
harmony/src/infra/opnsense/node_exporter.rs
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
use async_trait::async_trait;
|
||||||
|
use log::debug;
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
executors::ExecutorError, infra::opnsense::OPNSenseFirewall,
|
||||||
|
topology::node_exporter::NodeExporter,
|
||||||
|
};
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
|
impl NodeExporter for OPNSenseFirewall {
|
||||||
|
async fn ensure_initialized(&self) -> Result<(), ExecutorError> {
|
||||||
|
let mut config = self.opnsense_config.write().await;
|
||||||
|
let node_exporter = config.node_exporter();
|
||||||
|
if let Some(config) = node_exporter.get_full_config() {
|
||||||
|
debug!(
|
||||||
|
"Node exporter available in opnsense config, assuming it is already installed. {config:?}"
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
config
|
||||||
|
.install_package("os-node_exporter")
|
||||||
|
.await
|
||||||
|
.map_err(|e| {
|
||||||
|
ExecutorError::UnexpectedError(format!("Executor failed when trying to install os-node_exporter package with error {e:?}"
|
||||||
|
))
|
||||||
|
})?;
|
||||||
|
}
|
||||||
|
|
||||||
|
config.node_exporter().enable(true);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
async fn commit_config(&self) -> Result<(), ExecutorError> {
|
||||||
|
OPNSenseFirewall::commit_config(self).await
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn reload_restart(&self) -> Result<(), ExecutorError> {
|
||||||
|
self.opnsense_config
|
||||||
|
.write()
|
||||||
|
.await
|
||||||
|
.node_exporter()
|
||||||
|
.reload_restart()
|
||||||
|
.await
|
||||||
|
.map_err(|e| ExecutorError::UnexpectedError(e.to_string()))
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -2,11 +2,7 @@ use crate::modules::application::{
|
|||||||
Application, ApplicationFeature, InstallationError, InstallationOutcome,
|
Application, ApplicationFeature, InstallationError, InstallationOutcome,
|
||||||
};
|
};
|
||||||
use crate::modules::monitoring::application_monitoring::application_monitoring_score::ApplicationMonitoringScore;
|
use crate::modules::monitoring::application_monitoring::application_monitoring_score::ApplicationMonitoringScore;
|
||||||
use crate::modules::monitoring::grafana::grafana::Grafana;
|
|
||||||
use crate::modules::monitoring::kube_prometheus::crd::crd_alertmanager_config::CRDPrometheus;
|
use crate::modules::monitoring::kube_prometheus::crd::crd_alertmanager_config::CRDPrometheus;
|
||||||
use crate::modules::monitoring::kube_prometheus::crd::service_monitor::{
|
|
||||||
ServiceMonitor, ServiceMonitorSpec,
|
|
||||||
};
|
|
||||||
use crate::topology::MultiTargetTopology;
|
use crate::topology::MultiTargetTopology;
|
||||||
use crate::topology::ingress::Ingress;
|
use crate::topology::ingress::Ingress;
|
||||||
use crate::{
|
use crate::{
|
||||||
@ -18,7 +14,7 @@ use crate::{
|
|||||||
topology::{HelmCommand, K8sclient, Topology, tenant::TenantManager},
|
topology::{HelmCommand, K8sclient, Topology, tenant::TenantManager},
|
||||||
};
|
};
|
||||||
use crate::{
|
use crate::{
|
||||||
modules::prometheus::prometheus::PrometheusMonitoring,
|
modules::prometheus::prometheus::PrometheusApplicationMonitoring,
|
||||||
topology::oberservability::monitoring::AlertReceiver,
|
topology::oberservability::monitoring::AlertReceiver,
|
||||||
};
|
};
|
||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
@ -26,7 +22,6 @@ use base64::{Engine as _, engine::general_purpose};
|
|||||||
use harmony_secret::SecretManager;
|
use harmony_secret::SecretManager;
|
||||||
use harmony_secret_derive::Secret;
|
use harmony_secret_derive::Secret;
|
||||||
use harmony_types::net::Url;
|
use harmony_types::net::Url;
|
||||||
use kube::api::ObjectMeta;
|
|
||||||
use log::{debug, info};
|
use log::{debug, info};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
@ -45,8 +40,7 @@ impl<
|
|||||||
+ TenantManager
|
+ TenantManager
|
||||||
+ K8sclient
|
+ K8sclient
|
||||||
+ MultiTargetTopology
|
+ MultiTargetTopology
|
||||||
+ PrometheusMonitoring<CRDPrometheus>
|
+ PrometheusApplicationMonitoring<CRDPrometheus>
|
||||||
+ Grafana
|
|
||||||
+ Ingress
|
+ Ingress
|
||||||
+ std::fmt::Debug,
|
+ std::fmt::Debug,
|
||||||
> ApplicationFeature<T> for Monitoring
|
> ApplicationFeature<T> for Monitoring
|
||||||
@ -63,20 +57,10 @@ impl<
|
|||||||
.unwrap_or_else(|| self.application.name());
|
.unwrap_or_else(|| self.application.name());
|
||||||
let domain = topology.get_domain("ntfy").await.unwrap();
|
let domain = topology.get_domain("ntfy").await.unwrap();
|
||||||
|
|
||||||
let app_service_monitor = ServiceMonitor {
|
|
||||||
metadata: ObjectMeta {
|
|
||||||
name: Some(self.application.name()),
|
|
||||||
namespace: Some(namespace.clone()),
|
|
||||||
..Default::default()
|
|
||||||
},
|
|
||||||
spec: ServiceMonitorSpec::default(),
|
|
||||||
};
|
|
||||||
|
|
||||||
let mut alerting_score = ApplicationMonitoringScore {
|
let mut alerting_score = ApplicationMonitoringScore {
|
||||||
sender: CRDPrometheus {
|
sender: CRDPrometheus {
|
||||||
namespace: namespace.clone(),
|
namespace: namespace.clone(),
|
||||||
client: topology.k8s_client().await.unwrap(),
|
client: topology.k8s_client().await.unwrap(),
|
||||||
service_monitor: vec![app_service_monitor],
|
|
||||||
},
|
},
|
||||||
application: self.application.clone(),
|
application: self.application.clone(),
|
||||||
receivers: self.alert_receiver.clone(),
|
receivers: self.alert_receiver.clone(),
|
||||||
|
|||||||
@ -18,7 +18,7 @@ use crate::{
|
|||||||
topology::{HelmCommand, K8sclient, Topology, tenant::TenantManager},
|
topology::{HelmCommand, K8sclient, Topology, tenant::TenantManager},
|
||||||
};
|
};
|
||||||
use crate::{
|
use crate::{
|
||||||
modules::prometheus::prometheus::PrometheusMonitoring,
|
modules::prometheus::prometheus::PrometheusApplicationMonitoring,
|
||||||
topology::oberservability::monitoring::AlertReceiver,
|
topology::oberservability::monitoring::AlertReceiver,
|
||||||
};
|
};
|
||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
@ -42,7 +42,7 @@ impl<
|
|||||||
+ MultiTargetTopology
|
+ MultiTargetTopology
|
||||||
+ Ingress
|
+ Ingress
|
||||||
+ std::fmt::Debug
|
+ std::fmt::Debug
|
||||||
+ PrometheusMonitoring<RHOBObservability>,
|
+ PrometheusApplicationMonitoring<RHOBObservability>,
|
||||||
> ApplicationFeature<T> for Monitoring
|
> ApplicationFeature<T> for Monitoring
|
||||||
{
|
{
|
||||||
async fn ensure_installed(
|
async fn ensure_installed(
|
||||||
|
|||||||
@ -1,23 +1,21 @@
|
|||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use log::debug;
|
use async_trait::async_trait;
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
interpret::Interpret,
|
data::Version,
|
||||||
|
interpret::{Interpret, InterpretError, InterpretName, InterpretStatus, Outcome},
|
||||||
|
inventory::Inventory,
|
||||||
modules::{
|
modules::{
|
||||||
application::Application,
|
application::Application,
|
||||||
monitoring::{
|
monitoring::kube_prometheus::crd::crd_alertmanager_config::CRDPrometheus,
|
||||||
grafana::grafana::Grafana, kube_prometheus::crd::crd_alertmanager_config::CRDPrometheus,
|
prometheus::prometheus::PrometheusApplicationMonitoring,
|
||||||
},
|
|
||||||
prometheus::prometheus::PrometheusMonitoring,
|
|
||||||
},
|
},
|
||||||
score::Score,
|
score::Score,
|
||||||
topology::{
|
topology::{PreparationOutcome, Topology, oberservability::monitoring::AlertReceiver},
|
||||||
K8sclient, Topology,
|
|
||||||
oberservability::monitoring::{AlertReceiver, AlertingInterpret, ScrapeTarget},
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
|
use harmony_types::id::Id;
|
||||||
|
|
||||||
#[derive(Debug, Clone, Serialize)]
|
#[derive(Debug, Clone, Serialize)]
|
||||||
pub struct ApplicationMonitoringScore {
|
pub struct ApplicationMonitoringScore {
|
||||||
@ -26,16 +24,12 @@ pub struct ApplicationMonitoringScore {
|
|||||||
pub receivers: Vec<Box<dyn AlertReceiver<CRDPrometheus>>>,
|
pub receivers: Vec<Box<dyn AlertReceiver<CRDPrometheus>>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T: Topology + PrometheusMonitoring<CRDPrometheus> + K8sclient + Grafana> Score<T>
|
impl<T: Topology + PrometheusApplicationMonitoring<CRDPrometheus>> Score<T>
|
||||||
for ApplicationMonitoringScore
|
for ApplicationMonitoringScore
|
||||||
{
|
{
|
||||||
fn create_interpret(&self) -> Box<dyn Interpret<T>> {
|
fn create_interpret(&self) -> Box<dyn Interpret<T>> {
|
||||||
debug!("creating alerting interpret");
|
Box::new(ApplicationMonitoringInterpret {
|
||||||
Box::new(AlertingInterpret {
|
score: self.clone(),
|
||||||
sender: self.sender.clone(),
|
|
||||||
receivers: self.receivers.clone(),
|
|
||||||
rules: vec![],
|
|
||||||
scrape_targets: None,
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -46,3 +40,55 @@ impl<T: Topology + PrometheusMonitoring<CRDPrometheus> + K8sclient + Grafana> Sc
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct ApplicationMonitoringInterpret {
|
||||||
|
score: ApplicationMonitoringScore,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
|
impl<T: Topology + PrometheusApplicationMonitoring<CRDPrometheus>> Interpret<T>
|
||||||
|
for ApplicationMonitoringInterpret
|
||||||
|
{
|
||||||
|
async fn execute(
|
||||||
|
&self,
|
||||||
|
inventory: &Inventory,
|
||||||
|
topology: &T,
|
||||||
|
) -> Result<Outcome, InterpretError> {
|
||||||
|
let result = topology
|
||||||
|
.install_prometheus(
|
||||||
|
&self.score.sender,
|
||||||
|
inventory,
|
||||||
|
Some(self.score.receivers.clone()),
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
match result {
|
||||||
|
Ok(outcome) => match outcome {
|
||||||
|
PreparationOutcome::Success { details: _ } => {
|
||||||
|
Ok(Outcome::success("Prometheus installed".into()))
|
||||||
|
}
|
||||||
|
PreparationOutcome::Noop => {
|
||||||
|
Ok(Outcome::noop("Prometheus installation skipped".into()))
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Err(err) => Err(InterpretError::from(err)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_name(&self) -> InterpretName {
|
||||||
|
InterpretName::ApplicationMonitoring
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_version(&self) -> Version {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_status(&self) -> InterpretStatus {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_children(&self) -> Vec<Id> {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@ -12,7 +12,7 @@ use crate::{
|
|||||||
monitoring::kube_prometheus::crd::{
|
monitoring::kube_prometheus::crd::{
|
||||||
crd_alertmanager_config::CRDPrometheus, rhob_alertmanager_config::RHOBObservability,
|
crd_alertmanager_config::CRDPrometheus, rhob_alertmanager_config::RHOBObservability,
|
||||||
},
|
},
|
||||||
prometheus::prometheus::PrometheusMonitoring,
|
prometheus::prometheus::PrometheusApplicationMonitoring,
|
||||||
},
|
},
|
||||||
score::Score,
|
score::Score,
|
||||||
topology::{PreparationOutcome, Topology, oberservability::monitoring::AlertReceiver},
|
topology::{PreparationOutcome, Topology, oberservability::monitoring::AlertReceiver},
|
||||||
@ -26,7 +26,7 @@ pub struct ApplicationRHOBMonitoringScore {
|
|||||||
pub receivers: Vec<Box<dyn AlertReceiver<RHOBObservability>>>,
|
pub receivers: Vec<Box<dyn AlertReceiver<RHOBObservability>>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T: Topology + PrometheusMonitoring<RHOBObservability>> Score<T>
|
impl<T: Topology + PrometheusApplicationMonitoring<RHOBObservability>> Score<T>
|
||||||
for ApplicationRHOBMonitoringScore
|
for ApplicationRHOBMonitoringScore
|
||||||
{
|
{
|
||||||
fn create_interpret(&self) -> Box<dyn Interpret<T>> {
|
fn create_interpret(&self) -> Box<dyn Interpret<T>> {
|
||||||
@ -49,7 +49,7 @@ pub struct ApplicationRHOBMonitoringInterpret {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
impl<T: Topology + PrometheusMonitoring<RHOBObservability>> Interpret<T>
|
impl<T: Topology + PrometheusApplicationMonitoring<RHOBObservability>> Interpret<T>
|
||||||
for ApplicationRHOBMonitoringInterpret
|
for ApplicationRHOBMonitoringInterpret
|
||||||
{
|
{
|
||||||
async fn execute(
|
async fn execute(
|
||||||
|
|||||||
@ -1,17 +0,0 @@
|
|||||||
use async_trait::async_trait;
|
|
||||||
use k8s_openapi::Resource;
|
|
||||||
|
|
||||||
use crate::{
|
|
||||||
inventory::Inventory,
|
|
||||||
topology::{PreparationError, PreparationOutcome},
|
|
||||||
};
|
|
||||||
|
|
||||||
#[async_trait]
|
|
||||||
pub trait Grafana {
|
|
||||||
async fn ensure_grafana_operator(
|
|
||||||
&self,
|
|
||||||
inventory: &Inventory,
|
|
||||||
) -> Result<PreparationOutcome, PreparationError>;
|
|
||||||
|
|
||||||
async fn install_grafana(&self) -> Result<PreparationOutcome, PreparationError>;
|
|
||||||
}
|
|
||||||
@ -1,28 +1,27 @@
|
|||||||
use harmony_macros::hurl;
|
|
||||||
use non_blank_string_rs::NonBlankString;
|
use non_blank_string_rs::NonBlankString;
|
||||||
use std::{collections::HashMap, str::FromStr};
|
use std::str::FromStr;
|
||||||
|
|
||||||
use crate::modules::helm::chart::{HelmChartScore, HelmRepository};
|
use crate::modules::helm::chart::HelmChartScore;
|
||||||
|
|
||||||
|
pub fn grafana_helm_chart_score(ns: &str) -> HelmChartScore {
|
||||||
|
let values = r#"
|
||||||
|
rbac:
|
||||||
|
namespaced: true
|
||||||
|
sidecar:
|
||||||
|
dashboards:
|
||||||
|
enabled: true
|
||||||
|
"#
|
||||||
|
.to_string();
|
||||||
|
|
||||||
pub fn grafana_helm_chart_score(ns: &str, namespace_scope: bool) -> HelmChartScore {
|
|
||||||
let mut values_overrides = HashMap::new();
|
|
||||||
values_overrides.insert(
|
|
||||||
NonBlankString::from_str("namespaceScope").unwrap(),
|
|
||||||
namespace_scope.to_string(),
|
|
||||||
);
|
|
||||||
HelmChartScore {
|
HelmChartScore {
|
||||||
namespace: Some(NonBlankString::from_str(ns).unwrap()),
|
namespace: Some(NonBlankString::from_str(ns).unwrap()),
|
||||||
release_name: NonBlankString::from_str("grafana-operator").unwrap(),
|
release_name: NonBlankString::from_str("grafana").unwrap(),
|
||||||
chart_name: NonBlankString::from_str("grafana/grafana-operator").unwrap(),
|
chart_name: NonBlankString::from_str("oci://ghcr.io/grafana/helm-charts/grafana").unwrap(),
|
||||||
chart_version: None,
|
chart_version: None,
|
||||||
values_overrides: Some(values_overrides),
|
values_overrides: None,
|
||||||
values_yaml: None,
|
values_yaml: Some(values.to_string()),
|
||||||
create_namespace: true,
|
create_namespace: true,
|
||||||
install_only: true,
|
install_only: true,
|
||||||
repository: Some(HelmRepository::new(
|
repository: None,
|
||||||
"grafana".to_string(),
|
|
||||||
hurl!("https://grafana.github.io/helm-charts"),
|
|
||||||
true,
|
|
||||||
)),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,2 +1 @@
|
|||||||
pub mod grafana;
|
|
||||||
pub mod helm;
|
pub mod helm;
|
||||||
|
|||||||
@ -1,25 +1,12 @@
|
|||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use async_trait::async_trait;
|
|
||||||
use kube::CustomResource;
|
use kube::CustomResource;
|
||||||
use schemars::JsonSchema;
|
use schemars::JsonSchema;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
use crate::{
|
use crate::topology::{
|
||||||
interpret::{InterpretError, Outcome},
|
k8s::K8sClient,
|
||||||
inventory::Inventory,
|
oberservability::monitoring::{AlertReceiver, AlertSender},
|
||||||
modules::{
|
|
||||||
monitoring::{
|
|
||||||
grafana::grafana::Grafana, kube_prometheus::crd::service_monitor::ServiceMonitor,
|
|
||||||
},
|
|
||||||
prometheus::prometheus::PrometheusMonitoring,
|
|
||||||
},
|
|
||||||
topology::{
|
|
||||||
K8sclient, Topology,
|
|
||||||
installable::Installable,
|
|
||||||
k8s::K8sClient,
|
|
||||||
oberservability::monitoring::{AlertReceiver, AlertSender, ScrapeTarget},
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(CustomResource, Serialize, Deserialize, Debug, Clone, JsonSchema)]
|
#[derive(CustomResource, Serialize, Deserialize, Debug, Clone, JsonSchema)]
|
||||||
@ -39,7 +26,6 @@ pub struct AlertmanagerConfigSpec {
|
|||||||
pub struct CRDPrometheus {
|
pub struct CRDPrometheus {
|
||||||
pub namespace: String,
|
pub namespace: String,
|
||||||
pub client: Arc<K8sClient>,
|
pub client: Arc<K8sClient>,
|
||||||
pub service_monitor: Vec<ServiceMonitor>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AlertSender for CRDPrometheus {
|
impl AlertSender for CRDPrometheus {
|
||||||
@ -54,12 +40,6 @@ impl Clone for Box<dyn AlertReceiver<CRDPrometheus>> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Clone for Box<dyn ScrapeTarget<CRDPrometheus>> {
|
|
||||||
fn clone(&self) -> Self {
|
|
||||||
self.clone_box()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Serialize for Box<dyn AlertReceiver<CRDPrometheus>> {
|
impl Serialize for Box<dyn AlertReceiver<CRDPrometheus>> {
|
||||||
fn serialize<S>(&self, _serializer: S) -> Result<S::Ok, S::Error>
|
fn serialize<S>(&self, _serializer: S) -> Result<S::Ok, S::Error>
|
||||||
where
|
where
|
||||||
@ -68,24 +48,3 @@ impl Serialize for Box<dyn AlertReceiver<CRDPrometheus>> {
|
|||||||
todo!()
|
todo!()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[async_trait]
|
|
||||||
impl<T: Topology + K8sclient + PrometheusMonitoring<CRDPrometheus> + Grafana> Installable<T>
|
|
||||||
for CRDPrometheus
|
|
||||||
{
|
|
||||||
async fn configure(&self, inventory: &Inventory, topology: &T) -> Result<(), InterpretError> {
|
|
||||||
topology.ensure_grafana_operator(inventory).await?;
|
|
||||||
topology.ensure_prometheus_operator(self, inventory).await?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn ensure_installed(
|
|
||||||
&self,
|
|
||||||
inventory: &Inventory,
|
|
||||||
topology: &T,
|
|
||||||
) -> Result<(), InterpretError> {
|
|
||||||
topology.install_grafana().await?;
|
|
||||||
topology.install_prometheus(&self, inventory, None).await?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@ -103,34 +103,9 @@ pub struct GrafanaDashboardSpec {
|
|||||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||||
pub resync_period: Option<String>,
|
pub resync_period: Option<String>,
|
||||||
|
|
||||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
|
||||||
pub datasources: Option<Vec<GrafanaDashboardDatasource>>,
|
|
||||||
|
|
||||||
pub instance_selector: LabelSelector,
|
pub instance_selector: LabelSelector,
|
||||||
|
|
||||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
pub json: String,
|
||||||
pub json: Option<String>,
|
|
||||||
|
|
||||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
|
||||||
pub grafana_com: Option<GrafanaCom>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Debug, Clone, JsonSchema)]
|
|
||||||
#[serde(rename_all = "camelCase")]
|
|
||||||
pub struct GrafanaDashboardDatasource {
|
|
||||||
pub input_name: String,
|
|
||||||
pub datasource_name: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
// ------------------------------------------------------------------------------------------------
|
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Debug, Clone, JsonSchema)]
|
|
||||||
#[serde(rename_all = "camelCase")]
|
|
||||||
pub struct GrafanaCom {
|
|
||||||
pub id: u32,
|
|
||||||
|
|
||||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
|
||||||
pub revision: Option<u32>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ------------------------------------------------------------------------------------------------
|
// ------------------------------------------------------------------------------------------------
|
||||||
@ -151,79 +126,20 @@ pub struct GrafanaDatasourceSpec {
|
|||||||
pub allow_cross_namespace_import: Option<bool>,
|
pub allow_cross_namespace_import: Option<bool>,
|
||||||
|
|
||||||
pub datasource: GrafanaDatasourceConfig,
|
pub datasource: GrafanaDatasourceConfig,
|
||||||
|
|
||||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
|
||||||
pub values_from: Option<Vec<GrafanaValueFrom>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Debug, Clone, JsonSchema)]
|
|
||||||
#[serde(rename_all = "camelCase")]
|
|
||||||
pub struct GrafanaValueFrom {
|
|
||||||
pub target_path: String,
|
|
||||||
pub value_from: GrafanaValueSource,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Debug, Clone, JsonSchema)]
|
|
||||||
#[serde(rename_all = "camelCase")]
|
|
||||||
pub struct GrafanaValueSource {
|
|
||||||
pub secret_key_ref: GrafanaSecretKeyRef,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Debug, Clone, JsonSchema)]
|
|
||||||
#[serde(rename_all = "camelCase")]
|
|
||||||
pub struct GrafanaSecretKeyRef {
|
|
||||||
pub name: String,
|
|
||||||
pub key: String,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Debug, Clone, JsonSchema)]
|
#[derive(Serialize, Deserialize, Debug, Clone, JsonSchema)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct GrafanaDatasourceConfig {
|
pub struct GrafanaDatasourceConfig {
|
||||||
pub access: String,
|
pub access: String,
|
||||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
|
||||||
pub database: Option<String>,
|
pub database: Option<String>,
|
||||||
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||||
|
pub json_data: Option<BTreeMap<String, String>>,
|
||||||
pub name: String,
|
pub name: String,
|
||||||
pub r#type: String,
|
pub r#type: String,
|
||||||
pub url: String,
|
pub url: String,
|
||||||
/// Represents jsonData in the GrafanaDatasource spec
|
|
||||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
|
||||||
pub json_data: Option<GrafanaDatasourceJsonData>,
|
|
||||||
|
|
||||||
/// Represents secureJsonData (secrets)
|
|
||||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
|
||||||
pub secure_json_data: Option<GrafanaDatasourceSecureJsonData>,
|
|
||||||
|
|
||||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
|
||||||
pub is_default: Option<bool>,
|
|
||||||
|
|
||||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
|
||||||
pub editable: Option<bool>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Debug, Clone, JsonSchema)]
|
|
||||||
#[serde(rename_all = "camelCase")]
|
|
||||||
pub struct GrafanaDatasourceJsonData {
|
|
||||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
|
||||||
pub time_interval: Option<String>,
|
|
||||||
|
|
||||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
|
||||||
pub http_header_name1: Option<String>,
|
|
||||||
|
|
||||||
/// Disable TLS skip verification (false = verify)
|
|
||||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
|
||||||
pub tls_skip_verify: Option<bool>,
|
|
||||||
|
|
||||||
/// Auth type - set to "forward" for OpenShift OAuth identity
|
|
||||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
|
||||||
pub oauth_pass_thru: Option<bool>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Debug, Clone, JsonSchema)]
|
|
||||||
#[serde(rename_all = "camelCase")]
|
|
||||||
pub struct GrafanaDatasourceSecureJsonData {
|
|
||||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
|
||||||
pub http_header_value1: Option<String>,
|
|
||||||
}
|
|
||||||
// ------------------------------------------------------------------------------------------------
|
// ------------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Debug, Clone, JsonSchema, Default)]
|
#[derive(Serialize, Deserialize, Debug, Clone, JsonSchema, Default)]
|
||||||
|
|||||||
@ -1,187 +0,0 @@
|
|||||||
use std::net::IpAddr;
|
|
||||||
|
|
||||||
use async_trait::async_trait;
|
|
||||||
use kube::CustomResource;
|
|
||||||
use schemars::JsonSchema;
|
|
||||||
use serde::{Deserialize, Serialize};
|
|
||||||
|
|
||||||
use crate::{
|
|
||||||
modules::monitoring::kube_prometheus::crd::{
|
|
||||||
crd_alertmanager_config::CRDPrometheus, crd_prometheuses::LabelSelector,
|
|
||||||
},
|
|
||||||
topology::oberservability::monitoring::ScrapeTarget,
|
|
||||||
};
|
|
||||||
|
|
||||||
#[derive(CustomResource, Serialize, Deserialize, Debug, Clone, JsonSchema)]
|
|
||||||
#[kube(
|
|
||||||
group = "monitoring.coreos.com",
|
|
||||||
version = "v1alpha1",
|
|
||||||
kind = "ScrapeConfig",
|
|
||||||
plural = "scrapeconfigs",
|
|
||||||
namespaced
|
|
||||||
)]
|
|
||||||
#[serde(rename_all = "camelCase")]
|
|
||||||
pub struct ScrapeConfigSpec {
|
|
||||||
/// List of static configurations.
|
|
||||||
pub static_configs: Option<Vec<StaticConfig>>,
|
|
||||||
|
|
||||||
/// Kubernetes service discovery.
|
|
||||||
pub kubernetes_sd_configs: Option<Vec<KubernetesSDConfig>>,
|
|
||||||
|
|
||||||
/// HTTP-based service discovery.
|
|
||||||
pub http_sd_configs: Option<Vec<HttpSDConfig>>,
|
|
||||||
|
|
||||||
/// File-based service discovery.
|
|
||||||
pub file_sd_configs: Option<Vec<FileSDConfig>>,
|
|
||||||
|
|
||||||
/// DNS-based service discovery.
|
|
||||||
pub dns_sd_configs: Option<Vec<DnsSDConfig>>,
|
|
||||||
|
|
||||||
/// Consul service discovery.
|
|
||||||
pub consul_sd_configs: Option<Vec<ConsulSDConfig>>,
|
|
||||||
|
|
||||||
/// Relabeling configuration applied to discovered targets.
|
|
||||||
pub relabel_configs: Option<Vec<RelabelConfig>>,
|
|
||||||
|
|
||||||
/// Metric relabeling configuration applied to scraped samples.
|
|
||||||
pub metric_relabel_configs: Option<Vec<RelabelConfig>>,
|
|
||||||
|
|
||||||
/// Path to scrape metrics from (defaults to `/metrics`).
|
|
||||||
pub metrics_path: Option<String>,
|
|
||||||
|
|
||||||
/// Interval at which Prometheus scrapes targets (e.g., "30s").
|
|
||||||
pub scrape_interval: Option<String>,
|
|
||||||
|
|
||||||
/// Timeout for scraping (e.g., "10s").
|
|
||||||
pub scrape_timeout: Option<String>,
|
|
||||||
|
|
||||||
/// Optional job name override.
|
|
||||||
pub job_name: Option<String>,
|
|
||||||
|
|
||||||
/// Optional scheme (http or https).
|
|
||||||
pub scheme: Option<String>,
|
|
||||||
|
|
||||||
/// Authorization paramaters for snmp walk
|
|
||||||
pub params: Option<Params>,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Static configuration section of a ScrapeConfig.
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
|
|
||||||
#[serde(rename_all = "camelCase")]
|
|
||||||
pub struct StaticConfig {
|
|
||||||
pub targets: Vec<String>,
|
|
||||||
|
|
||||||
pub labels: Option<LabelSelector>,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Relabeling configuration for target or metric relabeling.
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
|
|
||||||
#[serde(rename_all = "camelCase")]
|
|
||||||
pub struct RelabelConfig {
|
|
||||||
pub source_labels: Option<Vec<String>>,
|
|
||||||
pub separator: Option<String>,
|
|
||||||
pub target_label: Option<String>,
|
|
||||||
pub regex: Option<String>,
|
|
||||||
pub modulus: Option<u64>,
|
|
||||||
pub replacement: Option<String>,
|
|
||||||
pub action: Option<String>,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Kubernetes service discovery configuration.
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
|
|
||||||
#[serde(rename_all = "camelCase")]
|
|
||||||
pub struct KubernetesSDConfig {
|
|
||||||
///"pod", "service", "endpoints"pub role: String,
|
|
||||||
pub namespaces: Option<NamespaceSelector>,
|
|
||||||
pub selectors: Option<Vec<LabelSelector>>,
|
|
||||||
pub api_server: Option<String>,
|
|
||||||
pub bearer_token_file: Option<String>,
|
|
||||||
pub tls_config: Option<TLSConfig>,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Namespace selector for Kubernetes service discovery.
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
|
|
||||||
#[serde(rename_all = "camelCase")]
|
|
||||||
pub struct NamespaceSelector {
|
|
||||||
pub any: Option<bool>,
|
|
||||||
pub match_names: Option<Vec<String>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// HTTP-based service discovery configuration.
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
|
|
||||||
#[serde(rename_all = "camelCase")]
|
|
||||||
pub struct HttpSDConfig {
|
|
||||||
pub url: String,
|
|
||||||
pub refresh_interval: Option<String>,
|
|
||||||
pub basic_auth: Option<BasicAuth>,
|
|
||||||
pub authorization: Option<Authorization>,
|
|
||||||
pub tls_config: Option<TLSConfig>,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// File-based service discovery configuration.
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
|
|
||||||
#[serde(rename_all = "camelCase")]
|
|
||||||
pub struct FileSDConfig {
|
|
||||||
pub files: Vec<String>,
|
|
||||||
pub refresh_interval: Option<String>,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// DNS-based service discovery configuration.
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
|
|
||||||
#[serde(rename_all = "camelCase")]
|
|
||||||
pub struct DnsSDConfig {
|
|
||||||
pub names: Vec<String>,
|
|
||||||
pub refresh_interval: Option<String>,
|
|
||||||
pub type_: Option<String>, // SRV, A, AAAA
|
|
||||||
pub port: Option<u16>,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Consul service discovery configuration.
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
|
|
||||||
#[serde(rename_all = "camelCase")]
|
|
||||||
pub struct ConsulSDConfig {
|
|
||||||
pub server: String,
|
|
||||||
pub services: Option<Vec<String>>,
|
|
||||||
pub scheme: Option<String>,
|
|
||||||
pub datacenter: Option<String>,
|
|
||||||
pub tag_separator: Option<String>,
|
|
||||||
pub refresh_interval: Option<String>,
|
|
||||||
pub tls_config: Option<TLSConfig>,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Basic authentication credentials.
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
|
|
||||||
#[serde(rename_all = "camelCase")]
|
|
||||||
pub struct BasicAuth {
|
|
||||||
pub username: String,
|
|
||||||
pub password: Option<String>,
|
|
||||||
pub password_file: Option<String>,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Bearer token or other auth mechanisms.
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
|
|
||||||
#[serde(rename_all = "camelCase")]
|
|
||||||
pub struct Authorization {
|
|
||||||
pub credentials: Option<String>,
|
|
||||||
pub credentials_file: Option<String>,
|
|
||||||
pub type_: Option<String>,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// TLS configuration for secure scraping.
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
|
|
||||||
#[serde(rename_all = "camelCase")]
|
|
||||||
pub struct TLSConfig {
|
|
||||||
pub ca_file: Option<String>,
|
|
||||||
pub cert_file: Option<String>,
|
|
||||||
pub key_file: Option<String>,
|
|
||||||
pub server_name: Option<String>,
|
|
||||||
pub insecure_skip_verify: Option<bool>,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Authorization parameters for SNMP walk.
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
|
|
||||||
#[serde(rename_all = "camelCase")]
|
|
||||||
pub struct Params {
|
|
||||||
pub auth: Option<Vec<String>>,
|
|
||||||
pub module: Option<Vec<String>>,
|
|
||||||
}
|
|
||||||
@ -4,7 +4,6 @@ pub mod crd_default_rules;
|
|||||||
pub mod crd_grafana;
|
pub mod crd_grafana;
|
||||||
pub mod crd_prometheus_rules;
|
pub mod crd_prometheus_rules;
|
||||||
pub mod crd_prometheuses;
|
pub mod crd_prometheuses;
|
||||||
pub mod crd_scrape_config;
|
|
||||||
pub mod grafana_default_dashboard;
|
pub mod grafana_default_dashboard;
|
||||||
pub mod grafana_operator;
|
pub mod grafana_operator;
|
||||||
pub mod prometheus_operator;
|
pub mod prometheus_operator;
|
||||||
|
|||||||
@ -31,7 +31,6 @@ impl<T: Topology + HelmCommand + TenantManager> Score<T> for HelmPrometheusAlert
|
|||||||
sender: KubePrometheus { config },
|
sender: KubePrometheus { config },
|
||||||
receivers: self.receivers.clone(),
|
receivers: self.receivers.clone(),
|
||||||
rules: self.rules.clone(),
|
rules: self.rules.clone(),
|
||||||
scrape_targets: None,
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
fn name(&self) -> String {
|
fn name(&self) -> String {
|
||||||
|
|||||||
@ -6,4 +6,3 @@ pub mod kube_prometheus;
|
|||||||
pub mod ntfy;
|
pub mod ntfy;
|
||||||
pub mod okd;
|
pub mod okd;
|
||||||
pub mod prometheus;
|
pub mod prometheus;
|
||||||
pub mod scrape_target;
|
|
||||||
|
|||||||
@ -114,7 +114,7 @@ impl Prometheus {
|
|||||||
};
|
};
|
||||||
|
|
||||||
if let Some(ns) = namespace.as_deref() {
|
if let Some(ns) = namespace.as_deref() {
|
||||||
grafana_helm_chart_score(ns, false)
|
grafana_helm_chart_score(ns)
|
||||||
.interpret(inventory, topology)
|
.interpret(inventory, topology)
|
||||||
.await
|
.await
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@ -1 +0,0 @@
|
|||||||
pub mod server;
|
|
||||||
@ -1,80 +0,0 @@
|
|||||||
use std::net::IpAddr;
|
|
||||||
|
|
||||||
use async_trait::async_trait;
|
|
||||||
use kube::api::ObjectMeta;
|
|
||||||
use serde::Serialize;
|
|
||||||
|
|
||||||
use crate::{
|
|
||||||
interpret::{InterpretError, Outcome},
|
|
||||||
modules::monitoring::kube_prometheus::crd::{
|
|
||||||
crd_alertmanager_config::CRDPrometheus,
|
|
||||||
crd_scrape_config::{Params, RelabelConfig, ScrapeConfig, ScrapeConfigSpec, StaticConfig},
|
|
||||||
},
|
|
||||||
topology::oberservability::monitoring::ScrapeTarget,
|
|
||||||
};
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, Serialize)]
|
|
||||||
pub struct Server {
|
|
||||||
pub name: String,
|
|
||||||
pub ip: IpAddr,
|
|
||||||
pub auth: String,
|
|
||||||
pub module: String,
|
|
||||||
pub domain: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[async_trait]
|
|
||||||
impl ScrapeTarget<CRDPrometheus> for Server {
|
|
||||||
async fn install(&self, sender: &CRDPrometheus) -> Result<Outcome, InterpretError> {
|
|
||||||
let scrape_config_spec = ScrapeConfigSpec {
|
|
||||||
static_configs: Some(vec![StaticConfig {
|
|
||||||
targets: vec![self.ip.to_string()],
|
|
||||||
labels: None,
|
|
||||||
}]),
|
|
||||||
scrape_interval: Some("2m".to_string()),
|
|
||||||
kubernetes_sd_configs: None,
|
|
||||||
http_sd_configs: None,
|
|
||||||
file_sd_configs: None,
|
|
||||||
dns_sd_configs: None,
|
|
||||||
params: Some(Params {
|
|
||||||
auth: Some(vec![self.auth.clone()]),
|
|
||||||
module: Some(vec![self.module.clone()]),
|
|
||||||
}),
|
|
||||||
consul_sd_configs: None,
|
|
||||||
relabel_configs: Some(vec![RelabelConfig {
|
|
||||||
action: None,
|
|
||||||
source_labels: Some(vec!["__address__".to_string()]),
|
|
||||||
separator: None,
|
|
||||||
target_label: Some("__param_target".to_string()),
|
|
||||||
regex: None,
|
|
||||||
replacement: Some(format!("snmp.{}:31080", self.domain.clone())),
|
|
||||||
modulus: None,
|
|
||||||
}]),
|
|
||||||
metric_relabel_configs: None,
|
|
||||||
metrics_path: Some("/snmp".to_string()),
|
|
||||||
scrape_timeout: Some("2m".to_string()),
|
|
||||||
job_name: Some(format!("snmp_exporter/cloud/{}", self.name.clone())),
|
|
||||||
scheme: None,
|
|
||||||
};
|
|
||||||
|
|
||||||
let scrape_config = ScrapeConfig {
|
|
||||||
metadata: ObjectMeta {
|
|
||||||
name: Some(self.name.clone()),
|
|
||||||
namespace: Some(sender.namespace.clone()),
|
|
||||||
..Default::default()
|
|
||||||
},
|
|
||||||
spec: scrape_config_spec,
|
|
||||||
};
|
|
||||||
sender
|
|
||||||
.client
|
|
||||||
.apply(&scrape_config, Some(&sender.namespace.clone()))
|
|
||||||
.await?;
|
|
||||||
Ok(Outcome::success(format!(
|
|
||||||
"installed scrape target {}",
|
|
||||||
self.name.clone()
|
|
||||||
)))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn clone_box(&self) -> Box<dyn ScrapeTarget<CRDPrometheus>> {
|
|
||||||
Box::new(self.clone())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,3 +1,4 @@
|
|||||||
|
pub mod node_exporter;
|
||||||
mod shell;
|
mod shell;
|
||||||
mod upgrade;
|
mod upgrade;
|
||||||
pub use shell::*;
|
pub use shell::*;
|
||||||
|
|||||||
70
harmony/src/modules/opnsense/node_exporter.rs
Normal file
70
harmony/src/modules/opnsense/node_exporter.rs
Normal file
@ -0,0 +1,70 @@
|
|||||||
|
use async_trait::async_trait;
|
||||||
|
use harmony_types::id::Id;
|
||||||
|
use log::info;
|
||||||
|
use serde::Serialize;
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
data::Version,
|
||||||
|
interpret::{Interpret, InterpretError, InterpretName, InterpretStatus, Outcome},
|
||||||
|
inventory::Inventory,
|
||||||
|
score::Score,
|
||||||
|
topology::{Topology, node_exporter::NodeExporter},
|
||||||
|
};
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Serialize)]
|
||||||
|
pub struct NodeExporterScore {}
|
||||||
|
|
||||||
|
impl<T: Topology + NodeExporter> Score<T> for NodeExporterScore {
|
||||||
|
fn name(&self) -> String {
|
||||||
|
"NodeExporterScore".to_string()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn create_interpret(&self) -> Box<dyn Interpret<T>> {
|
||||||
|
Box::new(NodeExporterInterpret {})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct NodeExporterInterpret {}
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
|
impl<T: Topology + NodeExporter> Interpret<T> for NodeExporterInterpret {
|
||||||
|
async fn execute(
|
||||||
|
&self,
|
||||||
|
_inventory: &Inventory,
|
||||||
|
node_exporter: &T,
|
||||||
|
) -> Result<Outcome, InterpretError> {
|
||||||
|
info!(
|
||||||
|
"Making sure node exporter is initiailized: {:?}",
|
||||||
|
node_exporter.ensure_initialized().await?
|
||||||
|
);
|
||||||
|
|
||||||
|
info!("Applying Node Exporter configuration");
|
||||||
|
|
||||||
|
node_exporter.commit_config().await?;
|
||||||
|
|
||||||
|
info!("Reloading and restarting Node Exporter");
|
||||||
|
|
||||||
|
node_exporter.reload_restart().await?;
|
||||||
|
|
||||||
|
Ok(Outcome::success(format!(
|
||||||
|
"NodeExporter successfully configured"
|
||||||
|
)))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_name(&self) -> InterpretName {
|
||||||
|
InterpretName::Custom("NodeExporter")
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_version(&self) -> Version {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_status(&self) -> InterpretStatus {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_children(&self) -> Vec<Id> {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -12,8 +12,7 @@ use crate::modules::monitoring::kube_prometheus::crd::crd_alertmanager_config::C
|
|||||||
use crate::modules::monitoring::kube_prometheus::crd::crd_default_rules::build_default_application_rules;
|
use crate::modules::monitoring::kube_prometheus::crd::crd_default_rules::build_default_application_rules;
|
||||||
use crate::modules::monitoring::kube_prometheus::crd::crd_grafana::{
|
use crate::modules::monitoring::kube_prometheus::crd::crd_grafana::{
|
||||||
Grafana, GrafanaDashboard, GrafanaDashboardSpec, GrafanaDatasource, GrafanaDatasourceConfig,
|
Grafana, GrafanaDashboard, GrafanaDashboardSpec, GrafanaDatasource, GrafanaDatasourceConfig,
|
||||||
GrafanaDatasourceJsonData, GrafanaDatasourceSpec, GrafanaSecretKeyRef, GrafanaSpec,
|
GrafanaDatasourceSpec, GrafanaSpec,
|
||||||
GrafanaValueFrom, GrafanaValueSource,
|
|
||||||
};
|
};
|
||||||
use crate::modules::monitoring::kube_prometheus::crd::crd_prometheus_rules::{
|
use crate::modules::monitoring::kube_prometheus::crd::crd_prometheus_rules::{
|
||||||
PrometheusRule, PrometheusRuleSpec, RuleGroup,
|
PrometheusRule, PrometheusRuleSpec, RuleGroup,
|
||||||
@ -40,7 +39,7 @@ use crate::{
|
|||||||
};
|
};
|
||||||
use harmony_types::id::Id;
|
use harmony_types::id::Id;
|
||||||
|
|
||||||
use super::prometheus::PrometheusMonitoring;
|
use super::prometheus::PrometheusApplicationMonitoring;
|
||||||
|
|
||||||
#[derive(Clone, Debug, Serialize)]
|
#[derive(Clone, Debug, Serialize)]
|
||||||
pub struct K8sPrometheusCRDAlertingScore {
|
pub struct K8sPrometheusCRDAlertingScore {
|
||||||
@ -50,7 +49,7 @@ pub struct K8sPrometheusCRDAlertingScore {
|
|||||||
pub prometheus_rules: Vec<RuleGroup>,
|
pub prometheus_rules: Vec<RuleGroup>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T: Topology + K8sclient + PrometheusMonitoring<CRDPrometheus>> Score<T>
|
impl<T: Topology + K8sclient + PrometheusApplicationMonitoring<CRDPrometheus>> Score<T>
|
||||||
for K8sPrometheusCRDAlertingScore
|
for K8sPrometheusCRDAlertingScore
|
||||||
{
|
{
|
||||||
fn create_interpret(&self) -> Box<dyn crate::interpret::Interpret<T>> {
|
fn create_interpret(&self) -> Box<dyn crate::interpret::Interpret<T>> {
|
||||||
@ -76,7 +75,7 @@ pub struct K8sPrometheusCRDAlertingInterpret {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
impl<T: Topology + K8sclient + PrometheusMonitoring<CRDPrometheus>> Interpret<T>
|
impl<T: Topology + K8sclient + PrometheusApplicationMonitoring<CRDPrometheus>> Interpret<T>
|
||||||
for K8sPrometheusCRDAlertingInterpret
|
for K8sPrometheusCRDAlertingInterpret
|
||||||
{
|
{
|
||||||
async fn execute(
|
async fn execute(
|
||||||
@ -467,13 +466,10 @@ impl K8sPrometheusCRDAlertingInterpret {
|
|||||||
match_labels: label.clone(),
|
match_labels: label.clone(),
|
||||||
match_expressions: vec![],
|
match_expressions: vec![],
|
||||||
};
|
};
|
||||||
|
let mut json_data = BTreeMap::new();
|
||||||
|
json_data.insert("timeInterval".to_string(), "5s".to_string());
|
||||||
let namespace = self.sender.namespace.clone();
|
let namespace = self.sender.namespace.clone();
|
||||||
let json_data = GrafanaDatasourceJsonData {
|
|
||||||
time_interval: Some("5s".to_string()),
|
|
||||||
http_header_name1: None,
|
|
||||||
tls_skip_verify: Some(true),
|
|
||||||
oauth_pass_thru: Some(true),
|
|
||||||
};
|
|
||||||
let json = build_default_dashboard(&namespace);
|
let json = build_default_dashboard(&namespace);
|
||||||
|
|
||||||
let graf_data_source = GrafanaDatasource {
|
let graf_data_source = GrafanaDatasource {
|
||||||
@ -499,11 +495,7 @@ impl K8sPrometheusCRDAlertingInterpret {
|
|||||||
"http://prometheus-operated.{}.svc.cluster.local:9090",
|
"http://prometheus-operated.{}.svc.cluster.local:9090",
|
||||||
self.sender.namespace.clone()
|
self.sender.namespace.clone()
|
||||||
),
|
),
|
||||||
secure_json_data: None,
|
|
||||||
is_default: None,
|
|
||||||
editable: None,
|
|
||||||
},
|
},
|
||||||
values_from: None,
|
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -524,9 +516,7 @@ impl K8sPrometheusCRDAlertingInterpret {
|
|||||||
spec: GrafanaDashboardSpec {
|
spec: GrafanaDashboardSpec {
|
||||||
resync_period: Some("30s".to_string()),
|
resync_period: Some("30s".to_string()),
|
||||||
instance_selector: labels.clone(),
|
instance_selector: labels.clone(),
|
||||||
json: Some(json),
|
json,
|
||||||
grafana_com: None,
|
|
||||||
datasources: None,
|
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -9,17 +9,11 @@ use crate::{
|
|||||||
};
|
};
|
||||||
|
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
pub trait PrometheusMonitoring<S: AlertSender> {
|
pub trait PrometheusApplicationMonitoring<S: AlertSender> {
|
||||||
async fn install_prometheus(
|
async fn install_prometheus(
|
||||||
&self,
|
&self,
|
||||||
sender: &S,
|
sender: &S,
|
||||||
inventory: &Inventory,
|
inventory: &Inventory,
|
||||||
receivers: Option<Vec<Box<dyn AlertReceiver<S>>>>,
|
receivers: Option<Vec<Box<dyn AlertReceiver<S>>>>,
|
||||||
) -> Result<PreparationOutcome, PreparationError>;
|
) -> Result<PreparationOutcome, PreparationError>;
|
||||||
|
|
||||||
async fn ensure_prometheus_operator(
|
|
||||||
&self,
|
|
||||||
sender: &S,
|
|
||||||
inventory: &Inventory,
|
|
||||||
) -> Result<PreparationOutcome, PreparationError>;
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -38,7 +38,7 @@ use crate::{
|
|||||||
};
|
};
|
||||||
use harmony_types::id::Id;
|
use harmony_types::id::Id;
|
||||||
|
|
||||||
use super::prometheus::PrometheusMonitoring;
|
use super::prometheus::PrometheusApplicationMonitoring;
|
||||||
|
|
||||||
#[derive(Clone, Debug, Serialize)]
|
#[derive(Clone, Debug, Serialize)]
|
||||||
pub struct RHOBAlertingScore {
|
pub struct RHOBAlertingScore {
|
||||||
@ -48,8 +48,8 @@ pub struct RHOBAlertingScore {
|
|||||||
pub prometheus_rules: Vec<RuleGroup>,
|
pub prometheus_rules: Vec<RuleGroup>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T: Topology + K8sclient + Ingress + PrometheusMonitoring<RHOBObservability>> Score<T>
|
impl<T: Topology + K8sclient + Ingress + PrometheusApplicationMonitoring<RHOBObservability>>
|
||||||
for RHOBAlertingScore
|
Score<T> for RHOBAlertingScore
|
||||||
{
|
{
|
||||||
fn create_interpret(&self) -> Box<dyn crate::interpret::Interpret<T>> {
|
fn create_interpret(&self) -> Box<dyn crate::interpret::Interpret<T>> {
|
||||||
Box::new(RHOBAlertingInterpret {
|
Box::new(RHOBAlertingInterpret {
|
||||||
@ -74,8 +74,8 @@ pub struct RHOBAlertingInterpret {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
impl<T: Topology + K8sclient + Ingress + PrometheusMonitoring<RHOBObservability>> Interpret<T>
|
impl<T: Topology + K8sclient + Ingress + PrometheusApplicationMonitoring<RHOBObservability>>
|
||||||
for RHOBAlertingInterpret
|
Interpret<T> for RHOBAlertingInterpret
|
||||||
{
|
{
|
||||||
async fn execute(
|
async fn execute(
|
||||||
&self,
|
&self,
|
||||||
|
|||||||
@ -433,7 +433,7 @@ pub struct OPNsenseXmlSection {
|
|||||||
#[yaserde(rename = "Interfaces")]
|
#[yaserde(rename = "Interfaces")]
|
||||||
pub interfaces: Option<ConfigInterfaces>,
|
pub interfaces: Option<ConfigInterfaces>,
|
||||||
#[yaserde(rename = "NodeExporter")]
|
#[yaserde(rename = "NodeExporter")]
|
||||||
pub node_exporter: Option<RawXml>,
|
pub node_exporter: Option<NodeExporter>,
|
||||||
#[yaserde(rename = "Kea")]
|
#[yaserde(rename = "Kea")]
|
||||||
pub kea: Option<RawXml>,
|
pub kea: Option<RawXml>,
|
||||||
pub monit: Option<Monit>,
|
pub monit: Option<Monit>,
|
||||||
@ -1595,3 +1595,21 @@ pub struct Ifgroups {
|
|||||||
#[yaserde(attribute = true)]
|
#[yaserde(attribute = true)]
|
||||||
pub version: String,
|
pub version: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Default, PartialEq, Debug, YaSerialize, YaDeserialize)]
|
||||||
|
pub struct NodeExporter {
|
||||||
|
pub enabled: u8,
|
||||||
|
pub listenaddress: Option<MaybeString>,
|
||||||
|
pub listenport: u16,
|
||||||
|
pub cpu: u8,
|
||||||
|
pub exec: u8,
|
||||||
|
pub filesystem: u8,
|
||||||
|
pub loadavg: u8,
|
||||||
|
pub meminfo: u8,
|
||||||
|
pub netdev: u8,
|
||||||
|
pub time: u8,
|
||||||
|
pub devstat: u8,
|
||||||
|
pub interrupts: u8,
|
||||||
|
pub ntp: u8,
|
||||||
|
pub zfs: u8,
|
||||||
|
}
|
||||||
|
|||||||
@ -5,7 +5,8 @@ use crate::{
|
|||||||
error::Error,
|
error::Error,
|
||||||
modules::{
|
modules::{
|
||||||
caddy::CaddyConfig, dhcp_legacy::DhcpConfigLegacyISC, dns::UnboundDnsConfig,
|
caddy::CaddyConfig, dhcp_legacy::DhcpConfigLegacyISC, dns::UnboundDnsConfig,
|
||||||
dnsmasq::DhcpConfigDnsMasq, load_balancer::LoadBalancerConfig, tftp::TftpConfig,
|
dnsmasq::DhcpConfigDnsMasq, load_balancer::LoadBalancerConfig,
|
||||||
|
node_exporter::NodeExporterConfig, tftp::TftpConfig,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
use log::{debug, info, trace, warn};
|
use log::{debug, info, trace, warn};
|
||||||
@ -71,6 +72,10 @@ impl Config {
|
|||||||
LoadBalancerConfig::new(&mut self.opnsense, self.shell.clone())
|
LoadBalancerConfig::new(&mut self.opnsense, self.shell.clone())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn node_exporter(&mut self) -> NodeExporterConfig<'_> {
|
||||||
|
NodeExporterConfig::new(&mut self.opnsense, self.shell.clone())
|
||||||
|
}
|
||||||
|
|
||||||
pub async fn upload_files(&self, source: &str, destination: &str) -> Result<String, Error> {
|
pub async fn upload_files(&self, source: &str, destination: &str) -> Result<String, Error> {
|
||||||
self.shell.upload_folder(source, destination).await
|
self.shell.upload_folder(source, destination).await
|
||||||
}
|
}
|
||||||
|
|||||||
@ -4,4 +4,5 @@ pub mod dhcp_legacy;
|
|||||||
pub mod dns;
|
pub mod dns;
|
||||||
pub mod dnsmasq;
|
pub mod dnsmasq;
|
||||||
pub mod load_balancer;
|
pub mod load_balancer;
|
||||||
|
pub mod node_exporter;
|
||||||
pub mod tftp;
|
pub mod tftp;
|
||||||
|
|||||||
54
opnsense-config/src/modules/node_exporter.rs
Normal file
54
opnsense-config/src/modules/node_exporter.rs
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
use opnsense_config_xml::{NodeExporter, OPNsense};
|
||||||
|
|
||||||
|
use crate::{config::OPNsenseShell, Error};
|
||||||
|
|
||||||
|
pub struct NodeExporterConfig<'a> {
|
||||||
|
opnsense: &'a mut OPNsense,
|
||||||
|
opnsense_shell: Arc<dyn OPNsenseShell>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> NodeExporterConfig<'a> {
|
||||||
|
pub fn new(opnsense: &'a mut OPNsense, opnsense_shell: Arc<dyn OPNsenseShell>) -> Self {
|
||||||
|
Self {
|
||||||
|
opnsense,
|
||||||
|
opnsense_shell,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_full_config(&self) -> &Option<NodeExporter> {
|
||||||
|
&self.opnsense.opnsense.node_exporter
|
||||||
|
}
|
||||||
|
|
||||||
|
fn with_node_exporter<F, R>(&mut self, f: F) -> Result<R, &'static str>
|
||||||
|
where
|
||||||
|
F: FnOnce(&mut NodeExporter) -> R,
|
||||||
|
{
|
||||||
|
match &mut self.opnsense.opnsense.node_exporter.as_mut() {
|
||||||
|
Some(node_exporter) => Ok(f(node_exporter)),
|
||||||
|
None => Err("node exporter is not yet installed"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn enable(&mut self, enabled: bool) -> Result<(), &'static str> {
|
||||||
|
self.with_node_exporter(|node_exporter| node_exporter.enabled = enabled as u8)
|
||||||
|
.map(|_| ())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn reload_restart(&self) -> Result<(), Error> {
|
||||||
|
self.opnsense_shell
|
||||||
|
.exec("configctl node_exporter stop")
|
||||||
|
.await?;
|
||||||
|
self.opnsense_shell
|
||||||
|
.exec("configctl template reload OPNsense/NodeExporter")
|
||||||
|
.await?;
|
||||||
|
self.opnsense_shell
|
||||||
|
.exec("configctl node_exporter configtest")
|
||||||
|
.await?;
|
||||||
|
self.opnsense_shell
|
||||||
|
.exec("configctl node_exporter start")
|
||||||
|
.await?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue
Block a user