From d7de1ea752568108970f8315688a6fd2f7707882 Mon Sep 17 00:00:00 2001 From: tahahawa Date: Thu, 10 Jul 2025 01:51:18 -0400 Subject: [PATCH] Move ntfy into monitoring feature --- .gitignore | 3 -- Cargo.lock | 1 + examples/rust/src/main.rs | 53 +------------------ harmony/Cargo.toml | 1 + .../features/continuous_delivery.rs | 9 +--- .../application/features/monitoring.rs | 53 +++++++++++++++++-- harmony/src/modules/application/mod.rs | 4 +- harmony/src/modules/application/rust.rs | 20 +++++-- 8 files changed, 72 insertions(+), 72 deletions(-) diff --git a/.gitignore b/.gitignore index f1dcd87..f478f21 100644 --- a/.gitignore +++ b/.gitignore @@ -2,7 +2,4 @@ target private_repos log/ *.tgz -examples/rust/examples/rust/webapp/helm/ -examples/rust/examples/rust/webapp/Dockerfile.harmony -examples/rust/webapp/helm/harmony-example-rust-webapp-chart/ .gitignore diff --git a/Cargo.lock b/Cargo.lock index 63df3b3..e19e6f3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1739,6 +1739,7 @@ name = "harmony" version = "0.1.0" dependencies = [ "async-trait", + "base64 0.22.1", "bollard", "chrono", "cidr", diff --git a/examples/rust/src/main.rs b/examples/rust/src/main.rs index e35266a..d7b8fce 100644 --- a/examples/rust/src/main.rs +++ b/examples/rust/src/main.rs @@ -44,12 +44,6 @@ async fn main() { }; let topology = K8sAnywhereTopology::from_env(); - - // topology - // .provision_tenant(&tenant.config) - // .await - // .expect("couldn't provision tenant"); - let mut maestro = Maestro::initialize(Inventory::autoload(), topology) .await .unwrap(); @@ -61,59 +55,16 @@ async fn main() { framework: Some(RustWebFramework::Leptos), }); - let ntfy = NtfyScore { - namespace: tenant.clone().config.name, - }; - - let ntfy_default_auth_username = "harmony"; - let ntfy_default_auth_password = "harmony"; - let ntfy_default_auth_header = format!( - "Basic {}", - general_purpose::STANDARD.encode(format!( - "{ntfy_default_auth_username}:{ntfy_default_auth_password}" - )) - ); - - let ntfy_default_auth_param = general_purpose::STANDARD - .encode(ntfy_default_auth_header) - .rsplit("=") - .collect::>()[0] - .to_string(); - - let ntfy_receiver = WebhookReceiver { - name: "ntfy-webhook".to_string(), - url: Url::Url( - url::Url::parse( - format!( - "http://ntfy.{}.svc.cluster.local/rust-web-app?auth={ntfy_default_auth_param}", - tenant.clone().config.name - ) - .as_str(), - ) - .unwrap(), - ), - }; - - let alerting_score = HelmPrometheusAlertingScore { - receivers: vec![Box::new(ntfy_receiver)], - rules: vec![], - service_monitors: vec![], - }; - let app = ApplicationScore { features: vec![ Box::new(ContinuousDelivery { application: application.clone(), }), // TODO add monitoring, backups, multisite ha, etc + Box::new(Monitoring {}), ], application, }; - maestro.register_all(vec![ - Box::new(tenant), - Box::new(ntfy), - Box::new(alerting_score), - Box::new(app), - ]); + maestro.register_all(vec![Box::new(app)]); harmony_cli::init(maestro, None).await.unwrap(); } diff --git a/harmony/Cargo.toml b/harmony/Cargo.toml index 53ad8c7..87b97ac 100644 --- a/harmony/Cargo.toml +++ b/harmony/Cargo.toml @@ -61,6 +61,7 @@ tempfile = "3.20.0" serde_with = "3.14.0" bollard.workspace = true tar.workspace = true +base64.workspace = true [dev-dependencies] pretty_assertions.workspace = true diff --git a/harmony/src/modules/application/features/continuous_delivery.rs b/harmony/src/modules/application/features/continuous_delivery.rs index 3ea8042..447b08e 100644 --- a/harmony/src/modules/application/features/continuous_delivery.rs +++ b/harmony/src/modules/application/features/continuous_delivery.rs @@ -204,14 +204,7 @@ impl< .unwrap(); } }; - - todo!("1. Create ArgoCD score that installs argo using helm chart, see if Taha's already done it - - [X] Package app (docker image, helm chart) - - [X] Push to registry - - [X] Push only if staging or prod - - [X] Deploy to local k3d when target is local - - [ ] Poke Argo - - [ ] Ensure app is up") + Ok(()) } fn name(&self) -> String { "ContinuousDelivery".to_string() diff --git a/harmony/src/modules/application/features/monitoring.rs b/harmony/src/modules/application/features/monitoring.rs index 5ca59c0..c89fa38 100644 --- a/harmony/src/modules/application/features/monitoring.rs +++ b/harmony/src/modules/application/features/monitoring.rs @@ -1,4 +1,5 @@ use async_trait::async_trait; +use base64::{Engine as _, engine::general_purpose}; use log::info; use crate::{ @@ -6,28 +7,74 @@ use crate::{ modules::{ application::ApplicationFeature, monitoring::{ + alert_channel::webhook_receiver::WebhookReceiver, application_monitoring::k8s_application_monitoring_score::ApplicationPrometheusMonitoringScore, kube_prometheus::types::{NamespaceSelector, ServiceMonitor}, + ntfy::ntfy::NtfyScore, }, }, score::Score, - topology::{HelmCommand, Topology, tenant::TenantManager}, + topology::{HelmCommand, K8sclient, Topology, Url, tenant::TenantManager}, }; #[derive(Debug, Default, Clone)] pub struct Monitoring {} #[async_trait] -impl ApplicationFeature for Monitoring { +impl ApplicationFeature + for Monitoring +{ async fn ensure_installed(&self, topology: &T) -> Result<(), String> { info!("Ensuring monitoring is available for application"); + + let ntfy = NtfyScore { + namespace: topology + .get_tenant_config() + .await + .expect("couldn't get tenant config") + .name, + }; + ntfy.create_interpret() + .execute(&Inventory::empty(), topology) + .await + .expect("couldn't create interpret for ntfy"); + + let ntfy_default_auth_username = "harmony"; + let ntfy_default_auth_password = "harmony"; + let ntfy_default_auth_header = format!( + "Basic {}", + general_purpose::STANDARD.encode(format!( + "{ntfy_default_auth_username}:{ntfy_default_auth_password}" + )) + ); + + let ntfy_default_auth_param = general_purpose::STANDARD + .encode(ntfy_default_auth_header) + .rsplit("=") + .collect::>()[0] + .to_string(); + + let ntfy_receiver = WebhookReceiver { + name: "ntfy-webhook".to_string(), + url: Url::Url( + url::Url::parse( + format!( + "http://ntfy.{}.svc.cluster.local/rust-web-app?auth={ntfy_default_auth_param}", + topology.get_tenant_config().await.expect("couldn't get tenant config").name + ) + .as_str(), + ) + .unwrap(), + ), + }; + let mut service_monitor = ServiceMonitor::default(); service_monitor.namespace_selector = Some(NamespaceSelector { any: true, match_names: vec![], }); let alerting_score = ApplicationPrometheusMonitoringScore { - receivers: vec![], + receivers: vec![Box::new(ntfy_receiver)], rules: vec![], service_monitors: vec![service_monitor], }; diff --git a/harmony/src/modules/application/mod.rs b/harmony/src/modules/application/mod.rs index afdb88c..3788217 100644 --- a/harmony/src/modules/application/mod.rs +++ b/harmony/src/modules/application/mod.rs @@ -59,9 +59,7 @@ impl Interpret for Application } }; } - todo!( - "Do I need to do anything more than this here?? I feel like the Application trait itself should expose something like ensure_ready but its becoming redundant. We'll see as this evolves." - ) + Ok(Outcome::success("successfully created app".to_string())) } fn get_name(&self) -> InterpretName { diff --git a/harmony/src/modules/application/rust.rs b/harmony/src/modules/application/rust.rs index f991dc2..3db36d1 100644 --- a/harmony/src/modules/application/rust.rs +++ b/harmony/src/modules/application/rust.rs @@ -360,7 +360,11 @@ impl RustWebapp { image_url: &str, ) -> Result> { let chart_name = format!("{}-chart", self.name); - let chart_dir = self.project_root.join("helm").join(&chart_name); + let chart_dir = self + .project_root + .join(".harmony_generated") + .join("helm") + .join(&chart_name); let templates_dir = chart_dir.join("templates"); fs::create_dir_all(&templates_dir)?; @@ -537,11 +541,15 @@ spec: info!( "Launching `helm package {}` cli with CWD {}", chart_dirname.to_string_lossy(), - &self.project_root.join("helm").to_string_lossy() + &self + .project_root + .join(".harmony_generated") + .join("helm") + .to_string_lossy() ); let output = process::Command::new("helm") .args(["package", chart_dirname.to_str().unwrap()]) - .current_dir(&self.project_root.join("helm")) // Run package from the parent dir + .current_dir(&self.project_root.join(".harmony_generated").join("helm")) // Run package from the parent dir .output()?; self.check_output(&output, "Failed to package Helm chart")?; @@ -558,7 +566,11 @@ spec: } // The output from helm is relative, so we join it with the execution directory. - Ok(self.project_root.join("helm").join(tgz_name)) + Ok(self + .project_root + .join(".harmony_generated") + .join("helm") + .join(tgz_name)) } /// Pushes a packaged Helm chart to an OCI registry.