fix(cli): reduce noise & better track progress within Harmony #91
							
								
								
									
										61
									
								
								Cargo.lock
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										61
									
								
								Cargo.lock
									
									
									
										generated
									
									
									
								
							| @ -650,6 +650,19 @@ dependencies = [ | |||||||
|  "crossbeam-utils", |  "crossbeam-utils", | ||||||
| ] | ] | ||||||
| 
 | 
 | ||||||
|  | [[package]] | ||||||
|  | name = "console" | ||||||
|  | version = "0.16.0" | ||||||
|  | source = "registry+https://github.com/rust-lang/crates.io-index" | ||||||
|  | checksum = "2e09ced7ebbccb63b4c65413d821f2e00ce54c5ca4514ddc6b3c892fdbcbc69d" | ||||||
|  | dependencies = [ | ||||||
|  |  "encode_unicode", | ||||||
|  |  "libc", | ||||||
|  |  "once_cell", | ||||||
|  |  "unicode-width 0.2.0", | ||||||
|  |  "windows-sys 0.60.2", | ||||||
|  | ] | ||||||
|  | 
 | ||||||
| [[package]] | [[package]] | ||||||
| name = "const-oid" | name = "const-oid" | ||||||
| version = "0.9.6" | version = "0.9.6" | ||||||
| @ -1137,6 +1150,12 @@ dependencies = [ | |||||||
|  "serde", |  "serde", | ||||||
| ] | ] | ||||||
| 
 | 
 | ||||||
|  | [[package]] | ||||||
|  | name = "encode_unicode" | ||||||
|  | version = "1.0.0" | ||||||
|  | source = "registry+https://github.com/rust-lang/crates.io-index" | ||||||
|  | checksum = "34aa73646ffb006b8f5147f3dc182bd4bcb190227ce861fc4a4844bf8e3cb2c0" | ||||||
|  | 
 | ||||||
| [[package]] | [[package]] | ||||||
| name = "encoding_rs" | name = "encoding_rs" | ||||||
| version = "0.8.35" | version = "0.8.35" | ||||||
| @ -1757,7 +1776,6 @@ dependencies = [ | |||||||
|  "helm-wrapper-rs", |  "helm-wrapper-rs", | ||||||
|  "hex", |  "hex", | ||||||
|  "http 1.3.1", |  "http 1.3.1", | ||||||
|  "inquire", |  | ||||||
|  "k3d-rs", |  "k3d-rs", | ||||||
|  "k8s-openapi", |  "k8s-openapi", | ||||||
|  "kube", |  "kube", | ||||||
| @ -1765,6 +1783,7 @@ dependencies = [ | |||||||
|  "libredfish", |  "libredfish", | ||||||
|  "log", |  "log", | ||||||
|  "non-blank-string-rs", |  "non-blank-string-rs", | ||||||
|  |  "once_cell", | ||||||
|  "opnsense-config", |  "opnsense-config", | ||||||
|  "opnsense-config-xml", |  "opnsense-config-xml", | ||||||
|  "pretty_assertions", |  "pretty_assertions", | ||||||
| @ -1796,10 +1815,15 @@ version = "0.1.0" | |||||||
| dependencies = [ | dependencies = [ | ||||||
|  "assert_cmd", |  "assert_cmd", | ||||||
|  "clap", |  "clap", | ||||||
|  |  "console", | ||||||
|  "env_logger", |  "env_logger", | ||||||
|  "harmony", |  "harmony", | ||||||
|  "harmony_tui", |  "harmony_tui", | ||||||
|  |  "indicatif", | ||||||
|  |  "indicatif-log-bridge", | ||||||
|  "inquire", |  "inquire", | ||||||
|  |  "lazy_static", | ||||||
|  |  "log", | ||||||
|  "tokio", |  "tokio", | ||||||
| ] | ] | ||||||
| 
 | 
 | ||||||
| @ -1810,10 +1834,16 @@ dependencies = [ | |||||||
|  "bollard", |  "bollard", | ||||||
|  "cargo_metadata", |  "cargo_metadata", | ||||||
|  "clap", |  "clap", | ||||||
|  |  "console", | ||||||
|  "current_platform", |  "current_platform", | ||||||
|  "env_logger", |  "env_logger", | ||||||
|  "futures-util", |  "futures-util", | ||||||
|  |  "harmony_cli", | ||||||
|  |  "indicatif", | ||||||
|  |  "indicatif-log-bridge", | ||||||
|  |  "lazy_static", | ||||||
|  "log", |  "log", | ||||||
|  |  "once_cell", | ||||||
|  "serde_json", |  "serde_json", | ||||||
|  "tokio", |  "tokio", | ||||||
| ] | ] | ||||||
| @ -2409,6 +2439,29 @@ dependencies = [ | |||||||
|  "serde", |  "serde", | ||||||
| ] | ] | ||||||
| 
 | 
 | ||||||
|  | [[package]] | ||||||
|  | name = "indicatif" | ||||||
|  | version = "0.18.0" | ||||||
|  | source = "registry+https://github.com/rust-lang/crates.io-index" | ||||||
|  | checksum = "70a646d946d06bedbbc4cac4c218acf4bbf2d87757a784857025f4d447e4e1cd" | ||||||
|  | dependencies = [ | ||||||
|  |  "console", | ||||||
|  |  "portable-atomic", | ||||||
|  |  "unicode-width 0.2.0", | ||||||
|  |  "unit-prefix", | ||||||
|  |  "web-time", | ||||||
|  | ] | ||||||
|  | 
 | ||||||
|  | [[package]] | ||||||
|  | name = "indicatif-log-bridge" | ||||||
|  | version = "0.2.3" | ||||||
|  | source = "registry+https://github.com/rust-lang/crates.io-index" | ||||||
|  | checksum = "63703cf9069b85dbe6fe26e1c5230d013dee99d3559cd3d02ba39e099ef7ab02" | ||||||
|  | dependencies = [ | ||||||
|  |  "indicatif", | ||||||
|  |  "log", | ||||||
|  | ] | ||||||
|  | 
 | ||||||
| [[package]] | [[package]] | ||||||
| name = "indoc" | name = "indoc" | ||||||
| version = "2.0.6" | version = "2.0.6" | ||||||
| @ -5181,6 +5234,12 @@ version = "0.2.6" | |||||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | source = "registry+https://github.com/rust-lang/crates.io-index" | ||||||
| checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" | checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" | ||||||
| 
 | 
 | ||||||
|  | [[package]] | ||||||
|  | name = "unit-prefix" | ||||||
|  | version = "0.5.1" | ||||||
|  | source = "registry+https://github.com/rust-lang/crates.io-index" | ||||||
|  | checksum = "323402cff2dd658f39ca17c789b502021b3f18707c91cdf22e3838e1b4023817" | ||||||
|  | 
 | ||||||
| [[package]] | [[package]] | ||||||
| name = "universal-hash" | name = "universal-hash" | ||||||
| version = "0.5.1" | version = "0.5.1" | ||||||
|  | |||||||
| @ -9,10 +9,11 @@ use harmony::{ | |||||||
|     }, |     }, | ||||||
|     topology::{K8sAnywhereTopology, Url}, |     topology::{K8sAnywhereTopology, Url}, | ||||||
| }; | }; | ||||||
|  | use harmony_cli::cli_logger; | ||||||
| 
 | 
 | ||||||
