* define Ntfy ingress (naive implementation) based on current target * use patched Ntfy Helm Chart * create Ntfy main user only if needed * add info logs * better error bubbling * instrument feature installations * upgrade prometheus alerting charts if already installed * harmony_composer params to control deployment `target` and `profile` Co-authored-by: Ian Letourneau <letourneau.ian@gmail.com> Co-authored-by: Jean-Gabriel Gill-Couture <jg@nationtech.io> Reviewed-on: #107
219 lines
9.3 KiB
Rust
219 lines
9.3 KiB
Rust
use harmony::{
|
|
instrumentation::{self, HarmonyEvent},
|
|
modules::application::ApplicationFeatureStatus,
|
|
topology::TopologyStatus,
|
|
};
|
|
use indicatif::MultiProgress;
|
|
use indicatif_log_bridge::LogWrapper;
|
|
use log::error;
|
|
use std::{
|
|
sync::{Arc, Mutex},
|
|
thread,
|
|
time::Duration,
|
|
};
|
|
|
|
use crate::progress::{IndicatifProgressTracker, ProgressTracker};
|
|
|
|
pub fn init() -> tokio::task::JoinHandle<()> {
|
|
let base_progress = configure_logger();
|
|
let handle = tokio::spawn(handle_events(base_progress));
|
|
|
|
loop {
|
|
if instrumentation::instrument(HarmonyEvent::HarmonyStarted).is_ok() {
|
|
break;
|
|
}
|
|
}
|
|
|
|
handle
|
|
}
|
|
|
|
fn configure_logger() -> MultiProgress {
|
|
let logger =
|
|
env_logger::Builder::from_env(env_logger::Env::default().default_filter_or("info")).build();
|
|
let level = logger.filter();
|
|
let progress = MultiProgress::new();
|
|
|
|
LogWrapper::new(progress.clone(), logger)
|
|
.try_init()
|
|
.unwrap();
|
|
log::set_max_level(level);
|
|
|
|
progress
|
|
}
|
|
|
|
async fn handle_events(base_progress: MultiProgress) {
|
|
let progress_tracker = Arc::new(IndicatifProgressTracker::new(base_progress.clone()));
|
|
let preparing_topology = Arc::new(Mutex::new(false));
|
|
let current_score: Arc<Mutex<Option<String>>> = Arc::new(Mutex::new(None));
|
|
|
|
instrumentation::subscribe("Harmony CLI Logger", {
|
|
move |event| {
|
|
let progress_tracker = Arc::clone(&progress_tracker);
|
|
let preparing_topology = Arc::clone(&preparing_topology);
|
|
let current_score = Arc::clone(¤t_score);
|
|
|
|
async move {
|
|
let mut preparing_topology = preparing_topology.lock().unwrap();
|
|
let mut current_score = current_score.lock().unwrap();
|
|
|
|
match event {
|
|
HarmonyEvent::HarmonyStarted => {}
|
|
HarmonyEvent::HarmonyFinished => {
|
|
progress_tracker.add_section(
|
|
"harmony-summary",
|
|
&format!("\n{} Harmony completed\n\n", crate::theme::EMOJI_HARMONY),
|
|
);
|
|
progress_tracker.add_section("harmony-finished", "\n\n");
|
|
thread::sleep(Duration::from_millis(200));
|
|
return false;
|
|
}
|
|
HarmonyEvent::TopologyStateChanged {
|
|
topology,
|
|
status,
|
|
message,
|
|
} => {
|
|
let section_key = topology_key(&topology);
|
|
|
|
match status {
|
|
TopologyStatus::Queued => {}
|
|
TopologyStatus::Preparing => {
|
|
progress_tracker.add_section(
|
|
§ion_key,
|
|
&format!(
|
|
"\n{} Preparing environment: {topology}...",
|
|
crate::theme::EMOJI_TOPOLOGY
|
|
),
|
|
);
|
|
(*preparing_topology) = true;
|
|
}
|
|
TopologyStatus::Success => {
|
|
(*preparing_topology) = false;
|
|
progress_tracker.add_task(§ion_key, "topology-success", "");
|
|
progress_tracker
|
|
.finish_task("topology-success", &message.unwrap_or("".into()));
|
|
}
|
|
TopologyStatus::Noop => {
|
|
(*preparing_topology) = false;
|
|
progress_tracker.add_task(§ion_key, "topology-skip", "");
|
|
progress_tracker
|
|
.skip_task("topology-skip", &message.unwrap_or("".into()));
|
|
}
|
|
TopologyStatus::Error => {
|
|
progress_tracker.add_task(§ion_key, "topology-error", "");
|
|
(*preparing_topology) = false;
|
|
progress_tracker
|
|
.fail_task("topology-error", &message.unwrap_or("".into()));
|
|
}
|
|
}
|
|
}
|
|
HarmonyEvent::InterpretExecutionStarted {
|
|
execution_id: task_key,
|
|
topology,
|
|
interpret: _,
|
|
score,
|
|
message,
|
|
} => {
|
|
let is_key_topology = (*preparing_topology)
|
|
&& progress_tracker.contains_section(&topology_key(&topology));
|
|
let is_key_current_score = current_score.is_some()
|
|
&& progress_tracker
|
|
.contains_section(&score_key(¤t_score.clone().unwrap()));
|
|
let is_key_score = progress_tracker.contains_section(&score_key(&score));
|
|
|
|
let section_key = if is_key_topology {
|
|
topology_key(&topology)
|
|
} else if is_key_current_score {
|
|
score_key(¤t_score.clone().unwrap())
|
|
} else if is_key_score {
|
|
score_key(&score)
|
|
} else {
|
|
(*current_score) = Some(score.clone());
|
|
let key = score_key(&score);
|
|
progress_tracker.add_section(
|
|
&key,
|
|
&format!(
|
|
"{} Interpreting score: {score}...",
|
|
crate::theme::EMOJI_SCORE
|
|
),
|
|
);
|
|
key
|
|
};
|
|
|
|
progress_tracker.add_task(§ion_key, &task_key, &message);
|
|
}
|
|
HarmonyEvent::InterpretExecutionFinished {
|
|
execution_id: task_key,
|
|
topology: _,
|
|
interpret: _,
|
|
score,
|
|
outcome,
|
|
} => {
|
|
if current_score.is_some() && current_score.clone().unwrap() == score {
|
|
(*current_score) = None;
|
|
}
|
|
|
|
match outcome {
|
|
Ok(outcome) => match outcome.status {
|
|
harmony::interpret::InterpretStatus::SUCCESS => {
|
|
progress_tracker.finish_task(&task_key, &outcome.message);
|
|
}
|
|
harmony::interpret::InterpretStatus::NOOP => {
|
|
progress_tracker.skip_task(&task_key, &outcome.message);
|
|
}
|
|
_ => progress_tracker.fail_task(&task_key, &outcome.message),
|
|
},
|
|
Err(err) => {
|
|
error!("Interpret error: {err}");
|
|
progress_tracker.fail_task(&task_key, &err.to_string());
|
|
}
|
|
}
|
|
}
|
|
HarmonyEvent::ApplicationFeatureStateChanged {
|
|
topology: _,
|
|
application,
|
|
feature,
|
|
status,
|
|
} => {
|
|
if let Some(score) = &(*current_score) {
|
|
let section_key = score_key(score);
|
|
let task_key = app_feature_key(&application, &feature);
|
|
|
|
match status {
|
|
ApplicationFeatureStatus::Installing => {
|
|
let message = format!("Feature '{}' installing...", feature);
|
|
progress_tracker.add_task(§ion_key, &task_key, &message);
|
|
}
|
|
ApplicationFeatureStatus::Installed => {
|
|
let message = format!("Feature '{}' installed", feature);
|
|
progress_tracker.finish_task(&task_key, &message);
|
|
}
|
|
ApplicationFeatureStatus::Failed { details } => {
|
|
let message = format!(
|
|
"Feature '{}' installation failed: {}",
|
|
feature, details
|
|
);
|
|
progress_tracker.fail_task(&task_key, &message);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
true
|
|
}
|
|
}
|
|
})
|
|
.await;
|
|
}
|
|
|
|
fn topology_key(topology: &str) -> String {
|
|
format!("topology-{topology}")
|
|
}
|
|
|
|
fn score_key(score: &str) -> String {
|
|
format!("score-{score}")
|
|
}
|
|
|
|
fn app_feature_key(application: &str, feature: &str) -> String {
|
|
format!("app-{application}-{feature}")
|
|
}
|