feat: add cert-manager module and helm repo support #31
| @ -1,6 +1,11 @@ | ||||
| use derive_new::new; | ||||
| use k8s_openapi::NamespaceResourceScope; | ||||
| use kube::{Api, Client, Error, Resource, api::PostParams}; | ||||
| use kube::{ | ||||
|     Api, Client, Config, Error, Resource, | ||||
|     api::PostParams, | ||||
|     config::{KubeConfigOptions, Kubeconfig}, | ||||
| }; | ||||
| use log::error; | ||||
| use serde::de::DeserializeOwned; | ||||
| 
 | ||||
| #[derive(new)] | ||||
| @ -57,4 +62,22 @@ impl K8sClient { | ||||
|         } | ||||
|         todo!("") | ||||
|     } | ||||
| 
 | ||||
|     pub(crate) async fn from_kubeconfig(path: &str) -> Option<K8sClient> { | ||||
|         let k = match Kubeconfig::read_from(path) { | ||||
|             Ok(k) => k, | ||||
|             Err(e) => { | ||||
|                 error!("Failed to load kubeconfig from {path} : {e}"); | ||||
|                 return None; | ||||
|             } | ||||
|         }; | ||||
|         Some(K8sClient::new( | ||||
|             Client::try_from( | ||||
|                 Config::from_custom_kubeconfig(k, &KubeConfigOptions::default()) | ||||
|                     .await | ||||
|                     .unwrap(), | ||||
|             ) | ||||
|             .unwrap(), | ||||
|         )) | ||||
|     } | ||||
| } | ||||
|  | ||||
| @ -17,12 +17,13 @@ use super::{HelmCommand, K8sclient, Topology, k8s::K8sClient}; | ||||
| 
 | ||||
| struct K8sState { | ||||
|     client: Arc<K8sClient>, | ||||
|     _source: K8sSource, | ||||
|     source: K8sSource, | ||||
|     message: String, | ||||
| } | ||||
| 
 | ||||
| enum K8sSource { | ||||
|     LocalK3d, | ||||
|     Kubeconfig, | ||||
| } | ||||
| 
 | ||||