| #[tokio::main] | #[tokio::main] | ||||||
| async fn main() { | async fn main() { | ||||||
|     env_logger::init(); |     let cli_logger_handle = cli_logger::init(); | ||||||
| 
 | 
 | ||||||
|     let topology = K8sAnywhereTopology::from_env(); |     let topology = K8sAnywhereTopology::from_env(); | ||||||
|     let mut maestro = Maestro::initialize(Inventory::autoload(), topology) |     let mut maestro = Maestro::initialize(Inventory::autoload(), topology) | ||||||
| @ -40,4 +41,6 @@ async fn main() { | |||||||
| 
 | 
 | ||||||
|     maestro.register_all(vec![Box::new(app)]); |     maestro.register_all(vec![Box::new(app)]); | ||||||
|     harmony_cli::init(maestro, None).await.unwrap(); |     harmony_cli::init(maestro, None).await.unwrap(); | ||||||
|  | 
 | ||||||
|  |     let _ = tokio::try_join!(cli_logger_handle); | ||||||
| } | } | ||||||
|  | |||||||
| @ -32,7 +32,6 @@ k8s-openapi.workspace = true | |||||||
| serde_yaml.workspace = true | serde_yaml.workspace = true | ||||||
| http.workspace = true | http.workspace = true | ||||||
| serde-value.workspace = true | serde-value.workspace = true | ||||||
| inquire.workspace = true |  | ||||||
| helm-wrapper-rs = "0.4.0" | helm-wrapper-rs = "0.4.0" | ||||||
| non-blank-string-rs = "1.0.4" | non-blank-string-rs = "1.0.4" | ||||||
| k3d-rs = { path = "../k3d" } | k3d-rs = { path = "../k3d" } | ||||||
| @ -62,6 +61,7 @@ serde_with = "3.14.0" | |||||||
| bollard.workspace = true | bollard.workspace = true | ||||||
| tar.workspace = true | tar.workspace = true | ||||||
| base64.workspace = true | base64.workspace = true | ||||||
|  | once_cell = "1.21.3" | ||||||
| 
 | 
 | ||||||
| [dev-dependencies] | [dev-dependencies] | ||||||
| pretty_assertions.workspace = true | pretty_assertions.workspace = true | ||||||
|  | |||||||
							
								
								
									
										63
									
								
								harmony/src/domain/instrumentation.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										63
									
								
								harmony/src/domain/instrumentation.rs
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,63 @@ | |||||||
|  | use log::debug; | ||||||
|  | use once_cell::sync::Lazy; | ||||||
|  | use tokio::sync::broadcast; | ||||||
|  | 
 | ||||||
|  | use super::interpret::{InterpretError, Outcome}; | ||||||
|  | 
 | ||||||
|  | #[derive(Debug, Clone)] | ||||||
|  | pub enum HarmonyEvent { | ||||||
|  |     HarmonyStarted, | ||||||
|  |     PrepareTopologyStarted { | ||||||
|  |         topology: String, | ||||||
|  |     }, | ||||||
|  |     TopologyPrepared { | ||||||
|  |         topology: String, | ||||||
|  |         outcome: Outcome, | ||||||
|  |     }, | ||||||
|  |     InterpretExecutionStarted { | ||||||
|  |         topology: String, | ||||||
|  |         interpret: String, | ||||||
|  |         message: String, | ||||||
|  |     }, | ||||||
|  |     InterpretExecutionFinished { | ||||||
|  |         topology: String, | ||||||
|  |         interpret: String, | ||||||
|  |         outcome: Result<Outcome, InterpretError>, | ||||||
|  |     }, | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static HARMONY_EVENT_BUS: Lazy<broadcast::Sender<HarmonyEvent>> = Lazy::new(|| { | ||||||
|  |     // TODO: Adjust channel capacity
 | ||||||
|  |     let (tx, _rx) = broadcast::channel(100); | ||||||
|  |     tx | ||||||
|  | }); | ||||||
|  | 
 | ||||||
|  | pub fn instrument(event: HarmonyEvent) -> Result<(), &'static str> { | ||||||
|  |     match HARMONY_EVENT_BUS.send(event) { | ||||||
|  |         Ok(_) => Ok(()), | ||||||
|  |         Err(_) => Err("send error: no subscribers"), | ||||||
|  | |||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | pub async fn subscribe<F, Fut>(name: &str, mut handler: F) | ||||||
|  | where | ||||||
|  |     F: FnMut(HarmonyEvent) -> Fut + Send + 'static, | ||||||
|  |     Fut: Future<Output = bool> + Send, | ||||||
|  | { | ||||||
|  |     let mut rx = HARMONY_EVENT_BUS.subscribe(); | ||||||
|  |     debug!("[{name}] Service started. Listening for events..."); | ||||||
|  |     loop { | ||||||
|  |         match rx.recv().await { | ||||||
|  |             Ok(event) => { | ||||||
|  |                 if !handler(event).await { | ||||||
|  |                     debug!("[{name}] Handler requested exit."); | ||||||
|  |                     break; | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |             Err(broadcast::error::RecvError::Lagged(n)) => { | ||||||
|  |                 debug!("[{name}] Lagged behind by {n} messages."); | ||||||
|  |             } | ||||||
|  |             Err(_) => break, | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
| @ -1,6 +1,8 @@ | |||||||
| use std::sync::{Arc, Mutex, RwLock}; | use std::sync::{Arc, Mutex, RwLock}; | ||||||
| 
 | 
 | ||||||
| use log::{info, warn}; | use log::{debug, info, warn}; | ||||||
|  | 
 | ||||||
|  | use crate::instrumentation::{self, HarmonyEvent}; | ||||||
| 
 | 
 | ||||||
| use super::{ | use super::{ | ||||||
|     interpret::{InterpretError, InterpretStatus, Outcome}, |     interpret::{InterpretError, InterpretStatus, Outcome}, | ||||||
| @ -40,13 +42,18 @@ impl<T: Topology> Maestro<T> { | |||||||
|     /// Ensures the associated Topology is ready for operations.
 |     /// Ensures the associated Topology is ready for operations.
 | ||||||
|     /// Delegates the readiness check and potential setup actions to the Topology.
 |     /// Delegates the readiness check and potential setup actions to the Topology.
 | ||||||
|     pub async fn prepare_topology(&self) -> Result<Outcome, InterpretError> { |     pub async fn prepare_topology(&self) -> Result<Outcome, InterpretError> { | ||||||
|         info!("Ensuring topology '{}' is ready...", self.topology.name()); |         instrumentation::instrument(HarmonyEvent::PrepareTopologyStarted { | ||||||
|  |             topology: self.topology.name().to_string(), | ||||||
|  |         }) | ||||||
|  |         .unwrap(); | ||||||
|  | 
 | ||||||
|         let outcome = self.topology.ensure_ready().await?; |         let outcome = self.topology.ensure_ready().await?; | ||||||
|         info!( | 
 | ||||||
|             "Topology '{}' readiness check complete: {}", |         instrumentation::instrument(HarmonyEvent::TopologyPrepared { | ||||||
|             self.topology.name(), |             topology: self.topology.name().to_string(), | ||||||
|             outcome.status |             outcome: outcome.clone(), | ||||||
|         ); |         }) | ||||||
|  |         .unwrap(); | ||||||
| 
 | 
 | ||||||
|         self.topology_preparation_result |         self.topology_preparation_result | ||||||
|             .lock() |             .lock() | ||||||
| @ -80,11 +87,11 @@ impl<T: Topology> Maestro<T> { | |||||||
|                 self.topology.name(), |                 self.topology.name(), | ||||||
|             ); |             ); | ||||||
|         } |         } | ||||||
|         info!("Running score {score:?}"); |         debug!("Running score {score:?}"); | ||||||
|         let interpret = score.create_interpret(); |         let interpret = score.create_interpret(); | ||||||
|         info!("Launching interpret {interpret:?}"); |         debug!("Launching interpret {interpret:?}"); | ||||||
|         let result = interpret.execute(&self.inventory, &self.topology).await; |         let result = interpret.execute(&self.inventory, &self.topology).await; | ||||||
|         info!("Got result {result:?}"); |         debug!("Got result {result:?}"); | ||||||
|         result |         result | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -3,6 +3,7 @@ pub mod data; | |||||||
| pub mod executors; | pub mod executors; | ||||||
| pub mod filter; | pub mod filter; | ||||||
| pub mod hardware; | pub mod hardware; | ||||||
|  | pub mod instrumentation; | ||||||
| pub mod interpret; | pub mod interpret; | ||||||
| pub mod inventory; | pub mod inventory; | ||||||
| pub mod maestro; | pub mod maestro; | ||||||
|  | |||||||
| @ -1,7 +1,6 @@ | |||||||
| use std::{process::Command, sync::Arc}; | use std::{process::Command, sync::Arc}; | ||||||
| 
 | 
 | ||||||
| use async_trait::async_trait; | use async_trait::async_trait; | ||||||
| use inquire::Confirm; |  | ||||||
| use log::{debug, info, warn}; | use log::{debug, info, warn}; | ||||||
| use serde::Serialize; | use serde::Serialize; | ||||||
| use tokio::sync::OnceCell; | use tokio::sync::OnceCell; | ||||||
| @ -93,9 +92,8 @@ impl K8sAnywhereTopology { | |||||||
|             return Err("Failed to run 'helm -version'".to_string()); |             return Err("Failed to run 'helm -version'".to_string()); | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         // Print the version output
 |  | ||||||
|         let version_output = String::from_utf8_lossy(&version_result.stdout); |         let version_output = String::from_utf8_lossy(&version_result.stdout); | ||||||
|         println!("Helm version: {}", version_output.trim()); |         debug!("Helm version: {}", version_output.trim()); | ||||||
| 
 | 
 | ||||||
|         Ok(()) |         Ok(()) | ||||||
|     } |     } | ||||||
| @ -126,7 +124,7 @@ impl K8sAnywhereTopology { | |||||||
|         // TODO this deserves some refactoring, it is becoming a bit hard to figure out
 |         // TODO this deserves some refactoring, it is becoming a bit hard to figure out
 | ||||||
|         // be careful when making modifications here
 |         // be careful when making modifications here
 | ||||||
|         if k8s_anywhere_config.use_local_k3d { |         if k8s_anywhere_config.use_local_k3d { | ||||||
|             info!("Using local k3d cluster because of use_local_k3d set to true"); |             debug!("Using local k3d cluster because of use_local_k3d set to true"); | ||||||
|         } else { |         } else { | ||||||
|             if let Some(kubeconfig) = &k8s_anywhere_config.kubeconfig { |             if let Some(kubeconfig) = &k8s_anywhere_config.kubeconfig { | ||||||
|                 debug!("Loading kubeconfig {kubeconfig}"); |                 debug!("Loading kubeconfig {kubeconfig}"); | ||||||
| @ -158,22 +156,13 @@ impl K8sAnywhereTopology { | |||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         if !k8s_anywhere_config.autoinstall { |         if !k8s_anywhere_config.autoinstall { | ||||||
|             debug!("Autoinstall confirmation prompt"); |  | ||||||
|             let confirmation = Confirm::new( "Harmony autoinstallation is not activated, do you wish to launch autoinstallation? : ") |  | ||||||
|                 .with_default(false) |  | ||||||
|                 .prompt() |  | ||||||
|                 .expect("Unexpected prompt error"); |  | ||||||
|             debug!("Autoinstall confirmation {confirmation}"); |  | ||||||
| 
 |  | ||||||
|             if !confirmation { |  | ||||||
|             warn!( |             warn!( | ||||||
|                 "Installation cancelled, K8sAnywhere could not initialize a valid Kubernetes client" |                 "Installation cancelled, K8sAnywhere could not initialize a valid Kubernetes client" | ||||||
|             ); |             ); | ||||||
|             return Ok(None); |             return Ok(None); | ||||||
|         } |         } | ||||||
|         } |  | ||||||
| 
 | 
 | ||||||
|         info!("Starting K8sAnywhere installation"); |         debug!("Starting K8sAnywhere installation"); | ||||||
|         self.try_install_k3d().await?; |         self.try_install_k3d().await?; | ||||||
|         let k3d_score = self.get_k3d_installation_score(); |         let k3d_score = self.get_k3d_installation_score(); | ||||||
|         // I feel like having to rely on the k3d_rs crate here is a smell
 |         // I feel like having to rely on the k3d_rs crate here is a smell
 | ||||||
| @ -186,7 +175,7 @@ impl K8sAnywhereTopology { | |||||||
|             Ok(client) => K8sState { |             Ok(client) => K8sState { | ||||||
|                 client: Arc::new(K8sClient::new(client)), |                 client: Arc::new(K8sClient::new(client)), | ||||||
|                 _source: K8sSource::LocalK3d, |                 _source: K8sSource::LocalK3d, | ||||||
|                 message: "Successfully installed K3D cluster and acquired client".to_string(), |                 message: "K8s client ready".to_string(), | ||||||
|             }, |             }, | ||||||
|             Err(_) => todo!(), |             Err(_) => todo!(), | ||||||
|         }; |         }; | ||||||
| @ -237,7 +226,7 @@ pub struct K8sAnywhereConfig { | |||||||
|     ///
 |     ///
 | ||||||
|     /// When enabled, autoinstall will setup a K3D cluster on the localhost. https://k3d.io/stable/
 |     /// When enabled, autoinstall will setup a K3D cluster on the localhost. https://k3d.io/stable/
 | ||||||
|     ///
 |     ///
 | ||||||
|     /// Default: false
 |     /// Default: true
 | ||||||
|     pub autoinstall: bool, |     pub autoinstall: bool, | ||||||
| 
 | 
 | ||||||
|     /// Whether to use local k3d cluster.
 |     /// Whether to use local k3d cluster.
 | ||||||
| @ -256,7 +245,7 @@ impl K8sAnywhereConfig { | |||||||
|             use_system_kubeconfig: std::env::var("HARMONY_USE_SYSTEM_KUBECONFIG") |             use_system_kubeconfig: std::env::var("HARMONY_USE_SYSTEM_KUBECONFIG") | ||||||
|                 .map_or_else(|_| false, |v| v.parse().ok().unwrap_or(false)), |                 .map_or_else(|_| false, |v| v.parse().ok().unwrap_or(false)), | ||||||
|             autoinstall: std::env::var("HARMONY_AUTOINSTALL") |             autoinstall: std::env::var("HARMONY_AUTOINSTALL") | ||||||
|                 .map_or_else(|_| false, |v| v.parse().ok().unwrap_or(false)), |                 .map_or_else(|_| true, |v| v.parse().ok().unwrap_or(false)), | ||||||
|             // TODO harmony_profile should be managed at a more core level than this
 |             // TODO harmony_profile should be managed at a more core level than this
 | ||||||
|             harmony_profile: std::env::var("HARMONY_PROFILE").map_or_else( |             harmony_profile: std::env::var("HARMONY_PROFILE").map_or_else( | ||||||
|                 |_| "dev".to_string(), |                 |_| "dev".to_string(), | ||||||
|  | |||||||
| @ -1,7 +1,7 @@ | |||||||
| use std::{io::Write, process::Command, sync::Arc}; | use std::{io::Write, process::Command, sync::Arc}; | ||||||
| 
 | 
 | ||||||
| use async_trait::async_trait; | use async_trait::async_trait; | ||||||
| use log::{error, info}; | use log::{debug, error}; | ||||||
| use serde_yaml::Value; | use serde_yaml::Value; | ||||||
| use tempfile::NamedTempFile; | use tempfile::NamedTempFile; | ||||||
| 
 | 
 | ||||||
| @ -63,7 +63,7 @@ impl<A: OCICompliant + HelmPackage> ContinuousDelivery<A> { | |||||||
|         error!("TODO hardcoded k3d bin path is wrong"); |         error!("TODO hardcoded k3d bin path is wrong"); | ||||||
|         let k3d_bin_path = (*HARMONY_DATA_DIR).join("k3d").join("k3d"); |         let k3d_bin_path = (*HARMONY_DATA_DIR).join("k3d").join("k3d"); | ||||||
|         // --- 1. Import the container image into the k3d cluster ---
 |         // --- 1. Import the container image into the k3d cluster ---
 | ||||||
|         info!( |         debug!( | ||||||
|             "Importing image '{}' into k3d cluster 'harmony'", |             "Importing image '{}' into k3d cluster 'harmony'", | ||||||
|             image_name |             image_name | ||||||
|         ); |         ); | ||||||
| @ -80,7 +80,7 @@ impl<A: OCICompliant + HelmPackage> ContinuousDelivery<A> { | |||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         // --- 2. Get the kubeconfig for the k3d cluster and write it to a temp file ---
 |         // --- 2. Get the kubeconfig for the k3d cluster and write it to a temp file ---
 | ||||||
|         info!("Retrieving kubeconfig for k3d cluster 'harmony'"); |         debug!("Retrieving kubeconfig for k3d cluster 'harmony'"); | ||||||
|         let kubeconfig_output = Command::new(&k3d_bin_path) |         let kubeconfig_output = Command::new(&k3d_bin_path) | ||||||
|             .args(["kubeconfig", "get", "harmony"]) |             .args(["kubeconfig", "get", "harmony"]) | ||||||
|             .output() |             .output() | ||||||
| @ -101,7 +101,7 @@ impl<A: OCICompliant + HelmPackage> ContinuousDelivery<A> { | |||||||
|         let kubeconfig_path = temp_kubeconfig.path().to_str().unwrap(); |         let kubeconfig_path = temp_kubeconfig.path().to_str().unwrap(); | ||||||
| 
 | 
 | ||||||
|         // --- 3. Install or upgrade the Helm chart in the cluster ---
 |         // --- 3. Install or upgrade the Helm chart in the cluster ---
 | ||||||
|         info!( |         debug!( | ||||||
|             "Deploying Helm chart '{}' to namespace '{}'", |             "Deploying Helm chart '{}' to namespace '{}'", | ||||||
|             chart_url, app_name |             chart_url, app_name | ||||||
|         ); |         ); | ||||||
| @ -131,7 +131,7 @@ impl<A: OCICompliant + HelmPackage> ContinuousDelivery<A> { | |||||||
|             )); |             )); | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         info!("Successfully deployed '{}' to local k3d cluster.", app_name); |         debug!("Successfully deployed '{}' to local k3d cluster.", app_name); | ||||||
|         Ok(()) |         Ok(()) | ||||||
|     } |     } | ||||||
| } | } | ||||||
| @ -145,24 +145,19 @@ impl< | |||||||
|     async fn ensure_installed(&self, topology: &T) -> Result<(), String> { |     async fn ensure_installed(&self, topology: &T) -> Result<(), String> { | ||||||
|         let image = self.application.image_name(); |         let image = self.application.image_name(); | ||||||
| 
 | 
 | ||||||
|         // TODO
 |  | ||||||
|         error!( |  | ||||||
|             "TODO reverse helm chart packaging and docker image build. I put helm package first for faster iterations" |  | ||||||
|         ); |  | ||||||
| 
 |  | ||||||
|         // TODO Write CI/CD workflow files
 |         // TODO Write CI/CD workflow files
 | ||||||
|         // we can autotedect the CI type using the remote url (default to github action for github
 |         // we can autotedect the CI type using the remote url (default to github action for github
 | ||||||
|         // url, etc..)
 |         // url, etc..)
 | ||||||
|         // Or ask for it when unknown
 |         // Or ask for it when unknown
 | ||||||
| 
 | 
 | ||||||
|         let helm_chart = self.application.build_push_helm_package(&image).await?; |         let helm_chart = self.application.build_push_helm_package(&image).await?; | ||||||
|         info!("Pushed new helm chart {helm_chart}"); |         debug!("Pushed new helm chart {helm_chart}"); | ||||||
| 
 | 
 | ||||||
|         error!("TODO Make building image configurable/skippable if image already exists (prompt)"); |         error!("TODO Make building image configurable/skippable if image already exists (prompt)"); | ||||||
|         let image = self.application.build_push_oci_image().await?; |         let image = self.application.build_push_oci_image().await?; | ||||||
|         info!("Pushed new docker image {image}"); |         debug!("Pushed new docker image {image}"); | ||||||
| 
 | 
 | ||||||
|         info!("Installing ContinuousDelivery feature"); |         debug!("Installing ContinuousDelivery feature"); | ||||||
|         // TODO this is a temporary hack for demo purposes, the deployment target should be driven
 |         // TODO this is a temporary hack for demo purposes, the deployment target should be driven
 | ||||||
|         // by the topology only and we should not have to know how to perform tasks like this for
 |         // by the topology only and we should not have to know how to perform tasks like this for
 | ||||||
|         // which the topology should be responsible.
 |         // which the topology should be responsible.
 | ||||||
| @ -182,7 +177,7 @@ impl< | |||||||
|                     .await?; |                     .await?; | ||||||
|             } |             } | ||||||
|             target => { |             target => { | ||||||
|                 info!("Deploying to target {target:?}"); |                 debug!("Deploying to target {target:?}"); | ||||||
|                 let score = ArgoHelmScore { |                 let score = ArgoHelmScore { | ||||||
|                     namespace: "harmonydemo-staging".to_string(), |                     namespace: "harmonydemo-staging".to_string(), | ||||||
|                     openshift: false, |                     openshift: false, | ||||||
|  | |||||||
| @ -5,7 +5,7 @@ mod rust; | |||||||
| use std::sync::Arc; | use std::sync::Arc; | ||||||
| 
 | 
 | ||||||
| pub use feature::*; | pub use feature::*; | ||||||
| use log::info; | use log::debug; | ||||||
| pub use oci::*; | pub use oci::*; | ||||||
| pub use rust::*; | pub use rust::*; | ||||||
| 
 | 
 | ||||||
| @ -36,7 +36,7 @@ impl<A: Application, T: Topology + std::fmt::Debug> Interpret<T> for Application | |||||||
|         topology: &T, |         topology: &T, | ||||||
|     ) -> Result<Outcome, InterpretError> { |     ) -> Result<Outcome, InterpretError> { | ||||||
|         let app_name = self.application.name(); |         let app_name = self.application.name(); | ||||||
|         info!( |         debug!( | ||||||
|             "Preparing {} features [{}] for application {app_name}", |             "Preparing {} features [{}] for application {app_name}", | ||||||
|             self.features.len(), |             self.features.len(), | ||||||
|             self.features |             self.features | ||||||
| @ -46,7 +46,7 @@ impl<A: Application, T: Topology + std::fmt::Debug> Interpret<T> for Application | |||||||
|                 .join(", ") |                 .join(", ") | ||||||
|         ); |         ); | ||||||
|         for feature in self.features.iter() { |         for feature in self.features.iter() { | ||||||
|             info!( |             debug!( | ||||||
|                 "Installing feature {} for application {app_name}", |                 "Installing feature {} for application {app_name}", | ||||||
|                 feature.name() |                 feature.name() | ||||||
|             ); |             ); | ||||||
|  | |||||||
| @ -10,7 +10,7 @@ use dockerfile_builder::Dockerfile; | |||||||
| use dockerfile_builder::instruction::{CMD, COPY, ENV, EXPOSE, FROM, RUN, USER, WORKDIR}; | use dockerfile_builder::instruction::{CMD, COPY, ENV, EXPOSE, FROM, RUN, USER, WORKDIR}; | ||||||
| use dockerfile_builder::instruction_builder::CopyBuilder; | use dockerfile_builder::instruction_builder::CopyBuilder; | ||||||
| use futures_util::StreamExt; | use futures_util::StreamExt; | ||||||
| use log::{debug, error, info}; | use log::{debug, error, log_enabled}; | ||||||
| use serde::Serialize; | use serde::Serialize; | ||||||
| use tar::Archive; | use tar::Archive; | ||||||
| 
 | 
 | ||||||
| @ -73,19 +73,19 @@ impl Application for RustWebapp { | |||||||
| #[async_trait] | #[async_trait] | ||||||
| impl HelmPackage for RustWebapp { | impl HelmPackage for RustWebapp { | ||||||
|     async fn build_push_helm_package(&self, image_url: &str) -> Result<String, String> { |     async fn build_push_helm_package(&self, image_url: &str) -> Result<String, String> { | ||||||
|         info!("Starting Helm chart build and push for '{}'", self.name); |         debug!("Starting Helm chart build and push for '{}'", self.name); | ||||||
| 
 | 
 | ||||||
|         // 1. Create the Helm chart files on disk.
 |         // 1. Create the Helm chart files on disk.
 | ||||||
|         let chart_dir = self |         let chart_dir = self | ||||||
|             .create_helm_chart_files(image_url) |             .create_helm_chart_files(image_url) | ||||||
|             .map_err(|e| format!("Failed to create Helm chart files: {}", e))?; |             .map_err(|e| format!("Failed to create Helm chart files: {}", e))?; | ||||||
|         info!("Successfully created Helm chart files in {:?}", chart_dir); |         debug!("Successfully created Helm chart files in {:?}", chart_dir); | ||||||
| 
 | 
 | ||||||
|         // 2. Package the chart into a .tgz archive.
 |         // 2. Package the chart into a .tgz archive.
 | ||||||
|         let packaged_chart_path = self |         let packaged_chart_path = self | ||||||
|             .package_helm_chart(&chart_dir) |             .package_helm_chart(&chart_dir) | ||||||
|             .map_err(|e| format!("Failed to package Helm chart: {}", e))?; |             .map_err(|e| format!("Failed to package Helm chart: {}", e))?; | ||||||
|         info!( |         debug!( | ||||||
|             "Successfully packaged Helm chart: {}", |             "Successfully packaged Helm chart: {}", | ||||||
|             packaged_chart_path.to_string_lossy() |             packaged_chart_path.to_string_lossy() | ||||||
|         ); |         ); | ||||||
| @ -94,7 +94,7 @@ impl HelmPackage for RustWebapp { | |||||||
|         let oci_chart_url = self |         let oci_chart_url = self | ||||||
|             .push_helm_chart(&packaged_chart_path) |             .push_helm_chart(&packaged_chart_path) | ||||||
|             .map_err(|e| format!("Failed to push Helm chart: {}", e))?; |             .map_err(|e| format!("Failed to push Helm chart: {}", e))?; | ||||||
|         info!("Successfully pushed Helm chart to: {}", oci_chart_url); |         debug!("Successfully pushed Helm chart to: {}", oci_chart_url); | ||||||
| 
 | 
 | ||||||
|         Ok(oci_chart_url) |         Ok(oci_chart_url) | ||||||
|     } |     } | ||||||
| @ -107,20 +107,20 @@ impl OCICompliant for RustWebapp { | |||||||
|     async fn build_push_oci_image(&self) -> Result<String, String> { |     async fn build_push_oci_image(&self) -> Result<String, String> { | ||||||
|         // This function orchestrates the build and push process.
 |         // This function orchestrates the build and push process.
 | ||||||
|         // It's async to match the trait definition, though the underlying docker commands are blocking.
 |         // It's async to match the trait definition, though the underlying docker commands are blocking.
 | ||||||
|         info!("Starting OCI image build and push for '{}'", self.name); |         debug!("Starting OCI image build and push for '{}'", self.name); | ||||||
| 
 | 
 | ||||||
|         // 1. Build the image by calling the synchronous helper function.
 |         // 1. Build the image by calling the synchronous helper function.
 | ||||||
|         let image_tag = self.image_name(); |         let image_tag = self.image_name(); | ||||||
|         self.build_docker_image(&image_tag) |         self.build_docker_image(&image_tag) | ||||||
|             .await |             .await | ||||||
|             .map_err(|e| format!("Failed to build Docker image: {}", e))?; |             .map_err(|e| format!("Failed to build Docker image: {}", e))?; | ||||||
|         info!("Successfully built Docker image: {}", image_tag); |         debug!("Successfully built Docker image: {}", image_tag); | ||||||
| 
 | 
 | ||||||
|         // 2. Push the image to the registry.
 |         // 2. Push the image to the registry.
 | ||||||
|         self.push_docker_image(&image_tag) |         self.push_docker_image(&image_tag) | ||||||
|             .await |             .await | ||||||
|             .map_err(|e| format!("Failed to push Docker image: {}", e))?; |             .map_err(|e| format!("Failed to push Docker image: {}", e))?; | ||||||
|         info!("Successfully pushed Docker image to: {}", image_tag); |         debug!("Successfully pushed Docker image to: {}", image_tag); | ||||||
| 
 | 
 | ||||||
|         Ok(image_tag) |         Ok(image_tag) | ||||||
|     } |     } | ||||||
| @ -159,15 +159,17 @@ impl RustWebapp { | |||||||
|         &self, |         &self, | ||||||
|         image_name: &str, |         image_name: &str, | ||||||
|     ) -> Result<String, Box<dyn std::error::Error>> { |     ) -> Result<String, Box<dyn std::error::Error>> { | ||||||
|         info!("Generating Dockerfile for '{}'", self.name); |         debug!("Generating Dockerfile for '{}'", self.name); | ||||||
|         let _dockerfile_path = self.build_dockerfile()?; |         let _dockerfile_path = self.build_dockerfile()?; | ||||||
| 
 | 
 | ||||||
|         let docker = Docker::connect_with_socket_defaults().unwrap(); |         let docker = Docker::connect_with_socket_defaults().unwrap(); | ||||||
| 
 | 
 | ||||||
|  |         let quiet = !log_enabled!(log::Level::Debug); | ||||||
|  | 
 | ||||||
|         let build_image_options = bollard::query_parameters::BuildImageOptionsBuilder::default() |         let build_image_options = bollard::query_parameters::BuildImageOptionsBuilder::default() | ||||||
|             .dockerfile("Dockerfile.harmony") |             .dockerfile("Dockerfile.harmony") | ||||||
|             .t(image_name) |             .t(image_name) | ||||||
|             .q(false) |             .q(quiet) | ||||||
|             .version(bollard::query_parameters::BuilderVersion::BuilderV1) |             .version(bollard::query_parameters::BuilderVersion::BuilderV1) | ||||||
|             .platform("linux/x86_64"); |             .platform("linux/x86_64"); | ||||||
| 
 | 
 | ||||||
| @ -204,7 +206,7 @@ impl RustWebapp { | |||||||
|         &self, |         &self, | ||||||
|         image_tag: &str, |         image_tag: &str, | ||||||
|     ) -> Result<String, Box<dyn std::error::Error>> { |     ) -> Result<String, Box<dyn std::error::Error>> { | ||||||
|         info!("Pushing docker image {image_tag}"); |         debug!("Pushing docker image {image_tag}"); | ||||||
| 
 | 
 | ||||||
|         let docker = Docker::connect_with_socket_defaults().unwrap(); |         let docker = Docker::connect_with_socket_defaults().unwrap(); | ||||||
| 
 | 
 | ||||||
| @ -533,7 +535,7 @@ spec: | |||||||
|         chart_dir: &PathBuf, |         chart_dir: &PathBuf, | ||||||
|     ) -> Result<PathBuf, Box<dyn std::error::Error>> { |     ) -> Result<PathBuf, Box<dyn std::error::Error>> { | ||||||
|         let chart_dirname = chart_dir.file_name().expect("Should find a chart dirname"); |         let chart_dirname = chart_dir.file_name().expect("Should find a chart dirname"); | ||||||
|         info!( |         debug!( | ||||||
|             "Launching `helm package {}` cli with CWD {}", |             "Launching `helm package {}` cli with CWD {}", | ||||||
|             chart_dirname.to_string_lossy(), |             chart_dirname.to_string_lossy(), | ||||||
|             &self |             &self | ||||||
| @ -578,7 +580,7 @@ spec: | |||||||
|         let oci_push_url = format!("oci://{}/{}", *REGISTRY_URL, *REGISTRY_PROJECT); |         let oci_push_url = format!("oci://{}/{}", *REGISTRY_URL, *REGISTRY_PROJECT); | ||||||
|         let oci_pull_url = format!("{oci_push_url}/{}-chart", self.name); |         let oci_pull_url = format!("{oci_push_url}/{}-chart", self.name); | ||||||
| 
 | 
 | ||||||
|         info!( |         debug!( | ||||||
|             "Pushing Helm chart {} to {}", |             "Pushing Helm chart {} to {}", | ||||||
|             packaged_chart_path.to_string_lossy(), |             packaged_chart_path.to_string_lossy(), | ||||||
|             oci_push_url |             oci_push_url | ||||||
|  | |||||||
| @ -1,12 +1,13 @@ | |||||||
| use std::path::PathBuf; | use std::path::PathBuf; | ||||||
| 
 | 
 | ||||||
| use async_trait::async_trait; | use async_trait::async_trait; | ||||||
| use log::info; | use log::{debug, info}; | ||||||
| use serde::Serialize; | 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, | ||||||
| @ -50,22 +51,38 @@ impl<T: Topology> Interpret<T> for K3dInstallationInterpret { | |||||||
|     async fn execute( |     async fn execute( | ||||||
|         &self, |         &self, | ||||||
|         _inventory: &Inventory, |         _inventory: &Inventory, | ||||||
|         _topology: &T, |         topology: &T, | ||||||
|     ) -> Result<Outcome, InterpretError> { |     ) -> Result<Outcome, InterpretError> { | ||||||
|  |         instrumentation::instrument(HarmonyEvent::InterpretExecutionStarted { | ||||||
|  |             topology: topology.name().into(), | ||||||
|  |             interpret: "k3d-installation".into(), | ||||||
|  |             message: "installing k3d...".into(), | ||||||
|  |         }) | ||||||
|  |         .unwrap(); | ||||||
|  | 
 | ||||||
|         let k3d = k3d_rs::K3d::new( |         let k3d = k3d_rs::K3d::new( | ||||||
|             self.score.installation_path.clone(), |             self.score.installation_path.clone(), | ||||||
|             Some(self.score.cluster_name.clone()), |             Some(self.score.cluster_name.clone()), | ||||||
|         ); |         ); | ||||||
|         match k3d.ensure_installed().await { |         let outcome = match k3d.ensure_installed().await { | ||||||
|             Ok(_client) => { |             Ok(_client) => { | ||||||
|                 let msg = format!("k3d cluster {} is installed ", self.score.cluster_name); |                 let msg = format!("k3d cluster '{}' installed ", self.score.cluster_name); | ||||||
|                 info!("{msg}"); |                 debug!("{msg}"); | ||||||
|                 Ok(Outcome::success(msg)) |                 Ok(Outcome::success(msg)) | ||||||
|             } |             } | ||||||
|             Err(msg) => Err(InterpretError::new(format!( |             Err(msg) => Err(InterpretError::new(format!( | ||||||
|                 "K3dInstallationInterpret failed to ensure k3d is installed : {msg}" |                 "failed to ensure k3d is installed : {msg}" | ||||||
|             ))), |             ))), | ||||||
|         } |         }; | ||||||
|  | 
 | ||||||
|  |         instrumentation::instrument(HarmonyEvent::InterpretExecutionFinished { | ||||||
|  |             topology: topology.name().into(), | ||||||
|  |             interpret: "k3d-installation".into(), | ||||||
|  |             outcome: outcome.clone(), | ||||||
|  |         }) | ||||||
|  |         .unwrap(); | ||||||
|  | 
 | ||||||
|  |         outcome | ||||||
|     } |     } | ||||||
|     fn get_name(&self) -> InterpretName { |     fn get_name(&self) -> InterpretName { | ||||||
|         InterpretName::K3dInstallation |         InterpretName::K3dInstallation | ||||||
|  | |||||||
| @ -13,6 +13,11 @@ harmony_tui = { path = "../harmony_tui", optional = true } | |||||||
| inquire.workspace = true | inquire.workspace = true | ||||||
| tokio.workspace = true | tokio.workspace = true | ||||||
| env_logger.workspace = true | env_logger.workspace = true | ||||||
|  | console = "0.16.0" | ||||||
|  | indicatif = "0.18.0" | ||||||
|  | lazy_static = "1.5.0" | ||||||
|  | log.workspace = true | ||||||
|  | indicatif-log-bridge = "0.2.3" | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| [features] | [features] | ||||||
|  | |||||||
							
								
								
									
										116
									
								
								harmony_cli/src/cli_logger.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										116
									
								
								harmony_cli/src/cli_logger.rs
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,116 @@ | |||||||
|  | use harmony::instrumentation::{self, HarmonyEvent}; | ||||||
|  | use indicatif::{MultiProgress, ProgressBar}; | ||||||
|  | use indicatif_log_bridge::LogWrapper; | ||||||
|  | use std::{ | ||||||
|  |     collections::{HashMap, hash_map}, | ||||||
|  |     sync::{Arc, Mutex}, | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | use crate::progress; | ||||||
|  | 
 | ||||||
|  | pub fn init() -> tokio::task::JoinHandle<()> { | ||||||
|  |     configure_logger(); | ||||||
|  |     let handle = tokio::spawn(handle_events()); | ||||||
|  | 
 | ||||||
|  |     loop { | ||||||
|  |         if instrumentation::instrument(HarmonyEvent::HarmonyStarted).is_ok() { | ||||||
|  |             break; | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     handle | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | fn configure_logger() { | ||||||
|  |     let logger = | ||||||
|  |         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); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | async fn handle_events() { | ||||||
|  |     instrumentation::subscribe("Harmony CLI Logger", { | ||||||
|  |         let sections: Arc<Mutex<HashMap<String, MultiProgress>>> = | ||||||
|  |             Arc::new(Mutex::new(HashMap::new())); | ||||||
|  |         let progress_bars: Arc<Mutex<HashMap<String, ProgressBar>>> = | ||||||
|  |             Arc::new(Mutex::new(HashMap::new())); | ||||||
|  | 
 | ||||||
|  |         move |event| { | ||||||
|  |             let sections_clone = Arc::clone(§ions); | ||||||
|  |             let progress_bars_clone = Arc::clone(&progress_bars); | ||||||
|  | 
 | ||||||
|  |             async move { | ||||||
|  |                 let mut sections = sections_clone.lock().unwrap(); | ||||||
|  |                 let mut progress_bars = progress_bars_clone.lock().unwrap(); | ||||||
|  | 
 | ||||||
|  |                 match event { | ||||||
|  |                     HarmonyEvent::HarmonyStarted => {} | ||||||
|  |                     HarmonyEvent::PrepareTopologyStarted { topology: name } => { | ||||||
|  |                         let section = progress::new_section(format!( | ||||||
|  |                             "{} Preparing environment: {name}...", | ||||||
|  |                             crate::theme::EMOJI_TOPOLOGY, | ||||||
|  |                         )); | ||||||
|  |                         (*sections).insert(name, section); | ||||||
|  |                     } | ||||||
|  |                     HarmonyEvent::TopologyPrepared { | ||||||
|  |                         topology: name, | ||||||
|  |                         outcome, | ||||||
|  |                     } => { | ||||||
|  |                         let section = (*sections).get(&name).unwrap(); | ||||||
|  |                         let progress = progress::add_spinner(section, "".into()); | ||||||
|  | 
 | ||||||
|  |                         match outcome.status { | ||||||
|  |                             harmony::interpret::InterpretStatus::SUCCESS => { | ||||||
|  |                                 progress::success(section, Some(progress), outcome.message); | ||||||
|  |                             } | ||||||
|  |                             harmony::interpret::InterpretStatus::FAILURE => { | ||||||
|  |                                 progress::error(section, Some(progress), outcome.message); | ||||||
|  |                             } | ||||||
|  |                             harmony::interpret::InterpretStatus::RUNNING => todo!(), | ||||||
|  |                             harmony::interpret::InterpretStatus::QUEUED => todo!(), | ||||||
|  |                             harmony::interpret::InterpretStatus::BLOCKED => todo!(), | ||||||
|  |                             harmony::interpret::InterpretStatus::NOOP => { | ||||||
|  |                                 progress::skip(section, Some(progress), outcome.message); | ||||||
|  |                             } | ||||||
|  |                         } | ||||||
|  |                     } | ||||||
|  |                     HarmonyEvent::InterpretExecutionStarted { | ||||||
|  |                         interpret: name, | ||||||
|  |                         topology, | ||||||
|  |                         message, | ||||||
|  |                     } => { | ||||||
|  |                         let section = (*sections).get(&topology).unwrap(); | ||||||
|  |                         let progress_bar = progress::add_spinner(section, message); | ||||||
|  | 
 | ||||||
|  |                         (*progress_bars).insert(name, progress_bar); | ||||||
|  |                     } | ||||||
|  |                     HarmonyEvent::InterpretExecutionFinished { | ||||||
|  |                         topology, | ||||||
|  |                         interpret: name, | ||||||
|  |                         outcome, | ||||||
|  |                     } => { | ||||||
|  |                         let section = (*sections).get(&topology).unwrap(); | ||||||
|  |                         let progress_bar = (*progress_bars).get(&name).cloned(); | ||||||
|  | 
 | ||||||
|  |                         let _ = section.clear(); | ||||||
|  | 
 | ||||||
|  |                         match outcome { | ||||||
|  |                             Ok(outcome) => { | ||||||
|  |                                 progress::success(section, progress_bar, outcome.message); | ||||||
|  |                             } | ||||||
|  |                             Err(err) => { | ||||||
|  |                                 progress::error(section, progress_bar, err.to_string()); | ||||||
|  |                             } | ||||||
|  |                         } | ||||||
|  | 
 | ||||||
|  |                         (*progress_bars).remove(&name); | ||||||
|  |                     } | ||||||
|  |                 } | ||||||
|  |                 true | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |     }) | ||||||
|  |     .await; | ||||||
|  | } | ||||||
| @ -4,8 +4,13 @@ use harmony; | |||||||
| use harmony::{score::Score, topology::Topology}; | use harmony::{score::Score, topology::Topology}; | ||||||
| use inquire::Confirm; | use inquire::Confirm; | ||||||
| 
 | 
 | ||||||
|  | pub mod cli_logger; // FIXME: Don't make me pub
 | ||||||
| 
				
					
						letian
						commented  See the PR comment above (in  See the PR comment above (in `examples/rust/main.rs`) for more info | |||||||
|  | pub mod progress; | ||||||
|  | pub mod theme; | ||||||
|  | 
 | ||||||
| #[cfg(feature = "tui")] | #[cfg(feature = "tui")] | ||||||
| use harmony_tui; | use harmony_tui; | ||||||
|  | use log::debug; | ||||||
| 
 | 
 | ||||||
| #[derive(Parser, Debug)] | #[derive(Parser, Debug)] | ||||||
| #[command(version, about, long_about = None)] | #[command(version, about, long_about = None)] | ||||||
| @ -134,7 +139,7 @@ pub async fn init<T: Topology + Send + Sync + 'static>( | |||||||
| 
 | 
 | ||||||
|     // Run filtered scores
 |     // Run filtered scores
 | ||||||
|     for s in scores_vec { |     for s in scores_vec { | ||||||
|         println!("Running: {}", s.name()); |         debug!("Running: {}", s.name()); | ||||||
|         maestro.interpret(s).await?; |         maestro.interpret(s).await?; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  | |||||||
							
								
								
									
										50
									
								
								harmony_cli/src/progress.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										50
									
								
								harmony_cli/src/progress.rs
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,50 @@ | |||||||
|  | use std::time::Duration; | ||||||
|  | 
 | ||||||
|  | use indicatif::{MultiProgress, ProgressBar}; | ||||||
|  | 
 | ||||||
|  | pub fn new_section(title: String) -> MultiProgress { | ||||||
|  |     let multi_progress = MultiProgress::new(); | ||||||
|  |     let _ = multi_progress.println(title); | ||||||
|  | 
 | ||||||
|  |     multi_progress | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | pub fn add_spinner(multi_progress: &MultiProgress, message: String) -> ProgressBar { | ||||||
|  |     let progress = multi_progress.add(ProgressBar::new_spinner()); | ||||||
|  | 
 | ||||||
|  |     progress.set_style(crate::theme::SPINNER_STYLE.clone()); | ||||||
|  |     progress.set_message(message); | ||||||
|  |     progress.enable_steady_tick(Duration::from_millis(100)); | ||||||
|  | 
 | ||||||
|  |     progress | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | pub fn success(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()); | ||||||
|  |     progress.set_style(crate::theme::SUCCESS_SPINNER_STYLE.clone()); | ||||||
|  |     progress.finish_with_message(message); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | pub fn error(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()); | ||||||
|  |     progress.set_style(crate::theme::ERROR_SPINNER_STYLE.clone()); | ||||||
|  |     progress.finish_with_message(message); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | 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()); | ||||||
|  |     progress.set_style(crate::theme::SKIP_SPINNER_STYLE.clone()); | ||||||
|  |     progress.finish_with_message(message); | ||||||
|  | } | ||||||
							
								
								
									
										26
									
								
								harmony_cli/src/theme.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										26
									
								
								harmony_cli/src/theme.rs
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,26 @@ | |||||||
|  | use console::Emoji; | ||||||
|  | use indicatif::ProgressStyle; | ||||||
|  | use lazy_static::lazy_static; | ||||||
|  | 
 | ||||||
|  | pub static EMOJI_HARMONY: Emoji<'_, '_> = Emoji("🎼", ""); | ||||||
|  | pub static EMOJI_SUCCESS: Emoji<'_, '_> = Emoji("✅", ""); | ||||||
|  | pub static EMOJI_SKIP: Emoji<'_, '_> = Emoji("⏭️", ""); | ||||||
|  | pub static EMOJI_ERROR: Emoji<'_, '_> = Emoji("⚠️", ""); | ||||||
|  | pub static EMOJI_DEPLOY: Emoji<'_, '_> = Emoji("🚀", ""); | ||||||
|  | pub static EMOJI_TOPOLOGY: Emoji<'_, '_> = Emoji("📦", ""); | ||||||
|  | 
 | ||||||
|  | lazy_static! { | ||||||
|  |     pub static ref SPINNER_STYLE: ProgressStyle = ProgressStyle::default_spinner() | ||||||
|  |         .template("    {spinner:.green} {msg}") | ||||||
|  |         .unwrap() | ||||||
|  |         .tick_strings(&["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"]); | ||||||
|  |     pub static ref SUCCESS_SPINNER_STYLE: ProgressStyle = SPINNER_STYLE | ||||||
|  |         .clone() | ||||||
|  |         .tick_strings(&[format!("{}", EMOJI_SUCCESS).as_str()]); | ||||||
|  |     pub static ref SKIP_SPINNER_STYLE: ProgressStyle = SPINNER_STYLE | ||||||
|  |         .clone() | ||||||
|  |         .tick_strings(&[format!("{}", EMOJI_SKIP).as_str()]); | ||||||
|  |     pub static ref ERROR_SPINNER_STYLE: ProgressStyle = SPINNER_STYLE | ||||||
|  |         .clone() | ||||||
|  |         .tick_strings(&[format!("{}", EMOJI_ERROR).as_str()]); | ||||||
|  | } | ||||||
| @ -15,3 +15,9 @@ current_platform = "0.2.0" | |||||||
| futures-util = "0.3.31" | futures-util = "0.3.31" | ||||||
| serde_json = "1.0.140" | serde_json = "1.0.140" | ||||||
| cargo_metadata = "0.20.0" | cargo_metadata = "0.20.0" | ||||||
|  | indicatif = "0.18.0" | ||||||
|  | console = "0.16.0" | ||||||
|  | lazy_static = "1.5.0" | ||||||
|  | once_cell = "1.21.3" | ||||||
|  | harmony_cli = { path = "../harmony_cli" } | ||||||
|  | indicatif-log-bridge = "0.2.3" | ||||||
|  | |||||||
							
								
								
									
										112
									
								
								harmony_composer/src/harmony_composer_logger.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										112
									
								
								harmony_composer/src/harmony_composer_logger.rs
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,112 @@ | |||||||
|  | use indicatif::{MultiProgress, ProgressBar}; | ||||||
|  | use indicatif_log_bridge::LogWrapper; | ||||||
|  | use log::error; | ||||||
|  | use std::{ | ||||||
|  |     collections::HashMap, | ||||||
|  |     sync::{Arc, Mutex}, | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | use crate::instrumentation::{self, HarmonyComposerEvent}; | ||||||
|  | 
 | ||||||
|  | pub fn init() -> tokio::task::JoinHandle<()> { | ||||||
|  |     configure_logger(); | ||||||
|  |     let handle = tokio::spawn(handle_events()); | ||||||
|  | 
 | ||||||
|  |     loop { | ||||||
|  |         if instrumentation::instrument(HarmonyComposerEvent::HarmonyComposerStarted).is_ok() { | ||||||
|  |             break; | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     handle | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | fn configure_logger() { | ||||||
|  |     let logger = | ||||||
|  |         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() { | ||||||
|  |     const PROGRESS_SETUP: &str = "project-initialization"; | ||||||
|  |     const PROGRESS_DEPLOYMENT: &str = "deployment"; | ||||||
|  | 
 | ||||||
|  |     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| { | ||||||
|  |             let progresses_clone = Arc::clone(&progresses); | ||||||
|  |             let compilation_progress_clone = Arc::clone(&compilation_progress); | ||||||
|  | 
 | ||||||
|  |             async move { | ||||||
|  |                 let mut progresses_guard = progresses_clone.lock().unwrap(); | ||||||
|  |                 let mut compilation_progress_guard = compilation_progress_clone.lock().unwrap(); | ||||||
|  | 
 | ||||||
|  |                 match event { | ||||||
|  |                     HarmonyComposerEvent::HarmonyComposerStarted => {} | ||||||
|  |                     HarmonyComposerEvent::ProjectInitializationStarted => { | ||||||
|  |                         let multi_progress = harmony_cli::progress::new_section(format!( | ||||||
|  |                             "{} Initializing Harmony project...", | ||||||
|  |                             harmony_cli::theme::EMOJI_HARMONY, | ||||||
|  |                         )); | ||||||
|  |                         (*progresses_guard).insert(PROGRESS_SETUP.to_string(), multi_progress); | ||||||
|  |                     } | ||||||
|  |                     HarmonyComposerEvent::ProjectInitialized => println!("\n"), | ||||||
|  |                     HarmonyComposerEvent::ProjectCompilationStarted { details } => { | ||||||
|  |                         let initialization_progress = | ||||||
|  |                             (*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 => { | ||||||
|  |                         let initialization_progress = | ||||||
|  |                             (*progresses_guard).get(PROGRESS_SETUP).unwrap(); | ||||||
|  | 
 | ||||||
|  |                         harmony_cli::progress::success( | ||||||
|  |                             initialization_progress, | ||||||
|  |                             (*compilation_progress_guard).take(), | ||||||
|  |                             "project compiled".to_string(), | ||||||
|  |                         ); | ||||||
|  |                     } | ||||||
|  |                     HarmonyComposerEvent::ProjectCompilationFailed { details } => { | ||||||
|  |                         let initialization_progress = | ||||||
|  |                             (*progresses_guard).get(PROGRESS_SETUP).unwrap(); | ||||||
|  | 
 | ||||||
|  |                         harmony_cli::progress::error( | ||||||
|  |                             initialization_progress, | ||||||
|  |                             (*compilation_progress_guard).take(), | ||||||
|  |                             "failed to compile project".to_string(), | ||||||
|  |                         ); | ||||||
|  | 
 | ||||||
|  |                         error!("{details}"); | ||||||
|  |                     } | ||||||
|  |                     HarmonyComposerEvent::DeploymentStarted { target } => { | ||||||
|  |                         let multi_progress = harmony_cli::progress::new_section(format!( | ||||||
|  |                             "{} Starting deployment to {target}...\n\n", | ||||||
|  |                             harmony_cli::theme::EMOJI_DEPLOY | ||||||
|  |                         )); | ||||||
|  |                         (*progresses_guard).insert(PROGRESS_DEPLOYMENT.to_string(), multi_progress); | ||||||
|  |                     } | ||||||
|  |                     HarmonyComposerEvent::DeploymentCompleted { details } => println!("\n"), | ||||||
|  |                     HarmonyComposerEvent::Shutdown => { | ||||||
|  |                         for (_, progresses) in (*progresses_guard).iter() { | ||||||
|  |                             progresses.clear().unwrap(); | ||||||
|  |                         } | ||||||
|  | 
 | ||||||
|  |                         return false; | ||||||
|  |                     } | ||||||
|  |                 } | ||||||
|  |                 true | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |     }) | ||||||
|  |     .await | ||||||
|  | } | ||||||
							
								
								
									
										53
									
								
								harmony_composer/src/instrumentation.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										53
									
								
								harmony_composer/src/instrumentation.rs
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,53 @@ | |||||||
|  | use log::debug; | ||||||
|  | use once_cell::sync::Lazy; | ||||||
|  | use tokio::sync::broadcast; | ||||||
|  | 
 | ||||||
|  | #[derive(Debug, Clone)] | ||||||
|  | pub enum HarmonyComposerEvent { | ||||||
|  |     HarmonyComposerStarted, | ||||||
|  |     ProjectInitializationStarted, | ||||||
|  |     ProjectInitialized, | ||||||
|  |     ProjectCompilationStarted { details: String }, | ||||||
|  |     ProjectCompiled, | ||||||
|  |     ProjectCompilationFailed { details: String }, | ||||||
|  |     DeploymentStarted { target: String }, | ||||||
|  |     DeploymentCompleted { details: String }, | ||||||
|  |     Shutdown, | ||||||
|  | } | ||||||
|  | 
 | ||||||
| 
				
					
						letian
						commented  I'm not a big fan of "duplicating" all the stuff below. It is for different concerns than the ones defined in  I'm not a big fan of "duplicating" all the stuff below. It is for different concerns than the ones defined in `harmony_cli`, but it follows the same pattern. | |||||||
|  | static HARMONY_COMPOSER_EVENT_BUS: Lazy<broadcast::Sender<HarmonyComposerEvent>> = | ||||||
|  |     Lazy::new(|| { | ||||||
|  |         // TODO: Adjust channel capacity
 | ||||||
|  |         let (tx, _rx) = broadcast::channel(16); | ||||||
|  |         tx | ||||||
|  |     }); | ||||||
|  | 
 | ||||||
|  | pub fn instrument(event: HarmonyComposerEvent) -> Result<(), &'static str> { | ||||||
|  |     match HARMONY_COMPOSER_EVENT_BUS.send(event) { | ||||||
|  |         Ok(_) => Ok(()), | ||||||
|  |         Err(_) => Err("send error: no subscribers"), | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | pub async fn subscribe<F, Fut>(name: &str, mut handler: F) | ||||||
|  | where | ||||||
|  |     F: FnMut(HarmonyComposerEvent) -> Fut + Send + 'static, | ||||||
|  |     Fut: Future<Output = bool> + Send, | ||||||
|  | { | ||||||
|  |     let mut rx = HARMONY_COMPOSER_EVENT_BUS.subscribe(); | ||||||
|  |     debug!("[{name}] Service started. Listening for events..."); | ||||||
|  |     loop { | ||||||
|  |         match rx.recv().await { | ||||||
|  |             Ok(event) => { | ||||||
|  |                 if !handler(event).await { | ||||||
|  |                     debug!("[{name}] Handler requested exit."); | ||||||
|  |                     break; | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |             Err(broadcast::error::RecvError::Lagged(n)) => { | ||||||
|  |                 debug!("[{name}] Lagged behind by {n} messages."); | ||||||
|  |             } | ||||||
|  |             Err(_) => break, | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
| @ -7,12 +7,16 @@ use bollard::secret::HostConfig; | |||||||
| use cargo_metadata::{Artifact, Message, MetadataCommand}; | use cargo_metadata::{Artifact, Message, MetadataCommand}; | ||||||
| use clap::{Args, Parser, Subcommand}; | use clap::{Args, Parser, Subcommand}; | ||||||
| use futures_util::StreamExt; | use futures_util::StreamExt; | ||||||
| use log::info; | use instrumentation::HarmonyComposerEvent; | ||||||
|  | use log::{debug, info, log_enabled}; | ||||||
| use std::collections::HashMap; | use std::collections::HashMap; | ||||||
| use std::path::{Path, PathBuf}; | use std::path::{Path, PathBuf}; | ||||||
| use std::process::{Command, Stdio}; | use std::process::{Command, Stdio}; | ||||||
| use tokio::fs; | use tokio::fs; | ||||||
| 
 | 
 | ||||||
|  | mod harmony_composer_logger; | ||||||
|  | mod instrumentation; | ||||||
|  | 
 | ||||||
| #[derive(Parser)] | #[derive(Parser)] | ||||||
| #[command(version, about, long_about = None, flatten_help = true, propagate_version = true)] | #[command(version, about, long_about = None, flatten_help = true, propagate_version = true)] | ||||||
| struct GlobalArgs { | struct GlobalArgs { | ||||||
| @ -66,13 +70,15 @@ struct AllArgs { | |||||||
| 
 | 
 | ||||||
| #[tokio::main] | #[tokio::main] | ||||||
| async fn main() { | async fn main() { | ||||||
|     env_logger::init(); |     let hc_logger_handle = harmony_composer_logger::init(); | ||||||
|     let cli_args = GlobalArgs::parse(); |     let cli_args = GlobalArgs::parse(); | ||||||
| 
 | 
 | ||||||
|     let harmony_path = Path::new(&cli_args.harmony_path) |     let harmony_path = Path::new(&cli_args.harmony_path) | ||||||
|         .try_exists() |         .try_exists() | ||||||
|         .expect("couldn't check if path exists"); |         .expect("couldn't check if path exists"); | ||||||
| 
 | 
 | ||||||
|  |     instrumentation::instrument(HarmonyComposerEvent::ProjectInitializationStarted).unwrap(); | ||||||
|  | 
 | ||||||
|     let harmony_bin_path: PathBuf = match harmony_path { |     let harmony_bin_path: PathBuf = match harmony_path { | ||||||
|         true => { |         true => { | ||||||
|             compile_harmony( |             compile_harmony( | ||||||
| @ -85,6 +91,8 @@ async fn main() { | |||||||
|         false => todo!("implement autodetect code"), |         false => todo!("implement autodetect code"), | ||||||
|     }; |     }; | ||||||
| 
 | 
 | ||||||
|  |     instrumentation::instrument(HarmonyComposerEvent::ProjectInitialized).unwrap(); | ||||||
|  | 
 | ||||||
|     match cli_args.command { |     match cli_args.command { | ||||||
|         Some(command) => match command { |         Some(command) => match command { | ||||||
|             Commands::Check(args) => { |             Commands::Check(args) => { | ||||||
| @ -116,19 +124,31 @@ async fn main() { | |||||||
|             } |             } | ||||||
|             Commands::Deploy(args) => { |             Commands::Deploy(args) => { | ||||||
|                 let deploy = if args.staging { |                 let deploy = if args.staging { | ||||||
|  |                     instrumentation::instrument(HarmonyComposerEvent::DeploymentStarted { | ||||||
|  |                         target: "staging".to_string(), | ||||||
|  |                     }) | ||||||
|  |                     .unwrap(); | ||||||
|                     todo!("implement staging deployment") |                     todo!("implement staging deployment") | ||||||
|                 } else if args.prod { |                 } else if args.prod { | ||||||
|  |                     instrumentation::instrument(HarmonyComposerEvent::DeploymentStarted { | ||||||
|  |                         target: "prod".to_string(), | ||||||
|  |                     }) | ||||||
|  |                     .unwrap(); | ||||||
|                     todo!("implement prod deployment") |                     todo!("implement prod deployment") | ||||||
|                 } else { |                 } else { | ||||||
|  |                     instrumentation::instrument(HarmonyComposerEvent::DeploymentStarted { | ||||||
|  |                         target: "dev".to_string(), | ||||||
|  |                     }) | ||||||
|  |                     .unwrap(); | ||||||
|                     Command::new(harmony_bin_path).arg("-y").arg("-a").spawn() |                     Command::new(harmony_bin_path).arg("-y").arg("-a").spawn() | ||||||
|                 } |                 } | ||||||
|                 .expect("failed to run harmony deploy"); |                 .expect("failed to run harmony deploy"); | ||||||
| 
 | 
 | ||||||
|                 let deploy_output = deploy.wait_with_output().unwrap(); |                 let deploy_output = deploy.wait_with_output().unwrap(); | ||||||
|                 println!( |                 instrumentation::instrument(HarmonyComposerEvent::DeploymentCompleted { | ||||||
|                     "deploy output: {}", |                     details: String::from_utf8(deploy_output.stdout).unwrap(), | ||||||
|                     String::from_utf8(deploy_output.stdout).expect("couldn't parse from utf8") |                 }) | ||||||
|                 ); |                 .unwrap(); | ||||||
|             } |             } | ||||||
|             Commands::All(_args) => todo!( |             Commands::All(_args) => todo!( | ||||||
|                 "take all previous match arms and turn them into separate functions, and call them all one after the other" |                 "take all previous match arms and turn them into separate functions, and call them all one after the other" | ||||||
| @ -137,6 +157,10 @@ async fn main() { | |||||||
|         }, |         }, | ||||||
|         None => todo!("run interactively, ask for info on CLI"), |         None => todo!("run interactively, ask for info on CLI"), | ||||||
|     } |     } | ||||||
|  | 
 | ||||||
|  |     instrumentation::instrument(HarmonyComposerEvent::Shutdown).unwrap(); | ||||||
|  | 
 | ||||||
|  |     let _ = tokio::try_join!(hc_logger_handle); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| #[derive(Clone, Debug, clap::ValueEnum)] | #[derive(Clone, Debug, clap::ValueEnum)] | ||||||
| @ -166,17 +190,32 @@ async fn compile_harmony( | |||||||
|         Some(m) => m, |         Some(m) => m, | ||||||
|         None => { |         None => { | ||||||
|             if cargo_exists { |             if cargo_exists { | ||||||
|                 return compile_cargo(platform, harmony_location).await; |                 CompileMethod::LocalCargo | ||||||
|             } else { |             } else { | ||||||
|                 return compile_docker(platform, harmony_location).await; |                 CompileMethod::Docker | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
|     }; |     }; | ||||||
| 
 | 
 | ||||||
|     match method { |     let path = match method { | ||||||
|         CompileMethod::LocalCargo => return compile_cargo(platform, harmony_location).await, |         CompileMethod::LocalCargo => { | ||||||
|         CompileMethod::Docker => return compile_docker(platform, harmony_location).await, |             instrumentation::instrument(HarmonyComposerEvent::ProjectCompilationStarted { | ||||||
|  |                 details: "compiling project with cargo".to_string(), | ||||||
|  |             }) | ||||||
|  |             .unwrap(); | ||||||
|  |             compile_cargo(platform, harmony_location).await | ||||||
|  |         } | ||||||
|  |         CompileMethod::Docker => { | ||||||
|  |             instrumentation::instrument(HarmonyComposerEvent::ProjectCompilationStarted { | ||||||
|  |                 details: "compiling project with docker".to_string(), | ||||||
|  |             }) | ||||||
|  |             .unwrap(); | ||||||
|  |             compile_docker(platform, harmony_location).await | ||||||
|  |         } | ||||||
|     }; |     }; | ||||||
|  | 
 | ||||||
|  |     instrumentation::instrument(HarmonyComposerEvent::ProjectCompiled).unwrap(); | ||||||
|  |     path | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // TODO: make sure this works with cargo workspaces
 | // TODO: make sure this works with cargo workspaces
 | ||||||
| @ -186,6 +225,12 @@ async fn compile_cargo(platform: String, harmony_location: String) -> PathBuf { | |||||||
|         .exec() |         .exec() | ||||||
|         .unwrap(); |         .unwrap(); | ||||||
| 
 | 
 | ||||||
|  |     let stderr = if log_enabled!(log::Level::Debug) { | ||||||
|  |         Stdio::inherit() | ||||||
|  |     } else { | ||||||
|  |         Stdio::piped() | ||||||
|  |     }; | ||||||
|  | 
 | ||||||
|     let mut cargo_build = Command::new("cargo") |     let mut cargo_build = Command::new("cargo") | ||||||
|         .current_dir(&harmony_location) |         .current_dir(&harmony_location) | ||||||
|         .args(vec![ |         .args(vec![ | ||||||
| @ -195,6 +240,7 @@ async fn compile_cargo(platform: String, harmony_location: String) -> PathBuf { | |||||||
|             "--message-format=json-render-diagnostics", |             "--message-format=json-render-diagnostics", | ||||||
|         ]) |         ]) | ||||||
|         .stdout(Stdio::piped()) |         .stdout(Stdio::piped()) | ||||||
|  |         .stderr(stderr) | ||||||
|         .spawn() |         .spawn() | ||||||
|         .expect("run cargo command failed"); |         .expect("run cargo command failed"); | ||||||
| 
 | 
 | ||||||
| @ -210,18 +256,20 @@ async fn compile_cargo(platform: String, harmony_location: String) -> PathBuf { | |||||||
|                         .expect("failed to get root package") |                         .expect("failed to get root package") | ||||||
|                         .manifest_path |                         .manifest_path | ||||||
|                 { |                 { | ||||||
|                     println!("{:?}", artifact); |                     debug!("{:?}", artifact); | ||||||
|                     artifacts.push(artifact); |                     artifacts.push(artifact); | ||||||
|                 } |                 } | ||||||
|             } |             } | ||||||
|             Message::BuildScriptExecuted(_script) => (), |             Message::BuildScriptExecuted(_script) => (), | ||||||
|             Message::BuildFinished(finished) => { |             Message::BuildFinished(finished) => { | ||||||
|                 println!("{:?}", finished); |                 debug!("{:?}", finished); | ||||||
|             } |             } | ||||||
|             _ => (), // Unknown message
 |             _ => (), // Unknown message
 | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     cargo_build.wait().expect("run cargo command failed"); | ||||||
|  | 
 | ||||||
|     let bin = artifacts |     let bin = artifacts | ||||||
|         .last() |         .last() | ||||||
|         .expect("no binaries built") |         .expect("no binaries built") | ||||||
| @ -237,7 +285,8 @@ async fn compile_cargo(platform: String, harmony_location: String) -> PathBuf { | |||||||
|         bin_out = PathBuf::from(format!("{}/harmony", harmony_location)); |         bin_out = PathBuf::from(format!("{}/harmony", harmony_location)); | ||||||
|         let _copy_res = fs::copy(&bin, &bin_out).await; |         let _copy_res = fs::copy(&bin, &bin_out).await; | ||||||
|     } |     } | ||||||
|     return bin_out; | 
 | ||||||
|  |     bin_out | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| async fn compile_docker(platform: String, harmony_location: String) -> PathBuf { | async fn compile_docker(platform: String, harmony_location: String) -> PathBuf { | ||||||
|  | |||||||
| @ -2,7 +2,7 @@ mod downloadable_asset; | |||||||
| use downloadable_asset::*; | use downloadable_asset::*; | ||||||
| 
 | 
 | ||||||
| use kube::Client; | use kube::Client; | ||||||
| use log::{debug, info, warn}; | use log::{debug, warn}; | ||||||
| use std::path::PathBuf; | use std::path::PathBuf; | ||||||
| 
 | 
 | ||||||
| const K3D_BIN_FILE_NAME: &str = "k3d"; | const K3D_BIN_FILE_NAME: &str = "k3d"; | ||||||
| @ -90,7 +90,7 @@ impl K3d { | |||||||
|         let latest_release = self.get_latest_release_tag().await.unwrap(); |         let latest_release = self.get_latest_release_tag().await.unwrap(); | ||||||
| 
 | 
 | ||||||
|         let release_binary = self.get_binary_for_current_platform(latest_release).await; |         let release_binary = self.get_binary_for_current_platform(latest_release).await; | ||||||
|         info!("Foudn K3d binary to install : {release_binary:#?}"); |         debug!("Foudn K3d binary to install : {release_binary:#?}"); | ||||||
|         release_binary.download_to_path(self.base_dir.clone()).await |         release_binary.download_to_path(self.base_dir.clone()).await | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
| @ -175,7 +175,7 @@ impl K3d { | |||||||
|             Err(_) => return Err("Could not get cluster_name, cannot initialize".to_string()), |             Err(_) => return Err("Could not get cluster_name, cannot initialize".to_string()), | ||||||
|         }; |         }; | ||||||
| 
 | 
 | ||||||
|         info!("Initializing k3d cluster '{}'", cluster_name); |         debug!("Initializing k3d cluster '{}'", cluster_name); | ||||||
| 
 | 
 | ||||||
|         self.create_cluster(cluster_name)?; |         self.create_cluster(cluster_name)?; | ||||||
|         self.create_kubernetes_client().await |         self.create_kubernetes_client().await | ||||||
| @ -205,7 +205,7 @@ impl K3d { | |||||||
|     /// - `Err(String)` - Error message if any step failed
 |     /// - `Err(String)` - Error message if any step failed
 | ||||||
|     pub async fn ensure_installed(&self) -> Result<Client, String> { |     pub async fn ensure_installed(&self) -> Result<Client, String> { | ||||||
|         if !self.is_installed() { |         if !self.is_installed() { | ||||||
|             info!("K3d is not installed, downloading latest release"); |             debug!("K3d is not installed, downloading latest release"); | ||||||
|             self.download_latest_release() |             self.download_latest_release() | ||||||
|                 .await |                 .await | ||||||
|                 .map_err(|e| format!("Failed to download k3d: {}", e))?; |                 .map_err(|e| format!("Failed to download k3d: {}", e))?; | ||||||
| @ -216,13 +216,13 @@ impl K3d { | |||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         if !self.is_cluster_initialized() { |         if !self.is_cluster_initialized() { | ||||||
|             info!("Cluster is not initialized, initializing now"); |             debug!("Cluster is not initialized, initializing now"); | ||||||
|             return self.initialize_cluster().await; |             return self.initialize_cluster().await; | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         self.start_cluster().await?; |         self.start_cluster().await?; | ||||||
| 
 | 
 | ||||||
|         info!("K3d and cluster are already properly set up"); |         debug!("K3d and cluster are already properly set up"); | ||||||
|         self.create_kubernetes_client().await |         self.create_kubernetes_client().await | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
| @ -325,12 +325,12 @@ impl K3d { | |||||||
|             return Err(format!("Failed to create cluster: {}", stderr)); |             return Err(format!("Failed to create cluster: {}", stderr)); | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         info!("Successfully created k3d cluster '{}'", cluster_name); |         debug!("Successfully created k3d cluster '{}'", cluster_name); | ||||||
|         Ok(()) |         Ok(()) | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     async fn create_kubernetes_client(&self) -> Result<Client, String> { |     async fn create_kubernetes_client(&self) -> Result<Client, String> { | ||||||
|         warn!("TODO this method is way too dumb, it should make sure that the client is connected to the k3d cluster actually represented by this instance, not just any default  client"); |         // TODO: Connect the client to the right k3d cluster (see https://git.nationtech.io/NationTech/harmony/issues/92)
 | ||||||
|         Client::try_default() |         Client::try_default() | ||||||
|             .await |             .await | ||||||
|             .map_err(|e| format!("Failed to create Kubernetes client: {}", e)) |             .map_err(|e| format!("Failed to create Kubernetes client: {}", e)) | ||||||
| @ -352,7 +352,7 @@ impl K3d { | |||||||
|             return Err(format!("Failed to start cluster: {}", stderr)); |             return Err(format!("Failed to start cluster: {}", stderr)); | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         info!("Successfully started k3d cluster '{}'", cluster_name); |         debug!("Successfully started k3d cluster '{}'", cluster_name); | ||||||
|         Ok(()) |         Ok(()) | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  | |||||||
		Loading…
	
		Reference in New Issue
	
	Block a user
	
some kind of retry mechanism could be useful here to make sure no events are lost