feat: Report execution outcome #151
| @ -1,7 +1,10 @@ | ||||
| use std::error::Error; | ||||
| 
 | ||||
| use async_trait::async_trait; | ||||
| use derive_new::new; | ||||
| use serde::Serialize; | ||||
| 
 | ||||
| use crate::topology::Topology; | ||||
| use crate::{executors::ExecutorError, topology::Topology}; | ||||
| 
 | ||||
| /// An ApplicationFeature provided by harmony, such as Backups, Monitoring, MultisiteAvailability,
 | ||||
| /// ContinuousIntegration, ContinuousDelivery
 | ||||
| @ -9,7 +12,10 @@ use crate::topology::Topology; | ||||
| pub trait ApplicationFeature<T: Topology>: | ||||
|     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; | ||||
| } | ||||
| 
 | ||||
| @ -40,3 +46,60 @@ impl<T: Topology> Clone for Box<dyn ApplicationFeature<T>> { | ||||
|         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}"), | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| @ -10,7 +10,7 @@ use crate::{ | ||||
|     data::Version, | ||||
|     inventory::Inventory, | ||||
|     modules::application::{ | ||||
|         ApplicationFeature, HelmPackage, OCICompliant, | ||||
|         ApplicationFeature, HelmPackage, InstallationError, InstallationOutcome, OCICompliant, | ||||
|         features::{ArgoApplication, ArgoHelmScore}, | ||||
|     }, | ||||
|     score::Score, | ||||
| @ -141,7 +141,10 @@ impl< | ||||
|     T: Topology + HelmCommand + MultiTargetTopology + K8sclient + Ingress + 'static, | ||||
| > ApplicationFeature<T> for ContinuousDelivery<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 domain = topology | ||||
|             .get_domain(&self.application.name()) | ||||
| @ -205,7 +208,11 @@ impl< | ||||
|                     .unwrap(); | ||||
|             } | ||||
|         }; | ||||
|         Ok(()) | ||||
| 
 | ||||