| pub struct K8sAnywhereTopology { | ||||
| @ -75,7 +76,7 @@ impl K8sAnywhereTopology { | ||||
|     } | ||||
| 
 | ||||
|     async fn try_load_kubeconfig(&self, path: &str) -> Option<K8sClient> { | ||||
|         todo!("Use kube-rs to load kubeconfig at path {path}"); | ||||
|         K8sClient::from_kubeconfig(path).await | ||||
|     } | ||||
| 
 | ||||
|     fn get_k3d_installation_score(&self) -> K3DInstallationScore { | ||||
| @ -109,8 +110,18 @@ impl K8sAnywhereTopology { | ||||
| 
 | ||||
|         if let Some(kubeconfig) = k8s_anywhere_config.kubeconfig { | ||||
|             match self.try_load_kubeconfig(&kubeconfig).await { | ||||
|                 Some(_client) => todo!(), | ||||
|                 None => todo!(), | ||||
|                 Some(client) => { | ||||
|                     return Ok(Some(K8sState { | ||||
|                         client: Arc::new(client), | ||||
|                         source: K8sSource::Kubeconfig, | ||||
|                         message: format!("Loaded k8s client from kubeconfig {kubeconfig}"), | ||||
|                     })); | ||||
|                 } | ||||
|                 None => { | ||||
|                     return Err(InterpretError::new(format!( | ||||
|                         "Failed to load kubeconfig from {kubeconfig}" | ||||
|                     ))); | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
| @ -142,7 +153,7 @@ impl K8sAnywhereTopology { | ||||
|         let state = match k3d.get_client().await { | ||||
|             Ok(client) => K8sState { | ||||
|                 client: Arc::new(K8sClient::new(client)), | ||||
|                 _source: K8sSource::LocalK3d, | ||||
|                 source: K8sSource::LocalK3d, | ||||
|                 message: "Successfully installed K3D cluster and acquired client".to_string(), | ||||
|             }, | ||||
|             Err(_) => todo!(), | ||||
|  | ||||
							
								
								
									
										46
									
								
								harmony/src/modules/cert_manager/helm.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										46
									
								
								harmony/src/modules/cert_manager/helm.rs
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,46 @@ | ||||
| use std::{collections::HashMap, str::FromStr}; | ||||
| 
 | ||||
| use non_blank_string_rs::NonBlankString; | ||||
| use serde::Serialize; | ||||
| use url::Url; | ||||
| 
 | ||||
| use crate::{ | ||||
|     modules::helm::chart::{HelmChartScore, HelmRepository}, | ||||
|     score::Score, | ||||
|     topology::{HelmCommand, Topology}, | ||||
| }; | ||||
| 
 | ||||
| #[derive(Debug, Serialize, Clone)] | ||||
| pub struct CertManagerHelmScore {} | ||||
| 
 | ||||
| impl<T: Topology + HelmCommand> Score<T> for CertManagerHelmScore { | ||||
|     fn create_interpret(&self) -> Box<dyn crate::interpret::Interpret<T>> { | ||||
|         let mut values_overrides = HashMap::new(); | ||||
|         values_overrides.insert( | ||||
|             NonBlankString::from_str("crds.enabled").unwrap(), | ||||
|             "true".to_string(), | ||||
|         ); | ||||
|         let values_overrides = Some(values_overrides); | ||||
| 
 | ||||
|         HelmChartScore { | ||||
|             namespace: Some(NonBlankString::from_str("cert-manager").unwrap()), | ||||
|             release_name: NonBlankString::from_str("cert-manager").unwrap(), | ||||
|             chart_name: NonBlankString::from_str("jetstack/cert-manager").unwrap(), | ||||
|             chart_version: None, | ||||
|             values_overrides, | ||||
|             values_yaml: None, | ||||
|             create_namespace: true, | ||||
|             install_only: true, | ||||
|             repository: Some(HelmRepository::new( | ||||
|                 "jetstack".to_string(), | ||||
|                 Url::parse("https://charts.jetstack.io").unwrap(), | ||||
|                 true, | ||||
|             )), | ||||
|         } | ||||
|         .create_interpret() | ||||
|     } | ||||
| 
 | ||||
|     fn name(&self) -> String { | ||||
|         format!("CertManagerHelmScore") | ||||
|     } | ||||
| } | ||||
							
								
								
									
										2
									
								
								harmony/src/modules/cert_manager/mod.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										2
									
								
								harmony/src/modules/cert_manager/mod.rs
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,2 @@ | ||||
| mod helm; | ||||
| pub use helm::*; | ||||
| @ -6,13 +6,31 @@ use crate::topology::{HelmCommand, Topology}; | ||||
| use async_trait::async_trait; | ||||
| use helm_wrapper_rs; | ||||
| use helm_wrapper_rs::blocking::{DefaultHelmExecutor, HelmExecutor}; | ||||
| use log::info; | ||||
| use log::{debug, error, info, warn}; | ||||
| pub use non_blank_string_rs::NonBlankString; | ||||
| use serde::Serialize; | ||||
| use std::collections::HashMap; | ||||
| use std::path::Path; | ||||
| use std::process::{Command, Output, Stdio}; | ||||
| use std::str::FromStr; | ||||
| use temp_file::TempFile; | ||||
| use url::Url; | ||||
| 
 | ||||
| #[derive(Debug, Clone, Serialize)] | ||||
| pub struct HelmRepository { | ||||
|     name: String, | ||||
|     url: Url, | ||||
|     force_update: bool, | ||||
| } | ||||
| impl HelmRepository { | ||||
|     pub(crate) fn new(name: String, url: Url, force_update: bool) -> Self { | ||||
|         Self { | ||||
|             name, | ||||
|             url, | ||||
|             force_update, | ||||
|         } | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| #[derive(Debug, Clone, Serialize)] | ||||
| pub struct HelmChartScore { | ||||
| @ -26,6 +44,7 @@ pub struct HelmChartScore { | ||||
| 
 | ||||
|     /// Wether to run `helm upgrade --install` under the hood or only install when not present
 | ||||
|     pub install_only: bool, | ||||
|     pub repository: Option<HelmRepository>, | ||||
| } | ||||
| 
 | ||||
| impl<T: Topology + HelmCommand> Score<T> for HelmChartScore { | ||||
| @ -44,6 +63,77 @@ impl<T: Topology + HelmCommand> Score<T> for HelmChartScore { | ||||
| pub struct HelmChartInterpret { | ||||
|     pub score: HelmChartScore, | ||||
| } | ||||
| impl HelmChartInterpret { | ||||
|     fn add_repo(&self) -> Result<(), InterpretError> { | ||||
|  | ||||
|         let repo = match &self.score.repository { | ||||
|             Some(repo) => repo, | ||||
|             None => { | ||||
|                 info!("No Helm repository specified in the score. Skipping repository setup."); | ||||
|                 return Ok(()); | ||||
|             } | ||||
|         }; | ||||
|         info!( | ||||
|             "Ensuring Helm repository exists: Name='{}', URL='{}', ForceUpdate={}", | ||||
|             repo.name, repo.url, repo.force_update | ||||
|         ); | ||||
| 
 | ||||
|         let mut add_args = vec!["repo", "add", &repo.name, repo.url.as_str()]; | ||||
|         if repo.force_update { | ||||
|             add_args.push("--force-update"); | ||||
|         } | ||||
| 
 | ||||
|         let add_output = run_helm_command(&add_args)?; | ||||
|         let full_output = format!( | ||||
|             "{}\n{}", | ||||
|             String::from_utf8_lossy(&add_output.stdout), | ||||
|             String::from_utf8_lossy(&add_output.stderr) | ||||
|         ); | ||||
| 
 | ||||
|         match add_output.status.success() { | ||||
|             true => { | ||||
|                 return Ok(()); | ||||
|             } | ||||
|             false => { | ||||
|                 return Err(InterpretError::new(format!( | ||||
|                     "Failed to add helm repository!\n{full_output}" | ||||
|                 ))); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| fn run_helm_command(args: &[&str]) -> Result<Output, InterpretError> { | ||||
|     let command_str = format!("helm {}", args.join(" ")); | ||||
|     debug!("Running Helm command: `{}`", command_str); | ||||
| 
 | ||||
|     let output = Command::new("helm") | ||||
|         .args(args) | ||||
|         .stdout(Stdio::piped()) | ||||
|         .stderr(Stdio::piped()) | ||||
|         .output() | ||||
|         .map_err(|e| { | ||||
|             InterpretError::new(format!( | ||||
|                 "Failed to execute helm command '{}': {}. Is helm installed and in PATH?", | ||||
|                 command_str, e | ||||
|             )) | ||||
|         })?; | ||||
| 
 | ||||
|     if !output.status.success() { | ||||
|         let stdout = String::from_utf8_lossy(&output.stdout); | ||||
|         let stderr = String::from_utf8_lossy(&output.stderr); | ||||
|         warn!( | ||||
|             "Helm command `{}` failed with status: {}\nStdout:\n{}\nStderr:\n{}", | ||||
|             command_str, output.status, stdout, stderr | ||||
|         ); | ||||
|     } else { | ||||
|         debug!( | ||||
|             "Helm command `{}` finished successfully. Status: {}", | ||||
|             command_str, output.status | ||||
|         ); | ||||
|     } | ||||
| 
 | ||||
|     Ok(output) | ||||
| } | ||||
| 
 | ||||
| #[async_trait] | ||||
| impl<T: Topology + HelmCommand> Interpret<T> for HelmChartInterpret { | ||||
| @ -67,6 +157,8 @@ impl<T: Topology + HelmCommand> Interpret<T> for HelmChartInterpret { | ||||
|             None => None, | ||||
|         }; | ||||
| 
 | ||||
|         self.add_repo()?; | ||||
| 
 | ||||
|         let helm_executor = DefaultHelmExecutor::new(); | ||||
| 
 | ||||
|         let mut helm_options = Vec::new(); | ||||
|  | ||||
| @ -179,6 +179,7 @@ impl LAMPInterpret { | ||||
|             create_namespace: true, | ||||
|             install_only: true, | ||||
|             values_yaml: None, | ||||
|             repository: None, | ||||
|         }; | ||||
| 
 | ||||
|         score.create_interpret().execute(inventory, topology).await | ||||
|  | ||||
| @ -1,3 +1,4 @@ | ||||
| pub mod cert_manager; | ||||
| pub mod dhcp; | ||||
| pub mod dns; | ||||
| pub mod dummy; | ||||
|  | ||||
		Loading…
	
		Reference in New Issue
	
	Block a user
	
https://github.com/kubernetes-sigs/kustomize/blob/master/plugin/builtin/helmchartinflationgenerator/HelmChartInflationGenerator.go#L326
Could be simplified, without needing to manage repos through
helm repo addetc