From b7dd3b50d95daf48f1cf0e83c66c114a9ba61f09 Mon Sep 17 00:00:00 2001 From: Taha Hawa Date: Wed, 23 Apr 2025 14:09:20 -0400 Subject: [PATCH] initial helm support/scaffolding --- Cargo.lock | 24 +++++++ examples/cli/src/main.rs | 1 + harmony/Cargo.toml | 2 + harmony/src/domain/topology/helm_command.rs | 1 + harmony/src/domain/topology/mod.rs | 3 + harmony/src/infra/opnsense/load_balancer.rs | 34 ++++++---- harmony/src/modules/helm/mod.rs | 2 +- harmony/src/modules/helm/resource.rs | 72 ++++++++++++++------- 8 files changed, 102 insertions(+), 37 deletions(-) create mode 100644 harmony/src/domain/topology/helm_command.rs diff --git a/Cargo.lock b/Cargo.lock index 8020fb8..e4688ec 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1343,12 +1343,14 @@ dependencies = [ "env_logger", "harmony_macros", "harmony_types", + "helm-wrapper-rs", "http 1.3.1", "inquire", "k8s-openapi", "kube", "libredfish", "log", + "non-blank-string-rs", "opnsense-config", "opnsense-config-xml", "reqwest 0.11.27", @@ -1453,6 +1455,19 @@ version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" +[[package]] +name = "helm-wrapper-rs" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc9253a7bbf4ba8ff6052d5ab7ddc6e2ca17cd8481d15636fb9f64611653880c" +dependencies = [ + "log", + "non-blank-string-rs", + "serde", + "serde_json", + "thiserror 1.0.69", +] + [[package]] name = "hermit-abi" version = "0.3.9" @@ -2328,6 +2343,15 @@ dependencies = [ "unicode-segmentation", ] +[[package]] +name = "non-blank-string-rs" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a05a02248b2e70f1943a59af287a28df78ef9adfc72ee5dc443381d3a1a1a5c" +dependencies = [ + "serde", +] + [[package]] name = "num-bigint" version = "0.4.6" diff --git a/examples/cli/src/main.rs b/examples/cli/src/main.rs index 8689b02..a6d697d 100644 --- a/examples/cli/src/main.rs +++ b/examples/cli/src/main.rs @@ -19,6 +19,7 @@ async fn main() { harmony_cli::init(maestro, None).await.unwrap(); } +#[allow(unused)] use assert_cmd::Command; #[test] diff --git a/harmony/Cargo.toml b/harmony/Cargo.toml index 8b26edb..aae188d 100644 --- a/harmony/Cargo.toml +++ b/harmony/Cargo.toml @@ -31,3 +31,5 @@ serde_yaml = { workspace = true } http = { workspace = true } serde-value = { workspace = true } inquire.workspace = true +helm-wrapper-rs = "0.4.0" +non-blank-string-rs = "1.0.4" diff --git a/harmony/src/domain/topology/helm_command.rs b/harmony/src/domain/topology/helm_command.rs new file mode 100644 index 0000000..f3dd697 --- /dev/null +++ b/harmony/src/domain/topology/helm_command.rs @@ -0,0 +1 @@ +pub trait HelmCommand {} diff --git a/harmony/src/domain/topology/mod.rs b/harmony/src/domain/topology/mod.rs index e792227..3d773ff 100644 --- a/harmony/src/domain/topology/mod.rs +++ b/harmony/src/domain/topology/mod.rs @@ -20,6 +20,9 @@ pub use network::*; use serde::Serialize; pub use tftp::*; +mod helm_command; +pub use helm_command::*; + use std::net::IpAddr; use super::interpret::{InterpretError, Outcome}; diff --git a/harmony/src/infra/opnsense/load_balancer.rs b/harmony/src/infra/opnsense/load_balancer.rs index dd32a03..cae414a 100644 --- a/harmony/src/infra/opnsense/load_balancer.rs +++ b/harmony/src/infra/opnsense/load_balancer.rs @@ -370,10 +370,13 @@ mod tests { let result = get_servers_for_backend(&backend, &haproxy); // Check the result - assert_eq!(result, vec![BackendServer { - address: "192.168.1.1".to_string(), - port: 80, - },]); + assert_eq!( + result, + vec![BackendServer { + address: "192.168.1.1".to_string(), + port: 80, + },] + ); } #[test] fn test_get_servers_for_backend_no_linked_servers() { @@ -430,15 +433,18 @@ mod tests { // Call the function let result = get_servers_for_backend(&backend, &haproxy); // Check the result - assert_eq!(result, vec![ - BackendServer { - address: "some-hostname.test.mcd".to_string(), - port: 80, - }, - BackendServer { - address: "192.168.1.2".to_string(), - port: 8080, - }, - ]); + assert_eq!( + result, + vec![ + BackendServer { + address: "some-hostname.test.mcd".to_string(), + port: 80, + }, + BackendServer { + address: "192.168.1.2".to_string(), + port: 8080, + }, + ] + ); } } diff --git a/harmony/src/modules/helm/mod.rs b/harmony/src/modules/helm/mod.rs index dd231cb..c6bee05 100644 --- a/harmony/src/modules/helm/mod.rs +++ b/harmony/src/modules/helm/mod.rs @@ -1 +1 @@ -pub mod resource; \ No newline at end of file +pub mod resource; diff --git a/harmony/src/modules/helm/resource.rs b/harmony/src/modules/helm/resource.rs index b23c4a4..737e15e 100644 --- a/harmony/src/modules/helm/resource.rs +++ b/harmony/src/modules/helm/resource.rs @@ -2,51 +2,79 @@ use crate::data::{Id, Version}; use crate::interpret::{Interpret, InterpretError, InterpretName, InterpretStatus, Outcome}; use crate::inventory::Inventory; use crate::score::Score; -use crate::topology::Topology; +use crate::topology::{HelmCommand, Topology}; use async_trait::async_trait; use helm_wrapper_rs; +use helm_wrapper_rs::blocking::{DefaultHelmExecutor, HelmExecutor}; +use non_blank_string_rs::NonBlankString; use serde::Serialize; +use serde::de::DeserializeOwned; +use std::collections::HashMap; +use std::path::PathBuf; #[derive(Debug, Clone, Serialize)] -pub struct HelmResourceScore { - pub namespace: String, - pub release_name: String, - pub chart_name: String, - pub chart_version: String, - pub values_overrides: String, - pub values_file: String, - pub helm_options: String, +pub struct HelmChartScore { + pub namespace: NonBlankString, + pub release_name: NonBlankString, + pub chart_name: NonBlankString, + pub chart_version: NonBlankString, + pub values_overrides: Option>, } -impl - Score for HelmResourceScore -{ +impl Score for HelmChartScore { fn create_interpret(&self) -> Box> { todo!() } fn name(&self) -> String { - format!("{}, {}", self.release_name, self.chart_name) + "HelmChartScore".to_string() } } #[derive(Debug, Serialize)] -pub struct HelmResourceInterpret { - pub score: HelmResourceScore, +pub struct HelmChartInterpret { + pub score: HelmChartScore, } #[async_trait] -impl Interpret - for HelmResourceInterpret -{ +impl Interpret for HelmChartInterpret { async fn execute( &self, _inventory: &Inventory, - topology: &T, + _topology: &T, ) -> Result { - Ok(Outcome::success( - "Successfully applied resource".to_string(), - )) + let helm_executor = DefaultHelmExecutor::new(); + let res = helm_executor.install_or_upgrade( + &self.score.namespace, + &self.score.release_name, + &self.score.chart_name, + Some(&self.score.chart_version), + self.score.values_overrides.as_ref(), + None, + None, + ); + let status = match res { + Ok(status) => status, + Err(err) => return Err(InterpretError::new(err.to_string())), + }; + + match status { + helm_wrapper_rs::HelmDeployStatus::Deployed => Ok(Outcome::new( + InterpretStatus::SUCCESS, + "Helm Chart deployed".to_string(), + )), + helm_wrapper_rs::HelmDeployStatus::PendingInstall => Ok(Outcome::new( + InterpretStatus::RUNNING, + "Helm Chart Pending install".to_string(), + )), + helm_wrapper_rs::HelmDeployStatus::PendingUpgrade => Ok(Outcome::new( + InterpretStatus::RUNNING, + "Helm Chart pending upgrade".to_string(), + )), + helm_wrapper_rs::HelmDeployStatus::Failed => Err(InterpretError::new( + "Failed to install helm chart".to_string(), + )), + } } fn get_name(&self) -> InterpretName { todo!()