feat: harmony now defaults to using local k3d cluster. Also created OCICompliant: Application trait to make building images cleaner
			#76
		
		
	| @ -1,20 +1,27 @@ | |||||||
|  | use std::sync::Arc; | ||||||
|  | 
 | ||||||
| use harmony::{ | use harmony::{ | ||||||
|     inventory::Inventory, |     inventory::Inventory, | ||||||
|     maestro::Maestro, |     maestro::Maestro, | ||||||
|     modules::application::{RustWebappScore, features::ContinuousDelivery}, |     modules::application::{RustWebapp, RustWebappScore, features::ContinuousDelivery}, | ||||||
|     topology::{K8sAnywhereTopology, Url}, |     topology::{K8sAnywhereTopology, Url}, | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| #[tokio::main] | #[tokio::main] | ||||||
| async fn main() { | async fn main() { | ||||||
|  |     env_logger::init(); | ||||||
|  |     let application = RustWebapp { | ||||||
|  |         name: "Example Harmony Rust Webapp".to_string(), | ||||||
|  |     }; | ||||||
|     let app = RustWebappScore { |     let app = RustWebappScore { | ||||||
|         name: "Example Rust Webapp".to_string(), |         name: "Example Rust Webapp".to_string(), | ||||||
|         domain: Url::Url(url::Url::parse("https://rustapp.harmony.example.com").unwrap()), |         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 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)]); |     maestro.register_all(vec![Box::new(app)]); | ||||||
|     harmony_cli::init(maestro, None).await.unwrap(); |     harmony_cli::init(maestro, None).await.unwrap(); | ||||||
| } | } | ||||||
|  | |||||||
| @ -19,7 +19,7 @@ pub struct Maestro<T: Topology> { | |||||||
| } | } | ||||||
| 
 | 
 | ||||||
