From a4aa685a4f14bcbd5f13845c2084eeedccdd787d Mon Sep 17 00:00:00 2001 From: Jean-Gabriel Gill-Couture Date: Wed, 2 Jul 2025 17:42:29 -0400 Subject: [PATCH] feat: harmony now defaults to using local k3d cluster. Also created `OCICompliant: Application` trait to make building images cleaner --- examples/rust/src/main.rs | 13 +++- harmony/src/domain/maestro/mod.rs | 2 +- harmony/src/domain/topology/k8s_anywhere.rs | 63 ++++++++++++------- harmony/src/modules/application/feature.rs | 2 + .../features/continuous_delivery.rs | 17 ++++- .../modules/application/features/endpoint.rs | 2 +- .../application/features/monitoring.rs | 2 +- harmony/src/modules/application/mod.rs | 6 +- harmony/src/modules/application/oci.rs | 8 +++ harmony/src/modules/application/rust.rs | 23 ++++--- 10 files changed, 98 insertions(+), 40 deletions(-) create mode 100644 harmony/src/modules/application/oci.rs diff --git a/examples/rust/src/main.rs b/examples/rust/src/main.rs index 28cd6f1..4ecf604 100644 --- a/examples/rust/src/main.rs +++ b/examples/rust/src/main.rs @@ -1,20 +1,27 @@ +use std::sync::Arc; + use harmony::{ inventory::Inventory, maestro::Maestro, - modules::application::{RustWebappScore, features::ContinuousDelivery}, + modules::application::{RustWebapp, RustWebappScore, features::ContinuousDelivery}, topology::{K8sAnywhereTopology, Url}, }; #[tokio::main] async fn main() { + env_logger::init(); + let application = RustWebapp { + name: "Example Harmony Rust Webapp".to_string(), + }; let app = RustWebappScore { name: "Example Rust Webapp".to_string(), domain: Url::Url(url::Url::parse("https://rustapp.harmony.example.com").unwrap()), - features: vec![Box::new(ContinuousDelivery {})], + features: vec![Box::new(ContinuousDelivery { application: Arc::new(application.clone()) })], + application, }; let topology = K8sAnywhereTopology::from_env(); - let mut maestro = Maestro::new(Inventory::autoload(), topology); + let mut maestro = Maestro::initialize(Inventory::autoload(), topology).await.unwrap(); maestro.register_all(vec![Box::new(app)]); harmony_cli::init(maestro, None).await.unwrap(); } diff --git a/harmony/src/domain/maestro/mod.rs b/harmony/src/domain/maestro/mod.rs index f53fbee..f28b1dd 100644 --- a/harmony/src/domain/maestro/mod.rs +++ b/harmony/src/domain/maestro/mod.rs @@ -19,7 +19,7 @@ pub struct Maestro { } impl Maestro { - pub fn new(inventory: Inventory, topology: T) -> Self { + fn new(inventory: Inventory, topology: T) -> Self { Self { inventory, topology, diff --git a/harmony/src/domain/topology/k8s_anywhere.rs b/harmony/src/domain/topology/k8s_anywhere.rs index 5eebd1d..1265de6 100644 --- a/harmony/src/domain/topology/k8s_anywhere.rs +++ b/harmony/src/domain/topology/k8s_anywhere.rs @@ -123,39 +123,47 @@ impl K8sAnywhereTopology { async fn try_get_or_install_k8s_client(&self) -> Result, InterpretError> { let k8s_anywhere_config = &self.config; - if let Some(kubeconfig) = &k8s_anywhere_config.kubeconfig { - debug!("Loading kubeconfig {kubeconfig}"); - match self.try_load_kubeconfig(&kubeconfig).await { - Some(client) => { - return Ok(Some(K8sState { - client: Arc::new(client), - _source: K8sSource::Kubeconfig, - message: format!("Loaded k8s client from kubeconfig {kubeconfig}"), - })); - } - None => { - return Err(InterpretError::new(format!( - "Failed to load kubeconfig from {kubeconfig}" - ))); + // TODO this deserves some refactoring, it is becoming a bit hard to figure out + // be careful when making modifications here + if k8s_anywhere_config.use_local_k3d { + info!("Using local k3d cluster because of use_local_k3d set to true"); + } else { + if let Some(kubeconfig) = &k8s_anywhere_config.kubeconfig { + debug!("Loading kubeconfig {kubeconfig}"); + match self.try_load_kubeconfig(&kubeconfig).await { + Some(client) => { + return Ok(Some(K8sState { + client: Arc::new(client), + _source: K8sSource::Kubeconfig, + message: format!("Loaded k8s client from kubeconfig {kubeconfig}"), + })); + } + None => { + return Err(InterpretError::new(format!( + "Failed to load kubeconfig from {kubeconfig}" + ))); + } } } - } - if k8s_anywhere_config.use_system_kubeconfig { - debug!("Loading system kubeconfig"); - match self.try_load_system_kubeconfig().await { - Some(_client) => todo!(), - None => todo!(), + if k8s_anywhere_config.use_system_kubeconfig { + debug!("Loading system kubeconfig"); + match self.try_load_system_kubeconfig().await { + Some(_client) => todo!(), + None => todo!(), + } } - } - info!("No kubernetes configuration found"); + info!("No kubernetes configuration found"); + } if !k8s_anywhere_config.autoinstall { + debug!("Autoinstall confirmation prompt"); let confirmation = Confirm::new( "Harmony autoinstallation is not activated, do you wish to launch autoinstallation? : ") .with_default(false) .prompt() .expect("Unexpected prompt error"); + debug!("Autoinstall confirmation {confirmation}"); if !confirmation { warn!( @@ -229,8 +237,15 @@ pub struct K8sAnywhereConfig { /// /// When enabled, autoinstall will setup a K3D cluster on the localhost. https://k3d.io/stable/ /// - /// Default: true + /// Default: false pub autoinstall: bool, + + /// Whether to use local k3d cluster. + /// + /// Takes precedence over other options, useful to avoid messing up a remote cluster by mistake + /// + /// default: true + pub use_local_k3d: bool, } impl K8sAnywhereConfig { @@ -241,6 +256,8 @@ impl K8sAnywhereConfig { .map_or_else(|_| false, |v| v.parse().ok().unwrap_or(false)), autoinstall: std::env::var("HARMONY_AUTOINSTALL") .map_or_else(|_| false, |v| v.parse().ok().unwrap_or(false)), + use_local_k3d: std::env::var("HARMONY_USE_LOCAL_K3D") + .map_or_else(|_| true, |v| v.parse().ok().unwrap_or(true)), } } } diff --git a/harmony/src/modules/application/feature.rs b/harmony/src/modules/application/feature.rs index 9c12553..260f818 100644 --- a/harmony/src/modules/application/feature.rs +++ b/harmony/src/modules/application/feature.rs @@ -2,6 +2,8 @@ use async_trait::async_trait; use serde::Serialize; use crate::topology::Topology; + +use super::Application; /// An ApplicationFeature provided by harmony, such as Backups, Monitoring, MultisiteAvailability, /// ContinuousIntegration, ContinuousDelivery #[async_trait] diff --git a/harmony/src/modules/application/features/continuous_delivery.rs b/harmony/src/modules/application/features/continuous_delivery.rs index e7dd968..fef126d 100644 --- a/harmony/src/modules/application/features/continuous_delivery.rs +++ b/harmony/src/modules/application/features/continuous_delivery.rs @@ -1,3 +1,5 @@ +use std::sync::Arc; + use async_trait::async_trait; use log::info; use serde_json::Value; @@ -5,7 +7,10 @@ use serde_json::Value; use crate::{ data::Version, inventory::Inventory, - modules::{application::ApplicationFeature, helm::chart::HelmChartScore}, + modules::{ + application::{Application, ApplicationFeature, OCICompliant}, + helm::chart::HelmChartScore, + }, score::Score, topology::{HelmCommand, Topology, Url}, }; @@ -38,11 +43,17 @@ use crate::{ /// - ArgoCD to install/upgrade/rollback/inspect k8s resources /// - Kubernetes for runtime orchestration #[derive(Debug, Default, Clone)] -pub struct ContinuousDelivery {} +pub struct ContinuousDelivery { + pub application: Arc, +} #[async_trait] -impl ApplicationFeature for ContinuousDelivery { +impl ApplicationFeature + for ContinuousDelivery +{ async fn ensure_installed(&self, topology: &T) -> Result<(), String> { + let image = self.application.build_push_oci_image().await?; + info!("Installing ContinuousDelivery feature"); let cd_server = HelmChartScore { namespace: todo!( diff --git a/harmony/src/modules/application/features/endpoint.rs b/harmony/src/modules/application/features/endpoint.rs index 042f0dd..d4bff3a 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::{Application, ApplicationFeature}, topology::{K8sclient, Topology}, }; diff --git a/harmony/src/modules/application/features/monitoring.rs b/harmony/src/modules/application/features/monitoring.rs index 33717a4..3bb7bae 100644 --- a/harmony/src/modules/application/features/monitoring.rs +++ b/harmony/src/modules/application/features/monitoring.rs @@ -2,7 +2,7 @@ use async_trait::async_trait; use log::info; use crate::{ - modules::application::ApplicationFeature, + modules::application::{Application, ApplicationFeature}, topology::{HelmCommand, Topology}, }; diff --git a/harmony/src/modules/application/mod.rs b/harmony/src/modules/application/mod.rs index 92669c0..9851e6e 100644 --- a/harmony/src/modules/application/mod.rs +++ b/harmony/src/modules/application/mod.rs @@ -1,6 +1,10 @@ mod feature; pub mod features; mod rust; +pub mod oci; +use std::sync::Arc; + +pub use oci::*; pub use feature::*; use log::info; pub use rust::*; @@ -21,7 +25,7 @@ pub trait Application: std::fmt::Debug + Send + Sync { #[derive(Debug)] pub struct ApplicationInterpret { features: Vec>>, - application: Box, + application: Arc>, } #[async_trait] diff --git a/harmony/src/modules/application/oci.rs b/harmony/src/modules/application/oci.rs new file mode 100644 index 0000000..4eb1d7e --- /dev/null +++ b/harmony/src/modules/application/oci.rs @@ -0,0 +1,8 @@ +use async_trait::async_trait; + +use super::Application; + +#[async_trait] +pub trait OCICompliant: Application { + async fn build_push_oci_image(&self) -> Result; // TODO consider using oci-spec and friends crates here +} diff --git a/harmony/src/modules/application/rust.rs b/harmony/src/modules/application/rust.rs index 43a4907..20b8c46 100644 --- a/harmony/src/modules/application/rust.rs +++ b/harmony/src/modules/application/rust.rs @@ -1,3 +1,6 @@ +use std::sync::Arc; + +use async_trait::async_trait; use serde::Serialize; use crate::{ @@ -5,22 +8,21 @@ use crate::{ topology::{Topology, Url}, }; -use super::{Application, ApplicationFeature, ApplicationInterpret}; +use super::{Application, ApplicationFeature, ApplicationInterpret, OCICompliant}; #[derive(Debug, Serialize, Clone)] pub struct RustWebappScore { pub name: String, pub domain: Url, pub features: Vec>>, + pub application: RustWebapp, } impl Score for RustWebappScore { fn create_interpret(&self) -> Box> { Box::new(ApplicationInterpret { features: self.features.clone(), - application: Box::new(RustWebapp { - name: self.name.clone(), - }), + application: Arc::new(Box::new(self.application.clone())), }) } @@ -29,9 +31,9 @@ impl Score for R } } -#[derive(Debug)] -struct RustWebapp { - name: String, +#[derive(Debug, Clone, Serialize)] +pub struct RustWebapp { + pub name: String, } impl Application for RustWebapp { @@ -39,3 +41,10 @@ impl Application for RustWebapp { self.name.clone() } } + +#[async_trait] +impl OCICompliant for RustWebapp { + async fn build_push_oci_image(&self) -> Result { + todo!() + } +}