From 7bc083701ee015e452e698ed9aaa1cfb9e0ea104 Mon Sep 17 00:00:00 2001 From: Ian Letourneau Date: Tue, 9 Sep 2025 22:18:00 -0400 Subject: [PATCH] report application deploy URL --- harmony/src/modules/application/feature.rs | 67 ++++++++++++++++++- .../features/continuous_delivery.rs | 13 +++- .../modules/application/features/endpoint.rs | 7 +- .../application/features/monitoring.rs | 11 ++- .../application/features/rhob_monitoring.rs | 11 ++- harmony/src/modules/application/mod.rs | 19 ++++-- harmony_cli/src/cli_logger.rs | 4 +- harmony_cli/src/cli_reporter.rs | 58 ++++++++++------ 8 files changed, 149 insertions(+), 41 deletions(-) diff --git a/harmony/src/modules/application/feature.rs b/harmony/src/modules/application/feature.rs index be4482f..9e1b1ae 100644 --- a/harmony/src/modules/application/feature.rs +++ b/harmony/src/modules/application/feature.rs @@ -1,7 +1,10 @@ +use std::error::Error; + use async_trait::async_trait; +use derive_new::new; use serde::Serialize; -use crate::topology::Topology; +use crate::{executors::ExecutorError, topology::Topology}; /// An ApplicationFeature provided by harmony, such as Backups, Monitoring, MultisiteAvailability, /// ContinuousIntegration, ContinuousDelivery @@ -9,7 +12,10 @@ use crate::topology::Topology; pub trait ApplicationFeature: std::fmt::Debug + Send + Sync + ApplicationFeatureClone { - async fn ensure_installed(&self, topology: &T) -> Result<(), String>; + async fn ensure_installed( + &self, + topology: &T, + ) -> Result; fn name(&self) -> String; } @@ -40,3 +46,60 @@ impl Clone for Box> { self.clone_box() } } + +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum InstallationOutcome { + Success { details: Vec }, + Noop, +} + +impl InstallationOutcome { + pub fn success() -> Self { + Self::Success { details: vec![] } + } + + pub fn success_with_details(details: Vec) -> Self { + Self::Success { details } + } + + pub fn noop() -> Self { + Self::Noop + } +} + +#[derive(Debug, Clone, new)] +pub struct InstallationError { + msg: String, +} + +impl std::fmt::Display for InstallationError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str(&self.msg) + } +} + +impl Error for InstallationError {} + +impl From for InstallationError { + fn from(value: ExecutorError) -> Self { + Self { + msg: format!("InstallationError : {value}"), + } + } +} + +impl From for InstallationError { + fn from(value: kube::Error) -> Self { + Self { + msg: format!("InstallationError : {value}"), + } + } +} + +impl From for InstallationError { + fn from(value: String) -> Self { + Self { + msg: format!("PreparationError : {value}"), + } + } +} diff --git a/harmony/src/modules/application/features/continuous_delivery.rs b/harmony/src/modules/application/features/continuous_delivery.rs index 63e34a6..703da4a 100644 --- a/harmony/src/modules/application/features/continuous_delivery.rs +++ b/harmony/src/modules/application/features/continuous_delivery.rs @@ -10,7 +10,7 @@ use crate::{ data::Version, inventory::Inventory, modules::application::{ - ApplicationFeature, HelmPackage, OCICompliant, + ApplicationFeature, HelmPackage, InstallationError, InstallationOutcome, OCICompliant, features::{ArgoApplication, ArgoHelmScore}, }, score::Score, @@ -141,7 +141,10 @@ impl< T: Topology + HelmCommand + MultiTargetTopology + K8sclient + Ingress + 'static, > ApplicationFeature for ContinuousDelivery { - async fn ensure_installed(&self, topology: &T) -> Result<(), String> { + async fn ensure_installed( + &self, + topology: &T, + ) -> Result { let image = self.application.image_name(); let domain = topology .get_domain(&self.application.name()) @@ -205,7 +208,11 @@ impl< .unwrap(); } }; - Ok(()) + + Ok(InstallationOutcome::success_with_details(vec![format!( + "{}: {domain}", + self.application.name() + )])) } fn name(&self) -> String { "ContinuousDelivery".to_string() diff --git a/harmony/src/modules/application/features/endpoint.rs b/harmony/src/modules/application/features/endpoint.rs index 042f0dd..d2b23db 100644 --- a/harmony/src/modules/application/features/endpoint.rs +++ b/harmony/src/modules/application/features/endpoint.rs @@ -2,7 +2,7 @@ use async_trait::async_trait; use log::info; use crate::{ - modules::application::ApplicationFeature, + modules::application::{ApplicationFeature, InstallationError, InstallationOutcome}, topology::{K8sclient, Topology}, }; @@ -29,7 +29,10 @@ impl Default for PublicEndpoint { /// For now we only suport K8s ingress, but we will support more stuff at some point #[async_trait] impl ApplicationFeature for PublicEndpoint { - async fn ensure_installed(&self, _topology: &T) -> Result<(), String> { + async fn ensure_installed( + &self, + _topology: &T, + ) -> Result { info!( "Making sure public endpoint is installed for port {}", self.application_port diff --git a/harmony/src/modules/application/features/monitoring.rs b/harmony/src/modules/application/features/monitoring.rs index b8531fe..1a60d00 100644 --- a/harmony/src/modules/application/features/monitoring.rs +++ b/harmony/src/modules/application/features/monitoring.rs @@ -1,4 +1,6 @@ -use crate::modules::application::{Application, ApplicationFeature}; +use crate::modules::application::{ + Application, ApplicationFeature, InstallationError, InstallationOutcome, +}; use crate::modules::monitoring::application_monitoring::application_monitoring_score::ApplicationMonitoringScore; use crate::modules::monitoring::kube_prometheus::crd::crd_alertmanager_config::CRDPrometheus; use crate::topology::MultiTargetTopology; @@ -43,7 +45,10 @@ impl< + std::fmt::Debug, > ApplicationFeature for Monitoring { - async fn ensure_installed(&self, topology: &T) -> Result<(), String> { + async fn ensure_installed( + &self, + topology: &T, + ) -> Result { info!("Ensuring monitoring is available for application"); let namespace = topology .get_tenant_config() @@ -103,7 +108,7 @@ impl< .await .map_err(|e| e.to_string())?; - Ok(()) + Ok(InstallationOutcome::success()) } fn name(&self) -> String { diff --git a/harmony/src/modules/application/features/rhob_monitoring.rs b/harmony/src/modules/application/features/rhob_monitoring.rs index e6f51a4..9075751 100644 --- a/harmony/src/modules/application/features/rhob_monitoring.rs +++ b/harmony/src/modules/application/features/rhob_monitoring.rs @@ -1,6 +1,8 @@ use std::sync::Arc; -use crate::modules::application::{Application, ApplicationFeature}; +use crate::modules::application::{ + Application, ApplicationFeature, InstallationError, InstallationOutcome, +}; use crate::modules::monitoring::application_monitoring::application_monitoring_score::ApplicationMonitoringScore; use crate::modules::monitoring::application_monitoring::rhobs_application_monitoring_score::ApplicationRHOBMonitoringScore; @@ -43,7 +45,10 @@ impl< + PrometheusApplicationMonitoring, > ApplicationFeature for RHOBMonitoring { - async fn ensure_installed(&self, topology: &T) -> Result<(), String> { + async fn ensure_installed( + &self, + topology: &T, + ) -> Result { info!("Ensuring monitoring is available for application"); let namespace = topology .get_tenant_config() @@ -106,7 +111,7 @@ impl< .interpret(&Inventory::empty(), topology) .await .map_err(|e| e.to_string())?; - Ok(()) + Ok(InstallationOutcome::success()) } fn name(&self) -> String { "Monitoring".to_string() diff --git a/harmony/src/modules/application/mod.rs b/harmony/src/modules/application/mod.rs index 8e60984..b7bb973 100644 --- a/harmony/src/modules/application/mod.rs +++ b/harmony/src/modules/application/mod.rs @@ -24,8 +24,8 @@ use harmony_types::id::Id; #[derive(Clone, Debug)] pub enum ApplicationFeatureStatus { Installing, - Installed, - Failed { details: String }, + Installed { details: Vec }, + Failed { message: String }, } pub trait Application: std::fmt::Debug + Send + Sync { @@ -65,27 +65,32 @@ impl Interpret for Application .unwrap(); let _ = match feature.ensure_installed(topology).await { - Ok(()) => { + Ok(outcome) => { instrumentation::instrument(HarmonyEvent::ApplicationFeatureStateChanged { topology: topology.name().into(), application: self.application.name(), feature: feature.name(), - status: ApplicationFeatureStatus::Installed, + status: ApplicationFeatureStatus::Installed { + details: match outcome { + InstallationOutcome::Success { details } => details, + InstallationOutcome::Noop => vec![], + }, + }, }) .unwrap(); } - Err(msg) => { + Err(error) => { instrumentation::instrument(HarmonyEvent::ApplicationFeatureStateChanged { topology: topology.name().into(), application: self.application.name(), feature: feature.name(), status: ApplicationFeatureStatus::Failed { - details: msg.clone(), + message: error.to_string(), }, }) .unwrap(); return Err(InterpretError::new(format!( - "Application Interpret failed to install feature : {msg}" + "Application Interpret failed to install feature : {error}" ))); } }; diff --git a/harmony_cli/src/cli_logger.rs b/harmony_cli/src/cli_logger.rs index be61c2a..2cb2a93 100644 --- a/harmony_cli/src/cli_logger.rs +++ b/harmony_cli/src/cli_logger.rs @@ -178,10 +178,10 @@ fn handle_events() { ApplicationFeatureStatus::Installing => { info!("Installing feature '{feature}' for '{application}'..."); } - ApplicationFeatureStatus::Installed => { + ApplicationFeatureStatus::Installed { details: _ } => { info!(status = "finished"; "Feature '{feature}' installed"); } - ApplicationFeatureStatus::Failed { details } => { + ApplicationFeatureStatus::Failed { message: details } => { error!(status = "failed"; "Feature '{feature}' installation failed: {details}"); } }, diff --git a/harmony_cli/src/cli_reporter.rs b/harmony_cli/src/cli_reporter.rs index 83570b6..f6095cc 100644 --- a/harmony_cli/src/cli_reporter.rs +++ b/harmony_cli/src/cli_reporter.rs @@ -1,6 +1,9 @@ use std::sync::Mutex; -use harmony::instrumentation::{self, HarmonyEvent}; +use harmony::{ + instrumentation::{self, HarmonyEvent}, + modules::application::ApplicationFeatureStatus, +}; use crate::theme; @@ -11,26 +14,43 @@ pub fn init() { move |event| { let mut details = details.lock().unwrap(); - if let HarmonyEvent::InterpretExecutionFinished { - execution_id: _, - topology: _, - interpret: _, - score: _, - outcome: Ok(outcome), - } = event - { - if outcome.status == harmony::interpret::InterpretStatus::SUCCESS { - details.extend(outcome.details.clone()); + match event { + HarmonyEvent::InterpretExecutionFinished { + execution_id: _, + topology: _, + interpret: _, + score: _, + outcome: Ok(outcome), + } => { + if outcome.status == harmony::interpret::InterpretStatus::SUCCESS { + details.extend(outcome.details.clone()); + } } - } else if let HarmonyEvent::HarmonyFinished = event - && !details.is_empty() - { - println!("\n{} All done! What's next for you:", theme::EMOJI_SUMMARY); - for detail in details.iter() { - println!("- {detail}"); + HarmonyEvent::ApplicationFeatureStateChanged { + topology: _, + application: _, + feature: _, + status: + ApplicationFeatureStatus::Installed { + details: feature_details, + }, + } => { + details.extend(feature_details.clone()); } - println!(); - } + HarmonyEvent::HarmonyFinished => { + if !details.is_empty() { + println!( + "\n{} All done! Here's what's next for you:", + theme::EMOJI_SUMMARY + ); + for detail in details.iter() { + println!("- {detail}"); + } + println!(); + } + } + _ => {} + }; } }); }