| impl<T: Topology> Maestro<T> { | impl<T: Topology> Maestro<T> { | ||||||
|     pub fn new(inventory: Inventory, topology: T) -> Self { |     fn new(inventory: Inventory, topology: T) -> Self { | ||||||
|         Self { |         Self { | ||||||
|             inventory, |             inventory, | ||||||
|             topology, |             topology, | ||||||
|  | |||||||
|  | |||||||
| @ -123,39 +123,47 @@ impl K8sAnywhereTopology { | |||||||
|     async fn try_get_or_install_k8s_client(&self) -> Result<Option<K8sState>, InterpretError> { |     async fn try_get_or_install_k8s_client(&self) -> Result<Option<K8sState>, InterpretError> { | ||||||
|         let k8s_anywhere_config = &self.config; |         let k8s_anywhere_config = &self.config; | ||||||
| 
 | 
 | ||||||
|         if let Some(kubeconfig) = &k8s_anywhere_config.kubeconfig { |         // TODO this deserves some refactoring, it is becoming a bit hard to figure out
 | ||||||
|             debug!("Loading kubeconfig {kubeconfig}"); |         // be careful when making modifications here
 | ||||||
|             match self.try_load_kubeconfig(&kubeconfig).await { |         if k8s_anywhere_config.use_local_k3d { | ||||||
|                 Some(client) => { |             info!("Using local k3d cluster because of use_local_k3d set to true"); | ||||||
|                     return Ok(Some(K8sState { |         } else { | ||||||
|                         client: Arc::new(client), |             if let Some(kubeconfig) = &k8s_anywhere_config.kubeconfig { | ||||||
|                         _source: K8sSource::Kubeconfig, |                 debug!("Loading kubeconfig {kubeconfig}"); | ||||||
|                         message: format!("Loaded k8s client from kubeconfig {kubeconfig}"), |                 match self.try_load_kubeconfig(&kubeconfig).await { | ||||||
|                     })); |                     Some(client) => { | ||||||
|                 } |                         return Ok(Some(K8sState { | ||||||
|                 None => { |                             client: Arc::new(client), | ||||||
|                     return Err(InterpretError::new(format!( |                             _source: K8sSource::Kubeconfig, | ||||||
|                         "Failed to load kubeconfig from {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 { |             if k8s_anywhere_config.use_system_kubeconfig { | ||||||
|             debug!("Loading system kubeconfig"); |                 debug!("Loading system kubeconfig"); | ||||||
|             match self.try_load_system_kubeconfig().await { |                 match self.try_load_system_kubeconfig().await { | ||||||
|                 Some(_client) => todo!(), |                     Some(_client) => todo!(), | ||||||
|                 None => todo!(), |                     None => todo!(), | ||||||
|  |                 } | ||||||
|             } |             } | ||||||
|         } |  | ||||||
| 
 | 
 | ||||||
|         info!("No kubernetes configuration found"); |             info!("No kubernetes configuration found"); | ||||||
|  |         } | ||||||
| 
 | 
 | ||||||
|         if !k8s_anywhere_config.autoinstall { |         if !k8s_anywhere_config.autoinstall { | ||||||
|  |             debug!("Autoinstall confirmation prompt"); | ||||||
|             let confirmation = Confirm::new( "Harmony autoinstallation is not activated, do you wish to launch autoinstallation? : ") |             let confirmation = Confirm::new( "Harmony autoinstallation is not activated, do you wish to launch autoinstallation? : ") | ||||||
|                 .with_default(false) |                 .with_default(false) | ||||||
|                 .prompt() |                 .prompt() | ||||||
|                 .expect("Unexpected prompt error"); |                 .expect("Unexpected prompt error"); | ||||||
|  |             debug!("Autoinstall confirmation {confirmation}"); | ||||||
| 
 | 
 | ||||||
|             if !confirmation { |             if !confirmation { | ||||||
|                 warn!( |                 warn!( | ||||||
| @ -229,8 +237,15 @@ pub struct K8sAnywhereConfig { | |||||||
|     ///
 |     ///
 | ||||||
|     /// When enabled, autoinstall will setup a K3D cluster on the localhost. https://k3d.io/stable/
 |     /// When enabled, autoinstall will setup a K3D cluster on the localhost. https://k3d.io/stable/
 | ||||||
|     ///
 |     ///
 | ||||||
|     /// Default: true
 |     /// Default: false
 | ||||||
|     pub autoinstall: bool, |     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 { | impl K8sAnywhereConfig { | ||||||
| @ -241,6 +256,8 @@ impl K8sAnywhereConfig { | |||||||
|                 .map_or_else(|_| false, |v| v.parse().ok().unwrap_or(false)), |                 .map_or_else(|_| false, |v| v.parse().ok().unwrap_or(false)), | ||||||
|             autoinstall: std::env::var("HARMONY_AUTOINSTALL") |             autoinstall: std::env::var("HARMONY_AUTOINSTALL") | ||||||
|                 .map_or_else(|_| false, |v| v.parse().ok().unwrap_or(false)), |                 .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)), | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  | |||||||
| @ -2,6 +2,8 @@ use async_trait::async_trait; | |||||||
| use serde::Serialize; | use serde::Serialize; | ||||||
| 
 | 
 | ||||||
| use crate::topology::Topology; | use crate::topology::Topology; | ||||||
|  | 
 | ||||||
|  | use super::Application; | ||||||
| /// An ApplicationFeature provided by harmony, such as Backups, Monitoring, MultisiteAvailability,
 | /// An ApplicationFeature provided by harmony, such as Backups, Monitoring, MultisiteAvailability,
 | ||||||
| /// ContinuousIntegration, ContinuousDelivery
 | /// ContinuousIntegration, ContinuousDelivery
 | ||||||
| #[async_trait] | #[async_trait] | ||||||
|  | |||||||
| @ -1,3 +1,5 @@ | |||||||
|  | use std::sync::Arc; | ||||||
|  | 
 | ||||||
| use async_trait::async_trait; | use async_trait::async_trait; | ||||||
| use log::info; | use log::info; | ||||||
| use serde_json::Value; | use serde_json::Value; | ||||||
| @ -5,7 +7,10 @@ use serde_json::Value; | |||||||
| use crate::{ | use crate::{ | ||||||
|     data::Version, |     data::Version, | ||||||
|     inventory::Inventory, |     inventory::Inventory, | ||||||
|     modules::{application::ApplicationFeature, helm::chart::HelmChartScore}, |     modules::{ | ||||||
|  |         application::{Application, ApplicationFeature, OCICompliant}, | ||||||
|  |         helm::chart::HelmChartScore, | ||||||
|  |     }, | ||||||
|     score::Score, |     score::Score, | ||||||
|     topology::{HelmCommand, Topology, Url}, |     topology::{HelmCommand, Topology, Url}, | ||||||
| }; | }; | ||||||
| @ -38,11 +43,17 @@ use crate::{ | |||||||
| /// - ArgoCD to install/upgrade/rollback/inspect k8s resources
 | /// - ArgoCD to install/upgrade/rollback/inspect k8s resources
 | ||||||
| /// - Kubernetes for runtime orchestration
 | /// - Kubernetes for runtime orchestration
 | ||||||
| #[derive(Debug, Default, Clone)] | #[derive(Debug, Default, Clone)] | ||||||
| pub struct ContinuousDelivery {} | pub struct ContinuousDelivery<A: OCICompliant> { | ||||||
|  |     pub application: Arc<A>, | ||||||
|  | } | ||||||
| 
 | 
 | ||||||
| #[async_trait] | #[async_trait] | ||||||
| impl<T: Topology + HelmCommand + 'static> ApplicationFeature<T> for ContinuousDelivery { | impl<A: OCICompliant + Clone + 'static, T: Topology + HelmCommand + 'static> ApplicationFeature<T> | ||||||
|  |     for ContinuousDelivery<A> | ||||||
|  | { | ||||||
|     async fn ensure_installed(&self, topology: &T) -> Result<(), String> { |     async fn ensure_installed(&self, topology: &T) -> Result<(), String> { | ||||||
|  |         let image = self.application.build_push_oci_image().await?; | ||||||
|  | 
 | ||||||
|         info!("Installing ContinuousDelivery feature"); |         info!("Installing ContinuousDelivery feature"); | ||||||
|         let cd_server = HelmChartScore { |         let cd_server = HelmChartScore { | ||||||
|             namespace: todo!( |             namespace: todo!( | ||||||
|  | |||||||
| @ -2,7 +2,7 @@ use async_trait::async_trait; | |||||||
| use log::info; | use log::info; | ||||||
| 
 | 
 | ||||||
| use crate::{ | use crate::{ | ||||||
|     modules::application::ApplicationFeature, |     modules::application::{Application, ApplicationFeature}, | ||||||
|     topology::{K8sclient, Topology}, |     topology::{K8sclient, Topology}, | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -2,7 +2,7 @@ use async_trait::async_trait; | |||||||
| use log::info; | use log::info; | ||||||
| 
 | 
 | ||||||
| use crate::{ | use crate::{ | ||||||
|     modules::application::ApplicationFeature, |     modules::application::{Application, ApplicationFeature}, | ||||||
|     topology::{HelmCommand, Topology}, |     topology::{HelmCommand, Topology}, | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -1,6 +1,10 @@ | |||||||
| mod feature; | mod feature; | ||||||
| pub mod features; | pub mod features; | ||||||
| mod rust; | mod rust; | ||||||
|  | pub mod oci; | ||||||
|  | use std::sync::Arc; | ||||||
|  | 
 | ||||||
|  | pub use oci::*; | ||||||
| pub use feature::*; | pub use feature::*; | ||||||
| use log::info; | use log::info; | ||||||
| pub use rust::*; | pub use rust::*; | ||||||
| @ -21,7 +25,7 @@ pub trait Application: std::fmt::Debug + Send + Sync { | |||||||
| #[derive(Debug)] | #[derive(Debug)] | ||||||
| pub struct ApplicationInterpret<T: Topology + std::fmt::Debug> { | pub struct ApplicationInterpret<T: Topology + std::fmt::Debug> { | ||||||
|     features: Vec<Box<dyn ApplicationFeature<T>>>, |     features: Vec<Box<dyn ApplicationFeature<T>>>, | ||||||
|     application: Box<dyn Application>, |     application: Arc<Box<dyn Application>>, | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| #[async_trait] | #[async_trait] | ||||||
|  | |||||||
							
								
								
									
										8
									
								
								harmony/src/modules/application/oci.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								harmony/src/modules/application/oci.rs
									
									
									
									
									
										Normal file
									
								
							| @ -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<String, String>; // TODO consider using oci-spec and friends crates here
 | ||||||
|  | } | ||||||
| @ -1,3 +1,6 @@ | |||||||
|  | use std::sync::Arc; | ||||||
|  | 
 | ||||||
|  | use async_trait::async_trait; | ||||||
| use serde::Serialize; | use serde::Serialize; | ||||||
| 
 | 
 | ||||||
| use crate::{ | use crate::{ | ||||||
| @ -5,22 +8,21 @@ use crate::{ | |||||||
|     topology::{Topology, Url}, |     topology::{Topology, Url}, | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| use super::{Application, ApplicationFeature, ApplicationInterpret}; | use super::{Application, ApplicationFeature, ApplicationInterpret, OCICompliant}; | ||||||
| 
 | 
 | ||||||
| #[derive(Debug, Serialize, Clone)] | #[derive(Debug, Serialize, Clone)] | ||||||
| pub struct RustWebappScore<T: Topology + Clone + Serialize> { | pub struct RustWebappScore<T: Topology + Clone + Serialize> { | ||||||
|     pub name: String, |     pub name: String, | ||||||
|     pub domain: Url, |     pub domain: Url, | ||||||
|     pub features: Vec<Box<dyn ApplicationFeature<T>>>, |     pub features: Vec<Box<dyn ApplicationFeature<T>>>, | ||||||
|  |     pub application: RustWebapp, | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| impl<T: Topology + std::fmt::Debug + Clone + Serialize + 'static> Score<T> for RustWebappScore<T> { | impl<T: Topology + std::fmt::Debug + Clone + Serialize + 'static> Score<T> for RustWebappScore<T> { | ||||||
|     fn create_interpret(&self) -> Box<dyn crate::interpret::Interpret<T>> { |     fn create_interpret(&self) -> Box<dyn crate::interpret::Interpret<T>> { | ||||||
|         Box::new(ApplicationInterpret { |         Box::new(ApplicationInterpret { | ||||||
|             features: self.features.clone(), |             features: self.features.clone(), | ||||||
|             application: Box::new(RustWebapp { |             application: Arc::new(Box::new(self.application.clone())), | ||||||
|                 name: self.name.clone(), |  | ||||||
|             }), |  | ||||||
|         }) |         }) | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
| @ -29,9 +31,9 @@ impl<T: Topology + std::fmt::Debug + Clone + Serialize + 'static> Score<T> for R | |||||||
|     } |     } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| #[derive(Debug)] | #[derive(Debug, Clone, Serialize)] | ||||||
| struct RustWebapp { | pub struct RustWebapp { | ||||||
|     name: String, |     pub name: String, | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| impl Application for RustWebapp { | impl Application for RustWebapp { | ||||||
| @ -39,3 +41,10 @@ impl Application for RustWebapp { | |||||||
|         self.name.clone() |         self.name.clone() | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  | 
 | ||||||
|  | #[async_trait] | ||||||
|  | impl OCICompliant for RustWebapp { | ||||||
|  |     async fn build_push_oci_image(&self) -> Result<String, String> { | ||||||
|  |         todo!() | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | |||||||
		Loading…
	
		Reference in New Issue
	
	Block a user
	
usually we see
::emptyto initialize something as naked as possible, would that make more sense here?makes more sense than
::newfor sure.new_without_initializationI agree is a bit uncanny. But technically the Maestro is not anymore "empty" when created via new than initialize, the only difference is a side-effect happening in initialize where the topology is initialized.I'm very much open to better ideas, but I don't feel like
emptyis much better... Maestro is not a collection, it's more of an "engine". Maybe something along the idea of "idle" or "stale" but both those options feel less obvious to me than new_without_initialization. But that may be because I know the internals.