fix(cli): reduce noise & better track progress within Harmony #91
43
Cargo.lock
generated
43
Cargo.lock
generated
@ -650,6 +650,19 @@ dependencies = [
|
||||
"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]]
|
||||
name = "const-oid"
|
||||
version = "0.9.6"
|
||||
@ -1137,6 +1150,12 @@ dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "encode_unicode"
|
||||
version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "34aa73646ffb006b8f5147f3dc182bd4bcb190227ce861fc4a4844bf8e3cb2c0"
|
||||
|
||||
[[package]]
|
||||
name = "encoding_rs"
|
||||
version = "0.8.35"
|
||||
@ -1765,6 +1784,7 @@ dependencies = [
|
||||
"libredfish",
|
||||
"log",
|
||||
"non-blank-string-rs",
|
||||
"once_cell",
|
||||
"opnsense-config",
|
||||
"opnsense-config-xml",
|
||||
"pretty_assertions",
|
||||
@ -1810,9 +1830,13 @@ dependencies = [
|
||||
"bollard",
|
||||
"cargo_metadata",
|
||||
"clap",
|
||||
"console",
|
||||
"current_platform",
|
||||
"env_logger",
|
||||
"futures-util",
|
||||
"harmony",
|
||||
"indicatif",
|
||||
"lazy_static",
|
||||
"log",
|
||||
"serde_json",
|
||||
"tokio",
|
||||
@ -2409,6 +2433,19 @@ dependencies = [
|
||||
"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 = "indoc"
|
||||
version = "2.0.6"
|
||||
@ -5181,6 +5218,12 @@ version = "0.2.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853"
|
||||
|
||||
[[package]]
|
||||
name = "unit-prefix"
|
||||
version = "0.5.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "323402cff2dd658f39ca17c789b502021b3f18707c91cdf22e3838e1b4023817"
|
||||
|
||||
[[package]]
|
||||
name = "universal-hash"
|
||||
version = "0.5.1"
|
||||
|
@ -62,6 +62,7 @@ serde_with = "3.14.0"
|
||||
bollard.workspace = true
|
||||
tar.workspace = true
|
||||
base64.workspace = true
|
||||
once_cell = "1.21.3"
|
||||
|
||||
[dev-dependencies]
|
||||
pretty_assertions.workspace = true
|
||||
|
51
harmony/src/domain/instrumentation.rs
Normal file
51
harmony/src/domain/instrumentation.rs
Normal file
@ -0,0 +1,51 @@
|
||||
use log::debug;
|
||||
use once_cell::sync::Lazy;
|
||||
use tokio::sync::broadcast;
|
||||
|
||||
// FIXME: Ce module d'instrumentation ne peut pas fonctionner à la fois pour Harmony Composer et
|
||||
// Harmony CLI. Elle n'a donc pas entièrement sa place ici et devra être séparée en deux.
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum HarmonyEvent {
|
||||
ProjectInitializationStarted,
|
||||
ProjectInitialized,
|
||||
ProjectCompilationStarted { details: String },
|
||||
ProjectCompiled,
|
||||
ProjectCompilationFailed { details: String },
|
||||
DeploymentStarted { target: String },
|
||||
DeploymentCompleted { details: String },
|
||||
PrepareTopologyStarted { name: String },
|
||||
Shutdown,
|
||||
}
|
||||
|
||||
static EVENT_BUS: Lazy<broadcast::Sender<HarmonyEvent>> = Lazy::new(|| {
|
||||
// TODO: Adjust channel capacity
|
||||
let (tx, _rx) = broadcast::channel(16);
|
||||
tx
|
||||
});
|
||||
|
||||
pub fn instrument(event: HarmonyEvent) {
|
||||
EVENT_BUS.send(event).expect("couldn't send event");
|
||||
}
|
||||
|
||||
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 = 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,
|
||||
}
|
||||
}
|
||||
}
|
@ -2,6 +2,8 @@ use std::sync::{Arc, Mutex, RwLock};
|
||||
|
||||
use log::{info, warn};
|
||||
|
||||
use crate::instrumentation::{self, HarmonyEvent};
|
||||
|
||||
use super::{
|
||||
interpret::{InterpretError, InterpretStatus, Outcome},
|
||||
inventory::Inventory,
|
||||
@ -40,7 +42,12 @@ impl<T: Topology> Maestro<T> {
|
||||
/// Ensures the associated Topology is ready for operations.
|
||||
/// Delegates the readiness check and potential setup actions to the Topology.
|
||||
pub async fn prepare_topology(&self) -> Result<Outcome, InterpretError> {
|
||||
info!("Ensuring topology '{}' is ready...", self.topology.name());
|
||||
// FIXME: Cette instrumentation ne peut pas communiquer avec Harmony Composer puisqu'il
|
||||
// s'agit d'un process différent.
|
||||
//
|
||||
// instrumentation::instrument(HarmonyEvent::PrepareTopologyStarted {
|
||||
// name: self.topology.name().to_string(),
|
||||
// });
|
||||
let outcome = self.topology.ensure_ready().await?;
|
||||
info!(
|
||||
"Topology '{}' readiness check complete: {}",
|
||||
|
@ -3,6 +3,7 @@ pub mod data;
|
||||
pub mod executors;
|
||||
pub mod filter;
|
||||
pub mod hardware;
|
||||
pub mod instrumentation;
|
||||
pub mod interpret;
|
||||
pub mod inventory;
|
||||
pub mod maestro;
|
||||
|
@ -159,7 +159,7 @@ impl K8sAnywhereTopology {
|
||||
|
||||
if !k8s_anywhere_config.autoinstall {
|
||||
debug!("Autoinstall confirmation prompt");
|
||||
let confirmation = Confirm::new( "Harmony autoinstallation is not activated, do you wish to launch autoinstallation? : ")
|
||||
let confirmation = Confirm::new( "Harmony autoinstallation is not activated, do you wish to launch autoinstallation? :")
|
||||
.with_default(false)
|
||||
.prompt()
|
||||
.expect("Unexpected prompt error");
|
||||
|
@ -15,3 +15,7 @@ current_platform = "0.2.0"
|
||||
futures-util = "0.3.31"
|
||||
serde_json = "1.0.140"
|
||||
cargo_metadata = "0.20.0"
|
||||
harmony = { path = "../harmony" }
|
||||
indicatif = "0.18.0"
|
||||
console = "0.16.0"
|
||||
lazy_static = "1.5.0"
|
||||
|
84
harmony_composer/src/cli_logger.rs
Normal file
84
harmony_composer/src/cli_logger.rs
Normal file
@ -0,0 +1,84 @@
|
||||
use console::Emoji;
|
||||
use harmony::instrumentation::{self, HarmonyEvent};
|
||||
use indicatif::{ProgressBar, ProgressStyle};
|
||||
use lazy_static::lazy_static;
|
||||
use log::error;
|
||||
use std::{
|
||||
sync::{Arc, Mutex},
|
||||
time::Duration,
|
||||
};
|
||||
|
||||
static EMOJI_HARMONY: Emoji<'_, '_> = Emoji("🎼", "");
|
||||
static EMOJI_SUCCESS: Emoji<'_, '_> = Emoji("✅", "");
|
||||
static EMOJI_ERROR: Emoji<'_, '_> = Emoji("⚠️", "");
|
||||
static EMOJI_DEPLOY: Emoji<'_, '_> = Emoji("🚀", "");
|
||||
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 ERROR_SPINNER_STYLE: ProgressStyle = SPINNER_STYLE
|
||||
.clone()
|
||||
.tick_strings(&[format!("{}", EMOJI_ERROR).as_str()]);
|
||||
}
|
||||
|
||||
pub async fn init() {
|
||||
instrumentation::subscribe("CLI Logger", {
|
||||
let current_spinner = Arc::new(Mutex::new(None::<ProgressBar>));
|
||||
|
||||
move |event| {
|
||||
let spinner_clone = Arc::clone(¤t_spinner);
|
||||
|
||||
async move {
|
||||
let mut spinner_guard = spinner_clone.lock().unwrap();
|
||||
|
||||
match event {
|
||||
HarmonyEvent::ProjectInitializationStarted => {
|
||||
println!("{} Initializing Harmony project...", EMOJI_HARMONY);
|
||||
}
|
||||
HarmonyEvent::ProjectInitialized => println!("\n"),
|
||||
HarmonyEvent::ProjectCompilationStarted { details } => {
|
||||
let progress = ProgressBar::new_spinner();
|
||||
progress.set_style(SPINNER_STYLE.clone());
|
||||
progress.set_message(details);
|
||||
progress.enable_steady_tick(Duration::from_millis(100));
|
||||
*spinner_guard = Some(progress);
|
||||
}
|
||||
HarmonyEvent::ProjectCompiled => {
|
||||
if let Some(progress) = spinner_guard.take() {
|
||||
progress.set_style(SUCCESS_SPINNER_STYLE.clone());
|
||||
progress.finish_with_message("project compiled");
|
||||
}
|
||||
}
|
||||
HarmonyEvent::ProjectCompilationFailed { details } => {
|
||||
if let Some(progress) = spinner_guard.take() {
|
||||
progress.set_style(ERROR_SPINNER_STYLE.clone());
|
||||
progress.finish_with_message("failed to compile project");
|
||||
error!("{details}");
|
||||
}
|
||||
}
|
||||
HarmonyEvent::DeploymentStarted { target } => {
|
||||
println!("{} Starting deployment to {target}...", EMOJI_DEPLOY);
|
||||
}
|
||||
HarmonyEvent::DeploymentCompleted { details } => println!("\n"),
|
||||
HarmonyEvent::PrepareTopologyStarted { name } => {
|
||||
println!("{} Preparing environment: {name}...", EMOJI_TOPOLOGY);
|
||||
}
|
||||
HarmonyEvent::Shutdown => {
|
||||
if let Some(progress) = spinner_guard.take() {
|
||||
progress.abandon();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
true
|
||||
}
|
||||
}
|
||||
})
|
||||
.await
|
||||
}
|
@ -7,12 +7,15 @@ use bollard::secret::HostConfig;
|
||||
use cargo_metadata::{Artifact, Message, MetadataCommand};
|
||||
use clap::{Args, Parser, Subcommand};
|
||||
use futures_util::StreamExt;
|
||||
use log::info;
|
||||
use harmony::instrumentation::{self, HarmonyEvent};
|
||||
use log::{debug, info, log_enabled};
|
||||
use std::collections::HashMap;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::process::{Command, Stdio};
|
||||
use tokio::fs;
|
||||
|
||||
mod cli_logger;
|
||||
|
||||
#[derive(Parser)]
|
||||
#[command(version, about, long_about = None, flatten_help = true, propagate_version = true)]
|
||||
struct GlobalArgs {
|
||||
@ -67,12 +70,15 @@ struct AllArgs {
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
env_logger::init();
|
||||
let cli_logger_handle = tokio::spawn(cli_logger::init());
|
||||
let cli_args = GlobalArgs::parse();
|
||||
|
||||
let harmony_path = Path::new(&cli_args.harmony_path)
|
||||
.try_exists()
|
||||
.expect("couldn't check if path exists");
|
||||
|
||||
instrumentation::instrument(HarmonyEvent::ProjectInitializationStarted);
|
||||
|
||||
let harmony_bin_path: PathBuf = match harmony_path {
|
||||
true => {
|
||||
compile_harmony(
|
||||
@ -85,6 +91,8 @@ async fn main() {
|
||||
false => todo!("implement autodetect code"),
|
||||
};
|
||||
|
||||
instrumentation::instrument(HarmonyEvent::ProjectInitialized);
|
||||
|
||||
match cli_args.command {
|
||||
Some(command) => match command {
|
||||
Commands::Check(args) => {
|
||||
@ -116,19 +124,27 @@ async fn main() {
|
||||
}
|
||||
Commands::Deploy(args) => {
|
||||
let deploy = if args.staging {
|
||||
instrumentation::instrument(HarmonyEvent::DeploymentStarted {
|
||||
target: "staging".to_string(),
|
||||
});
|
||||
todo!("implement staging deployment")
|
||||
} else if args.prod {
|
||||
instrumentation::instrument(HarmonyEvent::DeploymentStarted {
|
||||
target: "prod".to_string(),
|
||||
});
|
||||
todo!("implement prod deployment")
|
||||
} else {
|
||||
instrumentation::instrument(HarmonyEvent::DeploymentStarted {
|
||||
target: "dev".to_string(),
|
||||
});
|
||||
Command::new(harmony_bin_path).arg("-y").arg("-a").spawn()
|
||||
}
|
||||
.expect("failed to run harmony deploy");
|
||||
|
||||
let deploy_output = deploy.wait_with_output().unwrap();
|
||||
println!(
|
||||
"deploy output: {}",
|
||||
String::from_utf8(deploy_output.stdout).expect("couldn't parse from utf8")
|
||||
);
|
||||
instrumentation::instrument(HarmonyEvent::DeploymentCompleted {
|
||||
details: String::from_utf8(deploy_output.stdout).unwrap(),
|
||||
});
|
||||
}
|
||||
Commands::All(_args) => todo!(
|
||||
"take all previous match arms and turn them into separate functions, and call them all one after the other"
|
||||
@ -137,6 +153,10 @@ async fn main() {
|
||||
},
|
||||
None => todo!("run interactively, ask for info on CLI"),
|
||||
}
|
||||
|
||||
instrumentation::instrument(HarmonyEvent::Shutdown);
|
||||
|
||||
letian
commented
Some instrumentations are still missing, but this PR is mostly to get early feedback on the approach. Some instrumentations are still missing, but this PR is mostly to get early feedback on the approach.
|
||||
let _ = tokio::try_join!(cli_logger_handle);
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, clap::ValueEnum)]
|
||||
@ -166,17 +186,30 @@ async fn compile_harmony(
|
||||
Some(m) => m,
|
||||
None => {
|
||||
if cargo_exists {
|
||||
return compile_cargo(platform, harmony_location).await;
|
||||
CompileMethod::LocalCargo
|
||||
} else {
|
||||
return compile_docker(platform, harmony_location).await;
|
||||
CompileMethod::Docker
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
match method {
|
||||
CompileMethod::LocalCargo => return compile_cargo(platform, harmony_location).await,
|
||||
CompileMethod::Docker => return compile_docker(platform, harmony_location).await,
|
||||
let path = match method {
|
||||
CompileMethod::LocalCargo => {
|
||||
instrumentation::instrument(HarmonyEvent::ProjectCompilationStarted {
|
||||
details: "compiling project with cargo".to_string(),
|
||||
});
|
||||
compile_cargo(platform, harmony_location).await
|
||||
}
|
||||
CompileMethod::Docker => {
|
||||
instrumentation::instrument(HarmonyEvent::ProjectCompilationStarted {
|
||||
details: "compiling project with docker".to_string(),
|
||||
});
|
||||
compile_docker(platform, harmony_location).await
|
||||
}
|
||||
};
|
||||
|
||||
instrumentation::instrument(HarmonyEvent::ProjectCompiled);
|
||||
path
|
||||
}
|
||||
|
||||
// TODO: make sure this works with cargo workspaces
|
||||
@ -186,6 +219,12 @@ async fn compile_cargo(platform: String, harmony_location: String) -> PathBuf {
|
||||
.exec()
|
||||
.unwrap();
|
||||
|
||||
let stderr = if log_enabled!(log::Level::Debug) {
|
||||
Stdio::inherit()
|
||||
letian
commented
`cargo` prints warnings and other stuff in `stderr`, meaning it can get really noisy. So we'll "silence" those warnings unless we're asking for debug logs (`RUST_LOG=debug`)
|
||||
} else {
|
||||
Stdio::piped()
|
||||
};
|
||||
|
||||
let mut cargo_build = Command::new("cargo")
|
||||
.current_dir(&harmony_location)
|
||||
.args(vec![
|
||||
@ -195,6 +234,7 @@ async fn compile_cargo(platform: String, harmony_location: String) -> PathBuf {
|
||||
"--message-format=json-render-diagnostics",
|
||||
])
|
||||
.stdout(Stdio::piped())
|
||||
.stderr(stderr)
|
||||
.spawn()
|
||||
.expect("run cargo command failed");
|
||||
|
||||
@ -210,18 +250,20 @@ async fn compile_cargo(platform: String, harmony_location: String) -> PathBuf {
|
||||
.expect("failed to get root package")
|
||||
.manifest_path
|
||||
{
|
||||
println!("{:?}", artifact);
|
||||
debug!("{:?}", artifact);
|
||||
artifacts.push(artifact);
|
||||
}
|
||||
}
|
||||
Message::BuildScriptExecuted(_script) => (),
|
||||
Message::BuildFinished(finished) => {
|
||||
println!("{:?}", finished);
|
||||
debug!("{:?}", finished);
|
||||
}
|
||||
_ => (), // Unknown message
|
||||
}
|
||||
}
|
||||
|
||||
cargo_build.wait().expect("run cargo command failed");
|
||||
|
||||
let bin = artifacts
|
||||
.last()
|
||||
.expect("no binaries built")
|
||||
@ -237,7 +279,8 @@ async fn compile_cargo(platform: String, harmony_location: String) -> PathBuf {
|
||||
bin_out = PathBuf::from(format!("{}/harmony", harmony_location));
|
||||
let _copy_res = fs::copy(&bin, &bin_out).await;
|
||||
}
|
||||
return bin_out;
|
||||
|
||||
bin_out
|
||||
}
|
||||
|
||||
async fn compile_docker(platform: String, harmony_location: String) -> PathBuf {
|
||||
|
Loading…
Reference in New Issue
Block a user
some kind of retry mechanism could be useful here to make sure no events are lost