diff --git a/harmony/src/modules/helm/chart.rs b/harmony/src/modules/helm/chart.rs index d8e2231..65909d3 100644 --- a/harmony/src/modules/helm/chart.rs +++ b/harmony/src/modules/helm/chart.rs @@ -2,7 +2,7 @@ use crate::data::{Id, Version}; use crate::interpret::{Interpret, InterpretError, InterpretName, InterpretStatus, Outcome}; use crate::inventory::Inventory; use crate::score::Score; -use crate::topology::{HelmCommand, K8sAnywhereTopology, K8sclient, Topology}; +use crate::topology::{HelmCommand, Topology}; use async_trait::async_trait; use helm_wrapper_rs; use helm_wrapper_rs::blocking::{DefaultHelmExecutor, HelmExecutor}; @@ -10,14 +10,12 @@ use log::{debug, info, warn}; pub use non_blank_string_rs::NonBlankString; use serde::Serialize; use std::collections::HashMap; -use std::path::{Path, PathBuf}; +use std::path::Path; use std::process::{Command, Output, Stdio}; use std::str::FromStr; use temp_file::TempFile; use url::Url; -use super::command::{self, HelmChart, HelmGlobals}; - #[derive(Debug, Clone, Serialize)] pub struct HelmRepository { name: String, @@ -44,9 +42,9 @@ pub struct HelmChartScore { pub values_yaml: Option, pub create_namespace: bool, - /// Whether to run `helm upgrade --install` under the hood or only install when not present + /// Wether to run `helm upgrade --install` under the hood or only install when not present pub install_only: bool, - pub repo_url: Option, + pub repository: Option, } impl Score for HelmChartScore { @@ -106,6 +104,12 @@ impl HelmChartInterpret { fn run_helm_command(args: &[&str]) -> Result { let command_str = format!("helm {}", args.join(" ")); +<<<<<<< HEAD +||||||| parent of 54062fa (make separate modules) +impl HelmChartInterpret {} +======= + debug!("Got KUBECONFIG: `{}`", std::env::var("KUBECONFIG").unwrap()); +>>>>>>> 54062fa (make separate modules) debug!("Running Helm command: `{}`", command_str); let output = Command::new("helm") @@ -142,7 +146,7 @@ impl Interpret for HelmChartInterpret { async fn execute( &self, _inventory: &Inventory, - topology: &T, + _topology: &T, ) -> Result { let ns = self .score @@ -151,22 +155,87 @@ impl Interpret for HelmChartInterpret { .unwrap_or_else(|| todo!("Get namespace from active kubernetes cluster")); let tf: TempFile; - let yaml_path: Option = match self.score.values_yaml.as_ref() { + let yaml_path: Option<&Path> = match self.score.values_yaml.as_ref() { Some(yaml_str) => { tf = temp_file::with_contents(yaml_str.as_bytes()); - Some(PathBuf::from(tf.path())) + Some(tf.path()) } None => None, }; self.add_repo()?; +<<<<<<< HEAD let helm_executor = DefaultHelmExecutor::new(); +||||||| parent of 54062fa (make separate modules) + // let mut helm_options = Vec::new(); + // if self.score.create_namespace { + // helm_options.push(NonBlankString::from_str("--create-namespace").unwrap()); + // } +======= + let helm_executor = DefaultHelmExecutor::new_with_opts( + &NonBlankString::from_str("helm").unwrap(), + None, + 900, + false, + false, + ); +>>>>>>> 54062fa (make separate modules) let mut helm_options = Vec::new(); if self.score.create_namespace { helm_options.push(NonBlankString::from_str("--create-namespace").unwrap()); } +<<<<<<< HEAD + + if self.score.install_only { + let chart_list = match helm_executor.list(Some(ns)) { + Ok(charts) => charts, + Err(e) => { + return Err(InterpretError::new(format!( + "Failed to list scores in namespace {:?} because of error : {}", + self.score.namespace, e + ))); + } + }; + + if chart_list + .iter() + .any(|item| item.name == self.score.release_name.to_string()) + { + info!( + "Release '{}' already exists in namespace '{}'. Skipping installation as install_only is true.", + self.score.release_name, ns + ); + + return Ok(Outcome::new( + InterpretStatus::SUCCESS, + format!( + "Helm Chart '{}' already installed to namespace {ns} and install_only=true", + self.score.release_name + ), + )); + } else { + info!( + "Release '{}' not found in namespace '{}'. Proceeding with installation.", + self.score.release_name, ns + ); + } + } + + let res = helm_executor.install_or_upgrade( + &ns, + &self.score.release_name, + &self.score.chart_name, + self.score.chart_version.as_ref(), + self.score.values_overrides.as_ref(), + yaml_path, + Some(&helm_options), + ); +||||||| parent of 54062fa (make separate modules) + let res = helm_executor.generate(); +======= +>>>>>>> 54062fa (make separate modules) if self.score.install_only { let chart_list = match helm_executor.list(Some(ns)) { @@ -213,21 +282,29 @@ impl Interpret for HelmChartInterpret { Some(&helm_options), ); - let output = match res { - Ok(output) => output, + let status = match res { + Ok(status) => status, Err(err) => return Err(InterpretError::new(err.to_string())), }; - // TODO: Get k8s client and execute YAML from Helm using it - // let client = topology.get_k8s_client(); - // match client.apply_yaml(output) { - // Ok(_) => return Ok(Outcome::success("Helm chart deployed".to_string())), - // Err(e) => return Err(InterpretError::new(e)), - // } - - Ok(Outcome::success("Helm chart deployed".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!() } diff --git a/harmony/src/modules/helm/command.rs b/harmony/src/modules/helm/command.rs index eb50816..ffcdff5 100644 --- a/harmony/src/modules/helm/command.rs +++ b/harmony/src/modules/helm/command.rs @@ -1,5 +1,7 @@ +use async_trait::async_trait; use log::debug; use non_blank_string_rs::NonBlankString; +use serde::Serialize; use std::collections::HashMap; use std::env::temp_dir; use std::ffi::OsStr; @@ -7,6 +9,15 @@ use std::io::ErrorKind; use std::path::{Path, PathBuf}; use std::process::{Command, Output}; use temp_dir::{self, TempDir}; +use temp_file::TempFile; + +use serde::Serialize; + +use crate::data::{Id, Version}; +use crate::interpret::{Interpret, InterpretError, InterpretName, InterpretStatus, Outcome}; +use crate::inventory::Inventory; +use crate::score::Score; +use crate::topology::{HelmCommand, K8sclient, Topology}; #[derive(Clone)] pub struct HelmCommandExecutor { @@ -26,13 +37,13 @@ pub struct HelmGlobals { pub config_home: Option, } -#[derive(Clone)] +#[derive(Debug, Clone, Serialize)] pub struct HelmChart { pub name: String, - pub version: Option, - pub repo: Option, - pub release_name: Option, - pub namespace: Option, + pub version: Option, + pub repo: Option, + pub release_name: Option, + pub namespace: Option, pub additional_values_files: Vec, pub values_file: Option, pub values_inline: Option, @@ -123,6 +134,17 @@ impl HelmCommandExecutor { None => PathBuf::from(TempDir::new()?.path()), }; + match self.chart.values_inline { + Some(yaml_str) => { + let tf: TempFile; + tf = temp_file::with_contents(yaml_str.as_bytes()); + self.chart + .additional_values_files + .push(PathBuf::from(tf.path())); + } + None => (), + }; + self.env.insert( "HELM_CONFIG_HOME".to_string(), config_home.to_str().unwrap().to_string(), @@ -212,6 +234,11 @@ impl HelmChart { args.push(f.to_str().unwrap().to_string()); } + for f in self.additional_values_files { + args.push("-f".to_string()); + args.push(f.to_str().unwrap().to_string()); + } + if let Some(vv) = self.api_versions { for v in vv { args.push("--api-versions".to_string()); @@ -251,3 +278,104 @@ impl HelmChart { args } } + +#[derive(Debug, Clone, Serialize)] +pub struct HelmChartScoreV2 { + pub chart: HelmChart, +} + +impl Score for HelmChartScoreV2 { + fn create_interpret(&self) -> Box> { + Box::new(HelmChartInterpretV2 { + score: self.clone(), + }) + } + + fn name(&self) -> String { + format!( + "{} {} HelmChartScoreV2", + self.chart + .release_name + .clone() + .unwrap_or("Unknown".to_string()), + self.chart.name + ) + } +} + +#[derive(Debug, Serialize)] +pub struct HelmChartInterpretV2 { + pub score: HelmChartScoreV2, +} +impl HelmChartInterpretV2 {} + +#[async_trait] +impl Interpret for HelmChartInterpretV2 { + async fn execute( + &self, + _inventory: &Inventory, + _topology: &T, + ) -> Result { + let ns = self + .score + .chart + .namespace + .as_ref() + .unwrap_or_else(|| todo!("Get namespace from active kubernetes cluster")); + + let helm_executor = HelmCommandExecutor { + env: HashMap::new(), + path: None, + args: vec![], + api_versions: None, + kube_version: "v1.33.0".to_string(), + debug: Some(false), + globals: HelmGlobals { + chart_home: None, + config_home: None, + }, + chart: self.score.chart.clone(), + }; + + // let mut helm_options = Vec::new(); + // if self.score.create_namespace { + // helm_options.push(NonBlankString::from_str("--create-namespace").unwrap()); + // } + + let res = helm_executor.generate(); + + let output = match res { + Ok(output) => output, + Err(err) => return Err(InterpretError::new(err.to_string())), + }; + + // TODO: implement actually applying the YAML from the templating in the generate function to a k8s cluster, having trouble passing in straight YAML into the k8s client + + // let k8s_resource = k8s_openapi::serde_json::from_str(output.as_str()).unwrap(); + + // let client = topology + // .k8s_client() + // .await + // .expect("Environment should provide enough information to instanciate a client") + // .apply_namespaced(&vec![output], Some(ns.to_string().as_str())); + // match client.apply_yaml(output) { + // Ok(_) => return Ok(Outcome::success("Helm chart deployed".to_string())), + // Err(e) => return Err(InterpretError::new(e)), + // } + + Ok(Outcome::success("Helm chart deployed".to_string())) + } + + fn get_name(&self) -> InterpretName { + todo!() + } + fn get_version(&self) -> Version { + todo!() + } + fn get_status(&self) -> InterpretStatus { + todo!() + } + fn get_children(&self) -> Vec { + todo!() + } +}