Merge remote-tracking branch 'origin/master' into fix/demo
	
		
			
	
		
	
	
		
	
		
			All checks were successful
		
		
	
	
		
			
				
	
				Run Check Script / check (pull_request) Successful in 1m2s
				
			
		
		
	
	
				
					
				
			
		
			All checks were successful
		
		
	
	Run Check Script / check (pull_request) Successful in 1m2s
				
			This commit is contained in:
		
						commit
						4df451bc41
					
				| @ -34,6 +34,7 @@ pub enum InterpretName { | |||||||
|     CephClusterHealth, |     CephClusterHealth, | ||||||
|     Custom(&'static str), |     Custom(&'static str), | ||||||
|     RHOBAlerting, |     RHOBAlerting, | ||||||
|  |     K8sIngress, | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| impl std::fmt::Display for InterpretName { | impl std::fmt::Display for InterpretName { | ||||||
| @ -64,6 +65,7 @@ impl std::fmt::Display for InterpretName { | |||||||
|             InterpretName::CephClusterHealth => f.write_str("CephClusterHealth"), |             InterpretName::CephClusterHealth => f.write_str("CephClusterHealth"), | ||||||
|             InterpretName::Custom(name) => f.write_str(name), |             InterpretName::Custom(name) => f.write_str(name), | ||||||
|             InterpretName::RHOBAlerting => f.write_str("RHOBAlerting"), |             InterpretName::RHOBAlerting => f.write_str("RHOBAlerting"), | ||||||
|  |             InterpretName::K8sIngress => f.write_str("K8sIngress"), | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| } | } | ||||||
| @ -82,13 +84,15 @@ pub trait Interpret<T>: std::fmt::Debug + Send { | |||||||
| pub struct Outcome { | pub struct Outcome { | ||||||
|     pub status: InterpretStatus, |     pub status: InterpretStatus, | ||||||
|     pub message: String, |     pub message: String, | ||||||
|  |     pub details: Vec<String>, | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| impl Outcome { | impl Outcome { | ||||||
|     pub fn noop() -> Self { |     pub fn noop(message: String) -> Self { | ||||||
|         Self { |         Self { | ||||||
|             status: InterpretStatus::NOOP, |             status: InterpretStatus::NOOP, | ||||||
|             message: String::new(), |             message, | ||||||
|  |             details: vec![], | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
| @ -96,6 +100,23 @@ impl Outcome { | |||||||
|         Self { |         Self { | ||||||
|             status: InterpretStatus::SUCCESS, |             status: InterpretStatus::SUCCESS, | ||||||
|             message, |             message, | ||||||
|  |             details: vec![], | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     pub fn success_with_details(message: String, details: Vec<String>) -> Self { | ||||||
|  |         Self { | ||||||
|  |             status: InterpretStatus::SUCCESS, | ||||||
|  |             message, | ||||||
|  |             details, | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     pub fn running(message: String) -> Self { | ||||||
|  |         Self { | ||||||
|  |             status: InterpretStatus::RUNNING, | ||||||
|  |             message, | ||||||
|  |             details: vec![], | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  | |||||||
| @ -1,7 +1,10 @@ | |||||||
|  | use std::error::Error; | ||||||
|  | 
 | ||||||
| use async_trait::async_trait; | use async_trait::async_trait; | ||||||
|  | use derive_new::new; | ||||||
| use serde::Serialize; | use serde::Serialize; | ||||||
| 
 | 
 | ||||||
| use crate::topology::Topology; | use crate::{executors::ExecutorError, topology::Topology}; | ||||||
| 
 | 
 | ||||||
| /// An ApplicationFeature provided by harmony, such as Backups, Monitoring, MultisiteAvailability,
 | /// An ApplicationFeature provided by harmony, such as Backups, Monitoring, MultisiteAvailability,
 | ||||||
| /// ContinuousIntegration, ContinuousDelivery
 | /// ContinuousIntegration, ContinuousDelivery
 | ||||||
| @ -9,7 +12,10 @@ use crate::topology::Topology; | |||||||
| pub trait ApplicationFeature<T: Topology>: | pub trait ApplicationFeature<T: Topology>: | ||||||
|     std::fmt::Debug + Send + Sync + ApplicationFeatureClone<T> |     std::fmt::Debug + Send + Sync + ApplicationFeatureClone<T> | ||||||
| { | { | ||||||
|     async fn ensure_installed(&self, topology: &T) -> Result<(), String>; |     async fn ensure_installed( | ||||||
|  |         &self, | ||||||
|  |         topology: &T, | ||||||
|  |     ) -> Result<InstallationOutcome, InstallationError>; | ||||||
|     fn name(&self) -> String; |     fn name(&self) -> String; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| @ -40,3 +46,60 @@ impl<T: Topology> Clone for Box<dyn ApplicationFeature<T>> { | |||||||
|         self.clone_box() |         self.clone_box() | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  | 
 | ||||||
|  | #[derive(Debug, Clone, PartialEq, Eq)] | ||||||
|  | pub enum InstallationOutcome { | ||||||
|  |     Success { details: Vec<String> }, | ||||||
|  |     Noop, | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | impl InstallationOutcome { | ||||||
|  |     pub fn success() -> Self { | ||||||
|  |         Self::Success { details: vec![] } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     pub fn success_with_details(details: Vec<String>) -> Self { | ||||||
|  |         Self::Success { details } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     pub fn noop() -> Self { | ||||||
|  |         Self::Noop | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | #[derive(Debug, Clone, new)] | ||||||
|  | pub struct InstallationError { | ||||||
|  |     msg: String, | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | impl std::fmt::Display for InstallationError { | ||||||
|  |     fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { | ||||||
|  |         f.write_str(&self.msg) | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | impl Error for InstallationError {} | ||||||
|  | 
 | ||||||
|  | impl From<ExecutorError> for InstallationError { | ||||||
|  |     fn from(value: ExecutorError) -> Self { | ||||||
|  |         Self { | ||||||
|  |             msg: format!("InstallationError : {value}"), | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | impl From<kube::Error> for InstallationError { | ||||||
|  |     fn from(value: kube::Error) -> Self { | ||||||
|  |         Self { | ||||||
|  |             msg: format!("InstallationError : {value}"), | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | impl From<String> for InstallationError { | ||||||
|  |     fn from(value: String) -> Self { | ||||||
|  |         Self { | ||||||
|  |             msg: format!("PreparationError : {value}"), | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | |||||||
| @ -2,7 +2,7 @@ use async_trait::async_trait; | |||||||
| use log::info; | use log::info; | ||||||
| 
 | 
 | ||||||
| use crate::{ | use crate::{ | ||||||
|     modules::application::ApplicationFeature, |     modules::application::{ApplicationFeature, InstallationError, InstallationOutcome}, | ||||||
|     topology::{K8sclient, Topology}, |     topology::{K8sclient, Topology}, | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| @ -29,7 +29,10 @@ impl Default for PublicEndpoint { | |||||||
| /// For now we only suport K8s ingress, but we will support more stuff at some point
 | /// For now we only suport K8s ingress, but we will support more stuff at some point
 | ||||||
| #[async_trait] | #[async_trait] | ||||||
| impl<T: Topology + K8sclient + 'static> ApplicationFeature<T> for PublicEndpoint { | impl<T: Topology + K8sclient + 'static> ApplicationFeature<T> for PublicEndpoint { | ||||||
|     async fn ensure_installed(&self, _topology: &T) -> Result<(), String> { |     async fn ensure_installed( | ||||||
|  |         &self, | ||||||
|  |         _topology: &T, | ||||||
|  |     ) -> Result<InstallationOutcome, InstallationError> { | ||||||
|         info!( |         info!( | ||||||
|             "Making sure public endpoint is installed for port {}", |             "Making sure public endpoint is installed for port {}", | ||||||
|             self.application_port |             self.application_port | ||||||
|  | |||||||
| @ -1,4 +1,6 @@ | |||||||
| use crate::modules::application::{Application, ApplicationFeature}; | use crate::modules::application::{ | ||||||
|  |     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::kube_prometheus::crd::crd_alertmanager_config::CRDPrometheus; | use crate::modules::monitoring::kube_prometheus::crd::crd_alertmanager_config::CRDPrometheus; | ||||||
| use crate::topology::MultiTargetTopology; | use crate::topology::MultiTargetTopology; | ||||||
| @ -43,7 +45,10 @@ impl< | |||||||
|         + std::fmt::Debug, |         + std::fmt::Debug, | ||||||
| > ApplicationFeature<T> for Monitoring | > ApplicationFeature<T> for Monitoring | ||||||
| { | { | ||||||
|     async fn ensure_installed(&self, topology: &T) -> Result<(), String> { |     async fn ensure_installed( | ||||||
|  |         &self, | ||||||
|  |         topology: &T, | ||||||
|  |     ) -> Result<InstallationOutcome, InstallationError> { | ||||||
|         info!("Ensuring monitoring is available for application"); |         info!("Ensuring monitoring is available for application"); | ||||||
|         let namespace = topology |         let namespace = topology | ||||||
|             .get_tenant_config() |             .get_tenant_config() | ||||||
| @ -103,7 +108,7 @@ impl< | |||||||
|             .await |             .await | ||||||
|             .map_err(|e| e.to_string())?; |             .map_err(|e| e.to_string())?; | ||||||
| 
 | 
 | ||||||
|         Ok(()) |         Ok(InstallationOutcome::success()) | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     fn name(&self) -> String { |     fn name(&self) -> String { | ||||||
|  | |||||||
| @ -10,7 +10,7 @@ use crate::{ | |||||||
|     data::Version, |     data::Version, | ||||||
|     inventory::Inventory, |     inventory::Inventory, | ||||||
|     modules::application::{ |     modules::application::{ | ||||||
|         ApplicationFeature, HelmPackage, OCICompliant, |         ApplicationFeature, HelmPackage, InstallationError, InstallationOutcome, OCICompliant, | ||||||
|         features::{ArgoApplication, ArgoHelmScore}, |         features::{ArgoApplication, ArgoHelmScore}, | ||||||
|     }, |     }, | ||||||
|     score::Score, |     score::Score, | ||||||
| @ -141,7 +141,10 @@ impl< | |||||||
|     T: Topology + HelmCommand + MultiTargetTopology + K8sclient + Ingress + 'static, |     T: Topology + HelmCommand + MultiTargetTopology + K8sclient + Ingress + 'static, | ||||||
| > ApplicationFeature<T> for PackagingDeployment<A> | > ApplicationFeature<T> for PackagingDeployment<A> | ||||||
| { | { | ||||||
|     async fn ensure_installed(&self, topology: &T) -> Result<(), String> { |     async fn ensure_installed( | ||||||
|  |         &self, | ||||||
|  |         topology: &T, | ||||||
|  |     ) -> Result<InstallationOutcome, InstallationError> { | ||||||
|         let image = self.application.image_name(); |         let image = self.application.image_name(); | ||||||
|         let domain = topology |         let domain = topology | ||||||
|             .get_domain(&self.application.name()) |             .get_domain(&self.application.name()) | ||||||
| @ -205,7 +208,11 @@ impl< | |||||||
|                     .unwrap(); |                     .unwrap(); | ||||||
|             } |             } | ||||||
|         }; |         }; | ||||||
|         Ok(()) | 
 | ||||||
|  |         Ok(InstallationOutcome::success_with_details(vec![format!( | ||||||
|  |             "{}: http://{domain}", | ||||||
|  |             self.application.name() | ||||||
|  |         )])) | ||||||
|     } |     } | ||||||
|     fn name(&self) -> String { |     fn name(&self) -> String { | ||||||
|         "ContinuousDelivery".to_string() |         "ContinuousDelivery".to_string() | ||||||
|  | |||||||
| @ -1,6 +1,8 @@ | |||||||
| use std::sync::Arc; | use std::sync::Arc; | ||||||
| 
 | 
 | ||||||
| use crate::modules::application::{Application, ApplicationFeature}; | use crate::modules::application::{ | ||||||
|  |     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::application_monitoring::rhobs_application_monitoring_score::ApplicationRHOBMonitoringScore; | use crate::modules::monitoring::application_monitoring::rhobs_application_monitoring_score::ApplicationRHOBMonitoringScore; | ||||||
| 
 | 
 | ||||||
| @ -43,7 +45,10 @@ impl< | |||||||
|         + PrometheusApplicationMonitoring<RHOBObservability>, |         + PrometheusApplicationMonitoring<RHOBObservability>, | ||||||
| > ApplicationFeature<T> for Monitoring | > ApplicationFeature<T> for Monitoring | ||||||
| { | { | ||||||
|     async fn ensure_installed(&self, topology: &T) -> Result<(), String> { |     async fn ensure_installed( | ||||||
|  |         &self, | ||||||
|  |         topology: &T, | ||||||
|  |     ) -> Result<InstallationOutcome, InstallationError> { | ||||||
|         info!("Ensuring monitoring is available for application"); |         info!("Ensuring monitoring is available for application"); | ||||||
|         let namespace = topology |         let namespace = topology | ||||||
|             .get_tenant_config() |             .get_tenant_config() | ||||||
| @ -106,7 +111,7 @@ impl< | |||||||
|             .interpret(&Inventory::empty(), topology) |             .interpret(&Inventory::empty(), topology) | ||||||
|             .await |             .await | ||||||
|             .map_err(|e| e.to_string())?; |             .map_err(|e| e.to_string())?; | ||||||
|         Ok(()) |         Ok(InstallationOutcome::success()) | ||||||
|     } |     } | ||||||
|     fn name(&self) -> String { |     fn name(&self) -> String { | ||||||
|         "Monitoring".to_string() |         "Monitoring".to_string() | ||||||
|  | |||||||
| @ -24,8 +24,8 @@ use harmony_types::id::Id; | |||||||
| #[derive(Clone, Debug)] | #[derive(Clone, Debug)] | ||||||
| pub enum ApplicationFeatureStatus { | pub enum ApplicationFeatureStatus { | ||||||
|     Installing, |     Installing, | ||||||
|     Installed, |     Installed { details: Vec<String> }, | ||||||
|     Failed { details: String }, |     Failed { message: String }, | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| pub trait Application: std::fmt::Debug + Send + Sync { | pub trait Application: std::fmt::Debug + Send + Sync { | ||||||
| @ -65,27 +65,32 @@ impl<A: Application, T: Topology + std::fmt::Debug> Interpret<T> for Application | |||||||
|             .unwrap(); |             .unwrap(); | ||||||
| 
 | 
 | ||||||
|             let _ = match feature.ensure_installed(topology).await { |             let _ = match feature.ensure_installed(topology).await { | ||||||
|                 Ok(()) => { |                 Ok(outcome) => { | ||||||
|                     instrumentation::instrument(HarmonyEvent::ApplicationFeatureStateChanged { |                     instrumentation::instrument(HarmonyEvent::ApplicationFeatureStateChanged { | ||||||
|                         topology: topology.name().into(), |                         topology: topology.name().into(), | ||||||
|                         application: self.application.name(), |                         application: self.application.name(), | ||||||
|                         feature: feature.name(), |                         feature: feature.name(), | ||||||
|                         status: ApplicationFeatureStatus::Installed, |                         status: ApplicationFeatureStatus::Installed { | ||||||
|  |                             details: match outcome { | ||||||
|  |                                 InstallationOutcome::Success { details } => details, | ||||||
|  |                                 InstallationOutcome::Noop => vec![], | ||||||
|  |                             }, | ||||||
|  |                         }, | ||||||
|                     }) |                     }) | ||||||
|                     .unwrap(); |                     .unwrap(); | ||||||
|                 } |                 } | ||||||
|                 Err(msg) => { |                 Err(error) => { | ||||||
|                     instrumentation::instrument(HarmonyEvent::ApplicationFeatureStateChanged { |                     instrumentation::instrument(HarmonyEvent::ApplicationFeatureStateChanged { | ||||||
|                         topology: topology.name().into(), |                         topology: topology.name().into(), | ||||||
|                         application: self.application.name(), |                         application: self.application.name(), | ||||||
|                         feature: feature.name(), |                         feature: feature.name(), | ||||||
|                         status: ApplicationFeatureStatus::Failed { |                         status: ApplicationFeatureStatus::Failed { | ||||||
|                             details: msg.clone(), |                             message: error.to_string(), | ||||||
|                         }, |                         }, | ||||||
|                     }) |                     }) | ||||||
|                     .unwrap(); |                     .unwrap(); | ||||||
|                     return Err(InterpretError::new(format!( |                     return Err(InterpretError::new(format!( | ||||||
|                         "Application Interpret failed to install feature : {msg}" |                         "Application Interpret failed to install feature : {error}" | ||||||
|                     ))); |                     ))); | ||||||
|                 } |                 } | ||||||
|             }; |             }; | ||||||
|  | |||||||
| @ -69,17 +69,14 @@ impl DhcpInterpret { | |||||||
| 
 | 
 | ||||||
|         dhcp_server.set_pxe_options(pxe_options).await?; |         dhcp_server.set_pxe_options(pxe_options).await?; | ||||||
| 
 | 
 | ||||||
|         Ok(Outcome::new( |         Ok(Outcome::success(format!( | ||||||
|             InterpretStatus::SUCCESS, |  | ||||||
|             format!( |  | ||||||
|             "Dhcp Interpret Set next boot to [{:?}], boot_filename to [{:?}], filename to [{:?}], filename64 to [{:?}], filenameipxe to [:{:?}]", |             "Dhcp Interpret Set next boot to [{:?}], boot_filename to [{:?}], filename to [{:?}], filename64 to [{:?}], filenameipxe to [:{:?}]", | ||||||
|             self.score.boot_filename, |             self.score.boot_filename, | ||||||
|             self.score.boot_filename, |             self.score.boot_filename, | ||||||
|             self.score.filename, |             self.score.filename, | ||||||
|             self.score.filename64, |             self.score.filename64, | ||||||
|             self.score.filenameipxe |             self.score.filenameipxe | ||||||
|             ), |         ))) | ||||||
|         )) |  | ||||||
|     } |     } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| @ -122,8 +119,7 @@ impl<T: Topology + DhcpServer> Interpret<T> for DhcpInterpret { | |||||||
| 
 | 
 | ||||||
|         topology.commit_config().await?; |         topology.commit_config().await?; | ||||||
| 
 | 
 | ||||||
|         Ok(Outcome::new( |         Ok(Outcome::success( | ||||||
|             InterpretStatus::SUCCESS, |  | ||||||
|             "Dhcp Interpret execution successful".to_string(), |             "Dhcp Interpret execution successful".to_string(), | ||||||
|         )) |         )) | ||||||
|     } |     } | ||||||
| @ -197,10 +193,10 @@ impl DhcpHostBindingInterpret { | |||||||
|             } |             } | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         Ok(Outcome::new( |         Ok(Outcome::success(format!( | ||||||
|             InterpretStatus::SUCCESS, |             "Dhcp Interpret registered {} entries", | ||||||
|             format!("Dhcp Interpret registered {} entries", number_new_entries), |             number_new_entries | ||||||
|         )) |         ))) | ||||||
|     } |     } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| @ -236,12 +232,9 @@ impl<T: DhcpServer> Interpret<T> for DhcpHostBindingInterpret { | |||||||
| 
 | 
 | ||||||
|         topology.commit_config().await?; |         topology.commit_config().await?; | ||||||
| 
 | 
 | ||||||
|         Ok(Outcome::new( |         Ok(Outcome::success(format!( | ||||||
|             InterpretStatus::SUCCESS, |  | ||||||
|             format!( |  | ||||||
|             "Dhcp Host Binding Interpret execution successful on {} hosts", |             "Dhcp Host Binding Interpret execution successful on {} hosts", | ||||||
|             self.score.host_binding.len() |             self.score.host_binding.len() | ||||||
|             ), |         ))) | ||||||
|         )) |  | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  | |||||||
| @ -55,8 +55,7 @@ impl DnsInterpret { | |||||||
|             dns.register_dhcp_leases(register).await?; |             dns.register_dhcp_leases(register).await?; | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         Ok(Outcome::new( |         Ok(Outcome::success( | ||||||
|             InterpretStatus::SUCCESS, |  | ||||||
|             "DNS Interpret execution successfull".to_string(), |             "DNS Interpret execution successfull".to_string(), | ||||||
|         )) |         )) | ||||||
|     } |     } | ||||||
| @ -68,13 +67,10 @@ impl DnsInterpret { | |||||||
|         let entries = &self.score.dns_entries; |         let entries = &self.score.dns_entries; | ||||||
|         dns_server.ensure_hosts_registered(entries.clone()).await?; |         dns_server.ensure_hosts_registered(entries.clone()).await?; | ||||||
| 
 | 
 | ||||||
|         Ok(Outcome::new( |         Ok(Outcome::success(format!( | ||||||
|             InterpretStatus::SUCCESS, |  | ||||||
|             format!( |  | ||||||
|             "DnsInterpret registered {} hosts successfully", |             "DnsInterpret registered {} hosts successfully", | ||||||
|             entries.len() |             entries.len() | ||||||
|             ), |         ))) | ||||||
|         )) |  | ||||||
|     } |     } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| @ -111,8 +107,7 @@ impl<T: Topology + DnsServer> Interpret<T> for DnsInterpret { | |||||||
| 
 | 
 | ||||||
|         topology.commit_config().await?; |         topology.commit_config().await?; | ||||||
| 
 | 
 | ||||||
|         Ok(Outcome::new( |         Ok(Outcome::success( | ||||||
|             InterpretStatus::SUCCESS, |  | ||||||
|             "Dns Interpret execution successful".to_string(), |             "Dns Interpret execution successful".to_string(), | ||||||
|         )) |         )) | ||||||
|     } |     } | ||||||
|  | |||||||
| @ -197,13 +197,10 @@ impl<T: Topology + HelmCommand> Interpret<T> for HelmChartInterpret { | |||||||
|                     self.score.release_name, ns |                     self.score.release_name, ns | ||||||
|                 ); |                 ); | ||||||
| 
 | 
 | ||||||
|                 return Ok(Outcome::new( |                 return Ok(Outcome::success(format!( | ||||||
|                     InterpretStatus::SUCCESS, |  | ||||||
|                     format!( |  | ||||||
|                     "Helm Chart '{}' already installed to namespace {ns} and install_only=true", |                     "Helm Chart '{}' already installed to namespace {ns} and install_only=true", | ||||||
|                     self.score.release_name |                     self.score.release_name | ||||||
|                     ), |                 ))); | ||||||
|                 )); |  | ||||||
|             } else { |             } else { | ||||||
|                 info!( |                 info!( | ||||||
|                     "Release '{}' not found in namespace '{}'. Proceeding with installation.", |                     "Release '{}' not found in namespace '{}'. Proceeding with installation.", | ||||||
| @ -228,18 +225,18 @@ impl<T: Topology + HelmCommand> Interpret<T> for HelmChartInterpret { | |||||||
|         }; |         }; | ||||||
| 
 | 
 | ||||||
|         match status { |         match status { | ||||||
|             helm_wrapper_rs::HelmDeployStatus::Deployed => Ok(Outcome::new( |             helm_wrapper_rs::HelmDeployStatus::Deployed => Ok(Outcome::success(format!( | ||||||
|                 InterpretStatus::SUCCESS, |                 "Helm Chart {} deployed", | ||||||
|                 format!("Helm Chart {} deployed", self.score.release_name), |                 self.score.release_name | ||||||
|             )), |             ))), | ||||||
|             helm_wrapper_rs::HelmDeployStatus::PendingInstall => Ok(Outcome::new( |             helm_wrapper_rs::HelmDeployStatus::PendingInstall => Ok(Outcome::running(format!( | ||||||
|                 InterpretStatus::RUNNING, |                 "Helm Chart {} pending install...", | ||||||
|                 format!("Helm Chart {} pending install...", self.score.release_name), |                 self.score.release_name | ||||||
|             )), |             ))), | ||||||
|             helm_wrapper_rs::HelmDeployStatus::PendingUpgrade => Ok(Outcome::new( |             helm_wrapper_rs::HelmDeployStatus::PendingUpgrade => Ok(Outcome::running(format!( | ||||||
|                 InterpretStatus::RUNNING, |                 "Helm Chart {} pending upgrade...", | ||||||
|                 format!("Helm Chart {} pending upgrade...", self.score.release_name), |                 self.score.release_name | ||||||
|             )), |             ))), | ||||||
|             helm_wrapper_rs::HelmDeployStatus::Failed => Err(InterpretError::new(format!( |             helm_wrapper_rs::HelmDeployStatus::Failed => Err(InterpretError::new(format!( | ||||||
|                 "Helm Chart {} installation failed", |                 "Helm Chart {} installation failed", | ||||||
|                 self.score.release_name |                 self.score.release_name | ||||||
|  | |||||||
| @ -133,10 +133,9 @@ impl<T: Topology> Interpret<T> for DiscoverInventoryAgentInterpret { | |||||||
|             }, |             }, | ||||||
|         ) |         ) | ||||||
|         .await; |         .await; | ||||||
|         Ok(Outcome { |         Ok(Outcome::success( | ||||||
|             status: InterpretStatus::SUCCESS, |             "Discovery process completed successfully".to_string(), | ||||||
|             message: "Discovery process completed successfully".to_string(), |         )) | ||||||
|         }) |  | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     fn get_name(&self) -> InterpretName { |     fn get_name(&self) -> InterpretName { | ||||||
|  | |||||||
| @ -1,11 +1,15 @@ | |||||||
|  | use async_trait::async_trait; | ||||||
| use harmony_macros::ingress_path; | use harmony_macros::ingress_path; | ||||||
|  | use harmony_types::id::Id; | ||||||
| use k8s_openapi::api::networking::v1::Ingress; | use k8s_openapi::api::networking::v1::Ingress; | ||||||
| use log::{debug, trace}; | use log::{debug, trace}; | ||||||
| use serde::Serialize; | use serde::Serialize; | ||||||
| use serde_json::json; | use serde_json::json; | ||||||
| 
 | 
 | ||||||
| use crate::{ | use crate::{ | ||||||
|     interpret::Interpret, |     data::Version, | ||||||
|  |     interpret::{Interpret, InterpretError, InterpretName, InterpretStatus, Outcome}, | ||||||
|  |     inventory::Inventory, | ||||||
|     score::Score, |     score::Score, | ||||||
|     topology::{K8sclient, Topology}, |     topology::{K8sclient, Topology}, | ||||||
| }; | }; | ||||||
| @ -57,7 +61,7 @@ impl<T: Topology + K8sclient> Score<T> for K8sIngressScore { | |||||||
| 
 | 
 | ||||||
|         let ingress_class = match self.ingress_class_name.clone() { |         let ingress_class = match self.ingress_class_name.clone() { | ||||||
|             Some(ingress_class_name) => ingress_class_name, |             Some(ingress_class_name) => ingress_class_name, | ||||||
|             None => format!("\"default\""), |             None => "\"default\"".to_string(), | ||||||
|         }; |         }; | ||||||
| 
 | 
 | ||||||
|         let ingress = json!( |         let ingress = json!( | ||||||
| @ -97,11 +101,12 @@ impl<T: Topology + K8sclient> Score<T> for K8sIngressScore { | |||||||
|             "Successfully built Ingress for host {:?}", |             "Successfully built Ingress for host {:?}", | ||||||
|             ingress.metadata.name |             ingress.metadata.name | ||||||
|         ); |         ); | ||||||
|         Box::new(K8sResourceInterpret { | 
 | ||||||
|             score: K8sResourceScore::single( |         Box::new(K8sIngressInterpret { | ||||||
|                 ingress.clone(), |             ingress, | ||||||
|                 self.namespace.clone().map(|f| f.to_string()), |             service: self.name.to_string(), | ||||||
|             ), |             namespace: self.namespace.clone().map(|f| f.to_string()), | ||||||
|  |             host: self.host.clone(), | ||||||
|         }) |         }) | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
| @ -109,3 +114,59 @@ impl<T: Topology + K8sclient> Score<T> for K8sIngressScore { | |||||||
|         format!("{} K8sIngressScore", self.name) |         format!("{} K8sIngressScore", self.name) | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  | 
 | ||||||
|  | #[derive(std::fmt::Debug)] | ||||||
|  | struct K8sIngressInterpret { | ||||||
|  |     ingress: Ingress, | ||||||
|  |     service: String, | ||||||
|  |     namespace: Option<String>, | ||||||
|  |     host: fqdn::FQDN, | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | #[async_trait] | ||||||
|  | impl<T: Topology + K8sclient> Interpret<T> for K8sIngressInterpret { | ||||||
|  |     async fn execute( | ||||||
|  |         &self, | ||||||
|  |         inventory: &Inventory, | ||||||
|  |         topology: &T, | ||||||
|  |     ) -> Result<Outcome, InterpretError> { | ||||||
|  |         let result = K8sResourceInterpret { | ||||||
|  |             score: K8sResourceScore::single(self.ingress.clone(), self.namespace.clone()), | ||||||
|  |         } | ||||||
|  |         .execute(inventory, topology) | ||||||
|  |         .await; | ||||||
|  | 
 | ||||||
|  |         match result { | ||||||
|  |             Ok(outcome) => match outcome.status { | ||||||
|  |                 InterpretStatus::SUCCESS => { | ||||||
|  |                     let details = match &self.namespace { | ||||||
|  |                         Some(namespace) => { | ||||||
|  |                             vec![format!("{} ({namespace}): {}", self.service, self.host)] | ||||||
|  |                         } | ||||||
|  |                         None => vec![format!("{}: {}", self.service, self.host)], | ||||||
|  |                     }; | ||||||
|  | 
 | ||||||
|  |                     Ok(Outcome::success_with_details(outcome.message, details)) | ||||||
|  |                 } | ||||||
|  |                 _ => Ok(outcome), | ||||||
|  |             }, | ||||||
|  |             Err(e) => Err(e), | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     fn get_name(&self) -> InterpretName { | ||||||
|  |         InterpretName::K8sIngress | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     fn get_version(&self) -> Version { | ||||||
|  |         Version::from("0.0.1").unwrap() | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     fn get_status(&self) -> InterpretStatus { | ||||||
|  |         todo!() | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     fn get_children(&self) -> Vec<Id> { | ||||||
|  |         vec![] | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | |||||||
| @ -68,7 +68,9 @@ impl<T: Topology + PrometheusApplicationMonitoring<CRDPrometheus>> Interpret<T> | |||||||
|                 PreparationOutcome::Success { details: _ } => { |                 PreparationOutcome::Success { details: _ } => { | ||||||
|                     Ok(Outcome::success("Prometheus installed".into())) |                     Ok(Outcome::success("Prometheus installed".into())) | ||||||
|                 } |                 } | ||||||
|                 PreparationOutcome::Noop => Ok(Outcome::noop()), |                 PreparationOutcome::Noop => { | ||||||
|  |                     Ok(Outcome::noop("Prometheus installation skipped".into())) | ||||||
|  |                 } | ||||||
|             }, |             }, | ||||||
|             Err(err) => Err(InterpretError::from(err)), |             Err(err) => Err(InterpretError::from(err)), | ||||||
|         } |         } | ||||||
|  | |||||||
| @ -70,7 +70,9 @@ impl<T: Topology + PrometheusApplicationMonitoring<RHOBObservability>> Interpret | |||||||
|                 PreparationOutcome::Success { details: _ } => { |                 PreparationOutcome::Success { details: _ } => { | ||||||
|                     Ok(Outcome::success("Prometheus installed".into())) |                     Ok(Outcome::success("Prometheus installed".into())) | ||||||
|                 } |                 } | ||||||
|                 PreparationOutcome::Noop => Ok(Outcome::noop()), |                 PreparationOutcome::Noop => { | ||||||
|  |                     Ok(Outcome::noop("Prometheus installation skipped".into())) | ||||||
|  |                 } | ||||||
|             }, |             }, | ||||||
|             Err(err) => Err(InterpretError::from(err)), |             Err(err) => Err(InterpretError::from(err)), | ||||||
|         } |         } | ||||||
|  | |||||||
| @ -113,7 +113,13 @@ impl<T: Topology + HelmCommand + K8sclient + MultiTargetTopology> Interpret<T> f | |||||||
|             .await?; |             .await?; | ||||||
|         info!("user added"); |         info!("user added"); | ||||||
| 
 | 
 | ||||||
|         Ok(Outcome::success("Ntfy installed".to_string())) |         Ok(Outcome::success_with_details( | ||||||
|  |             "Ntfy installed".to_string(), | ||||||
|  |             vec![format!( | ||||||
|  |                 "Ntfy ({}): http://{}", | ||||||
|  |                 self.score.namespace, self.score.host | ||||||
|  |             )], | ||||||
|  |         )) | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     fn get_name(&self) -> InterpretName { |     fn get_name(&self) -> InterpretName { | ||||||
|  | |||||||
| @ -1,19 +1,19 @@ | |||||||
| use async_trait::async_trait; |  | ||||||
| use derive_new::new; |  | ||||||
| use harmony_types::id::Id; |  | ||||||
| use log::{error, info, warn}; |  | ||||||
| use serde::Serialize; |  | ||||||
| 
 |  | ||||||
| use crate::{ | use crate::{ | ||||||
|     data::Version, |     data::Version, | ||||||
|     hardware::PhysicalHost, |     hardware::PhysicalHost, | ||||||
|     infra::inventory::InventoryRepositoryFactory, |     infra::inventory::InventoryRepositoryFactory, | ||||||
|     interpret::{Interpret, InterpretError, InterpretName, InterpretStatus, Outcome}, |     interpret::{Interpret, InterpretError, InterpretName, InterpretStatus, Outcome}, | ||||||
|     inventory::{HostRole, Inventory}, |     inventory::{HostRole, Inventory}, | ||||||
|     modules::inventory::{DiscoverHostForRoleScore, LaunchDiscoverInventoryAgentScore}, |     modules::inventory::DiscoverHostForRoleScore, | ||||||
|     score::Score, |     score::Score, | ||||||
|     topology::HAClusterTopology, |     topology::HAClusterTopology, | ||||||
| }; | }; | ||||||
|  | use async_trait::async_trait; | ||||||
|  | use derive_new::new; | ||||||
|  | use harmony_types::id::Id; | ||||||
|  | use log::info; | ||||||
|  | use serde::Serialize; | ||||||
|  | 
 | ||||||
| // -------------------------------------------------------------------------------------------------
 | // -------------------------------------------------------------------------------------------------
 | ||||||
| // Step 01: Inventory (default PXE + Kickstart in RAM + Rust agent)
 | // Step 01: Inventory (default PXE + Kickstart in RAM + Rust agent)
 | ||||||
| // - This score exposes/ensures the default inventory assets and waits for discoveries.
 | // - This score exposes/ensures the default inventory assets and waits for discoveries.
 | ||||||
| @ -109,12 +109,9 @@ When you can dig them, confirm to continue. | |||||||
|             .await?; |             .await?; | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         Ok(Outcome::new( |         Ok(Outcome::success(format!( | ||||||
|             InterpretStatus::SUCCESS, |  | ||||||
|             format!( |  | ||||||
|             "Found and assigned bootstrap node: {}", |             "Found and assigned bootstrap node: {}", | ||||||
|             bootstrap_host.unwrap().summary() |             bootstrap_host.unwrap().summary() | ||||||
|             ), |         ))) | ||||||
|         )) |  | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  | |||||||
| @ -1,25 +1,13 @@ | |||||||
| use std::{fmt::Write, path::PathBuf}; |  | ||||||
| 
 |  | ||||||
| use async_trait::async_trait; |  | ||||||
| use derive_new::new; |  | ||||||
| use harmony_secret::SecretManager; |  | ||||||
| use harmony_types::id::Id; |  | ||||||
| use log::{debug, error, info, warn}; |  | ||||||
| use serde::{Deserialize, Serialize}; |  | ||||||
| use tokio::{fs::File, io::AsyncWriteExt, process::Command}; |  | ||||||
| 
 |  | ||||||
| use crate::{ | use crate::{ | ||||||
|     config::secret::{RedhatSecret, SshKeyPair}, |     config::secret::{RedhatSecret, SshKeyPair}, | ||||||
|     data::{FileContent, FilePath, Version}, |     data::{FileContent, FilePath, Version}, | ||||||
|     hardware::PhysicalHost, |     hardware::PhysicalHost, | ||||||
|     infra::inventory::InventoryRepositoryFactory, |     infra::inventory::InventoryRepositoryFactory, | ||||||
|     instrumentation::{HarmonyEvent, instrument}, |  | ||||||
|     interpret::{Interpret, InterpretError, InterpretName, InterpretStatus, Outcome}, |     interpret::{Interpret, InterpretError, InterpretName, InterpretStatus, Outcome}, | ||||||
|     inventory::{HostRole, Inventory}, |     inventory::{HostRole, Inventory}, | ||||||
|     modules::{ |     modules::{ | ||||||
|         dhcp::DhcpHostBindingScore, |         dhcp::DhcpHostBindingScore, | ||||||
|         http::{IPxeMacBootFileScore, StaticFilesHttpScore}, |         http::{IPxeMacBootFileScore, StaticFilesHttpScore}, | ||||||
|         inventory::LaunchDiscoverInventoryAgentScore, |  | ||||||
|         okd::{ |         okd::{ | ||||||
|             bootstrap_load_balancer::OKDBootstrapLoadBalancerScore, |             bootstrap_load_balancer::OKDBootstrapLoadBalancerScore, | ||||||
|             templates::{BootstrapIpxeTpl, InstallConfigYaml}, |             templates::{BootstrapIpxeTpl, InstallConfigYaml}, | ||||||
| @ -28,6 +16,15 @@ use crate::{ | |||||||
|     score::Score, |     score::Score, | ||||||
|     topology::{HAClusterTopology, HostBinding}, |     topology::{HAClusterTopology, HostBinding}, | ||||||
| }; | }; | ||||||
|  | use async_trait::async_trait; | ||||||
|  | use derive_new::new; | ||||||
|  | use harmony_secret::SecretManager; | ||||||
|  | use harmony_types::id::Id; | ||||||
|  | use log::{debug, info}; | ||||||
|  | use serde::Serialize; | ||||||
|  | use std::path::PathBuf; | ||||||
|  | use tokio::{fs::File, io::AsyncWriteExt, process::Command}; | ||||||
|  | 
 | ||||||
| // -------------------------------------------------------------------------------------------------
 | // -------------------------------------------------------------------------------------------------
 | ||||||
| // Step 02: Bootstrap
 | // Step 02: Bootstrap
 | ||||||
| // - Select bootstrap node (from discovered set).
 | // - Select bootstrap node (from discovered set).
 | ||||||
| @ -313,7 +310,7 @@ impl OKDSetup02BootstrapInterpret { | |||||||
|         info!("[Bootstrap] Rebooting bootstrap node via SSH"); |         info!("[Bootstrap] Rebooting bootstrap node via SSH"); | ||||||
|         // TODO reboot programatically, there are some logical checks and refactoring to do such as
 |         // TODO reboot programatically, there are some logical checks and refactoring to do such as
 | ||||||
|         // accessing the bootstrap node config (ip address) from the inventory
 |         // accessing the bootstrap node config (ip address) from the inventory
 | ||||||
|         let confirmation = inquire::Confirm::new( |         let _ = inquire::Confirm::new( | ||||||
|                 "Now reboot the bootstrap node so it picks up its pxe boot file. Press enter when ready.", |                 "Now reboot the bootstrap node so it picks up its pxe boot file. Press enter when ready.", | ||||||
|         ) |         ) | ||||||
|         .prompt() |         .prompt() | ||||||
| @ -379,9 +376,6 @@ impl Interpret<HAClusterTopology> for OKDSetup02BootstrapInterpret { | |||||||
|         self.reboot_target().await?; |         self.reboot_target().await?; | ||||||
|         self.wait_for_bootstrap_complete().await?; |         self.wait_for_bootstrap_complete().await?; | ||||||
| 
 | 
 | ||||||
|         Ok(Outcome::new( |         Ok(Outcome::success("Bootstrap phase complete".into())) | ||||||
|             InterpretStatus::SUCCESS, |  | ||||||
|             "Bootstrap phase complete".into(), |  | ||||||
|         )) |  | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  | |||||||
| @ -1,11 +1,3 @@ | |||||||
| use std::{fmt::Write, path::PathBuf}; |  | ||||||
| 
 |  | ||||||
| use async_trait::async_trait; |  | ||||||
| use derive_new::new; |  | ||||||
| use harmony_types::id::Id; |  | ||||||
| use log::{debug, info}; |  | ||||||
| use serde::Serialize; |  | ||||||
| 
 |  | ||||||
| use crate::{ | use crate::{ | ||||||
|     data::Version, |     data::Version, | ||||||
|     hardware::PhysicalHost, |     hardware::PhysicalHost, | ||||||
| @ -19,6 +11,12 @@ use crate::{ | |||||||
|     score::Score, |     score::Score, | ||||||
|     topology::{HAClusterTopology, HostBinding}, |     topology::{HAClusterTopology, HostBinding}, | ||||||
| }; | }; | ||||||
|  | use async_trait::async_trait; | ||||||
|  | use derive_new::new; | ||||||
|  | use harmony_types::id::Id; | ||||||
|  | use log::{debug, info}; | ||||||
|  | use serde::Serialize; | ||||||
|  | 
 | ||||||
| // -------------------------------------------------------------------------------------------------
 | // -------------------------------------------------------------------------------------------------
 | ||||||
| // Step 03: Control Plane
 | // Step 03: Control Plane
 | ||||||
| // - Render per-MAC PXE & ignition for cp0/cp1/cp2.
 | // - Render per-MAC PXE & ignition for cp0/cp1/cp2.
 | ||||||
| @ -269,8 +267,7 @@ impl Interpret<HAClusterTopology> for OKDSetup03ControlPlaneInterpret { | |||||||
|         // the `wait-for bootstrap-complete` command.
 |         // the `wait-for bootstrap-complete` command.
 | ||||||
|         info!("[ControlPlane] Provisioning initiated. Monitor the cluster convergence manually."); |         info!("[ControlPlane] Provisioning initiated. Monitor the cluster convergence manually."); | ||||||
| 
 | 
 | ||||||
|         Ok(Outcome::new( |         Ok(Outcome::success( | ||||||
|             InterpretStatus::SUCCESS, |  | ||||||
|             "Control plane provisioning has been successfully initiated.".into(), |             "Control plane provisioning has been successfully initiated.".into(), | ||||||
|         )) |         )) | ||||||
|     } |     } | ||||||
|  | |||||||
| @ -1,33 +1,17 @@ | |||||||
| use std::{fmt::Write, path::PathBuf}; |  | ||||||
| 
 |  | ||||||
| use async_trait::async_trait; | use async_trait::async_trait; | ||||||
| use derive_new::new; | use derive_new::new; | ||||||
| use harmony_secret::SecretManager; |  | ||||||
| use harmony_types::id::Id; | use harmony_types::id::Id; | ||||||
| use log::{debug, error, info, warn}; | use log::info; | ||||||
| use serde::{Deserialize, Serialize}; | use serde::Serialize; | ||||||
| use tokio::{fs::File, io::AsyncWriteExt, process::Command}; |  | ||||||
| 
 | 
 | ||||||
| use crate::{ | use crate::{ | ||||||
|     config::secret::{RedhatSecret, SshKeyPair}, |     data::Version, | ||||||
|     data::{FileContent, FilePath, Version}, |  | ||||||
|     hardware::PhysicalHost, |  | ||||||
|     infra::inventory::InventoryRepositoryFactory, |  | ||||||
|     instrumentation::{HarmonyEvent, instrument}, |  | ||||||
|     interpret::{Interpret, InterpretError, InterpretName, InterpretStatus, Outcome}, |     interpret::{Interpret, InterpretError, InterpretName, InterpretStatus, Outcome}, | ||||||
|     inventory::{HostRole, Inventory}, |     inventory::Inventory, | ||||||
|     modules::{ |  | ||||||
|         dhcp::DhcpHostBindingScore, |  | ||||||
|         http::{IPxeMacBootFileScore, StaticFilesHttpScore}, |  | ||||||
|         inventory::LaunchDiscoverInventoryAgentScore, |  | ||||||
|         okd::{ |  | ||||||
|             bootstrap_load_balancer::OKDBootstrapLoadBalancerScore, |  | ||||||
|             templates::{BootstrapIpxeTpl, InstallConfigYaml}, |  | ||||||
|         }, |  | ||||||
|     }, |  | ||||||
|     score::Score, |     score::Score, | ||||||
|     topology::{HAClusterTopology, HostBinding}, |     topology::HAClusterTopology, | ||||||
| }; | }; | ||||||
|  | 
 | ||||||
| // -------------------------------------------------------------------------------------------------
 | // -------------------------------------------------------------------------------------------------
 | ||||||
| // Step 04: Workers
 | // Step 04: Workers
 | ||||||
| // - Render per-MAC PXE & ignition for workers; join nodes.
 | // - Render per-MAC PXE & ignition for workers; join nodes.
 | ||||||
| @ -94,9 +78,6 @@ impl Interpret<HAClusterTopology> for OKDSetup04WorkersInterpret { | |||||||
|         _topology: &HAClusterTopology, |         _topology: &HAClusterTopology, | ||||||
|     ) -> Result<Outcome, InterpretError> { |     ) -> Result<Outcome, InterpretError> { | ||||||
|         self.render_and_reboot().await?; |         self.render_and_reboot().await?; | ||||||
|         Ok(Outcome::new( |         Ok(Outcome::success("Workers provisioned".into())) | ||||||
|             InterpretStatus::SUCCESS, |  | ||||||
|             "Workers provisioned".into(), |  | ||||||
|         )) |  | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  | |||||||
| @ -1,33 +1,16 @@ | |||||||
| use std::{fmt::Write, path::PathBuf}; | use crate::{ | ||||||
| 
 |     data::Version, | ||||||
|  |     interpret::{Interpret, InterpretError, InterpretName, InterpretStatus, Outcome}, | ||||||
|  |     inventory::Inventory, | ||||||
|  |     score::Score, | ||||||
|  |     topology::HAClusterTopology, | ||||||
|  | }; | ||||||
| use async_trait::async_trait; | use async_trait::async_trait; | ||||||
| use derive_new::new; | use derive_new::new; | ||||||
| use harmony_secret::SecretManager; |  | ||||||
| use harmony_types::id::Id; | use harmony_types::id::Id; | ||||||
| use log::{debug, error, info, warn}; | use log::info; | ||||||
| use serde::{Deserialize, Serialize}; | use serde::Serialize; | ||||||
| use tokio::{fs::File, io::AsyncWriteExt, process::Command}; |  | ||||||
| 
 | 
 | ||||||
| use crate::{ |  | ||||||
|     config::secret::{RedhatSecret, SshKeyPair}, |  | ||||||
|     data::{FileContent, FilePath, Version}, |  | ||||||
|     hardware::PhysicalHost, |  | ||||||
|     infra::inventory::InventoryRepositoryFactory, |  | ||||||
|     instrumentation::{HarmonyEvent, instrument}, |  | ||||||
|     interpret::{Interpret, InterpretError, InterpretName, InterpretStatus, Outcome}, |  | ||||||
|     inventory::{HostRole, Inventory}, |  | ||||||
|     modules::{ |  | ||||||
|         dhcp::DhcpHostBindingScore, |  | ||||||
|         http::{IPxeMacBootFileScore, StaticFilesHttpScore}, |  | ||||||
|         inventory::LaunchDiscoverInventoryAgentScore, |  | ||||||
|         okd::{ |  | ||||||
|             bootstrap_load_balancer::OKDBootstrapLoadBalancerScore, |  | ||||||
|             templates::{BootstrapIpxeTpl, InstallConfigYaml}, |  | ||||||
|         }, |  | ||||||
|     }, |  | ||||||
|     score::Score, |  | ||||||
|     topology::{HAClusterTopology, HostBinding}, |  | ||||||
| }; |  | ||||||
| // -------------------------------------------------------------------------------------------------
 | // -------------------------------------------------------------------------------------------------
 | ||||||
| // Step 05: Sanity Check
 | // Step 05: Sanity Check
 | ||||||
| // - Validate API reachability, ClusterOperators, ingress, and SDN status.
 | // - Validate API reachability, ClusterOperators, ingress, and SDN status.
 | ||||||
| @ -93,9 +76,6 @@ impl Interpret<HAClusterTopology> for OKDSetup05SanityCheckInterpret { | |||||||
|         _topology: &HAClusterTopology, |         _topology: &HAClusterTopology, | ||||||
|     ) -> Result<Outcome, InterpretError> { |     ) -> Result<Outcome, InterpretError> { | ||||||
|         self.run_checks().await?; |         self.run_checks().await?; | ||||||
|         Ok(Outcome::new( |         Ok(Outcome::success("Sanity checks passed".into())) | ||||||
|             InterpretStatus::SUCCESS, |  | ||||||
|             "Sanity checks passed".into(), |  | ||||||
|         )) |  | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  | |||||||
| @ -1,32 +1,15 @@ | |||||||
| // -------------------------------------------------------------------------------------------------
 |  | ||||||
| use async_trait::async_trait; | use async_trait::async_trait; | ||||||
| use derive_new::new; | use derive_new::new; | ||||||
| use harmony_secret::SecretManager; |  | ||||||
| use harmony_types::id::Id; | use harmony_types::id::Id; | ||||||
| use log::{debug, error, info, warn}; | use log::info; | ||||||
| use serde::{Deserialize, Serialize}; | use serde::Serialize; | ||||||
| use std::{fmt::Write, path::PathBuf}; |  | ||||||
| use tokio::{fs::File, io::AsyncWriteExt, process::Command}; |  | ||||||
| 
 | 
 | ||||||
| use crate::{ | use crate::{ | ||||||
|     config::secret::{RedhatSecret, SshKeyPair}, |     data::Version, | ||||||
|     data::{FileContent, FilePath, Version}, |  | ||||||
|     hardware::PhysicalHost, |  | ||||||
|     infra::inventory::InventoryRepositoryFactory, |  | ||||||
|     instrumentation::{HarmonyEvent, instrument}, |  | ||||||
|     interpret::{Interpret, InterpretError, InterpretName, InterpretStatus, Outcome}, |     interpret::{Interpret, InterpretError, InterpretName, InterpretStatus, Outcome}, | ||||||
|     inventory::{HostRole, Inventory}, |     inventory::Inventory, | ||||||
|     modules::{ |  | ||||||
|         dhcp::DhcpHostBindingScore, |  | ||||||
|         http::{IPxeMacBootFileScore, StaticFilesHttpScore}, |  | ||||||
|         inventory::LaunchDiscoverInventoryAgentScore, |  | ||||||
|         okd::{ |  | ||||||
|             bootstrap_load_balancer::OKDBootstrapLoadBalancerScore, |  | ||||||
|             templates::{BootstrapIpxeTpl, InstallConfigYaml}, |  | ||||||
|         }, |  | ||||||
|     }, |  | ||||||
|     score::Score, |     score::Score, | ||||||
|     topology::{HAClusterTopology, HostBinding}, |     topology::HAClusterTopology, | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| // Step 06: Installation Report
 | // Step 06: Installation Report
 | ||||||
| @ -93,9 +76,6 @@ impl Interpret<HAClusterTopology> for OKDSetup06InstallationReportInterpret { | |||||||
|         _topology: &HAClusterTopology, |         _topology: &HAClusterTopology, | ||||||
|     ) -> Result<Outcome, InterpretError> { |     ) -> Result<Outcome, InterpretError> { | ||||||
|         self.generate().await?; |         self.generate().await?; | ||||||
|         Ok(Outcome::new( |         Ok(Outcome::success("Installation report generated".into())) | ||||||
|             InterpretStatus::SUCCESS, |  | ||||||
|             "Installation report generated".into(), |  | ||||||
|         )) |  | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  | |||||||
| @ -178,10 +178,10 @@ fn handle_events() { | |||||||
|                     ApplicationFeatureStatus::Installing => { |                     ApplicationFeatureStatus::Installing => { | ||||||
|                         info!("Installing feature '{feature}' for '{application}'..."); |                         info!("Installing feature '{feature}' for '{application}'..."); | ||||||
|                     } |                     } | ||||||
|                     ApplicationFeatureStatus::Installed => { |                     ApplicationFeatureStatus::Installed { details: _ } => { | ||||||
|                         info!(status = "finished"; "Feature '{feature}' installed"); |                         info!(status = "finished"; "Feature '{feature}' installed"); | ||||||
|                     } |                     } | ||||||
|                     ApplicationFeatureStatus::Failed { details } => { |                     ApplicationFeatureStatus::Failed { message: details } => { | ||||||
|                         error!(status = "failed"; "Feature '{feature}' installation failed: {details}"); |                         error!(status = "failed"; "Feature '{feature}' installation failed: {details}"); | ||||||
|                     } |                     } | ||||||
|                 }, |                 }, | ||||||
|  | |||||||
							
								
								
									
										56
									
								
								harmony_cli/src/cli_reporter.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										56
									
								
								harmony_cli/src/cli_reporter.rs
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,56 @@ | |||||||
|  | use std::sync::Mutex; | ||||||
|  | 
 | ||||||
|  | use harmony::{ | ||||||
|  |     instrumentation::{self, HarmonyEvent}, | ||||||
|  |     modules::application::ApplicationFeatureStatus, | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | use crate::theme; | ||||||
|  | 
 | ||||||
|  | pub fn init() { | ||||||
|  |     let details: Mutex<Vec<String>> = Mutex::new(vec![]); | ||||||
|  | 
 | ||||||
|  |     instrumentation::subscribe("Harmony CLI Reporter", { | ||||||
|  |         move |event| { | ||||||
|  |             let mut details = details.lock().unwrap(); | ||||||
|  | 
 | ||||||
|  |             match event { | ||||||
|  |                 HarmonyEvent::InterpretExecutionFinished { | ||||||
|  |                     execution_id: _, | ||||||
|  |                     topology: _, | ||||||
|  |                     interpret: _, | ||||||
|  |                     score: _, | ||||||
|  |                     outcome: Ok(outcome), | ||||||
|  |                 } => { | ||||||
|  |                     if outcome.status == harmony::interpret::InterpretStatus::SUCCESS { | ||||||
|  |                         details.extend(outcome.details.clone()); | ||||||
|  |                     } | ||||||
|  |                 } | ||||||
|  |                 HarmonyEvent::ApplicationFeatureStateChanged { | ||||||
|  |                     topology: _, | ||||||
|  |                     application: _, | ||||||
|  |                     feature: _, | ||||||
|  |                     status: | ||||||
|  |                         ApplicationFeatureStatus::Installed { | ||||||
|  |                             details: feature_details, | ||||||
|  |                         }, | ||||||
|  |                 } => { | ||||||
|  |                     details.extend(feature_details.clone()); | ||||||
|  |                 } | ||||||
|  |                 HarmonyEvent::HarmonyFinished => { | ||||||
|  |                     if !details.is_empty() { | ||||||
|  |                         println!( | ||||||
|  |                             "\n{} All done! Here's what's next for you:", | ||||||
|  |                             theme::EMOJI_SUMMARY | ||||||
|  |                         ); | ||||||
|  |                         for detail in details.iter() { | ||||||
|  |                             println!("- {detail}"); | ||||||
|  |                         } | ||||||
|  |                         println!(); | ||||||
|  |                     } | ||||||
|  |                 } | ||||||
|  |                 _ => {} | ||||||
|  |             }; | ||||||
|  |         } | ||||||
|  |     }); | ||||||
|  | } | ||||||
| @ -8,6 +8,7 @@ use inquire::Confirm; | |||||||
| use log::debug; | use log::debug; | ||||||
| 
 | 
 | ||||||
| pub mod cli_logger; // FIXME: Don't make me pub
 | pub mod cli_logger; // FIXME: Don't make me pub
 | ||||||
|  | mod cli_reporter; | ||||||
| pub mod progress; | pub mod progress; | ||||||
| pub mod theme; | pub mod theme; | ||||||
| 
 | 
 | ||||||
| @ -116,6 +117,7 @@ pub async fn run_cli<T: Topology + Send + Sync + 'static>( | |||||||
|     args: Args, |     args: Args, | ||||||
| ) -> Result<(), Box<dyn std::error::Error>> { | ) -> Result<(), Box<dyn std::error::Error>> { | ||||||
|     cli_logger::init(); |     cli_logger::init(); | ||||||
|  |     cli_reporter::init(); | ||||||
| 
 | 
 | ||||||
|     let mut maestro = Maestro::initialize(inventory, topology).await.unwrap(); |     let mut maestro = Maestro::initialize(inventory, topology).await.unwrap(); | ||||||
|     maestro.register_all(scores); |     maestro.register_all(scores); | ||||||
|  | |||||||
| @ -9,6 +9,7 @@ pub static EMOJI_ERROR: Emoji<'_, '_> = Emoji("⚠️", ""); | |||||||
| pub static EMOJI_DEPLOY: Emoji<'_, '_> = Emoji("🚀", ""); | pub static EMOJI_DEPLOY: Emoji<'_, '_> = Emoji("🚀", ""); | ||||||
| pub static EMOJI_TOPOLOGY: Emoji<'_, '_> = Emoji("📦", ""); | pub static EMOJI_TOPOLOGY: Emoji<'_, '_> = Emoji("📦", ""); | ||||||
| pub static EMOJI_SCORE: Emoji<'_, '_> = Emoji("🎶", ""); | pub static EMOJI_SCORE: Emoji<'_, '_> = Emoji("🎶", ""); | ||||||
|  | pub static EMOJI_SUMMARY: Emoji<'_, '_> = Emoji("🚀", ""); | ||||||
| 
 | 
 | ||||||
| lazy_static! { | lazy_static! { | ||||||
|     pub static ref SECTION_STYLE: ProgressStyle = ProgressStyle::default_spinner() |     pub static ref SECTION_STYLE: ProgressStyle = ProgressStyle::default_spinner() | ||||||
|  | |||||||
| @ -21,7 +21,6 @@ pub fn handle_events() { | |||||||
| 
 | 
 | ||||||
|     instrumentation::subscribe("Harmony Composer Logger", { |     instrumentation::subscribe("Harmony Composer Logger", { | ||||||
|         move |event| match event { |         move |event| match event { | ||||||
|             HarmonyComposerEvent::HarmonyComposerStarted => {} |  | ||||||
|             HarmonyComposerEvent::ProjectInitializationStarted => { |             HarmonyComposerEvent::ProjectInitializationStarted => { | ||||||
|                 progress_tracker.add_section( |                 progress_tracker.add_section( | ||||||
|                     SETUP_SECTION, |                     SETUP_SECTION, | ||||||
|  | |||||||
| @ -5,7 +5,6 @@ use crate::{HarmonyProfile, HarmonyTarget}; | |||||||
| 
 | 
 | ||||||
| #[derive(Debug, Clone)] | #[derive(Debug, Clone)] | ||||||
| pub enum HarmonyComposerEvent { | pub enum HarmonyComposerEvent { | ||||||
|     HarmonyComposerStarted, |  | ||||||
|     ProjectInitializationStarted, |     ProjectInitializationStarted, | ||||||
|     ProjectInitialized, |     ProjectInitialized, | ||||||
|     ProjectCompilationStarted { |     ProjectCompilationStarted { | ||||||
|  | |||||||
		Loading…
	
		Reference in New Issue
	
	Block a user