feat: Report execution outcome #151
| @ -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}"), | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | |||||||
| @ -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 ContinuousDelivery<A> | > 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 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!( | ||||||
|  |             "{}: {domain}", | ||||||
|  |             self.application.name() | ||||||
|  |         )])) | ||||||
|     } |     } | ||||||
|     fn name(&self) -> String { |     fn name(&self) -> String { | ||||||
|         "ContinuousDelivery".to_string() |         "ContinuousDelivery".to_string() | ||||||
|  | |||||||
| @ -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 { | ||||||
|  | |||||||
| @ -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 RHOBMonitoring | > 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"); |         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}" | ||||||
|                     ))); |                     ))); | ||||||
|                 } |                 } | ||||||
|             }; |             }; | ||||||
|  | |||||||
| @ -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}"); | ||||||
|                     } |                     } | ||||||
|                 }, |                 }, | ||||||
|  | |||||||
| @ -1,6 +1,9 @@ | |||||||
| use std::sync::Mutex; | use std::sync::Mutex; | ||||||
| 
 | 
 | ||||||
| use harmony::instrumentation::{self, HarmonyEvent}; | use harmony::{ | ||||||
|  |     instrumentation::{self, HarmonyEvent}, | ||||||
|  |     modules::application::ApplicationFeatureStatus, | ||||||
|  | }; | ||||||
| 
 | 
 | ||||||
| use crate::theme; | use crate::theme; | ||||||
| 
 | 
 | ||||||
| @ -11,26 +14,43 @@ pub fn init() { | |||||||
|         move |event| { |         move |event| { | ||||||
|             let mut details = details.lock().unwrap(); |             let mut details = details.lock().unwrap(); | ||||||
| 
 | 
 | ||||||
|             if let HarmonyEvent::InterpretExecutionFinished { |             match event { | ||||||
|                 execution_id: _, |                 HarmonyEvent::InterpretExecutionFinished { | ||||||
|                 topology: _, |                     execution_id: _, | ||||||
|                 interpret: _, |                     topology: _, | ||||||
|                 score: _, |                     interpret: _, | ||||||
|                 outcome: Ok(outcome), |                     score: _, | ||||||
|             } = event |                     outcome: Ok(outcome), | ||||||
|             { |                 } => { | ||||||
|                 if outcome.status == harmony::interpret::InterpretStatus::SUCCESS { |                     if outcome.status == harmony::interpret::InterpretStatus::SUCCESS { | ||||||
|                     details.extend(outcome.details.clone()); |                         details.extend(outcome.details.clone()); | ||||||
|  |                     } | ||||||
|                 } |                 } | ||||||
|             } else if let HarmonyEvent::HarmonyFinished = event |                 HarmonyEvent::ApplicationFeatureStateChanged { | ||||||
|                 && !details.is_empty() |                     topology: _, | ||||||
|             { |                     application: _, | ||||||
|                 println!("\n{} All done! What's next for you:", theme::EMOJI_SUMMARY); |                     feature: _, | ||||||
|                 for detail in details.iter() { |                     status: | ||||||
|                     println!("- {detail}"); |                         ApplicationFeatureStatus::Installed { | ||||||
|  |                             details: feature_details, | ||||||
|  |                         }, | ||||||
|  |                 } => { | ||||||
|  |                     details.extend(feature_details.clone()); | ||||||
|                 } |                 } | ||||||
|                 println!(); |                 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