From 22847fc42a598f3ed00b5f863d32bf5c71ba6cdc Mon Sep 17 00:00:00 2001 From: Jean-Gabriel Gill-Couture Date: Tue, 24 Jun 2025 11:19:46 -0400 Subject: [PATCH 01/10] feat: WIP application module that will allow easy design of application/solution scores with standard delivery features like CI/CD/Backups/Multisite --- harmony/src/modules/application/mod.rs | 83 ++++++++++++++++++++++++++ harmony/src/modules/mod.rs | 1 + 2 files changed, 84 insertions(+) create mode 100644 harmony/src/modules/application/mod.rs diff --git a/harmony/src/modules/application/mod.rs b/harmony/src/modules/application/mod.rs new file mode 100644 index 0000000..54422aa --- /dev/null +++ b/harmony/src/modules/application/mod.rs @@ -0,0 +1,83 @@ +use async_trait::async_trait; +use serde::Serialize; + +use crate::{ + data::{Id, Version}, + interpret::{Interpret, InterpretError, InterpretName, InterpretStatus, Outcome}, + inventory::Inventory, + score::Score, + topology::Topology, +}; + +#[derive(Clone, Serialize, Debug)] +pub struct GoApplicationConfig {} + +#[derive(Clone, Serialize, Debug)] +pub struct GoApplicationScore { + pub config: GoApplicationConfig, +} + +impl Score for GoApplicationScore { + fn create_interpret(&self) -> Box> { + todo!() + } + + fn name(&self) -> String { + todo!() + } +} + +#[derive(Debug)] +pub struct ApplicationInterpret { + pub features: Vec>, +} + +#[async_trait] +impl Interpret for ApplicationInterpret { + async fn execute(&self, _inventory: &Inventory, _topology: &T) -> Result { + todo!() + } + + fn get_name(&self) -> InterpretName { + todo!() + } + + fn get_version(&self) -> Version { + todo!() + } + + fn get_status(&self) -> InterpretStatus { + todo!() + } + + fn get_children(&self) -> Vec { + todo!() + } +} + +/// An ApplicationFeature provided by harmony, such as Backups, Monitoring, MultisiteAvailability, +/// ContinuousIntegration, ContinuousDelivery +#[async_trait] +pub trait ApplicationFeature: std::fmt::Debug + Send + Sync { + async fn ensure_installed(&self) -> Result<(), String>; + async fn is_installed(&self) -> Result; + async fn uninstall(&self) -> Result<(), String>; +} + +#[derive(Debug)] +pub struct BackupFeature; + +#[async_trait] +impl ApplicationFeature for BackupFeature { + async fn ensure_installed(&self) -> Result<(), String> { + todo!() + } + + async fn is_installed(&self) -> Result { + todo!() + } + + async fn uninstall(&self) -> Result<(), String> { + todo!() + } +} diff --git a/harmony/src/modules/mod.rs b/harmony/src/modules/mod.rs index 1427515..465587b 100644 --- a/harmony/src/modules/mod.rs +++ b/harmony/src/modules/mod.rs @@ -14,3 +14,4 @@ pub mod okd; pub mod opnsense; pub mod tenant; pub mod tftp; +pub mod application; -- 2.39.5 From 923de4506ef954dff133ceed0889e66a31383fc3 Mon Sep 17 00:00:00 2001 From: Jean-Gabriel Gill-Couture Date: Sat, 28 Jun 2025 22:50:06 -0400 Subject: [PATCH 02/10] feat: ApplicationModule with tentative architecture and placeholder implementation of first few features --- .../modules/application/features/endpoint.rs | 75 +++++++++++++++++++ .../src/modules/application/features/mod.rs | 54 +++++++++++++ harmony/src/modules/application/mod.rs | 23 +++++- harmony/src/modules/application/rust.rs | 26 +++++++ 4 files changed, 177 insertions(+), 1 deletion(-) create mode 100644 harmony/src/modules/application/features/endpoint.rs create mode 100644 harmony/src/modules/application/features/mod.rs create mode 100644 harmony/src/modules/application/rust.rs diff --git a/harmony/src/modules/application/features/endpoint.rs b/harmony/src/modules/application/features/endpoint.rs new file mode 100644 index 0000000..1e12ae4 --- /dev/null +++ b/harmony/src/modules/application/features/endpoint.rs @@ -0,0 +1,75 @@ +use async_trait::async_trait; + +use crate::modules::application::{Application, ApplicationFeature}; + +#[derive(Debug)] +pub struct PublicEndpoint { + application_port: u16, +} + +/// Use port 3000 as default port. Harmony wants to provide "sane defaults" in general, and in this +/// particular context, using port 80 goes against our philosophy to provide production grade +/// defaults out of the box. Using an unprivileged port is a good security practice and will allow +/// for unprivileged containers to work with this out of the box. +/// +/// Now, why 3000 specifically? Many popular web/network frameworks use it by default, there is no +/// perfect answer for this but many Rust and Python libraries tend to use 3000. +impl Default for PublicEndpoint { + fn default() -> Self { + Self { + application_port: 3000, + } + } +} + +#[async_trait] +impl ApplicationFeature for PublicEndpoint { + async fn ensure_installed(&self) -> Result<(), String> { + todo!() + } + + async fn is_installed(&self) -> Result { + todo!() + } + + async fn uninstall(&self) -> Result<(), String> { + todo!() + } +} + +// Design options here : +// +// 1. Forget about ApplicationFeature trait. The Features are just other scores that are passed on to +// the ApplicationInterpret as children (and we can rename children dependencies maybe?) +// +// 2. Go forward with the ApplicationFeature trait. There are important question marks here : +// - What about the installation lifecycle management? This was defined as being handled by a +// Topology. The thing here is that I am not sure wether application features belong at the +// Topology level or not. Functionnaly they are pretty similar. Topology provides software +// infrastructure features that Scores will then install themselves on. Most of the time those very +// features are installed using Scores with lower level dependencies. For example, : +// +// AlertingFeature depends on T: Topology + AlertSender +// AlertSender is implemented as KubePrometheus which depends on T: Topology + HelmCommand +// HelmCommand relies on T: Topology + K8sClient +// +// With that said, would it work with `features: Vec>` instead of `features: +// Vec>` ? +// +// Let's unpack this : +// +// RustWebappScore { +// features: Vec>, +// } +// +// This brings in a significant problem : RustWebappScore becomes generic, which is a problem for +// Clone and Serialize bounds. +// +// But that can be fixed easily I think ? +// +// RustWebappScore { +// features: Vec>, +// } +// +// Oh right not quite because it is `dyn`. +// diff --git a/harmony/src/modules/application/features/mod.rs b/harmony/src/modules/application/features/mod.rs new file mode 100644 index 0000000..b37be6c --- /dev/null +++ b/harmony/src/modules/application/features/mod.rs @@ -0,0 +1,54 @@ +mod endpoint; +use async_trait::async_trait; +pub use endpoint::*; + +use super::ApplicationFeature; + +#[derive(Debug, Default)] +pub struct SoftwareQualityChecks {} + +#[async_trait] +impl ApplicationFeature for SoftwareQualityChecks { + async fn ensure_installed(&self) -> Result<(), String> { + todo!() + } + async fn is_installed(&self) -> Result { + todo!() + } + async fn uninstall(&self) -> Result<(), String> { + todo!() + } +} + +#[derive(Debug, Default)] +pub struct ContinuousDelivery {} + +#[async_trait] +impl ApplicationFeature for ContinuousDelivery { + async fn ensure_installed(&self) -> Result<(), String> { + todo!() + } + async fn is_installed(&self) -> Result { + todo!() + } + async fn uninstall(&self) -> Result<(), String> { + todo!() + } +} + +#[derive(Debug, Default)] +pub struct Monitoring {} + +#[async_trait] +impl ApplicationFeature for Monitoring { + async fn ensure_installed(&self) -> Result<(), String> { + todo!() + } + async fn is_installed(&self) -> Result { + todo!() + } + + async fn uninstall(&self) -> Result<(), String> { + todo!() + } +} diff --git a/harmony/src/modules/application/mod.rs b/harmony/src/modules/application/mod.rs index 54422aa..b4a5825 100644 --- a/harmony/src/modules/application/mod.rs +++ b/harmony/src/modules/application/mod.rs @@ -1,3 +1,7 @@ +mod rust; +pub mod features; +pub use rust::*; + use async_trait::async_trait; use serde::Serialize; @@ -29,7 +33,7 @@ impl Score for GoApplicationScore { #[derive(Debug)] pub struct ApplicationInterpret { - pub features: Vec>, + features: Vec>, } #[async_trait] @@ -55,6 +59,8 @@ impl Interpret for ApplicationInterpret { } } +trait Application {} + /// An ApplicationFeature provided by harmony, such as Backups, Monitoring, MultisiteAvailability, /// ContinuousIntegration, ContinuousDelivery #[async_trait] @@ -64,6 +70,21 @@ pub trait ApplicationFeature: std::fmt::Debug + Send + Sync { async fn uninstall(&self) -> Result<(), String>; } +impl Serialize for Box { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + todo!() + } +} + +impl Clone for Box { + fn clone(&self) -> Self { + todo!() + } +} + #[derive(Debug)] pub struct BackupFeature; diff --git a/harmony/src/modules/application/rust.rs b/harmony/src/modules/application/rust.rs new file mode 100644 index 0000000..f7fd569 --- /dev/null +++ b/harmony/src/modules/application/rust.rs @@ -0,0 +1,26 @@ +use serde::Serialize; + +use crate::{ + score::Score, + topology::{Topology, Url}, +}; + +use super::{ApplicationFeature, ApplicationInterpret}; + +#[derive(Debug, Serialize, Clone)] +pub struct RustWebappScore { + pub name: String, + pub domain: Url, + pub features: Vec>, +} + +impl Score for RustWebappScore { + fn create_interpret(&self) -> Box> { + Box::new(ApplicationInterpret { features: todo!() }) + } + + fn name(&self) -> String { + format!("{}-RustWebapp", self.name) + } +} + -- 2.39.5 From 34abe3af24c3aa9608c9fd19e62864512ad6c027 Mon Sep 17 00:00:00 2001 From: Jean-Gabriel Gill-Couture Date: Mon, 30 Jun 2025 15:40:18 -0400 Subject: [PATCH 03/10] feat: Application module architecture and placeholder features With this architecture, we have an extensible application module for which we can easily define new features and add them to application scores. All this is driven by the ApplicationInterpret, who understands features and make sure they are "installed". The drawback of this design is that we now have three different places to launch scores within Harmony : Maestro, Topology and Interpret. This is an architectural smell and I am not sure how to deal with it at the moment. However, all these places where execution is performed make sense semantically : an ApplicationInterpret must understand ApplicationFeatures and can very well be responsible of them. Same goes for a Topology which provides features itself by composition (ex. K8sAnywhereTopology implements TenantManager) so it is natural for this very implementation to know how to install itself. --- harmony/src/domain/topology/k8s.rs | 10 ++- harmony/src/domain/topology/k8s_anywhere.rs | 32 +++++++--- harmony/src/domain/topology/tenant/k8s.rs | 2 +- .../modules/application/features/endpoint.rs | 46 ++------------ .../src/modules/application/features/mod.rs | 62 ++++++++++++++++--- harmony/src/modules/application/mod.rs | 26 ++++---- harmony/src/modules/application/rust.rs | 7 +-- 7 files changed, 111 insertions(+), 74 deletions(-) diff --git a/harmony/src/domain/topology/k8s.rs b/harmony/src/domain/topology/k8s.rs index 9e0a6db..806de0a 100644 --- a/harmony/src/domain/topology/k8s.rs +++ b/harmony/src/domain/topology/k8s.rs @@ -8,11 +8,19 @@ use kube::{ use log::{debug, error, trace}; use serde::de::DeserializeOwned; -#[derive(new)] +#[derive(new, Clone)] pub struct K8sClient { client: Client, } +impl std::fmt::Debug for K8sClient { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + // This is a poor man's debug implementation for now as kube::Client does not provide much + // useful information + f.write_fmt(format_args!("K8sClient {{ kube client using default namespace {} }}", self.client.default_namespace())) + } +} + impl K8sClient { pub async fn try_default() -> Result { Ok(Self { diff --git a/harmony/src/domain/topology/k8s_anywhere.rs b/harmony/src/domain/topology/k8s_anywhere.rs index 6742b5a..267fb63 100644 --- a/harmony/src/domain/topology/k8s_anywhere.rs +++ b/harmony/src/domain/topology/k8s_anywhere.rs @@ -3,6 +3,7 @@ use std::{process::Command, sync::Arc}; use async_trait::async_trait; use inquire::Confirm; use log::{debug, info, warn}; +use serde::Serialize; use tokio::sync::OnceCell; use crate::{ @@ -20,22 +21,24 @@ use super::{ tenant::{TenantConfig, TenantManager, k8s::K8sTenantManager}, }; +#[derive(Clone, Debug)] struct K8sState { client: Arc, _source: K8sSource, message: String, } -#[derive(Debug)] +#[derive(Debug, Clone)] enum K8sSource { LocalK3d, Kubeconfig, } +#[derive(Clone, Debug)] pub struct K8sAnywhereTopology { - k8s_state: OnceCell>, - tenant_manager: OnceCell, - config: K8sAnywhereConfig, + k8s_state: Arc>>, + tenant_manager: Arc>, + config: Arc, } #[async_trait] @@ -55,20 +58,28 @@ impl K8sclient for K8sAnywhereTopology { } } +impl Serialize for K8sAnywhereTopology { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer { + todo!() + } +} + impl K8sAnywhereTopology { pub fn from_env() -> Self { Self { - k8s_state: OnceCell::new(), - tenant_manager: OnceCell::new(), - config: K8sAnywhereConfig::from_env(), + k8s_state: Arc::new(OnceCell::new()), + tenant_manager: Arc::new(OnceCell::new()), + config: Arc::new(K8sAnywhereConfig::from_env()), } } pub fn with_config(config: K8sAnywhereConfig) -> Self { Self { - k8s_state: OnceCell::new(), - tenant_manager: OnceCell::new(), - config, + k8s_state: Arc::new(OnceCell::new()), + tenant_manager: Arc::new(OnceCell::new()), + config: Arc::new(config), } } @@ -200,6 +211,7 @@ impl K8sAnywhereTopology { } } +#[derive(Clone, Debug)] pub struct K8sAnywhereConfig { /// The path of the KUBECONFIG file that Harmony should use to interact with the Kubernetes /// cluster diff --git a/harmony/src/domain/topology/tenant/k8s.rs b/harmony/src/domain/topology/tenant/k8s.rs index a03e8d7..260cf3f 100644 --- a/harmony/src/domain/topology/tenant/k8s.rs +++ b/harmony/src/domain/topology/tenant/k8s.rs @@ -22,7 +22,7 @@ use serde_json::json; use super::{TenantConfig, TenantManager}; -#[derive(new)] +#[derive(new, Clone, Debug)] pub struct K8sTenantManager { k8s_client: Arc, } diff --git a/harmony/src/modules/application/features/endpoint.rs b/harmony/src/modules/application/features/endpoint.rs index 1e12ae4..b743e8a 100644 --- a/harmony/src/modules/application/features/endpoint.rs +++ b/harmony/src/modules/application/features/endpoint.rs @@ -1,6 +1,7 @@ use async_trait::async_trait; +use log::info; -use crate::modules::application::{Application, ApplicationFeature}; +use crate::{modules::application::{Application, ApplicationFeature}, topology::{K8sclient, Topology}}; #[derive(Debug)] pub struct PublicEndpoint { @@ -22,9 +23,11 @@ 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) -> Result<(), String> { +impl ApplicationFeature for PublicEndpoint { + async fn ensure_installed(&self, _topology: &T) -> Result<(), String> { + info!("Making sure public endpoint is installed for port {}", self.application_port); todo!() } @@ -36,40 +39,3 @@ impl ApplicationFeature for PublicEndpoint { todo!() } } - -// Design options here : -// -// 1. Forget about ApplicationFeature trait. The Features are just other scores that are passed on to -// the ApplicationInterpret as children (and we can rename children dependencies maybe?) -// -// 2. Go forward with the ApplicationFeature trait. There are important question marks here : -// - What about the installation lifecycle management? This was defined as being handled by a -// Topology. The thing here is that I am not sure wether application features belong at the -// Topology level or not. Functionnaly they are pretty similar. Topology provides software -// infrastructure features that Scores will then install themselves on. Most of the time those very -// features are installed using Scores with lower level dependencies. For example, : -// -// AlertingFeature depends on T: Topology + AlertSender -// AlertSender is implemented as KubePrometheus which depends on T: Topology + HelmCommand -// HelmCommand relies on T: Topology + K8sClient -// -// With that said, would it work with `features: Vec>` instead of `features: -// Vec>` ? -// -// Let's unpack this : -// -// RustWebappScore { -// features: Vec>, -// } -// -// This brings in a significant problem : RustWebappScore becomes generic, which is a problem for -// Clone and Serialize bounds. -// -// But that can be fixed easily I think ? -// -// RustWebappScore { -// features: Vec>, -// } -// -// Oh right not quite because it is `dyn`. -// diff --git a/harmony/src/modules/application/features/mod.rs b/harmony/src/modules/application/features/mod.rs index b37be6c..792946c 100644 --- a/harmony/src/modules/application/features/mod.rs +++ b/harmony/src/modules/application/features/mod.rs @@ -1,6 +1,9 @@ mod endpoint; use async_trait::async_trait; pub use endpoint::*; +use log::info; + +use crate::topology::{HelmCommand, Topology}; use super::ApplicationFeature; @@ -8,29 +11,72 @@ use super::ApplicationFeature; pub struct SoftwareQualityChecks {} #[async_trait] -impl ApplicationFeature for SoftwareQualityChecks { - async fn ensure_installed(&self) -> Result<(), String> { +impl ApplicationFeature for SoftwareQualityChecks { + // Either allow ApplicationFeature to self-install, which means passing Topology and Inventory + // here. This would be a very easy thing to be done reliably by the ApplicationInterpret. + // However, I feel like this would probably better be a list of Scores (or some sort of + // executable) to be orchestrated by the maestro. We will soon have to manage more complex + // lifecycles, dependencies, parallelism, etc. + // + // + // Or change the ApplicationFeature trait to a Score trait. + // + // For now I'll go with the first option + async fn ensure_installed(&self, _topology: &T) -> Result<(), String> { + info!("Ensuring SoftwareQualityChecks are installed for application"); todo!() } + async fn is_installed(&self) -> Result { todo!() } + async fn uninstall(&self) -> Result<(), String> { todo!() } } +/// ContinuousDelivery in Harmony provides this functionality : +/// +/// - **Package** the application +/// - **Push** to an artifact registry +/// - **Deploy** to a testing environment +/// - **Deploy** to a production environment +/// +/// It is intended to be used as an application feature passed down to an ApplicationInterpret. For +/// example : +/// +/// ```rust +/// let app = RustApplicationScore { +/// name: "My Rust App".to_string(), +/// features: vec![ContinuousDelivery::default()], +/// }; +/// ``` +/// +/// *Note :* +/// +/// By default, the Harmony Opinionated Pipeline is built using these technologies : +/// +/// - Gitea Action (executes pipeline steps) +/// - Docker to build an OCI container image +/// - Helm chart to package Kubernetes resources +/// - Harbor as artifact registru +/// - ArgoCD to install/upgrade/rollback/inspect k8s resources +/// - Kubernetes for runtime orchestration #[derive(Debug, Default)] pub struct ContinuousDelivery {} #[async_trait] -impl ApplicationFeature for ContinuousDelivery { - async fn ensure_installed(&self) -> Result<(), String> { +impl ApplicationFeature for ContinuousDelivery { + async fn ensure_installed(&self, _topology: &T) -> Result<(), String> { + info!("Installing ContinuousDelivery feature"); todo!() } + async fn is_installed(&self) -> Result { todo!() } + async fn uninstall(&self) -> Result<(), String> { todo!() } @@ -40,10 +86,12 @@ impl ApplicationFeature for ContinuousDelivery { pub struct Monitoring {} #[async_trait] -impl ApplicationFeature for Monitoring { - async fn ensure_installed(&self) -> Result<(), String> { - todo!() +impl ApplicationFeature for Monitoring { + async fn ensure_installed(&self, _topology: &T) -> Result<(), String> { + info!("Ensuring monitoring is available for application"); + todo!("create and execute k8s prometheus score, depends on Will's work") } + async fn is_installed(&self) -> Result { todo!() } diff --git a/harmony/src/modules/application/mod.rs b/harmony/src/modules/application/mod.rs index b4a5825..f428ce1 100644 --- a/harmony/src/modules/application/mod.rs +++ b/harmony/src/modules/application/mod.rs @@ -1,5 +1,5 @@ -mod rust; pub mod features; +mod rust; pub use rust::*; use async_trait::async_trait; @@ -32,13 +32,17 @@ impl Score for GoApplicationScore { } #[derive(Debug)] -pub struct ApplicationInterpret { - features: Vec>, +pub struct ApplicationInterpret { + features: Vec>>, } #[async_trait] -impl Interpret for ApplicationInterpret { - async fn execute(&self, _inventory: &Inventory, _topology: &T) -> Result { +impl Interpret for ApplicationInterpret { + async fn execute( + &self, + _inventory: &Inventory, + _topology: &T, + ) -> Result { todo!() } @@ -64,13 +68,13 @@ trait Application {} /// An ApplicationFeature provided by harmony, such as Backups, Monitoring, MultisiteAvailability, /// ContinuousIntegration, ContinuousDelivery #[async_trait] -pub trait ApplicationFeature: std::fmt::Debug + Send + Sync { - async fn ensure_installed(&self) -> Result<(), String>; +pub trait ApplicationFeature: std::fmt::Debug + Send + Sync { + async fn ensure_installed(&self, topology: &T) -> Result<(), String>; async fn is_installed(&self) -> Result; async fn uninstall(&self) -> Result<(), String>; } -impl Serialize for Box { +impl Serialize for Box> { fn serialize(&self, serializer: S) -> Result where S: serde::Serializer, @@ -79,7 +83,7 @@ impl Serialize for Box { } } -impl Clone for Box { +impl Clone for Box> { fn clone(&self) -> Self { todo!() } @@ -89,8 +93,8 @@ impl Clone for Box { pub struct BackupFeature; #[async_trait] -impl ApplicationFeature for BackupFeature { - async fn ensure_installed(&self) -> Result<(), String> { +impl ApplicationFeature for BackupFeature { + async fn ensure_installed(&self, _topology: &T) -> Result<(), String> { todo!() } diff --git a/harmony/src/modules/application/rust.rs b/harmony/src/modules/application/rust.rs index f7fd569..0ef41b3 100644 --- a/harmony/src/modules/application/rust.rs +++ b/harmony/src/modules/application/rust.rs @@ -8,13 +8,13 @@ use crate::{ use super::{ApplicationFeature, ApplicationInterpret}; #[derive(Debug, Serialize, Clone)] -pub struct RustWebappScore { +pub struct RustWebappScore { pub name: String, pub domain: Url, - pub features: Vec>, + pub features: Vec>>, } -impl Score for RustWebappScore { +impl Score for RustWebappScore { fn create_interpret(&self) -> Box> { Box::new(ApplicationInterpret { features: todo!() }) } @@ -23,4 +23,3 @@ impl Score for RustWebappScore { format!("{}-RustWebapp", self.name) } } - -- 2.39.5 From ae95c44867703d8941de4c952a7a09e532a4523c Mon Sep 17 00:00:00 2001 From: Jean-Gabriel Gill-Couture Date: Tue, 1 Jul 2025 10:18:52 -0400 Subject: [PATCH 04/10] chore: Refactor application module after code review --- .../features/continuous_delivery.rs | 37 +++++++ .../modules/application/features/endpoint.rs | 20 ++-- .../src/modules/application/features/mod.rs | 104 +----------------- .../application/features/monitoring.rs | 10 ++ harmony/src/modules/application/mod.rs | 10 -- 5 files changed, 60 insertions(+), 121 deletions(-) create mode 100644 harmony/src/modules/application/features/continuous_delivery.rs create mode 100644 harmony/src/modules/application/features/monitoring.rs diff --git a/harmony/src/modules/application/features/continuous_delivery.rs b/harmony/src/modules/application/features/continuous_delivery.rs new file mode 100644 index 0000000..4cd2946 --- /dev/null +++ b/harmony/src/modules/application/features/continuous_delivery.rs @@ -0,0 +1,37 @@ +/// ContinuousDelivery in Harmony provides this functionality : +/// +/// - **Package** the application +/// - **Push** to an artifact registry +/// - **Deploy** to a testing environment +/// - **Deploy** to a production environment +/// +/// It is intended to be used as an application feature passed down to an ApplicationInterpret. For +/// example : +/// +/// ```rust +/// let app = RustApplicationScore { +/// name: "My Rust App".to_string(), +/// features: vec![ContinuousDelivery::default()], +/// }; +/// ``` +/// +/// *Note :* +/// +/// By default, the Harmony Opinionated Pipeline is built using these technologies : +/// +/// - Gitea Action (executes pipeline steps) +/// - Docker to build an OCI container image +/// - Helm chart to package Kubernetes resources +/// - Harbor as artifact registru +/// - ArgoCD to install/upgrade/rollback/inspect k8s resources +/// - Kubernetes for runtime orchestration +#[derive(Debug, Default)] +pub struct ContinuousDelivery {} + +#[async_trait] +impl ApplicationFeature for ContinuousDelivery { + async fn ensure_installed(&self, _topology: &T) -> Result<(), String> { + info!("Installing ContinuousDelivery feature"); + todo!() + } +} diff --git a/harmony/src/modules/application/features/endpoint.rs b/harmony/src/modules/application/features/endpoint.rs index b743e8a..f4940ed 100644 --- a/harmony/src/modules/application/features/endpoint.rs +++ b/harmony/src/modules/application/features/endpoint.rs @@ -1,7 +1,10 @@ use async_trait::async_trait; use log::info; -use crate::{modules::application::{Application, ApplicationFeature}, topology::{K8sclient, Topology}}; +use crate::{ + modules::application::ApplicationFeature, + topology::{K8sclient, Topology}, +}; #[derive(Debug)] pub struct PublicEndpoint { @@ -25,17 +28,12 @@ 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 { +impl ApplicationFeature for PublicEndpoint { async fn ensure_installed(&self, _topology: &T) -> Result<(), String> { - info!("Making sure public endpoint is installed for port {}", self.application_port); - todo!() - } - - async fn is_installed(&self) -> Result { - todo!() - } - - async fn uninstall(&self) -> Result<(), String> { + info!( + "Making sure public endpoint is installed for port {}", + self.application_port + ); todo!() } } diff --git a/harmony/src/modules/application/features/mod.rs b/harmony/src/modules/application/features/mod.rs index 792946c..e58a814 100644 --- a/harmony/src/modules/application/features/mod.rs +++ b/harmony/src/modules/application/features/mod.rs @@ -1,102 +1,6 @@ mod endpoint; -use async_trait::async_trait; pub use endpoint::*; -use log::info; - -use crate::topology::{HelmCommand, Topology}; - -use super::ApplicationFeature; - -#[derive(Debug, Default)] -pub struct SoftwareQualityChecks {} - -#[async_trait] -impl ApplicationFeature for SoftwareQualityChecks { - // Either allow ApplicationFeature to self-install, which means passing Topology and Inventory - // here. This would be a very easy thing to be done reliably by the ApplicationInterpret. - // However, I feel like this would probably better be a list of Scores (or some sort of - // executable) to be orchestrated by the maestro. We will soon have to manage more complex - // lifecycles, dependencies, parallelism, etc. - // - // - // Or change the ApplicationFeature trait to a Score trait. - // - // For now I'll go with the first option - async fn ensure_installed(&self, _topology: &T) -> Result<(), String> { - info!("Ensuring SoftwareQualityChecks are installed for application"); - todo!() - } - - async fn is_installed(&self) -> Result { - todo!() - } - - async fn uninstall(&self) -> Result<(), String> { - todo!() - } -} - -/// ContinuousDelivery in Harmony provides this functionality : -/// -/// - **Package** the application -/// - **Push** to an artifact registry -/// - **Deploy** to a testing environment -/// - **Deploy** to a production environment -/// -/// It is intended to be used as an application feature passed down to an ApplicationInterpret. For -/// example : -/// -/// ```rust -/// let app = RustApplicationScore { -/// name: "My Rust App".to_string(), -/// features: vec![ContinuousDelivery::default()], -/// }; -/// ``` -/// -/// *Note :* -/// -/// By default, the Harmony Opinionated Pipeline is built using these technologies : -/// -/// - Gitea Action (executes pipeline steps) -/// - Docker to build an OCI container image -/// - Helm chart to package Kubernetes resources -/// - Harbor as artifact registru -/// - ArgoCD to install/upgrade/rollback/inspect k8s resources -/// - Kubernetes for runtime orchestration -#[derive(Debug, Default)] -pub struct ContinuousDelivery {} - -#[async_trait] -impl ApplicationFeature for ContinuousDelivery { - async fn ensure_installed(&self, _topology: &T) -> Result<(), String> { - info!("Installing ContinuousDelivery feature"); - todo!() - } - - async fn is_installed(&self) -> Result { - todo!() - } - - async fn uninstall(&self) -> Result<(), String> { - todo!() - } -} - -#[derive(Debug, Default)] -pub struct Monitoring {} - -#[async_trait] -impl ApplicationFeature for Monitoring { - async fn ensure_installed(&self, _topology: &T) -> Result<(), String> { - info!("Ensuring monitoring is available for application"); - todo!("create and execute k8s prometheus score, depends on Will's work") - } - - async fn is_installed(&self) -> Result { - todo!() - } - - async fn uninstall(&self) -> Result<(), String> { - todo!() - } -} +mod monitoring; +pub use monitoring::*; +mod continuous_delivery; +pub use endpoint::*; diff --git a/harmony/src/modules/application/features/monitoring.rs b/harmony/src/modules/application/features/monitoring.rs new file mode 100644 index 0000000..dccfa88 --- /dev/null +++ b/harmony/src/modules/application/features/monitoring.rs @@ -0,0 +1,10 @@ +#[derive(Debug, Default)] +pub struct Monitoring {} + +#[async_trait] +impl ApplicationFeature for Monitoring { + async fn ensure_installed(&self, _topology: &T) -> Result<(), String> { + info!("Ensuring monitoring is available for application"); + todo!("create and execute k8s prometheus score, depends on Will's work") + } +} diff --git a/harmony/src/modules/application/mod.rs b/harmony/src/modules/application/mod.rs index f428ce1..1d9b8b0 100644 --- a/harmony/src/modules/application/mod.rs +++ b/harmony/src/modules/application/mod.rs @@ -70,8 +70,6 @@ trait Application {} #[async_trait] pub trait ApplicationFeature: std::fmt::Debug + Send + Sync { async fn ensure_installed(&self, topology: &T) -> Result<(), String>; - async fn is_installed(&self) -> Result; - async fn uninstall(&self) -> Result<(), String>; } impl Serialize for Box> { @@ -97,12 +95,4 @@ impl ApplicationFeature for BackupFeature { async fn ensure_installed(&self, _topology: &T) -> Result<(), String> { todo!() } - - async fn is_installed(&self) -> Result { - todo!() - } - - async fn uninstall(&self) -> Result<(), String> { - todo!() - } } -- 2.39.5 From c02763a6fc5826edd725759df80c5a9d23a08fbd Mon Sep 17 00:00:00 2001 From: Ian Letourneau Date: Tue, 1 Jul 2025 11:33:05 -0400 Subject: [PATCH 05/10] Adjust imports --- .../application/features/continuous_delivery.rs | 7 ++++++- harmony/src/modules/application/features/mod.rs | 4 +++- .../src/modules/application/features/monitoring.rs | 10 +++++++++- harmony_cli/src/lib.rs | 12 ++++++------ harmony_composer/src/main.rs | 1 + 5 files changed, 25 insertions(+), 9 deletions(-) diff --git a/harmony/src/modules/application/features/continuous_delivery.rs b/harmony/src/modules/application/features/continuous_delivery.rs index 4cd2946..982b5c4 100644 --- a/harmony/src/modules/application/features/continuous_delivery.rs +++ b/harmony/src/modules/application/features/continuous_delivery.rs @@ -1,3 +1,8 @@ +use async_trait::async_trait; +use log::info; + +use crate::{modules::application::ApplicationFeature, topology::Topology}; + /// ContinuousDelivery in Harmony provides this functionality : /// /// - **Package** the application @@ -29,7 +34,7 @@ pub struct ContinuousDelivery {} #[async_trait] -impl ApplicationFeature for ContinuousDelivery { +impl ApplicationFeature for ContinuousDelivery { async fn ensure_installed(&self, _topology: &T) -> Result<(), String> { info!("Installing ContinuousDelivery feature"); todo!() diff --git a/harmony/src/modules/application/features/mod.rs b/harmony/src/modules/application/features/mod.rs index e58a814..0e034fc 100644 --- a/harmony/src/modules/application/features/mod.rs +++ b/harmony/src/modules/application/features/mod.rs @@ -1,6 +1,8 @@ mod endpoint; pub use endpoint::*; + mod monitoring; pub use monitoring::*; + mod continuous_delivery; -pub use endpoint::*; +pub use continuous_delivery::*; diff --git a/harmony/src/modules/application/features/monitoring.rs b/harmony/src/modules/application/features/monitoring.rs index dccfa88..91ad72d 100644 --- a/harmony/src/modules/application/features/monitoring.rs +++ b/harmony/src/modules/application/features/monitoring.rs @@ -1,8 +1,16 @@ +use async_trait::async_trait; +use log::info; + +use crate::{ + modules::application::ApplicationFeature, + topology::{HelmCommand, Topology}, +}; + #[derive(Debug, Default)] 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"); todo!("create and execute k8s prometheus score, depends on Will's work") diff --git a/harmony_cli/src/lib.rs b/harmony_cli/src/lib.rs index 33759fa..50beb6e 100644 --- a/harmony_cli/src/lib.rs +++ b/harmony_cli/src/lib.rs @@ -12,15 +12,15 @@ use harmony_tui; pub struct Args { /// Run score(s) without prompt #[arg(short, long, default_value_t = false, conflicts_with = "interactive")] - yes: bool, + pub yes: bool, /// Filter query #[arg(short, long, conflicts_with = "interactive")] - filter: Option, + pub filter: Option, /// Run interactive TUI or not #[arg(short, long, default_value_t = false)] - interactive: bool, + pub interactive: bool, /// Run all or nth, defaults to all #[arg( @@ -31,15 +31,15 @@ pub struct Args { conflicts_with = "number", conflicts_with = "interactive" )] - all: bool, + pub all: bool, /// Run nth matching, zero indexed #[arg(short, long, default_value_t = 0, conflicts_with = "interactive")] - number: usize, + pub number: usize, /// list scores, will also be affected by run filter #[arg(short, long, default_value_t = false, conflicts_with = "interactive")] - list: bool, + pub list: bool, } fn maestro_scores_filter( diff --git a/harmony_composer/src/main.rs b/harmony_composer/src/main.rs index 0bdb7be..5bda800 100644 --- a/harmony_composer/src/main.rs +++ b/harmony_composer/src/main.rs @@ -119,6 +119,7 @@ async fn main() { if args.prod { todo!("implement prod deployment"); } + println!("harmony_bin_path: {}", harmony_bin_path.display()); let deploy_output = Command::new(harmony_bin_path) .arg("-y") .arg("-a") -- 2.39.5 From 2c90d50168d885ae223b04c3ead98c4c9c485b37 Mon Sep 17 00:00:00 2001 From: Ian Letourneau Date: Tue, 1 Jul 2025 11:49:52 -0400 Subject: [PATCH 06/10] format code --- harmony/src/domain/topology/k8s.rs | 5 ++++- harmony/src/domain/topology/k8s_anywhere.rs | 3 ++- .../src/modules/application/features/continuous_delivery.rs | 2 +- harmony/src/modules/application/mod.rs | 2 +- harmony/src/modules/mod.rs | 2 +- 5 files changed, 9 insertions(+), 5 deletions(-) diff --git a/harmony/src/domain/topology/k8s.rs b/harmony/src/domain/topology/k8s.rs index 806de0a..bf91726 100644 --- a/harmony/src/domain/topology/k8s.rs +++ b/harmony/src/domain/topology/k8s.rs @@ -17,7 +17,10 @@ impl std::fmt::Debug for K8sClient { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { // This is a poor man's debug implementation for now as kube::Client does not provide much // useful information - f.write_fmt(format_args!("K8sClient {{ kube client using default namespace {} }}", self.client.default_namespace())) + f.write_fmt(format_args!( + "K8sClient {{ kube client using default namespace {} }}", + self.client.default_namespace() + )) } } diff --git a/harmony/src/domain/topology/k8s_anywhere.rs b/harmony/src/domain/topology/k8s_anywhere.rs index 267fb63..65567f1 100644 --- a/harmony/src/domain/topology/k8s_anywhere.rs +++ b/harmony/src/domain/topology/k8s_anywhere.rs @@ -61,7 +61,8 @@ impl K8sclient for K8sAnywhereTopology { impl Serialize for K8sAnywhereTopology { fn serialize(&self, serializer: S) -> Result where - S: serde::Serializer { + S: serde::Serializer, + { todo!() } } diff --git a/harmony/src/modules/application/features/continuous_delivery.rs b/harmony/src/modules/application/features/continuous_delivery.rs index 982b5c4..3530142 100644 --- a/harmony/src/modules/application/features/continuous_delivery.rs +++ b/harmony/src/modules/application/features/continuous_delivery.rs @@ -13,7 +13,7 @@ use crate::{modules::application::ApplicationFeature, topology::Topology}; /// It is intended to be used as an application feature passed down to an ApplicationInterpret. For /// example : /// -/// ```rust +/// ```rust,ignore /// let app = RustApplicationScore { /// name: "My Rust App".to_string(), /// features: vec![ContinuousDelivery::default()], diff --git a/harmony/src/modules/application/mod.rs b/harmony/src/modules/application/mod.rs index 1d9b8b0..3ed376d 100644 --- a/harmony/src/modules/application/mod.rs +++ b/harmony/src/modules/application/mod.rs @@ -91,7 +91,7 @@ impl Clone for Box> { pub struct BackupFeature; #[async_trait] -impl ApplicationFeature for BackupFeature { +impl ApplicationFeature for BackupFeature { async fn ensure_installed(&self, _topology: &T) -> Result<(), String> { todo!() } diff --git a/harmony/src/modules/mod.rs b/harmony/src/modules/mod.rs index 465587b..8b5720e 100644 --- a/harmony/src/modules/mod.rs +++ b/harmony/src/modules/mod.rs @@ -1,3 +1,4 @@ +pub mod application; pub mod cert_manager; pub mod dhcp; pub mod dns; @@ -14,4 +15,3 @@ pub mod okd; pub mod opnsense; pub mod tenant; pub mod tftp; -pub mod application; -- 2.39.5 From b621a92c822c5ab1ab9f4e058c7a6ea8ac8920c2 Mon Sep 17 00:00:00 2001 From: Ian Letourneau Date: Tue, 1 Jul 2025 14:40:06 -0400 Subject: [PATCH 07/10] remove unused go application score --- harmony/src/modules/application/mod.rs | 18 ------------------ 1 file changed, 18 deletions(-) diff --git a/harmony/src/modules/application/mod.rs b/harmony/src/modules/application/mod.rs index 3ed376d..1e95d3f 100644 --- a/harmony/src/modules/application/mod.rs +++ b/harmony/src/modules/application/mod.rs @@ -13,24 +13,6 @@ use crate::{ topology::Topology, }; -#[derive(Clone, Serialize, Debug)] -pub struct GoApplicationConfig {} - -#[derive(Clone, Serialize, Debug)] -pub struct GoApplicationScore { - pub config: GoApplicationConfig, -} - -impl Score for GoApplicationScore { - fn create_interpret(&self) -> Box> { - todo!() - } - - fn name(&self) -> String { - todo!() - } -} - #[derive(Debug)] pub struct ApplicationInterpret { features: Vec>>, -- 2.39.5 From 702827c75399637ea6e41281935d14c62ef831dd Mon Sep 17 00:00:00 2001 From: Ian Letourneau Date: Tue, 1 Jul 2025 15:09:46 -0400 Subject: [PATCH 08/10] remove unused statements --- harmony/src/modules/application/mod.rs | 3 --- 1 file changed, 3 deletions(-) diff --git a/harmony/src/modules/application/mod.rs b/harmony/src/modules/application/mod.rs index 1e95d3f..da0a087 100644 --- a/harmony/src/modules/application/mod.rs +++ b/harmony/src/modules/application/mod.rs @@ -9,7 +9,6 @@ use crate::{ data::{Id, Version}, interpret::{Interpret, InterpretError, InterpretName, InterpretStatus, Outcome}, inventory::Inventory, - score::Score, topology::Topology, }; @@ -45,8 +44,6 @@ impl Interpret for ApplicationInterpret { } } -trait Application {} - /// An ApplicationFeature provided by harmony, such as Backups, Monitoring, MultisiteAvailability, /// ContinuousIntegration, ContinuousDelivery #[async_trait] -- 2.39.5 From f512d8307a1f61238a86c314e7e7538ecb039a8f Mon Sep 17 00:00:00 2001 From: Ian Letourneau Date: Tue, 1 Jul 2025 15:16:42 -0400 Subject: [PATCH 09/10] remove unused statements - again --- harmony/src/modules/application/mod.rs | 3 --- harmony_composer/src/main.rs | 1 - 2 files changed, 4 deletions(-) diff --git a/harmony/src/modules/application/mod.rs b/harmony/src/modules/application/mod.rs index da0a087..b00a938 100644 --- a/harmony/src/modules/application/mod.rs +++ b/harmony/src/modules/application/mod.rs @@ -66,9 +66,6 @@ impl Clone for Box> { } } -#[derive(Debug)] -pub struct BackupFeature; - #[async_trait] impl ApplicationFeature for BackupFeature { async fn ensure_installed(&self, _topology: &T) -> Result<(), String> { diff --git a/harmony_composer/src/main.rs b/harmony_composer/src/main.rs index 5bda800..0bdb7be 100644 --- a/harmony_composer/src/main.rs +++ b/harmony_composer/src/main.rs @@ -119,7 +119,6 @@ async fn main() { if args.prod { todo!("implement prod deployment"); } - println!("harmony_bin_path: {}", harmony_bin_path.display()); let deploy_output = Command::new(harmony_bin_path) .arg("-y") .arg("-a") -- 2.39.5 From eb27a712644c659cb083aa9957cac5df0b463dc2 Mon Sep 17 00:00:00 2001 From: Ian Letourneau Date: Tue, 1 Jul 2025 15:19:17 -0400 Subject: [PATCH 10/10] oops --- harmony/src/modules/application/mod.rs | 7 ------- 1 file changed, 7 deletions(-) diff --git a/harmony/src/modules/application/mod.rs b/harmony/src/modules/application/mod.rs index b00a938..2a3ee24 100644 --- a/harmony/src/modules/application/mod.rs +++ b/harmony/src/modules/application/mod.rs @@ -65,10 +65,3 @@ impl Clone for Box> { todo!() } } - -#[async_trait] -impl ApplicationFeature for BackupFeature { - async fn ensure_installed(&self, _topology: &T) -> Result<(), String> { - todo!() - } -} -- 2.39.5