|         Ok(InstallationOutcome::success_with_details(vec![format!( | ||||
|             "{}: {domain}", | ||||
|             self.application.name() | ||||
|         )])) | ||||
|     } | ||||
|     fn name(&self) -> String { | ||||
|         "ContinuousDelivery".to_string() | ||||
|  | ||||
| @ -2,7 +2,7 @@ use async_trait::async_trait; | ||||
| use log::info; | ||||
| 
 | ||||
| use crate::{ | ||||
|     modules::application::ApplicationFeature, | ||||
|     modules::application::{ApplicationFeature, InstallationError, InstallationOutcome}, | ||||
|     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
 | ||||
| #[async_trait] | ||||
| 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!( | ||||
|             "Making sure public endpoint is installed for 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::kube_prometheus::crd::crd_alertmanager_config::CRDPrometheus; | ||||
| use crate::topology::MultiTargetTopology; | ||||
| @ -43,7 +45,10 @@ impl< | ||||
|         + std::fmt::Debug, | ||||
| > 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"); | ||||
|         let namespace = topology | ||||
|             .get_tenant_config() | ||||
| @ -103,7 +108,7 @@ impl< | ||||
|             .await | ||||
|             .map_err(|e| e.to_string())?; | ||||
| 
 | ||||
|         Ok(()) | ||||
|         Ok(InstallationOutcome::success()) | ||||
|     } | ||||
| 
 | ||||
|     fn name(&self) -> String { | ||||
|  | ||||
| @ -1,6 +1,8 @@ | ||||
| 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::rhobs_application_monitoring_score::ApplicationRHOBMonitoringScore; | ||||
| 
 | ||||
| @ -43,7 +45,10 @@ impl< | ||||
|         + PrometheusApplicationMonitoring<RHOBObservability>, | ||||
| > ApplicationFeature<T> for RHOBMonitoring | ||||
| { | ||||
|     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"); | ||||
|         let namespace = topology | ||||
|             .get_tenant_config() | ||||
| @ -106,7 +111,7 @@ impl< | ||||
|             .interpret(&Inventory::empty(), topology) | ||||
|             .await | ||||
|             .map_err(|e| e.to_string())?; | ||||
|         Ok(()) | ||||
|         Ok(InstallationOutcome::success()) | ||||
|     } | ||||
|     fn name(&self) -> String { | ||||
|         "Monitoring".to_string() | ||||
|  | ||||
| @ -24,8 +24,8 @@ use harmony_types::id::Id; | ||||
| #[derive(Clone, Debug)] | ||||
| pub enum ApplicationFeatureStatus { | ||||
|     Installing, | ||||
|     Installed, | ||||
|     Failed { details: String }, | ||||
|     Installed { details: Vec<String> }, | ||||
|     Failed { message: String }, | ||||
| } | ||||
| 
 | ||||
| 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(); | ||||
| 
 | ||||
|             let _ = match feature.ensure_installed(topology).await { | ||||
|                 Ok(()) => { | ||||
|                 Ok(outcome) => { | ||||
|                     instrumentation::instrument(HarmonyEvent::ApplicationFeatureStateChanged { | ||||
|                         topology: topology.name().into(), | ||||
|                         application: self.application.name(), | ||||
|                         feature: feature.name(), | ||||
|                         status: ApplicationFeatureStatus::Installed, | ||||
|                         status: ApplicationFeatureStatus::Installed { | ||||
|                             details: match outcome { | ||||
|                                 InstallationOutcome::Success { details } => details, | ||||
|                                 InstallationOutcome::Noop => vec![], | ||||
|                             }, | ||||
|                         }, | ||||
|                     }) | ||||
|                     .unwrap(); | ||||
|                 } | ||||
|                 Err(msg) => { | ||||
|                 Err(error) => { | ||||
|                     instrumentation::instrument(HarmonyEvent::ApplicationFeatureStateChanged { | ||||
|                         topology: topology.name().into(), | ||||
|                         application: self.application.name(), | ||||
|                         feature: feature.name(), | ||||
|                         status: ApplicationFeatureStatus::Failed { | ||||
|                             details: msg.clone(), | ||||
|                             message: error.to_string(), | ||||
|                         }, | ||||
|                     }) | ||||
|                     .unwrap(); | ||||
|                     return Err(InterpretError::new(format!( | ||||
|                         "Application Interpret failed to install feature : {msg}" | ||||
|                         "Application Interpret failed to install feature : {error}" | ||||
|                     ))); | ||||
|                 } | ||||
|             }; | ||||
|  | ||||
| @ -178,10 +178,10 @@ fn handle_events() { | ||||
|                     ApplicationFeatureStatus::Installing => { | ||||
|                         info!("Installing feature '{feature}' for '{application}'..."); | ||||
|                     } | ||||
|                     ApplicationFeatureStatus::Installed => { | ||||
|                     ApplicationFeatureStatus::Installed { details: _ } => { | ||||
|                         info!(status = "finished"; "Feature '{feature}' installed"); | ||||
|                     } | ||||
|                     ApplicationFeatureStatus::Failed { details } => { | ||||
|                     ApplicationFeatureStatus::Failed { message: details } => { | ||||
|                         error!(status = "failed"; "Feature '{feature}' installation failed: {details}"); | ||||
|                     } | ||||
|                 }, | ||||
|  | ||||
| @ -1,6 +1,9 @@ | ||||
| use std::sync::Mutex; | ||||
| 
 | ||||
| use harmony::instrumentation::{self, HarmonyEvent}; | ||||
| use harmony::{ | ||||
|     instrumentation::{self, HarmonyEvent}, | ||||
|     modules::application::ApplicationFeatureStatus, | ||||
| }; | ||||
| 
 | ||||
| use crate::theme; | ||||
| 
 | ||||
| @ -11,26 +14,43 @@ pub fn init() { | ||||
|         move |event| { | ||||
|             let mut details = details.lock().unwrap(); | ||||
| 
 | ||||
|             if let HarmonyEvent::InterpretExecutionFinished { | ||||
|             match event { | ||||
|                 HarmonyEvent::InterpretExecutionFinished { | ||||
|                     execution_id: _, | ||||
|                     topology: _, | ||||
|                     interpret: _, | ||||
|                     score: _, | ||||
|                     outcome: Ok(outcome), | ||||
|             } = event | ||||
|             { | ||||
|                 } => { | ||||
|                     if outcome.status == harmony::interpret::InterpretStatus::SUCCESS { | ||||
|                         details.extend(outcome.details.clone()); | ||||
|                     } | ||||
|             } else if let HarmonyEvent::HarmonyFinished = event | ||||
|                 && !details.is_empty() | ||||
|             { | ||||
|                 println!("\n{} All done! What's next for you:", theme::EMOJI_SUMMARY); | ||||
|                 } | ||||
|                 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!(); | ||||
|                     } | ||||
|                 } | ||||
|                 _ => {} | ||||
|             }; | ||||
|         } | ||||
|     }); | ||||
| } | ||||
|  | ||||
		Loading…
	
		Reference in New Issue
	
	Block a user