Merge pull request 'feat: add cert-manager module and helm repo support' (#31) from feat/awsOKD into master
Reviewed-on: https://git.nationtech.io/NationTech/harmony/pulls/31
This commit is contained in:
		
						commit
						e5ddd296db
					
				| @ -1,6 +1,11 @@ | |||||||
| use derive_new::new; | use derive_new::new; | ||||||
| use k8s_openapi::NamespaceResourceScope; | 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; | use serde::de::DeserializeOwned; | ||||||
| 
 | 
 | ||||||
| #[derive(new)] | #[derive(new)] | ||||||
| @ -57,4 +62,22 @@ impl K8sClient { | |||||||
|         } |         } | ||||||
|         todo!("") |         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 { | struct K8sState { | ||||||
|     client: Arc<K8sClient>, |     client: Arc<K8sClient>, | ||||||
|     _source: K8sSource, |     source: K8sSource, | ||||||
|     message: String, |     message: String, | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| enum K8sSource { | enum K8sSource { | ||||||
|     LocalK3d, |     LocalK3d, | ||||||
|  |     Kubeconfig, | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| pub struct K8sAnywhereTopology { | pub struct K8sAnywhereTopology { | ||||||
| @ -75,7 +76,7 @@ impl K8sAnywhereTopology { | |||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     async fn try_load_kubeconfig(&self, path: &str) -> Option<K8sClient> { |     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 { |     fn get_k3d_installation_score(&self) -> K3DInstallationScore { | ||||||
| @ -109,8 +110,18 @@ impl K8sAnywhereTopology { | |||||||
| 
 | 
 | ||||||
|         if let Some(kubeconfig) = k8s_anywhere_config.kubeconfig { |         if let Some(kubeconfig) = k8s_anywhere_config.kubeconfig { | ||||||
|             match self.try_load_kubeconfig(&kubeconfig).await { |             match self.try_load_kubeconfig(&kubeconfig).await { | ||||||
|                 Some(_client) => todo!(), |                 Some(client) => { | ||||||
|                 None => todo!(), |                     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 { |         let state = match k3d.get_client().await { | ||||||
|             Ok(client) => K8sState { |             Ok(client) => K8sState { | ||||||
|                 client: Arc::new(K8sClient::new(client)), |                 client: Arc::new(K8sClient::new(client)), | ||||||
|                 _source: K8sSource::LocalK3d, |                 source: K8sSource::LocalK3d, | ||||||
|                 message: "Successfully installed K3D cluster and acquired client".to_string(), |                 message: "Successfully installed K3D cluster and acquired client".to_string(), | ||||||
|             }, |             }, | ||||||
|             Err(_) => todo!(), |             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 async_trait::async_trait; | ||||||
| use helm_wrapper_rs; | use helm_wrapper_rs; | ||||||
| use helm_wrapper_rs::blocking::{DefaultHelmExecutor, HelmExecutor}; | use helm_wrapper_rs::blocking::{DefaultHelmExecutor, HelmExecutor}; | ||||||
| use log::info; | use log::{debug, error, info, warn}; | ||||||
| pub use non_blank_string_rs::NonBlankString; | pub use non_blank_string_rs::NonBlankString; | ||||||
| use serde::Serialize; | use serde::Serialize; | ||||||
| use std::collections::HashMap; | use std::collections::HashMap; | ||||||
| use std::path::Path; | use std::path::Path; | ||||||
|  | use std::process::{Command, Output, Stdio}; | ||||||
| use std::str::FromStr; | use std::str::FromStr; | ||||||
| use temp_file::TempFile; | 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)] | #[derive(Debug, Clone, Serialize)] | ||||||
| pub struct HelmChartScore { | 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
 |     /// Wether to run `helm upgrade --install` under the hood or only install when not present
 | ||||||
|     pub install_only: bool, |     pub install_only: bool, | ||||||
|  |     pub repository: Option<HelmRepository>, | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| impl<T: Topology + HelmCommand> Score<T> for HelmChartScore { | impl<T: Topology + HelmCommand> Score<T> for HelmChartScore { | ||||||
| @ -44,6 +63,77 @@ impl<T: Topology + HelmCommand> Score<T> for HelmChartScore { | |||||||
| pub struct HelmChartInterpret { | pub struct HelmChartInterpret { | ||||||
|     pub score: HelmChartScore, |     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] | #[async_trait] | ||||||
| impl<T: Topology + HelmCommand> Interpret<T> for HelmChartInterpret { | impl<T: Topology + HelmCommand> Interpret<T> for HelmChartInterpret { | ||||||
| @ -67,6 +157,8 @@ impl<T: Topology + HelmCommand> Interpret<T> for HelmChartInterpret { | |||||||
|             None => None, |             None => None, | ||||||
|         }; |         }; | ||||||
| 
 | 
 | ||||||
|  |         self.add_repo()?; | ||||||
|  | 
 | ||||||
|         let helm_executor = DefaultHelmExecutor::new(); |         let helm_executor = DefaultHelmExecutor::new(); | ||||||
| 
 | 
 | ||||||
|         let mut helm_options = Vec::new(); |         let mut helm_options = Vec::new(); | ||||||
|  | |||||||
| @ -179,6 +179,7 @@ impl LAMPInterpret { | |||||||
|             create_namespace: true, |             create_namespace: true, | ||||||
|             install_only: true, |             install_only: true, | ||||||
|             values_yaml: None, |             values_yaml: None, | ||||||
|  |             repository: None, | ||||||
|         }; |         }; | ||||||
| 
 | 
 | ||||||
|         score.create_interpret().execute(inventory, topology).await |         score.create_interpret().execute(inventory, topology).await | ||||||
|  | |||||||
| @ -1,3 +1,4 @@ | |||||||
|  | pub mod cert_manager; | ||||||
| pub mod dhcp; | pub mod dhcp; | ||||||
| pub mod dns; | pub mod dns; | ||||||
| pub mod dummy; | pub mod dummy; | ||||||
|  | |||||||
		Loading…
	
		Reference in New Issue
	
	Block a user