forked from NationTech/harmony
		
	Compare commits
	
		
			3 Commits
		
	
	
		
			master
			...
			better-ind
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|  | 387ae9f494 | ||
|  | 336e1cfefe | ||
|  | 403e199062 | 
| @ -10,13 +10,16 @@ use super::{ | |||||||
| #[derive(Debug, Clone)] | #[derive(Debug, Clone)] | ||||||
| pub enum HarmonyEvent { | pub enum HarmonyEvent { | ||||||
|     HarmonyStarted, |     HarmonyStarted, | ||||||
|  |     HarmonyFinished, | ||||||
|     InterpretExecutionStarted { |     InterpretExecutionStarted { | ||||||
|  |         execution_id: String, | ||||||
|         topology: String, |         topology: String, | ||||||
|         interpret: String, |         interpret: String, | ||||||
|         score: String, |         score: String, | ||||||
|         message: String, |         message: String, | ||||||
|     }, |     }, | ||||||
|     InterpretExecutionFinished { |     InterpretExecutionFinished { | ||||||
|  |         execution_id: String, | ||||||
|         topology: String, |         topology: String, | ||||||
|         interpret: String, |         interpret: String, | ||||||
|         score: String, |         score: String, | ||||||
|  | |||||||
| @ -5,6 +5,7 @@ use serde::Serialize; | |||||||
| use serde_value::Value; | use serde_value::Value; | ||||||
| 
 | 
 | ||||||
| use super::{ | use super::{ | ||||||
|  |     data::Id, | ||||||
|     instrumentation::{self, HarmonyEvent}, |     instrumentation::{self, HarmonyEvent}, | ||||||
|     interpret::{Interpret, InterpretError, Outcome}, |     interpret::{Interpret, InterpretError, Outcome}, | ||||||
|     inventory::Inventory, |     inventory::Inventory, | ||||||
| @ -20,9 +21,11 @@ pub trait Score<T: Topology>: | |||||||
|         inventory: &Inventory, |         inventory: &Inventory, | ||||||
|         topology: &T, |         topology: &T, | ||||||
|     ) -> Result<Outcome, InterpretError> { |     ) -> Result<Outcome, InterpretError> { | ||||||
|  |         let id = Id::default(); | ||||||
|         let interpret = self.create_interpret(); |         let interpret = self.create_interpret(); | ||||||
| 
 | 
 | ||||||
|         instrumentation::instrument(HarmonyEvent::InterpretExecutionStarted { |         instrumentation::instrument(HarmonyEvent::InterpretExecutionStarted { | ||||||
|  |             execution_id: id.clone().to_string(), | ||||||
|             topology: topology.name().into(), |             topology: topology.name().into(), | ||||||
|             interpret: interpret.get_name().to_string(), |             interpret: interpret.get_name().to_string(), | ||||||
|             score: self.name(), |             score: self.name(), | ||||||
| @ -32,6 +35,7 @@ pub trait Score<T: Topology>: | |||||||
|         let result = interpret.execute(inventory, topology).await; |         let result = interpret.execute(inventory, topology).await; | ||||||
| 
 | 
 | ||||||
|         instrumentation::instrument(HarmonyEvent::InterpretExecutionFinished { |         instrumentation::instrument(HarmonyEvent::InterpretExecutionFinished { | ||||||
|  |             execution_id: id.clone().to_string(), | ||||||
|             topology: topology.name().into(), |             topology: topology.name().into(), | ||||||
|             interpret: interpret.get_name().to_string(), |             interpret: interpret.get_name().to_string(), | ||||||
|             score: self.name(), |             score: self.name(), | ||||||
|  | |||||||
| @ -59,7 +59,7 @@ impl<T: Topology + K8sclient + HelmCommand> Interpret<T> for ArgoInterpret { | |||||||
|             .await |             .await | ||||||
|             .unwrap(); |             .unwrap(); | ||||||
|         Ok(Outcome::success(format!( |         Ok(Outcome::success(format!( | ||||||
|             "Successfully installed ArgoCD and {} Applications", |             "ArgoCD installed with {} applications", | ||||||
|             self.argo_apps.len() |             self.argo_apps.len() | ||||||
|         ))) |         ))) | ||||||
|     } |     } | ||||||
|  | |||||||
| @ -60,7 +60,7 @@ impl<A: Application, T: Topology + std::fmt::Debug> Interpret<T> for Application | |||||||
|                 } |                 } | ||||||
|             }; |             }; | ||||||
|         } |         } | ||||||
|         Ok(Outcome::success("successfully created app".to_string())) |         Ok(Outcome::success("Application created".to_string())) | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     fn get_name(&self) -> InterpretName { |     fn get_name(&self) -> InterpretName { | ||||||
|  | |||||||
| @ -46,7 +46,7 @@ where | |||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     fn name(&self) -> String { |     fn name(&self) -> String { | ||||||
|         format!("Application: {}", self.application.name()) |         format!("{} [ApplicationScore]", self.application.name()) | ||||||
|     } |     } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -55,7 +55,7 @@ impl<T: Topology + HelmCommand> Score<T> for HelmChartScore { | |||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     fn name(&self) -> String { |     fn name(&self) -> String { | ||||||
|         format!("{} {} HelmChartScore", self.release_name, self.chart_name) |         format!("{} [HelmChartScore]", self.release_name) | ||||||
|     } |     } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| @ -225,19 +225,20 @@ 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::new( | ||||||
|                 InterpretStatus::SUCCESS, |                 InterpretStatus::SUCCESS, | ||||||
|                 "Helm Chart deployed".to_string(), |                 format!("Helm Chart {} deployed", self.score.release_name), | ||||||
|             )), |             )), | ||||||
|             helm_wrapper_rs::HelmDeployStatus::PendingInstall => Ok(Outcome::new( |             helm_wrapper_rs::HelmDeployStatus::PendingInstall => Ok(Outcome::new( | ||||||
|                 InterpretStatus::RUNNING, |                 InterpretStatus::RUNNING, | ||||||
|                 "Helm Chart Pending install".to_string(), |                 format!("Helm Chart {} pending install...", self.score.release_name), | ||||||
|             )), |             )), | ||||||
|             helm_wrapper_rs::HelmDeployStatus::PendingUpgrade => Ok(Outcome::new( |             helm_wrapper_rs::HelmDeployStatus::PendingUpgrade => Ok(Outcome::new( | ||||||
|                 InterpretStatus::RUNNING, |                 InterpretStatus::RUNNING, | ||||||
|                 "Helm Chart pending upgrade".to_string(), |                 format!("Helm Chart {} pending upgrade...", self.score.release_name), | ||||||
|             )), |  | ||||||
|             helm_wrapper_rs::HelmDeployStatus::Failed => Err(InterpretError::new( |  | ||||||
|                 "Failed to install helm chart".to_string(), |  | ||||||
|             )), |             )), | ||||||
|  |             helm_wrapper_rs::HelmDeployStatus::Failed => Err(InterpretError::new(format!( | ||||||
|  |                 "Helm Chart {} installation failed", | ||||||
|  |                 self.score.release_name | ||||||
|  |             ))), | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -7,7 +7,6 @@ use serde::Serialize; | |||||||
| use crate::{ | use crate::{ | ||||||
|     config::HARMONY_DATA_DIR, |     config::HARMONY_DATA_DIR, | ||||||
|     data::{Id, Version}, |     data::{Id, Version}, | ||||||
|     instrumentation::{self, HarmonyEvent}, |  | ||||||
|     interpret::{Interpret, InterpretError, InterpretName, InterpretStatus, Outcome}, |     interpret::{Interpret, InterpretError, InterpretName, InterpretStatus, Outcome}, | ||||||
|     inventory::Inventory, |     inventory::Inventory, | ||||||
|     score::Score, |     score::Score, | ||||||
|  | |||||||
| @ -33,7 +33,10 @@ impl<T: Topology + PrometheusApplicationMonitoring<CRDPrometheus>> Score<T> | |||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     fn name(&self) -> String { |     fn name(&self) -> String { | ||||||
|         "ApplicationMonitoringScore".to_string() |         format!( | ||||||
|  |             "{} monitoring [ApplicationMonitoringScore]", | ||||||
|  |             self.application.name() | ||||||
|  |         ) | ||||||
|     } |     } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| @ -61,7 +64,9 @@ impl<T: Topology + PrometheusApplicationMonitoring<CRDPrometheus>> Interpret<T> | |||||||
| 
 | 
 | ||||||
|         match result { |         match result { | ||||||
|             Ok(outcome) => match outcome { |             Ok(outcome) => match outcome { | ||||||
|                 PreparationOutcome::Success { details } => Ok(Outcome::success(details)), |                 PreparationOutcome::Success { details: _ } => { | ||||||
|  |                     Ok(Outcome::success("Prometheus installed".into())) | ||||||
|  |                 } | ||||||
|                 PreparationOutcome::Noop => Ok(Outcome::noop()), |                 PreparationOutcome::Noop => Ok(Outcome::noop()), | ||||||
|             }, |             }, | ||||||
|             Err(err) => Err(InterpretError::from(err)), |             Err(err) => Err(InterpretError::from(err)), | ||||||
|  | |||||||
| @ -28,7 +28,7 @@ impl<T: Topology + HelmCommand + K8sclient> Score<T> for NtfyScore { | |||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     fn name(&self) -> String { |     fn name(&self) -> String { | ||||||
|         "NtfyScore".to_string() |         "alert receiver [NtfyScore]".into() | ||||||
|     } |     } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| @ -119,7 +119,7 @@ impl<T: Topology + HelmCommand + K8sclient> Interpret<T> for NtfyInterpret { | |||||||
| 
 | 
 | ||||||
|         debug!("exec into pod done"); |         debug!("exec into pod done"); | ||||||
| 
 | 
 | ||||||
|         Ok(Outcome::success("installed ntfy".to_string())) |         Ok(Outcome::success("Ntfy installed".to_string())) | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     fn get_name(&self) -> InterpretName { |     fn get_name(&self) -> InterpretName { | ||||||
|  | |||||||
| @ -61,7 +61,7 @@ impl<T: Topology + K8sclient + PrometheusApplicationMonitoring<CRDPrometheus>> S | |||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     fn name(&self) -> String { |     fn name(&self) -> String { | ||||||
|         "CRDApplicationAlertingScore".into() |         "prometheus alerting [CRDAlertingScore]".into() | ||||||
|     } |     } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| @ -94,7 +94,7 @@ impl<T: Topology + K8sclient + PrometheusApplicationMonitoring<CRDPrometheus>> I | |||||||
|         self.install_monitors(self.service_monitors.clone(), &client) |         self.install_monitors(self.service_monitors.clone(), &client) | ||||||
|             .await?; |             .await?; | ||||||
|         Ok(Outcome::success( |         Ok(Outcome::success( | ||||||
|             "deployed application monitoring composants".to_string(), |             "K8s monitoring components installed".to_string(), | ||||||
|         )) |         )) | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -28,7 +28,7 @@ impl<T: Topology + TenantManager> Score<T> for TenantScore { | |||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     fn name(&self) -> String { |     fn name(&self) -> String { | ||||||
|         format!("{} TenantScore", self.config.name) |         format!("{} [TenantScore]", self.config.name) | ||||||
|     } |     } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| @ -47,8 +47,8 @@ impl<T: Topology + TenantManager> Interpret<T> for TenantInterpret { | |||||||
|         topology.provision_tenant(&self.tenant_config).await?; |         topology.provision_tenant(&self.tenant_config).await?; | ||||||
| 
 | 
 | ||||||
|         Ok(Outcome::success(format!( |         Ok(Outcome::success(format!( | ||||||
|             "Successfully provisioned tenant {} with id {}", |             "Tenant provisioned with id '{}'", | ||||||
|             self.tenant_config.name, self.tenant_config.id |             self.tenant_config.id | ||||||
|         ))) |         ))) | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -2,18 +2,15 @@ use harmony::{ | |||||||
|     instrumentation::{self, HarmonyEvent}, |     instrumentation::{self, HarmonyEvent}, | ||||||
|     topology::TopologyStatus, |     topology::TopologyStatus, | ||||||
| }; | }; | ||||||
| use indicatif::{MultiProgress, ProgressBar}; | use indicatif::MultiProgress; | ||||||
| use indicatif_log_bridge::LogWrapper; | use indicatif_log_bridge::LogWrapper; | ||||||
| use std::{ | use std::sync::{Arc, Mutex}; | ||||||
|     collections::HashMap, |  | ||||||
|     sync::{Arc, Mutex}, |  | ||||||
| }; |  | ||||||
| 
 | 
 | ||||||
| use crate::progress; | use crate::progress::{IndicatifProgressTracker, ProgressTracker}; | ||||||
| 
 | 
 | ||||||
| pub fn init() -> tokio::task::JoinHandle<()> { | pub fn init() -> tokio::task::JoinHandle<()> { | ||||||
|     configure_logger(); |     let base_progress = configure_logger(); | ||||||
|     let handle = tokio::spawn(handle_events()); |     let handle = tokio::spawn(handle_events(base_progress)); | ||||||
| 
 | 
 | ||||||
|     loop { |     loop { | ||||||
|         if instrumentation::instrument(HarmonyEvent::HarmonyStarted).is_ok() { |         if instrumentation::instrument(HarmonyEvent::HarmonyStarted).is_ok() { | ||||||
| @ -24,32 +21,45 @@ pub fn init() -> tokio::task::JoinHandle<()> { | |||||||
|     handle |     handle | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| fn configure_logger() { | fn configure_logger() -> MultiProgress { | ||||||
|     let logger = |     let logger = | ||||||
|         env_logger::Builder::from_env(env_logger::Env::default().default_filter_or("info")).build(); |         env_logger::Builder::from_env(env_logger::Env::default().default_filter_or("info")).build(); | ||||||
|     let level = logger.filter(); |     let level = logger.filter(); | ||||||
|     let multi = MultiProgress::new(); |     let progress = MultiProgress::new(); | ||||||
|     LogWrapper::new(multi.clone(), logger).try_init().unwrap(); | 
 | ||||||
|  |     LogWrapper::new(progress.clone(), logger) | ||||||
|  |         .try_init() | ||||||
|  |         .unwrap(); | ||||||
|     log::set_max_level(level); |     log::set_max_level(level); | ||||||
|  | 
 | ||||||
|  |     progress | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| async fn handle_events() { | async fn handle_events(base_progress: MultiProgress) { | ||||||
|     instrumentation::subscribe("Harmony CLI Logger", { |     let progress_tracker = Arc::new(IndicatifProgressTracker::new(base_progress.clone())); | ||||||
|         let sections: Arc<Mutex<HashMap<String, MultiProgress>>> = |     let preparing_topology = Arc::new(Mutex::new(false)); | ||||||
|             Arc::new(Mutex::new(HashMap::new())); |     let current_score: Arc<Mutex<Option<String>>> = Arc::new(Mutex::new(None)); | ||||||
|         let progress_bars: Arc<Mutex<HashMap<String, ProgressBar>>> = |  | ||||||
|             Arc::new(Mutex::new(HashMap::new())); |  | ||||||
| 
 | 
 | ||||||
|  |     instrumentation::subscribe("Harmony CLI Logger", { | ||||||
|         move |event| { |         move |event| { | ||||||
|             let sections_clone = Arc::clone(§ions); |             let progress_tracker = Arc::clone(&progress_tracker); | ||||||
|             let progress_bars_clone = Arc::clone(&progress_bars); |             let preparing_topology = Arc::clone(&preparing_topology); | ||||||
|  |             let current_score = Arc::clone(¤t_score); | ||||||
| 
 | 
 | ||||||
|             async move { |             async move { | ||||||
|                 let mut sections = sections_clone.lock().unwrap(); |                 let mut preparing_topology = preparing_topology.lock().unwrap(); | ||||||
|                 let mut progress_bars = progress_bars_clone.lock().unwrap(); |                 let mut current_score = current_score.lock().unwrap(); | ||||||
| 
 | 
 | ||||||
|                 match event { |                 match event { | ||||||
|                     HarmonyEvent::HarmonyStarted => {} |                     HarmonyEvent::HarmonyStarted => {} | ||||||
|  |                     HarmonyEvent::HarmonyFinished => { | ||||||
|  |                         progress_tracker.add_section( | ||||||
|  |                             "harmony-summary", | ||||||
|  |                             &format!("\n{} Harmony completed\n\n", crate::theme::EMOJI_HARMONY), | ||||||
|  |                         ); | ||||||
|  |                         progress_tracker.add_section("harmony-finished", "\n\n"); | ||||||
|  |                         return false; | ||||||
|  |                     } | ||||||
|                     HarmonyEvent::TopologyStateChanged { |                     HarmonyEvent::TopologyStateChanged { | ||||||
|                         topology, |                         topology, | ||||||
|                         status, |                         status, | ||||||
| @ -60,111 +70,95 @@ async fn handle_events() { | |||||||
|                         match status { |                         match status { | ||||||
|                             TopologyStatus::Queued => {} |                             TopologyStatus::Queued => {} | ||||||
|                             TopologyStatus::Preparing => { |                             TopologyStatus::Preparing => { | ||||||
|                                 let section = progress::new_section(format!( |                                 progress_tracker.add_section( | ||||||
|                                     "{} Preparing environment: {topology}...", |                                     §ion_key, | ||||||
|                                     crate::theme::EMOJI_TOPOLOGY, |                                     &format!( | ||||||
|                                 )); |                                         "\n{} Preparing environment: {topology}...", | ||||||
|                                 (*sections).insert(section_key, section); |                                         crate::theme::EMOJI_TOPOLOGY | ||||||
|  |                                     ), | ||||||
|  |                                 ); | ||||||
|  |                                 (*preparing_topology) = true; | ||||||
|                             } |                             } | ||||||
|                             TopologyStatus::Success => { |                             TopologyStatus::Success => { | ||||||
|                                 let section = (*sections).get(§ion_key).unwrap(); |                                 (*preparing_topology) = false; | ||||||
|                                 let progress = progress::add_spinner(section, "".into()); |                                 progress_tracker.add_task(§ion_key, "topology-success", ""); | ||||||
| 
 |                                 progress_tracker | ||||||
|                                 progress::success( |                                     .finish_task("topology-success", &message.unwrap_or("".into())); | ||||||
|                                     section, |  | ||||||
|                                     Some(progress), |  | ||||||
|                                     message.unwrap_or("".into()), |  | ||||||
|                                 ); |  | ||||||
| 
 |  | ||||||
|                                 (*sections).remove(§ion_key); |  | ||||||
|                             } |                             } | ||||||
|                             TopologyStatus::Noop => { |                             TopologyStatus::Noop => { | ||||||
|                                 let section = (*sections).get(§ion_key).unwrap(); |                                 (*preparing_topology) = false; | ||||||
|                                 let progress = progress::add_spinner(section, "".into()); |                                 progress_tracker.add_task(§ion_key, "topology-skip", ""); | ||||||
| 
 |                                 progress_tracker | ||||||
|                                 progress::skip( |                                     .skip_task("topology-skip", &message.unwrap_or("".into())); | ||||||
|                                     section, |  | ||||||
|                                     Some(progress), |  | ||||||
|                                     message.unwrap_or("".into()), |  | ||||||
|                                 ); |  | ||||||
| 
 |  | ||||||
|                                 (*sections).remove(§ion_key); |  | ||||||
|                             } |                             } | ||||||
|                             TopologyStatus::Error => { |                             TopologyStatus::Error => { | ||||||
|                                 let section = (*sections).get(§ion_key).unwrap(); |                                 progress_tracker.add_task(§ion_key, "topology-error", ""); | ||||||
|                                 let progress = progress::add_spinner(section, "".into()); |                                 (*preparing_topology) = false; | ||||||
| 
 |                                 progress_tracker | ||||||
|                                 progress::error( |                                     .fail_task("topology-error", &message.unwrap_or("".into())); | ||||||
|                                     section, |  | ||||||
|                                     Some(progress), |  | ||||||
|                                     message.unwrap_or("".into()), |  | ||||||
|                                 ); |  | ||||||
| 
 |  | ||||||
|                                 (*sections).remove(§ion_key); |  | ||||||
|                             } |                             } | ||||||
|                         } |                         } | ||||||
|                     } |                     } | ||||||
|                     HarmonyEvent::InterpretExecutionStarted { |                     HarmonyEvent::InterpretExecutionStarted { | ||||||
|  |                         execution_id: task_key, | ||||||
|                         topology, |                         topology, | ||||||
|                         interpret, |                         interpret: _, | ||||||
|                         score, |                         score, | ||||||
|                         message, |                         message, | ||||||
|                     } => { |                     } => { | ||||||
|                         let section_key = if (*sections).contains_key(&topology_key(&topology)) { |                         let is_key_topology = (*preparing_topology) | ||||||
|  |                             && progress_tracker.contains_section(&topology_key(&topology)); | ||||||
|  |                         let is_key_current_score = current_score.is_some() | ||||||
|  |                             && progress_tracker | ||||||
|  |                                 .contains_section(&score_key(¤t_score.clone().unwrap())); | ||||||
|  |                         let is_key_score = progress_tracker.contains_section(&score_key(&score)); | ||||||
|  | 
 | ||||||
|  |                         let section_key = if is_key_topology { | ||||||
|                             topology_key(&topology) |                             topology_key(&topology) | ||||||
|                         } else if (*sections).contains_key(&score_key(&score)) { |                         } else if is_key_current_score { | ||||||
|                             score_key(&interpret) |                             score_key(¤t_score.clone().unwrap()) | ||||||
|  |                         } else if is_key_score { | ||||||
|  |                             score_key(&score) | ||||||
|                         } else { |                         } else { | ||||||
|  |                             (*current_score) = Some(score.clone()); | ||||||
|                             let key = score_key(&score); |                             let key = score_key(&score); | ||||||
|                             let section = progress::new_section(format!( |                             progress_tracker.add_section( | ||||||
|                                 "\n{} Interpreting score: {score}...", |                                 &key, | ||||||
|                                 crate::theme::EMOJI_SCORE, |                                 &format!( | ||||||
|                             )); |                                     "{} Interpreting score: {score}...", | ||||||
|                             (*sections).insert(key.clone(), section); |                                     crate::theme::EMOJI_SCORE | ||||||
|  |                                 ), | ||||||
|  |                             ); | ||||||
|                             key |                             key | ||||||
|                         }; |                         }; | ||||||
|                         let section = (*sections).get(§ion_key).unwrap(); |  | ||||||
|                         let progress_bar = progress::add_spinner(section, message); |  | ||||||
| 
 | 
 | ||||||
|                         (*progress_bars).insert(interpret_key(&interpret), progress_bar); |                         progress_tracker.add_task(§ion_key, &task_key, &message); | ||||||
|                     } |                     } | ||||||
|                     HarmonyEvent::InterpretExecutionFinished { |                     HarmonyEvent::InterpretExecutionFinished { | ||||||
|                         topology, |                         execution_id: task_key, | ||||||
|                         interpret, |                         topology: _, | ||||||
|  |                         interpret: _, | ||||||
|                         score, |                         score, | ||||||
|                         outcome, |                         outcome, | ||||||
|                     } => { |                     } => { | ||||||
|                         let has_topology = (*sections).contains_key(&topology_key(&topology)); |                         if current_score.is_some() && current_score.clone().unwrap() == score { | ||||||
|                         let section_key = if has_topology { |                             (*current_score) = None; | ||||||
|                             topology_key(&topology) |                         } | ||||||
|                         } else { |  | ||||||
|                             score_key(&score) |  | ||||||
|                         }; |  | ||||||
| 
 |  | ||||||
|                         let section = (*sections).get(§ion_key).unwrap(); |  | ||||||
|                         let progress_bar = |  | ||||||
|                             (*progress_bars).get(&interpret_key(&interpret)).cloned(); |  | ||||||
| 
 |  | ||||||
|                         let _ = section.clear(); |  | ||||||
| 
 | 
 | ||||||
|                         match outcome { |                         match outcome { | ||||||
|                             Ok(outcome) => match outcome.status { |                             Ok(outcome) => match outcome.status { | ||||||
|                                 harmony::interpret::InterpretStatus::SUCCESS => { |                                 harmony::interpret::InterpretStatus::SUCCESS => { | ||||||
|                                     progress::success(section, progress_bar, outcome.message) |                                     progress_tracker.finish_task(&task_key, &outcome.message); | ||||||
|                                 } |                                 } | ||||||
|                                 harmony::interpret::InterpretStatus::NOOP => { |                                 harmony::interpret::InterpretStatus::NOOP => { | ||||||
|                                     progress::skip(section, progress_bar, outcome.message) |                                     progress_tracker.skip_task(&task_key, &outcome.message); | ||||||
|                                 } |                                 } | ||||||
|                                 _ => progress::error(section, progress_bar, outcome.message), |                                 _ => progress_tracker.fail_task(&task_key, &outcome.message), | ||||||
|                             }, |                             }, | ||||||
|                             Err(err) => { |                             Err(err) => { | ||||||
|                                 progress::error(section, progress_bar, err.to_string()); |                                 progress_tracker.fail_task(&task_key, &err.to_string()); | ||||||
|                             } |                             } | ||||||
|                         } |                         } | ||||||
| 
 |  | ||||||
|                         if !has_topology { |  | ||||||
|                             (*progress_bars).remove(§ion_key); |  | ||||||
|                         } |  | ||||||
|                     } |                     } | ||||||
|                 } |                 } | ||||||
|                 true |                 true | ||||||
| @ -181,7 +175,3 @@ fn topology_key(topology: &str) -> String { | |||||||
| fn score_key(score: &str) -> String { | fn score_key(score: &str) -> String { | ||||||
|     format!("score-{score}") |     format!("score-{score}") | ||||||
| } | } | ||||||
| 
 |  | ||||||
| fn interpret_key(interpret: &str) -> String { |  | ||||||
|     format!("interpret-{interpret}") |  | ||||||
| } |  | ||||||
|  | |||||||
| @ -1,5 +1,6 @@ | |||||||
| use clap::Parser; | use clap::Parser; | ||||||
| use clap::builder::ArgPredicate; | use clap::builder::ArgPredicate; | ||||||
|  | use harmony::instrumentation; | ||||||
| use harmony::inventory::Inventory; | use harmony::inventory::Inventory; | ||||||
| use harmony::maestro::Maestro; | use harmony::maestro::Maestro; | ||||||
| use harmony::{score::Score, topology::Topology}; | use harmony::{score::Score, topology::Topology}; | ||||||
| @ -97,6 +98,7 @@ pub async fn run<T: Topology + Send + Sync + 'static>( | |||||||
| 
 | 
 | ||||||
|     let result = init(maestro, args_struct).await; |     let result = init(maestro, args_struct).await; | ||||||
| 
 | 
 | ||||||
|  |     instrumentation::instrument(instrumentation::HarmonyEvent::HarmonyFinished).unwrap(); | ||||||
|     let _ = tokio::try_join!(cli_logger_handle); |     let _ = tokio::try_join!(cli_logger_handle); | ||||||
|     result |     result | ||||||
| } | } | ||||||
|  | |||||||
| @ -1,50 +1,163 @@ | |||||||
|  | use indicatif::{MultiProgress, ProgressBar}; | ||||||
|  | use std::collections::HashMap; | ||||||
|  | use std::sync::{Arc, Mutex}; | ||||||
| use std::time::Duration; | use std::time::Duration; | ||||||
| 
 | 
 | ||||||
| use indicatif::{MultiProgress, ProgressBar}; | pub trait ProgressTracker: Send + Sync { | ||||||
| 
 |     fn contains_section(&self, id: &str) -> bool; | ||||||
| pub fn new_section(title: String) -> MultiProgress { |     fn add_section(&self, id: &str, message: &str); | ||||||
|     let multi_progress = MultiProgress::new(); |     fn add_task(&self, section_id: &str, task_id: &str, message: &str); | ||||||
|     let _ = multi_progress.println(title); |     fn finish_task(&self, id: &str, message: &str); | ||||||
| 
 |     fn fail_task(&self, id: &str, message: &str); | ||||||
|     multi_progress |     fn skip_task(&self, id: &str, message: &str); | ||||||
|  |     fn clear(&self); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| pub fn add_spinner(multi_progress: &MultiProgress, message: String) -> ProgressBar { | struct Section { | ||||||
|     let progress = multi_progress.add(ProgressBar::new_spinner()); |     header_index: usize, | ||||||
| 
 |     task_count: usize, | ||||||
|     progress.set_style(crate::theme::SPINNER_STYLE.clone()); |     pb: ProgressBar, | ||||||
|     progress.set_message(message); |  | ||||||
|     progress.enable_steady_tick(Duration::from_millis(100)); |  | ||||||
| 
 |  | ||||||
|     progress |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| pub fn success(multi_progress: &MultiProgress, progress: Option<ProgressBar>, message: String) { | struct IndicatifProgressTrackerState { | ||||||
|     if let Some(progress) = progress { |     sections: HashMap<String, Section>, | ||||||
|         multi_progress.remove(&progress) |     tasks: HashMap<String, ProgressBar>, | ||||||
|  |     pb_count: usize, | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | #[derive(Clone)] | ||||||
|  | pub struct IndicatifProgressTracker { | ||||||
|  |     mp: MultiProgress, | ||||||
|  |     state: Arc<Mutex<IndicatifProgressTrackerState>>, | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | impl IndicatifProgressTracker { | ||||||
|  |     pub fn new(base: MultiProgress) -> Self { | ||||||
|  |         // The indicatif log bridge will insert a progress bar at the top.
 | ||||||
|  |         // To prevent our first section from being erased, we need to create
 | ||||||
|  |         // a dummy progress bar as our first progress bar.
 | ||||||
|  |         let _ = base.clear(); | ||||||
|  |         let log_pb = base.add(ProgressBar::new(1)); | ||||||
|  | 
 | ||||||
|  |         let mut sections = HashMap::new(); | ||||||
|  |         sections.insert( | ||||||
|  |             "__log__".into(), | ||||||
|  |             Section { | ||||||
|  |                 header_index: 0, | ||||||
|  |                 task_count: 0, | ||||||
|  |                 pb: log_pb.clone(), | ||||||
|  |             }, | ||||||
|  |         ); | ||||||
|  | 
 | ||||||
|  |         let mut tasks = HashMap::new(); | ||||||
|  |         tasks.insert("__log__".into(), log_pb); | ||||||
|  | 
 | ||||||
|  |         let state = Arc::new(Mutex::new(IndicatifProgressTrackerState { | ||||||
|  |             sections, | ||||||
|  |             tasks, | ||||||
|  |             pb_count: 1, | ||||||
|  |         })); | ||||||
|  | 
 | ||||||
|  |         Self { mp: base, state } | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | impl ProgressTracker for IndicatifProgressTracker { | ||||||
|  |     fn add_section(&self, id: &str, message: &str) { | ||||||
|  |         let mut state = self.state.lock().unwrap(); | ||||||
|  | 
 | ||||||
|  |         let header_pb = self | ||||||
|  |             .mp | ||||||
|  |             .add(ProgressBar::new(1).with_style(crate::theme::SECTION_STYLE.clone())); | ||||||
|  |         header_pb.finish_with_message(message.to_string()); | ||||||
|  | 
 | ||||||
|  |         let header_index = state.pb_count; | ||||||
|  |         state.pb_count += 1; | ||||||
|  | 
 | ||||||
|  |         state.sections.insert( | ||||||
|  |             id.to_string(), | ||||||
|  |             Section { | ||||||
|  |                 header_index, | ||||||
|  |                 task_count: 0, | ||||||
|  |                 pb: header_pb, | ||||||
|  |             }, | ||||||
|  |         ); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     let progress = multi_progress.add(ProgressBar::new_spinner()); |     fn add_task(&self, section_id: &str, task_id: &str, message: &str) { | ||||||
|     progress.set_style(crate::theme::SUCCESS_SPINNER_STYLE.clone()); |         let mut state = self.state.lock().unwrap(); | ||||||
|     progress.finish_with_message(message); |  | ||||||
| } |  | ||||||
| 
 | 
 | ||||||
| pub fn error(multi_progress: &MultiProgress, progress: Option<ProgressBar>, message: String) { |         let insertion_index = { | ||||||
|     if let Some(progress) = progress { |             let current_section = state | ||||||
|         multi_progress.remove(&progress) |                 .sections | ||||||
|  |                 .get(section_id) | ||||||
|  |                 .expect("Section ID not found"); | ||||||
|  |             current_section.header_index + current_section.task_count + 1 // +1 to insert after header
 | ||||||
|  |         }; | ||||||
|  | 
 | ||||||
|  |         let pb = self.mp.insert(insertion_index, ProgressBar::new_spinner()); | ||||||
|  |         pb.set_style(crate::theme::SPINNER_STYLE.clone()); | ||||||
|  |         pb.set_prefix("  "); | ||||||
|  |         pb.set_message(message.to_string()); | ||||||
|  |         pb.enable_steady_tick(Duration::from_millis(80)); | ||||||
|  | 
 | ||||||
|  |         state.pb_count += 1; | ||||||
|  | 
 | ||||||
|  |         let section = state | ||||||
|  |             .sections | ||||||
|  |             .get_mut(section_id) | ||||||
|  |             .expect("Section ID not found"); | ||||||
|  |         section.task_count += 1; | ||||||
|  | 
 | ||||||
|  |         // We inserted a new progress bar, so we must update the header_index
 | ||||||
|  |         // for all subsequent sections.
 | ||||||
|  |         for (id, s) in state.sections.iter_mut() { | ||||||
|  |             if id != section_id && s.header_index >= insertion_index { | ||||||
|  |                 s.header_index += 1; | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         state.tasks.insert(task_id.to_string(), pb); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     let progress = multi_progress.add(ProgressBar::new_spinner()); |     fn finish_task(&self, id: &str, message: &str) { | ||||||
|     progress.set_style(crate::theme::ERROR_SPINNER_STYLE.clone()); |         let state = self.state.lock().unwrap(); | ||||||
|     progress.finish_with_message(message); |         if let Some(pb) = state.tasks.get(id) { | ||||||
| } |             pb.set_style(crate::theme::SUCCESS_SPINNER_STYLE.clone()); | ||||||
| 
 |             pb.finish_with_message(message.to_string()); | ||||||
| pub fn skip(multi_progress: &MultiProgress, progress: Option<ProgressBar>, message: String) { |         } | ||||||
|     if let Some(progress) = progress { |  | ||||||
|         multi_progress.remove(&progress) |  | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     let progress = multi_progress.add(ProgressBar::new_spinner()); |     fn fail_task(&self, id: &str, message: &str) { | ||||||
|     progress.set_style(crate::theme::SKIP_SPINNER_STYLE.clone()); |         let state = self.state.lock().unwrap(); | ||||||
|     progress.finish_with_message(message); |         if let Some(pb) = state.tasks.get(id) { | ||||||
|  |             pb.set_style(crate::theme::ERROR_SPINNER_STYLE.clone()); | ||||||
|  |             pb.finish_with_message(message.to_string()); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     fn skip_task(&self, id: &str, message: &str) { | ||||||
|  |         let state = self.state.lock().unwrap(); | ||||||
|  |         if let Some(pb) = state.tasks.get(id) { | ||||||
|  |             pb.set_style(crate::theme::SKIP_SPINNER_STYLE.clone()); | ||||||
|  |             pb.finish_with_message(message.to_string()); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     fn contains_section(&self, id: &str) -> bool { | ||||||
|  |         let state = self.state.lock().unwrap(); | ||||||
|  |         state.sections.contains_key(id) | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     fn clear(&self) { | ||||||
|  |         let mut state = self.state.lock().unwrap(); | ||||||
|  | 
 | ||||||
|  |         state.tasks.values().for_each(|p| self.mp.remove(p)); | ||||||
|  |         state.tasks.clear(); | ||||||
|  |         state.sections.values().for_each(|s| self.mp.remove(&s.pb)); | ||||||
|  |         state.sections.clear(); | ||||||
|  |         state.pb_count = 0; | ||||||
|  | 
 | ||||||
|  |         let _ = self.mp.clear(); | ||||||
|  |     } | ||||||
| } | } | ||||||
|  | |||||||
| @ -11,8 +11,11 @@ pub static EMOJI_TOPOLOGY: Emoji<'_, '_> = Emoji("📦", ""); | |||||||
| pub static EMOJI_SCORE: Emoji<'_, '_> = Emoji("🎶", ""); | pub static EMOJI_SCORE: Emoji<'_, '_> = Emoji("🎶", ""); | ||||||
| 
 | 
 | ||||||
| lazy_static! { | lazy_static! { | ||||||
|  |     pub static ref SECTION_STYLE: ProgressStyle = ProgressStyle::default_spinner() | ||||||
|  |         .template("{wide_msg:.bold}") | ||||||
|  |         .unwrap(); | ||||||
|     pub static ref SPINNER_STYLE: ProgressStyle = ProgressStyle::default_spinner() |     pub static ref SPINNER_STYLE: ProgressStyle = ProgressStyle::default_spinner() | ||||||
|         .template("    {spinner:.green} {msg}") |         .template("    {spinner:.green} {wide_msg}") | ||||||
|         .unwrap() |         .unwrap() | ||||||
|         .tick_strings(&["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"]); |         .tick_strings(&["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"]); | ||||||
|     pub static ref SUCCESS_SPINNER_STYLE: ProgressStyle = SPINNER_STYLE |     pub static ref SUCCESS_SPINNER_STYLE: ProgressStyle = SPINNER_STYLE | ||||||
|  | |||||||
| @ -1,10 +1,7 @@ | |||||||
| use indicatif::{MultiProgress, ProgressBar}; | use harmony_cli::progress::{IndicatifProgressTracker, ProgressTracker}; | ||||||
| use indicatif_log_bridge::LogWrapper; | use indicatif::MultiProgress; | ||||||
| use log::error; | use log::error; | ||||||
| use std::{ | use std::sync::Arc; | ||||||
|     collections::HashMap, |  | ||||||
|     sync::{Arc, Mutex}, |  | ||||||
| }; |  | ||||||
| 
 | 
 | ||||||
| use crate::instrumentation::{self, HarmonyComposerEvent}; | use crate::instrumentation::{self, HarmonyComposerEvent}; | ||||||
| 
 | 
 | ||||||
| @ -22,85 +19,57 @@ pub fn init() -> tokio::task::JoinHandle<()> { | |||||||
| } | } | ||||||
| 
 | 
 | ||||||
| fn configure_logger() { | fn configure_logger() { | ||||||
|     let logger = |     env_logger::Builder::from_env(env_logger::Env::default().default_filter_or("info")).build(); | ||||||
|         env_logger::Builder::from_env(env_logger::Env::default().default_filter_or("info")).build(); |  | ||||||
|     let level = logger.filter(); |  | ||||||
|     let multi = MultiProgress::new(); |  | ||||||
|     LogWrapper::new(multi.clone(), logger).try_init().unwrap(); |  | ||||||
|     log::set_max_level(level); |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| pub async fn handle_events() { | pub async fn handle_events() { | ||||||
|     const PROGRESS_SETUP: &str = "project-initialization"; |     let progress_tracker = Arc::new(IndicatifProgressTracker::new(MultiProgress::new())); | ||||||
|  | 
 | ||||||
|  |     const SETUP_SECTION: &str = "project-initialization"; | ||||||
|  |     const COMPILTATION_TASK: &str = "compilation"; | ||||||
|     const PROGRESS_DEPLOYMENT: &str = "deployment"; |     const PROGRESS_DEPLOYMENT: &str = "deployment"; | ||||||
| 
 | 
 | ||||||
|     instrumentation::subscribe("Harmony Composer Logger", { |     instrumentation::subscribe("Harmony Composer Logger", { | ||||||
|         let progresses: Arc<Mutex<HashMap<String, MultiProgress>>> = |  | ||||||
|             Arc::new(Mutex::new(HashMap::new())); |  | ||||||
|         let compilation_progress = Arc::new(Mutex::new(None::<ProgressBar>)); |  | ||||||
| 
 |  | ||||||
|         move |event| { |         move |event| { | ||||||
|             let progresses_clone = Arc::clone(&progresses); |             let progress_tracker = Arc::clone(&progress_tracker); | ||||||
|             let compilation_progress_clone = Arc::clone(&compilation_progress); |  | ||||||
| 
 | 
 | ||||||
|             async move { |             async move { | ||||||
|                 let mut progresses_guard = progresses_clone.lock().unwrap(); |  | ||||||
|                 let mut compilation_progress_guard = compilation_progress_clone.lock().unwrap(); |  | ||||||
| 
 |  | ||||||
|                 match event { |                 match event { | ||||||
|                     HarmonyComposerEvent::HarmonyComposerStarted => {} |                     HarmonyComposerEvent::HarmonyComposerStarted => {} | ||||||
|                     HarmonyComposerEvent::ProjectInitializationStarted => { |                     HarmonyComposerEvent::ProjectInitializationStarted => { | ||||||
|                         let multi_progress = harmony_cli::progress::new_section(format!( |                         progress_tracker.add_section( | ||||||
|                             "{} Initializing Harmony project...", |                             SETUP_SECTION, | ||||||
|                             harmony_cli::theme::EMOJI_HARMONY, |                             &format!( | ||||||
|                         )); |                                 "{} Initializing Harmony project...", | ||||||
|                         (*progresses_guard).insert(PROGRESS_SETUP.to_string(), multi_progress); |                                 harmony_cli::theme::EMOJI_HARMONY, | ||||||
|  |                             ), | ||||||
|  |                         ); | ||||||
|                     } |                     } | ||||||
|                     HarmonyComposerEvent::ProjectInitialized => println!("\n"), |                     HarmonyComposerEvent::ProjectInitialized => {} | ||||||
|                     HarmonyComposerEvent::ProjectCompilationStarted { details } => { |                     HarmonyComposerEvent::ProjectCompilationStarted { details } => { | ||||||
|                         let initialization_progress = |                         progress_tracker.add_task(SETUP_SECTION, COMPILTATION_TASK, &details); | ||||||
|                             (*progresses_guard).get(PROGRESS_SETUP).unwrap(); |  | ||||||
|                         let _ = initialization_progress.clear(); |  | ||||||
| 
 |  | ||||||
|                         let progress = |  | ||||||
|                             harmony_cli::progress::add_spinner(initialization_progress, details); |  | ||||||
|                         *compilation_progress_guard = Some(progress); |  | ||||||
|                     } |                     } | ||||||
|                     HarmonyComposerEvent::ProjectCompiled => { |                     HarmonyComposerEvent::ProjectCompiled => { | ||||||
|                         let initialization_progress = |                         progress_tracker.finish_task(COMPILTATION_TASK, "project compiled"); | ||||||
|                             (*progresses_guard).get(PROGRESS_SETUP).unwrap(); |  | ||||||
| 
 |  | ||||||
|                         harmony_cli::progress::success( |  | ||||||
|                             initialization_progress, |  | ||||||
|                             (*compilation_progress_guard).take(), |  | ||||||
|                             "project compiled".to_string(), |  | ||||||
|                         ); |  | ||||||
|                     } |                     } | ||||||
|                     HarmonyComposerEvent::ProjectCompilationFailed { details } => { |                     HarmonyComposerEvent::ProjectCompilationFailed { details } => { | ||||||
|                         let initialization_progress = |                         progress_tracker.fail_task(COMPILTATION_TASK, "failed to compile project"); | ||||||
|                             (*progresses_guard).get(PROGRESS_SETUP).unwrap(); |  | ||||||
| 
 |  | ||||||
|                         harmony_cli::progress::error( |  | ||||||
|                             initialization_progress, |  | ||||||
|                             (*compilation_progress_guard).take(), |  | ||||||
|                             "failed to compile project".to_string(), |  | ||||||
|                         ); |  | ||||||
| 
 | 
 | ||||||
|                         error!("{details}"); |                         error!("{details}"); | ||||||
|                     } |                     } | ||||||
|                     HarmonyComposerEvent::DeploymentStarted { target } => { |                     HarmonyComposerEvent::DeploymentStarted { target } => { | ||||||
|                         let multi_progress = harmony_cli::progress::new_section(format!( |                         progress_tracker.add_section( | ||||||
|                             "{} Starting deployment to {target}...\n\n", |                             PROGRESS_DEPLOYMENT, | ||||||
|                             harmony_cli::theme::EMOJI_DEPLOY |                             &format!( | ||||||
|                         )); |                                 "\n{} Deploying project to {target}...\n", | ||||||
|                         (*progresses_guard).insert(PROGRESS_DEPLOYMENT.to_string(), multi_progress); |                                 harmony_cli::theme::EMOJI_DEPLOY, | ||||||
|  |                             ), | ||||||
|  |                         ); | ||||||
|  |                     } | ||||||
|  |                     HarmonyComposerEvent::DeploymentCompleted => { | ||||||
|  |                         progress_tracker.clear(); | ||||||
|                     } |                     } | ||||||
|                     HarmonyComposerEvent::DeploymentCompleted => println!("\n"), |  | ||||||
|                     HarmonyComposerEvent::Shutdown => { |                     HarmonyComposerEvent::Shutdown => { | ||||||
|                         for (_, progresses) in (*progresses_guard).iter() { |  | ||||||
|                             progresses.clear().unwrap(); |  | ||||||
|                         } |  | ||||||
| 
 |  | ||||||
|                         return false; |                         return false; | ||||||
|                     } |                     } | ||||||
|                 } |                 } | ||||||
|  | |||||||
		Loading…
	
		Reference in New Issue
	
	Block a user