feat/secrets #111
							
								
								
									
										1
									
								
								Cargo.lock
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										1
									
								
								Cargo.lock
									
									
									
										generated
									
									
									
								
							| @ -1872,6 +1872,7 @@ name = "harmony_cli" | |||||||
| version = "0.1.0" | version = "0.1.0" | ||||||
| dependencies = [ | dependencies = [ | ||||||
|  "assert_cmd", |  "assert_cmd", | ||||||
|  |  "chrono", | ||||||
|  "clap", |  "clap", | ||||||
|  "console", |  "console", | ||||||
|  "env_logger", |  "env_logger", | ||||||
|  | |||||||
| @ -22,7 +22,7 @@ readme = "README.md" | |||||||
| license = "GNU AGPL v3" | license = "GNU AGPL v3" | ||||||
| 
 | 
 | ||||||
| [workspace.dependencies] | [workspace.dependencies] | ||||||
| log = "0.4" | log = { version = "0.4", features = ["kv"] } | ||||||
| env_logger = "0.11" | env_logger = "0.11" | ||||||
| derive-new = "0.7" | derive-new = "0.7" | ||||||
| async-trait = "0.1" | async-trait = "0.1" | ||||||
|  | |||||||
							
								
								
									
										
											BIN
										
									
								
								examples/application_monitoring_with_tenant/harmony
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								examples/application_monitoring_with_tenant/harmony
									
									
									
									
									
										Executable file
									
								
							
										
											Binary file not shown.
										
									
								
							| @ -8,7 +8,6 @@ use harmony::{ | |||||||
|     hardware::{FirewallGroup, HostCategory, Location, PhysicalHost, SwitchGroup}, |     hardware::{FirewallGroup, HostCategory, Location, PhysicalHost, SwitchGroup}, | ||||||
|     infra::opnsense::OPNSenseManagementInterface, |     infra::opnsense::OPNSenseManagementInterface, | ||||||
|     inventory::Inventory, |     inventory::Inventory, | ||||||
|     maestro::Maestro, |  | ||||||
|     modules::{ |     modules::{ | ||||||
|         http::StaticFilesHttpScore, |         http::StaticFilesHttpScore, | ||||||
|         ipxe::IpxeScore, |         ipxe::IpxeScore, | ||||||
| @ -130,8 +129,11 @@ async fn main() { | |||||||
|         "./data/watchguard/pxe-http-files".to_string(), |         "./data/watchguard/pxe-http-files".to_string(), | ||||||
|     )); |     )); | ||||||
|     let ipxe_score = IpxeScore::new(); |     let ipxe_score = IpxeScore::new(); | ||||||
|     let mut maestro = Maestro::initialize(inventory, topology).await.unwrap(); | 
 | ||||||
|     maestro.register_all(vec![ |     harmony_tui::run( | ||||||
|  |         inventory, | ||||||
|  |         topology, | ||||||
|  |         vec![ | ||||||
|             Box::new(dns_score), |             Box::new(dns_score), | ||||||
|             Box::new(bootstrap_dhcp_score), |             Box::new(bootstrap_dhcp_score), | ||||||
|             Box::new(bootstrap_load_balancer_score), |             Box::new(bootstrap_load_balancer_score), | ||||||
| @ -140,6 +142,8 @@ async fn main() { | |||||||
|             Box::new(http_score), |             Box::new(http_score), | ||||||
|             Box::new(ipxe_score), |             Box::new(ipxe_score), | ||||||
|             Box::new(dhcp_score), |             Box::new(dhcp_score), | ||||||
|     ]); |         ], | ||||||
|     harmony_tui::init(maestro).await.unwrap(); |     ) | ||||||
|  |     .await | ||||||
|  |     .unwrap(); | ||||||
| } | } | ||||||
|  | |||||||
| @ -8,7 +8,6 @@ use harmony::{ | |||||||
|     hardware::{FirewallGroup, HostCategory, Location, PhysicalHost, SwitchGroup}, |     hardware::{FirewallGroup, HostCategory, Location, PhysicalHost, SwitchGroup}, | ||||||
|     infra::opnsense::OPNSenseManagementInterface, |     infra::opnsense::OPNSenseManagementInterface, | ||||||
|     inventory::Inventory, |     inventory::Inventory, | ||||||
|     maestro::Maestro, |  | ||||||
|     modules::{ |     modules::{ | ||||||
|         dummy::{ErrorScore, PanicScore, SuccessScore}, |         dummy::{ErrorScore, PanicScore, SuccessScore}, | ||||||
|         http::StaticFilesHttpScore, |         http::StaticFilesHttpScore, | ||||||
| @ -84,8 +83,11 @@ async fn main() { | |||||||
|     let http_score = StaticFilesHttpScore::new(Url::LocalFolder( |     let http_score = StaticFilesHttpScore::new(Url::LocalFolder( | ||||||
|         "./data/watchguard/pxe-http-files".to_string(), |         "./data/watchguard/pxe-http-files".to_string(), | ||||||
|     )); |     )); | ||||||
|     let mut maestro = Maestro::initialize(inventory, topology).await.unwrap(); | 
 | ||||||
|     maestro.register_all(vec![ |     harmony_tui::run( | ||||||
|  |         inventory, | ||||||
|  |         topology, | ||||||
|  |         vec![ | ||||||
|             Box::new(dns_score), |             Box::new(dns_score), | ||||||
|             Box::new(dhcp_score), |             Box::new(dhcp_score), | ||||||
|             Box::new(load_balancer_score), |             Box::new(load_balancer_score), | ||||||
| @ -98,6 +100,8 @@ async fn main() { | |||||||
|             Box::new(SuccessScore {}), |             Box::new(SuccessScore {}), | ||||||
|             Box::new(ErrorScore {}), |             Box::new(ErrorScore {}), | ||||||
|             Box::new(PanicScore {}), |             Box::new(PanicScore {}), | ||||||
|     ]); |         ], | ||||||
|     harmony_tui::init(maestro).await.unwrap(); |     ) | ||||||
|  |     .await | ||||||
|  |     .unwrap(); | ||||||
| } | } | ||||||
|  | |||||||
| @ -2,7 +2,6 @@ use std::net::{SocketAddr, SocketAddrV4}; | |||||||
| 
 | 
 | ||||||
| use harmony::{ | use harmony::{ | ||||||
|     inventory::Inventory, |     inventory::Inventory, | ||||||
|     maestro::Maestro, |  | ||||||
|     modules::{ |     modules::{ | ||||||
|         dns::DnsScore, |         dns::DnsScore, | ||||||
|         dummy::{ErrorScore, PanicScore, SuccessScore}, |         dummy::{ErrorScore, PanicScore, SuccessScore}, | ||||||
| @ -16,18 +15,19 @@ use harmony_macros::ipv4; | |||||||
| 
 | 
 | ||||||
| #[tokio::main] | #[tokio::main] | ||||||
| async fn main() { | async fn main() { | ||||||
|     let inventory = Inventory::autoload(); |     harmony_tui::run( | ||||||
|     let topology = DummyInfra {}; |         Inventory::autoload(), | ||||||
|     let mut maestro = Maestro::initialize(inventory, topology).await.unwrap(); |         DummyInfra {}, | ||||||
| 
 |         vec![ | ||||||
|     maestro.register_all(vec![ |  | ||||||
|             Box::new(SuccessScore {}), |             Box::new(SuccessScore {}), | ||||||
|             Box::new(ErrorScore {}), |             Box::new(ErrorScore {}), | ||||||
|             Box::new(PanicScore {}), |             Box::new(PanicScore {}), | ||||||
|             Box::new(DnsScore::new(vec![], None)), |             Box::new(DnsScore::new(vec![], None)), | ||||||
|             Box::new(build_large_score()), |             Box::new(build_large_score()), | ||||||
|     ]); |         ], | ||||||
|     harmony_tui::init(maestro).await.unwrap(); |     ) | ||||||
|  |     .await | ||||||
|  |     .unwrap(); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| fn build_large_score() -> LoadBalancerScore { | fn build_large_score() -> LoadBalancerScore { | ||||||
|  | |||||||
| @ -241,7 +241,7 @@ pub struct DummyInfra; | |||||||
| #[async_trait] | #[async_trait] | ||||||
| impl Topology for DummyInfra { | impl Topology for DummyInfra { | ||||||
|     fn name(&self) -> &str { |     fn name(&self) -> &str { | ||||||
|         todo!() |         "DummyInfra" | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     async fn ensure_ready(&self) -> Result<PreparationOutcome, PreparationError> { |     async fn ensure_ready(&self) -> Result<PreparationOutcome, PreparationError> { | ||||||
|  | |||||||
| @ -22,6 +22,7 @@ indicatif = "0.18.0" | |||||||
| lazy_static = "1.5.0" | lazy_static = "1.5.0" | ||||||
| log.workspace = true | log.workspace = true | ||||||
| indicatif-log-bridge = "0.2.3" | indicatif-log-bridge = "0.2.3" | ||||||
|  | chrono.workspace = true | ||||||
| 
 | 
 | ||||||
| [dev-dependencies] | [dev-dependencies] | ||||||
| harmony = { path = "../harmony", features = ["testing"] } | harmony = { path = "../harmony", features = ["testing"] } | ||||||
|  | |||||||
| @ -1,22 +1,17 @@ | |||||||
|  | use chrono::Local; | ||||||
|  | use console::style; | ||||||
| use harmony::{ | use harmony::{ | ||||||
|     instrumentation::{self, HarmonyEvent}, |     instrumentation::{self, HarmonyEvent}, | ||||||
|     modules::application::ApplicationFeatureStatus, |     modules::application::ApplicationFeatureStatus, | ||||||
|     topology::TopologyStatus, |     topology::TopologyStatus, | ||||||
| }; | }; | ||||||
| use indicatif::MultiProgress; | use log::{error, info, log_enabled}; | ||||||
| use indicatif_log_bridge::LogWrapper; | use std::io::Write; | ||||||
| use log::error; | use std::sync::{Arc, Mutex}; | ||||||
| use std::{ |  | ||||||
|     sync::{Arc, Mutex}, |  | ||||||
|     thread, |  | ||||||
|     time::Duration, |  | ||||||
| }; |  | ||||||
| 
 |  | ||||||
| use crate::progress::{IndicatifProgressTracker, ProgressTracker}; |  | ||||||
| 
 | 
 | ||||||
| pub fn init() -> tokio::task::JoinHandle<()> { | pub fn init() -> tokio::task::JoinHandle<()> { | ||||||
|     let base_progress = configure_logger(); |     configure_logger(); | ||||||
|     let handle = tokio::spawn(handle_events(base_progress)); |     let handle = tokio::spawn(handle_events()); | ||||||
| 
 | 
 | ||||||
|     loop { |     loop { | ||||||
|         if instrumentation::instrument(HarmonyEvent::HarmonyStarted).is_ok() { |         if instrumentation::instrument(HarmonyEvent::HarmonyStarted).is_ok() { | ||||||
| @ -27,28 +22,76 @@ pub fn init() -> tokio::task::JoinHandle<()> { | |||||||
|     handle |     handle | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| fn configure_logger() -> MultiProgress { | fn configure_logger() { | ||||||
|     let logger = |     env_logger::Builder::from_env(env_logger::Env::default().default_filter_or("info")) | ||||||
|         env_logger::Builder::from_env(env_logger::Env::default().default_filter_or("info")).build(); |         .format(|buf, record| { | ||||||
|     let level = logger.filter(); |             let debug_mode = log_enabled!(log::Level::Debug); | ||||||
|     let progress = MultiProgress::new(); |             let timestamp = Local::now().format("%Y-%m-%d %H:%M:%S"); | ||||||
| 
 | 
 | ||||||
|     LogWrapper::new(progress.clone(), logger) |             let level = match record.level() { | ||||||
|         .try_init() |                 log::Level::Error => style("ERROR").red(), | ||||||
|         .unwrap(); |                 log::Level::Warn => style("WARN").yellow(), | ||||||
|     log::set_max_level(level); |                 log::Level::Info => style("INFO").green(), | ||||||
| 
 |                 log::Level::Debug => style("DEBUG").blue(), | ||||||
|     progress |                 log::Level::Trace => style("TRACE").magenta(), | ||||||
|  |             }; | ||||||
|  |             if let Some(status) = record.key_values().get(log::kv::Key::from("status")) { | ||||||
|  |                 let status = status.to_borrowed_str().unwrap(); | ||||||
|  |                 let emoji = match status { | ||||||
|  |                     "finished" => style(crate::theme::EMOJI_SUCCESS.to_string()).green(), | ||||||
|  |                     "skipped" => style(crate::theme::EMOJI_SKIP.to_string()).yellow(), | ||||||
|  |                     "failed" => style(crate::theme::EMOJI_ERROR.to_string()).red(), | ||||||
|  |                     _ => style("".into()), | ||||||
|  |                 }; | ||||||
|  |                 if debug_mode { | ||||||
|  |                     writeln!( | ||||||
|  |                         buf, | ||||||
|  |                         "[{} {:<5} {}] {} {}", | ||||||
|  |                         timestamp, | ||||||
|  |                         level, | ||||||
|  |                         record.target(), | ||||||
|  |                         emoji, | ||||||
|  |                         record.args() | ||||||
|  |                     ) | ||||||
|  |                 } else { | ||||||
|  |                     writeln!(buf, "[{:<5}] {} {}", level, emoji, record.args()) | ||||||
|  |                 } | ||||||
|  |             } else if let Some(emoji) = record.key_values().get(log::kv::Key::from("emoji")) { | ||||||
|  |                 if debug_mode { | ||||||
|  |                     writeln!( | ||||||
|  |                         buf, | ||||||
|  |                         "[{} {:<5} {}] {} {}", | ||||||
|  |                         timestamp, | ||||||
|  |                         level, | ||||||
|  |                         record.target(), | ||||||
|  |                         emoji, | ||||||
|  |                         record.args() | ||||||
|  |                     ) | ||||||
|  |                 } else { | ||||||
|  |                     writeln!(buf, "[{:<5}] {} {}", level, emoji, record.args()) | ||||||
|  |                 } | ||||||
|  |             } else if debug_mode { | ||||||
|  |                 writeln!( | ||||||
|  |                     buf, | ||||||
|  |                     "[{} {:<5} {}] {}", | ||||||
|  |                     timestamp, | ||||||
|  |                     level, | ||||||
|  |                     record.target(), | ||||||
|  |                     record.args() | ||||||
|  |                 ) | ||||||
|  |             } else { | ||||||
|  |                 writeln!(buf, "[{:<5}] {}", level, record.args()) | ||||||
|  |             } | ||||||
|  |         }) | ||||||
|  |         .init(); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| async fn handle_events(base_progress: MultiProgress) { | async fn handle_events() { | ||||||
|     let progress_tracker = Arc::new(IndicatifProgressTracker::new(base_progress.clone())); |  | ||||||
|     let preparing_topology = Arc::new(Mutex::new(false)); |     let preparing_topology = Arc::new(Mutex::new(false)); | ||||||
|     let current_score: Arc<Mutex<Option<String>>> = Arc::new(Mutex::new(None)); |     let current_score: Arc<Mutex<Option<String>>> = Arc::new(Mutex::new(None)); | ||||||
| 
 | 
 | ||||||
|     instrumentation::subscribe("Harmony CLI Logger", { |     instrumentation::subscribe("Harmony CLI Logger", { | ||||||
|         move |event| { |         move |event| { | ||||||
|             let progress_tracker = Arc::clone(&progress_tracker); |  | ||||||
|             let preparing_topology = Arc::clone(&preparing_topology); |             let preparing_topology = Arc::clone(&preparing_topology); | ||||||
|             let current_score = Arc::clone(¤t_score); |             let current_score = Arc::clone(¤t_score); | ||||||
| 
 | 
 | ||||||
| @ -59,90 +102,57 @@ async fn handle_events(base_progress: MultiProgress) { | |||||||
|                 match event { |                 match event { | ||||||
|                     HarmonyEvent::HarmonyStarted => {} |                     HarmonyEvent::HarmonyStarted => {} | ||||||
|                     HarmonyEvent::HarmonyFinished => { |                     HarmonyEvent::HarmonyFinished => { | ||||||
|                         progress_tracker.add_section( |                         let emoji = crate::theme::EMOJI_HARMONY.to_string(); | ||||||
|                             "harmony-summary", |                         info!(emoji = emoji.as_str(); "Harmony completed"); | ||||||
|                             &format!("\n{} Harmony completed\n\n", crate::theme::EMOJI_HARMONY), |  | ||||||
|                         ); |  | ||||||
|                         progress_tracker.add_section("harmony-finished", "\n\n"); |  | ||||||
|                         thread::sleep(Duration::from_millis(200)); |  | ||||||
|                         return false; |                         return false; | ||||||
|                     } |                     } | ||||||
|                     HarmonyEvent::TopologyStateChanged { |                     HarmonyEvent::TopologyStateChanged { | ||||||
|                         topology, |                         topology, | ||||||
|                         status, |                         status, | ||||||
|                         message, |                         message, | ||||||
|                     } => { |                     } => match status { | ||||||
|                         let section_key = topology_key(&topology); |  | ||||||
| 
 |  | ||||||
|                         match status { |  | ||||||
|                         TopologyStatus::Queued => {} |                         TopologyStatus::Queued => {} | ||||||
|                         TopologyStatus::Preparing => { |                         TopologyStatus::Preparing => { | ||||||
|                                 progress_tracker.add_section( |                             let emoji = format!("{}", style(crate::theme::EMOJI_TOPOLOGY.to_string()).yellow()); | ||||||
|                                     §ion_key, |                             info!(emoji = emoji.as_str(); "Preparing environment: {topology}..."); | ||||||
|                                     &format!( |  | ||||||
|                                         "\n{} Preparing environment: {topology}...", |  | ||||||
|                                         crate::theme::EMOJI_TOPOLOGY |  | ||||||
|                                     ), |  | ||||||
|                                 ); |  | ||||||
|                             (*preparing_topology) = true; |                             (*preparing_topology) = true; | ||||||
|                         } |                         } | ||||||
|                         TopologyStatus::Success => { |                         TopologyStatus::Success => { | ||||||
|                             (*preparing_topology) = false; |                             (*preparing_topology) = false; | ||||||
|                                 progress_tracker.add_task(§ion_key, "topology-success", ""); |                             if let Some(message) = message { | ||||||
|                                 progress_tracker |                                 info!(status = "finished"; "{message}"); | ||||||
|                                     .finish_task("topology-success", &message.unwrap_or("".into())); |                             } | ||||||
|                         } |                         } | ||||||
|                         TopologyStatus::Noop => { |                         TopologyStatus::Noop => { | ||||||
|                             (*preparing_topology) = false; |                             (*preparing_topology) = false; | ||||||
|                                 progress_tracker.add_task(§ion_key, "topology-skip", ""); |                             if let Some(message) = message { | ||||||
|                                 progress_tracker |                                 info!(status = "skipped"; "{message}"); | ||||||
|                                     .skip_task("topology-skip", &message.unwrap_or("".into())); |                             } | ||||||
|                         } |                         } | ||||||
|                         TopologyStatus::Error => { |                         TopologyStatus::Error => { | ||||||
|                                 progress_tracker.add_task(§ion_key, "topology-error", ""); |  | ||||||
|                             (*preparing_topology) = false; |                             (*preparing_topology) = false; | ||||||
|                                 progress_tracker |                             if let Some(message) = message { | ||||||
|                                     .fail_task("topology-error", &message.unwrap_or("".into())); |                                 error!(status = "failed"; "{message}"); | ||||||
|                             } |  | ||||||
|                             } |                             } | ||||||
|                         } |                         } | ||||||
|  |                     }, | ||||||
|                     HarmonyEvent::InterpretExecutionStarted { |                     HarmonyEvent::InterpretExecutionStarted { | ||||||
|                         execution_id: task_key, |                         execution_id: _, | ||||||
|                         topology, |                         topology: _, | ||||||
|                         interpret: _, |                         interpret: _, | ||||||
|                         score, |                         score, | ||||||
|                         message, |                         message, | ||||||
|                     } => { |                     } => { | ||||||
|                         let is_key_topology = (*preparing_topology) |                         if *preparing_topology || current_score.is_some() { | ||||||
|                             && progress_tracker.contains_section(&topology_key(&topology)); |                             info!("{message}"); | ||||||
|                         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) |  | ||||||
|                         } else if is_key_current_score { |  | ||||||
|                             score_key(¤t_score.clone().unwrap()) |  | ||||||
|                         } else if is_key_score { |  | ||||||
|                             score_key(&score) |  | ||||||
|                         } else { |                         } else { | ||||||
|                             (*current_score) = Some(score.clone()); |                             (*current_score) = Some(score.clone()); | ||||||
|                             let key = score_key(&score); |                             let emoji = format!("{}", style(crate::theme::EMOJI_SCORE).blue()); | ||||||
|                             progress_tracker.add_section( |                             info!(emoji = emoji.as_str(); "Interpreting score: {score}..."); | ||||||
|                                 &key, |                         } | ||||||
|                                 &format!( |  | ||||||
|                                     "{} Interpreting score: {score}...", |  | ||||||
|                                     crate::theme::EMOJI_SCORE |  | ||||||
|                                 ), |  | ||||||
|                             ); |  | ||||||
|                             key |  | ||||||
|                         }; |  | ||||||
| 
 |  | ||||||
|                         progress_tracker.add_task(§ion_key, &task_key, &message); |  | ||||||
|                     } |                     } | ||||||
|                     HarmonyEvent::InterpretExecutionFinished { |                     HarmonyEvent::InterpretExecutionFinished { | ||||||
|                         execution_id: task_key, |                         execution_id: _, | ||||||
|                         topology: _, |                         topology: _, | ||||||
|                         interpret: _, |                         interpret: _, | ||||||
|                         score, |                         score, | ||||||
| @ -155,16 +165,17 @@ async fn handle_events(base_progress: MultiProgress) { | |||||||
|                         match outcome { |                         match outcome { | ||||||
|                             Ok(outcome) => match outcome.status { |                             Ok(outcome) => match outcome.status { | ||||||
|                                 harmony::interpret::InterpretStatus::SUCCESS => { |                                 harmony::interpret::InterpretStatus::SUCCESS => { | ||||||
|                                     progress_tracker.finish_task(&task_key, &outcome.message); |                                     info!(status = "finished"; "{}", outcome.message); | ||||||
|                                 } |                                 } | ||||||
|                                 harmony::interpret::InterpretStatus::NOOP => { |                                 harmony::interpret::InterpretStatus::NOOP => { | ||||||
|                                     progress_tracker.skip_task(&task_key, &outcome.message); |                                     info!(status = "skipped"; "{}", outcome.message); | ||||||
|  |                                 } | ||||||
|  |                                 _ => { | ||||||
|  |                                     error!(status = "failed"; "{}", outcome.message); | ||||||
|                                 } |                                 } | ||||||
|                                 _ => progress_tracker.fail_task(&task_key, &outcome.message), |  | ||||||
|                             }, |                             }, | ||||||
|                             Err(err) => { |                             Err(err) => { | ||||||
|                                 error!("Interpret error: {err}"); |                                 error!(status = "failed"; "{}", err); | ||||||
|                                 progress_tracker.fail_task(&task_key, &err.to_string()); |  | ||||||
|                             } |                             } | ||||||
|                         } |                         } | ||||||
|                     } |                     } | ||||||
| @ -173,30 +184,17 @@ async fn handle_events(base_progress: MultiProgress) { | |||||||
|                         application, |                         application, | ||||||
|                         feature, |                         feature, | ||||||
|                         status, |                         status, | ||||||
|                     } => { |                     } => match status { | ||||||
|                         if let Some(score) = &(*current_score) { |  | ||||||
|                             let section_key = score_key(score); |  | ||||||
|                             let task_key = app_feature_key(&application, &feature); |  | ||||||
| 
 |  | ||||||
|                             match status { |  | ||||||
|                         ApplicationFeatureStatus::Installing => { |                         ApplicationFeatureStatus::Installing => { | ||||||
|                                     let message = format!("Feature '{}' installing...", feature); |                             info!("Installing feature '{}' for '{}'...", feature, application); | ||||||
|                                     progress_tracker.add_task(§ion_key, &task_key, &message); |  | ||||||
|                         } |                         } | ||||||
|                         ApplicationFeatureStatus::Installed => { |                         ApplicationFeatureStatus::Installed => { | ||||||
|                                     let message = format!("Feature '{}' installed", feature); |                             info!(status = "finished"; "Feature '{}' installed", feature); | ||||||
|                                     progress_tracker.finish_task(&task_key, &message); |  | ||||||
|                         } |                         } | ||||||
|                         ApplicationFeatureStatus::Failed { details } => { |                         ApplicationFeatureStatus::Failed { details } => { | ||||||
|                                     let message = format!( |                             error!(status = "failed"; "Feature '{}' installation failed: {}", feature, details); | ||||||
|                                         "Feature '{}' installation failed: {}", |  | ||||||
|                                         feature, details |  | ||||||
|                                     ); |  | ||||||
|                                     progress_tracker.fail_task(&task_key, &message); |  | ||||||
|                                 } |  | ||||||
|                             } |  | ||||||
|                         } |  | ||||||
|                         } |                         } | ||||||
|  |                     }, | ||||||
|                 } |                 } | ||||||
|                 true |                 true | ||||||
|             } |             } | ||||||
| @ -204,15 +202,3 @@ async fn handle_events(base_progress: MultiProgress) { | |||||||
|     }) |     }) | ||||||
|     .await; |     .await; | ||||||
| } | } | ||||||
| 
 |  | ||||||
| fn topology_key(topology: &str) -> String { |  | ||||||
|     format!("topology-{topology}") |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| fn score_key(score: &str) -> String { |  | ||||||
|     format!("score-{score}") |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| fn app_feature_key(application: &str, feature: &str) -> String { |  | ||||||
|     format!("app-{application}-{feature}") |  | ||||||
| } |  | ||||||
|  | |||||||
| @ -90,13 +90,37 @@ pub async fn run<T: Topology + Send + Sync + 'static>( | |||||||
|     topology: T, |     topology: T, | ||||||
|     scores: Vec<Box<dyn Score<T>>>, |     scores: Vec<Box<dyn Score<T>>>, | ||||||
|     args_struct: Option<Args>, |     args_struct: Option<Args>, | ||||||
|  | ) -> Result<(), Box<dyn std::error::Error>> { | ||||||
|  |     let args = match args_struct { | ||||||
|  |         Some(args) => args, | ||||||
|  |         None => Args::parse(), | ||||||
|  |     }; | ||||||
|  | 
 | ||||||
|  |     #[cfg(not(feature = "tui"))] | ||||||
|  |     if args.interactive { | ||||||
|  |         return Err("Not compiled with interactive support".into()); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     #[cfg(feature = "tui")] | ||||||
|  |     if args.interactive { | ||||||
|  |         return harmony_tui::run(inventory, topology, scores).await; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     run_cli(inventory, topology, scores, args).await | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | pub async fn run_cli<T: Topology + Send + Sync + 'static>( | ||||||
|  |     inventory: Inventory, | ||||||
|  |     topology: T, | ||||||
|  |     scores: Vec<Box<dyn Score<T>>>, | ||||||
|  |     args: Args, | ||||||
| ) -> Result<(), Box<dyn std::error::Error>> { | ) -> Result<(), Box<dyn std::error::Error>> { | ||||||
|     let cli_logger_handle = cli_logger::init(); |     let cli_logger_handle = cli_logger::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); | ||||||
| 
 | 
 | ||||||
|     let result = init(maestro, args_struct).await; |     let result = init(maestro, args).await; | ||||||
| 
 | 
 | ||||||
|     instrumentation::instrument(instrumentation::HarmonyEvent::HarmonyFinished).unwrap(); |     instrumentation::instrument(instrumentation::HarmonyEvent::HarmonyFinished).unwrap(); | ||||||
|     let _ = tokio::try_join!(cli_logger_handle); |     let _ = tokio::try_join!(cli_logger_handle); | ||||||
| @ -105,23 +129,8 @@ pub async fn run<T: Topology + Send + Sync + 'static>( | |||||||
| 
 | 
 | ||||||
| async fn init<T: Topology + Send + Sync + 'static>( | async fn init<T: Topology + Send + Sync + 'static>( | ||||||
|     maestro: harmony::maestro::Maestro<T>, |     maestro: harmony::maestro::Maestro<T>, | ||||||
|     args_struct: Option<Args>, |     args: Args, | ||||||
| ) -> Result<(), Box<dyn std::error::Error>> { | ) -> Result<(), Box<dyn std::error::Error>> { | ||||||
|     let args = match args_struct { |  | ||||||
|         Some(args) => args, |  | ||||||
|         None => Args::parse(), |  | ||||||
|     }; |  | ||||||
| 
 |  | ||||||
|     #[cfg(feature = "tui")] |  | ||||||
|     if args.interactive { |  | ||||||
|         return harmony_tui::init(maestro).await; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     #[cfg(not(feature = "tui"))] |  | ||||||
|     if args.interactive { |  | ||||||
|         return Err("Not compiled with interactive support".into()); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     let _ = env_logger::builder().try_init(); |     let _ = env_logger::builder().try_init(); | ||||||
| 
 | 
 | ||||||
|     let scores_vec = maestro_scores_filter(&maestro, args.all, args.filter, args.number); |     let scores_vec = maestro_scores_filter(&maestro, args.all, args.filter, args.number); | ||||||
| @ -193,14 +202,14 @@ mod tests { | |||||||
|         let maestro = init_test_maestro(); |         let maestro = init_test_maestro(); | ||||||
|         let res = crate::init( |         let res = crate::init( | ||||||
|             maestro, |             maestro, | ||||||
|             Some(crate::Args { |             crate::Args { | ||||||
|                 yes: true, |                 yes: true, | ||||||
|                 filter: Some("SuccessScore".to_owned()), |                 filter: Some("SuccessScore".to_owned()), | ||||||
|                 interactive: false, |                 interactive: false, | ||||||
|                 all: true, |                 all: true, | ||||||
|                 number: 0, |                 number: 0, | ||||||
|                 list: false, |                 list: false, | ||||||
|             }), |             }, | ||||||
|         ) |         ) | ||||||
|         .await; |         .await; | ||||||
| 
 | 
 | ||||||
| @ -213,14 +222,14 @@ mod tests { | |||||||
| 
 | 
 | ||||||
|         let res = crate::init( |         let res = crate::init( | ||||||
|             maestro, |             maestro, | ||||||
|             Some(crate::Args { |             crate::Args { | ||||||
|                 yes: true, |                 yes: true, | ||||||
|                 filter: Some("ErrorScore".to_owned()), |                 filter: Some("ErrorScore".to_owned()), | ||||||
|                 interactive: false, |                 interactive: false, | ||||||
|                 all: true, |                 all: true, | ||||||
|                 number: 0, |                 number: 0, | ||||||
|                 list: false, |                 list: false, | ||||||
|             }), |             }, | ||||||
|         ) |         ) | ||||||
|         .await; |         .await; | ||||||
| 
 | 
 | ||||||
| @ -233,14 +242,14 @@ mod tests { | |||||||
| 
 | 
 | ||||||
|         let res = crate::init( |         let res = crate::init( | ||||||
|             maestro, |             maestro, | ||||||
|             Some(crate::Args { |             crate::Args { | ||||||
|                 yes: true, |                 yes: true, | ||||||
|                 filter: None, |                 filter: None, | ||||||
|                 interactive: false, |                 interactive: false, | ||||||
|                 all: false, |                 all: false, | ||||||
|                 number: 0, |                 number: 0, | ||||||
|                 list: false, |                 list: false, | ||||||
|             }), |             }, | ||||||
|         ) |         ) | ||||||
|         .await; |         .await; | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -9,7 +9,13 @@ use widget::{help::HelpWidget, score::ScoreListWidget}; | |||||||
| use std::{panic, sync::Arc, time::Duration}; | use std::{panic, sync::Arc, time::Duration}; | ||||||
| 
 | 
 | ||||||
| use crossterm::event::{Event, EventStream, KeyCode, KeyEventKind}; | use crossterm::event::{Event, EventStream, KeyCode, KeyEventKind}; | ||||||
| use harmony::{maestro::Maestro, score::Score, topology::Topology}; | use harmony::{ | ||||||
|  |     instrumentation::{self, HarmonyEvent}, | ||||||
|  |     inventory::Inventory, | ||||||
|  |     maestro::Maestro, | ||||||
|  |     score::Score, | ||||||
|  |     topology::Topology, | ||||||
|  | }; | ||||||
| use ratatui::{ | use ratatui::{ | ||||||
|     self, Frame, |     self, Frame, | ||||||
|     layout::{Constraint, Layout, Position}, |     layout::{Constraint, Layout, Position}, | ||||||
| @ -39,22 +45,62 @@ pub mod tui { | |||||||
| ///
 | ///
 | ||||||
| /// #[tokio::main]
 | /// #[tokio::main]
 | ||||||
| /// async fn main() {
 | /// async fn main() {
 | ||||||
| ///     let inventory = Inventory::autoload();
 | ///     harmony_tui::run(
 | ||||||
| ///     let topology = HAClusterTopology::autoload();
 | ///         Inventory::autoload(),
 | ||||||
| ///     let mut maestro = Maestro::new_without_initialization(inventory, topology);
 | ///         HAClusterTopology::autoload(),
 | ||||||
| ///
 | ///         vec![
 | ||||||
| ///     maestro.register_all(vec![
 |  | ||||||
| ///             Box::new(SuccessScore {}),
 | ///             Box::new(SuccessScore {}),
 | ||||||
| ///             Box::new(ErrorScore {}),
 | ///             Box::new(ErrorScore {}),
 | ||||||
| ///             Box::new(PanicScore {}),
 | ///             Box::new(PanicScore {}),
 | ||||||
| ///     ]);
 | ///         ]
 | ||||||
| ///     harmony_tui::init(maestro).await.unwrap();
 | ///     ).await.unwrap();
 | ||||||
| /// }
 | /// }
 | ||||||
| /// ```
 | /// ```
 | ||||||
| pub async fn init<T: Topology + Send + Sync + 'static>( | pub async fn run<T: Topology + Send + Sync + 'static>( | ||||||
|  |     inventory: Inventory, | ||||||
|  |     topology: T, | ||||||
|  |     scores: Vec<Box<dyn Score<T>>>, | ||||||
|  | ) -> Result<(), Box<dyn std::error::Error>> { | ||||||
|  |     let handle = init_instrumentation().await; | ||||||
|  | 
 | ||||||
|  |     let mut maestro = Maestro::initialize(inventory, topology).await.unwrap(); | ||||||
|  |     maestro.register_all(scores); | ||||||
|  | 
 | ||||||
|  |     let result = init(maestro).await; | ||||||
|  | 
 | ||||||
|  |     let _ = tokio::try_join!(handle); | ||||||
|  |     result | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | async fn init<T: Topology + Send + Sync + 'static>( | ||||||
|     maestro: Maestro<T>, |     maestro: Maestro<T>, | ||||||
| ) -> Result<(), Box<dyn std::error::Error>> { | ) -> Result<(), Box<dyn std::error::Error>> { | ||||||
|     HarmonyTUI::new(maestro).init().await |     let result = HarmonyTUI::new(maestro).init().await; | ||||||
|  | 
 | ||||||
|  |     instrumentation::instrument(HarmonyEvent::HarmonyFinished).unwrap(); | ||||||
|  |     result | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | async fn init_instrumentation() -> tokio::task::JoinHandle<()> { | ||||||
|  |     let handle = tokio::spawn(handle_harmony_events()); | ||||||
|  | 
 | ||||||
|  |     loop { | ||||||
|  |         if instrumentation::instrument(HarmonyEvent::HarmonyStarted).is_ok() { | ||||||
|  |             break; | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     handle | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | async fn handle_harmony_events() { | ||||||
|  |     instrumentation::subscribe("Harmony TUI Logger", async |event| { | ||||||
|  |         if let HarmonyEvent::HarmonyFinished = event { | ||||||
|  |             return false; | ||||||
|  |         }; | ||||||
|  |         true | ||||||
|  |     }) | ||||||
|  |     .await; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| pub struct HarmonyTUI<T: Topology> { | pub struct HarmonyTUI<T: Topology> { | ||||||
|  | |||||||
		Loading…
	
		Reference in New Issue
	
	Block a user