feat: Application module architecture and placeholder features #70
| @ -10,11 +10,22 @@ use log::{debug, error, trace}; | |||||||
| use serde::de::DeserializeOwned; | use serde::de::DeserializeOwned; | ||||||
| use similar::TextDiff; | use similar::TextDiff; | ||||||
| 
 | 
 | ||||||
| #[derive(new)] | #[derive(new, Clone)] | ||||||
| pub struct K8sClient { | pub struct K8sClient { | ||||||
|     client: Client, |     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 { | impl K8sClient { | ||||||
|     pub async fn try_default() -> Result<Self, Error> { |     pub async fn try_default() -> Result<Self, Error> { | ||||||
|         Ok(Self { |         Ok(Self { | ||||||
|  | |||||||
| @ -3,6 +3,7 @@ use std::{process::Command, sync::Arc}; | |||||||
| use async_trait::async_trait; | use async_trait::async_trait; | ||||||
| use inquire::Confirm; | use inquire::Confirm; | ||||||
| use log::{debug, info, warn}; | use log::{debug, info, warn}; | ||||||
|  | use serde::Serialize; | ||||||
| use tokio::sync::OnceCell; | use tokio::sync::OnceCell; | ||||||
| 
 | 
 | ||||||
| use crate::{ | use crate::{ | ||||||
| @ -20,22 +21,24 @@ use super::{ | |||||||
|     tenant::{TenantConfig, TenantManager, k8s::K8sTenantManager}, |     tenant::{TenantConfig, TenantManager, k8s::K8sTenantManager}, | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
|  | #[derive(Clone, Debug)] | ||||||
| struct K8sState { | struct K8sState { | ||||||
|     client: Arc<K8sClient>, |     client: Arc<K8sClient>, | ||||||
|     _source: K8sSource, |     _source: K8sSource, | ||||||
|     message: String, |     message: String, | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| #[derive(Debug)] | #[derive(Debug, Clone)] | ||||||
| enum K8sSource { | enum K8sSource { | ||||||
|     LocalK3d, |     LocalK3d, | ||||||
|     Kubeconfig, |     Kubeconfig, | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | #[derive(Clone, Debug)] | ||||||
| pub struct K8sAnywhereTopology { | pub struct K8sAnywhereTopology { | ||||||
|     k8s_state: OnceCell<Option<K8sState>>, |     k8s_state: Arc<OnceCell<Option<K8sState>>>, | ||||||
|     tenant_manager: OnceCell<K8sTenantManager>, |     tenant_manager: Arc<OnceCell<K8sTenantManager>>, | ||||||
|     config: K8sAnywhereConfig, |     config: Arc<K8sAnywhereConfig>, | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| #[async_trait] | #[async_trait] | ||||||
| @ -55,20 +58,29 @@ impl K8sclient for K8sAnywhereTopology { | |||||||
|     } |     } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | impl Serialize for K8sAnywhereTopology { | ||||||
|  |     fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> | ||||||
|  |     where | ||||||
|  |         S: serde::Serializer, | ||||||
|  |     { | ||||||
|  |         todo!() | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
| impl K8sAnywhereTopology { | impl K8sAnywhereTopology { | ||||||
|     pub fn from_env() -> Self { |     pub fn from_env() -> Self { | ||||||
|         Self { |         Self { | ||||||
|             k8s_state: OnceCell::new(), |             k8s_state: Arc::new(OnceCell::new()), | ||||||
|             tenant_manager: OnceCell::new(), |             tenant_manager: Arc::new(OnceCell::new()), | ||||||
|             config: K8sAnywhereConfig::from_env(), |             config: Arc::new(K8sAnywhereConfig::from_env()), | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     pub fn with_config(config: K8sAnywhereConfig) -> Self { |     pub fn with_config(config: K8sAnywhereConfig) -> Self { | ||||||
|         Self { |         Self { | ||||||
|             k8s_state: OnceCell::new(), |             k8s_state: Arc::new(OnceCell::new()), | ||||||
|             tenant_manager: OnceCell::new(), |             tenant_manager: Arc::new(OnceCell::new()), | ||||||
|             config, |             config: Arc::new(config), | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
| @ -200,6 +212,7 @@ impl K8sAnywhereTopology { | |||||||
|     } |     } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | #[derive(Clone, Debug)] | ||||||
| pub struct K8sAnywhereConfig { | pub struct K8sAnywhereConfig { | ||||||
|     /// The path of the KUBECONFIG file that Harmony should use to interact with the Kubernetes
 |     /// The path of the KUBECONFIG file that Harmony should use to interact with the Kubernetes
 | ||||||
|     /// cluster
 |     /// cluster
 | ||||||
|  | |||||||
| @ -22,7 +22,7 @@ use serde_json::json; | |||||||
| 
 | 
 | ||||||
| use super::{TenantConfig, TenantManager}; | use super::{TenantConfig, TenantManager}; | ||||||
| 
 | 
 | ||||||
| #[derive(new)] | #[derive(new, Clone, Debug)] | ||||||
| pub struct K8sTenantManager { | pub struct K8sTenantManager { | ||||||
|     k8s_client: Arc<K8sClient>, |     k8s_client: Arc<K8sClient>, | ||||||
| } | } | ||||||
|  | |||||||
| @ -0,0 +1,42 @@ | |||||||
|  | use async_trait::async_trait; | ||||||
|  | use log::info; | ||||||
|  | 
 | ||||||
|  | use crate::{modules::application::ApplicationFeature, topology::Topology}; | ||||||
|  | 
 | ||||||
|  | /// 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,ignore
 | ||||||
|  | /// 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<T: Topology + 'static> ApplicationFeature<T> for ContinuousDelivery { | ||||||
|  |     async fn ensure_installed(&self, _topology: &T) -> Result<(), String> { | ||||||
|  |         info!("Installing ContinuousDelivery feature"); | ||||||
|  |         todo!() | ||||||
|  |     } | ||||||
|  | } | ||||||
							
								
								
									
										39
									
								
								harmony/src/modules/application/features/endpoint.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										39
									
								
								harmony/src/modules/application/features/endpoint.rs
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,39 @@ | |||||||
|  | use async_trait::async_trait; | ||||||
|  | use log::info; | ||||||
|  | 
 | ||||||
|  | use crate::{ | ||||||
|  |     modules::application::ApplicationFeature, | ||||||
|  |     topology::{K8sclient, Topology}, | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | #[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, | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /// For now we only suport K8s ingress, but we will support more stuff at some point
 | ||||||
|  | #[async_trait] | ||||||
|  | impl<T: Topology + K8sclient + 'static> ApplicationFeature<T> for PublicEndpoint { | ||||||
|  |     async fn ensure_installed(&self, _topology: &T) -> Result<(), String> { | ||||||
|  |         info!( | ||||||
|  |             "Making sure public endpoint is installed for port {}", | ||||||
|  |             self.application_port | ||||||
|  |         ); | ||||||
|  |         todo!() | ||||||
|  |     } | ||||||
|  | } | ||||||
							
								
								
									
										8
									
								
								harmony/src/modules/application/features/mod.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								harmony/src/modules/application/features/mod.rs
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,8 @@ | |||||||
|  | mod endpoint; | ||||||
|  | pub use endpoint::*; | ||||||
|  | 
 | ||||||
|  | mod monitoring; | ||||||
|  | pub use monitoring::*; | ||||||
|  | 
 | ||||||
|  | mod continuous_delivery; | ||||||
|  | pub use continuous_delivery::*; | ||||||
							
								
								
									
										18
									
								
								harmony/src/modules/application/features/monitoring.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										18
									
								
								harmony/src/modules/application/features/monitoring.rs
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,18 @@ | |||||||
|  | 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<T: Topology + HelmCommand + 'static> ApplicationFeature<T> 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") | ||||||
|  |     } | ||||||
|  | } | ||||||
							
								
								
									
										67
									
								
								harmony/src/modules/application/mod.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										67
									
								
								harmony/src/modules/application/mod.rs
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,67 @@ | |||||||
|  | pub mod features; | ||||||
|  | mod rust; | ||||||
|  | pub use rust::*; | ||||||
|  | 
 | ||||||
|  | use async_trait::async_trait; | ||||||
|  | use serde::Serialize; | ||||||
|  | 
 | ||||||
|  | use crate::{ | ||||||
|  |     data::{Id, Version}, | ||||||
|  |     interpret::{Interpret, InterpretError, InterpretName, InterpretStatus, Outcome}, | ||||||
|  |     inventory::Inventory, | ||||||
|  |     topology::Topology, | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | #[derive(Debug)] | ||||||
|  | pub struct ApplicationInterpret<T: Topology + std::fmt::Debug> { | ||||||
|  |     features: Vec<Box<dyn ApplicationFeature<T>>>, | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | #[async_trait] | ||||||
|  | impl<T: Topology + std::fmt::Debug> Interpret<T> for ApplicationInterpret<T> { | ||||||
|  |     async fn execute( | ||||||
|  |         &self, | ||||||
|  |         _inventory: &Inventory, | ||||||
|  |         _topology: &T, | ||||||
|  |     ) -> Result<Outcome, InterpretError> { | ||||||
|  |         todo!() | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     fn get_name(&self) -> InterpretName { | ||||||
|  |         todo!() | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     fn get_version(&self) -> Version { | ||||||
|  |         todo!() | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     fn get_status(&self) -> InterpretStatus { | ||||||
|  |         todo!() | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     fn get_children(&self) -> Vec<Id> { | ||||||
|  |         todo!() | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /// An ApplicationFeature provided by harmony, such as Backups, Monitoring, MultisiteAvailability,
 | ||||||
|  | /// ContinuousIntegration, ContinuousDelivery
 | ||||||
|  | #[async_trait] | ||||||
|  | pub trait ApplicationFeature<T: Topology>: std::fmt::Debug + Send + Sync { | ||||||
|  |     async fn ensure_installed(&self, topology: &T) -> Result<(), String>; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | impl<T: Topology> Serialize for Box<dyn ApplicationFeature<T>> { | ||||||
|  |     fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> | ||||||
|  |     where | ||||||
|  |         S: serde::Serializer, | ||||||
|  |     { | ||||||
|  |         todo!() | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | impl<T: Topology> Clone for Box<dyn ApplicationFeature<T>> { | ||||||
|  |     fn clone(&self) -> Self { | ||||||
|  |         todo!() | ||||||
|  |     } | ||||||
|  | } | ||||||
							
								
								
									
										25
									
								
								harmony/src/modules/application/rust.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										25
									
								
								harmony/src/modules/application/rust.rs
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,25 @@ | |||||||
|  | use serde::Serialize; | ||||||
|  | 
 | ||||||
|  | use crate::{ | ||||||
|  |     score::Score, | ||||||
|  |     topology::{Topology, Url}, | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | use super::{ApplicationFeature, ApplicationInterpret}; | ||||||
|  | 
 | ||||||
|  | #[derive(Debug, Serialize, Clone)] | ||||||
|  | pub struct RustWebappScore<T: Topology + Clone + Serialize> { | ||||||
|  |     pub name: String, | ||||||
|  |     pub domain: Url, | ||||||
|  |     pub features: Vec<Box<dyn ApplicationFeature<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>> { | ||||||
|  |         Box::new(ApplicationInterpret { features: todo!() }) | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     fn name(&self) -> String { | ||||||
|  |         format!("{}-RustWebapp", self.name) | ||||||
|  |     } | ||||||
|  | } | ||||||
| @ -1,3 +1,4 @@ | |||||||
|  | pub mod application; | ||||||
| pub mod cert_manager; | pub mod cert_manager; | ||||||
| pub mod dhcp; | pub mod dhcp; | ||||||
| pub mod dns; | pub mod dns; | ||||||
|  | |||||||
| @ -12,15 +12,15 @@ use harmony_tui; | |||||||
| pub struct Args { | pub struct Args { | ||||||
|     /// Run score(s) without prompt
 |     /// Run score(s) without prompt
 | ||||||
|     #[arg(short, long, default_value_t = false, conflicts_with = "interactive")] |     #[arg(short, long, default_value_t = false, conflicts_with = "interactive")] | ||||||
|     yes: bool, |     pub yes: bool, | ||||||
| 
 | 
 | ||||||
|     /// Filter query
 |     /// Filter query
 | ||||||
|     #[arg(short, long, conflicts_with = "interactive")] |     #[arg(short, long, conflicts_with = "interactive")] | ||||||
|     filter: Option<String>, |     pub filter: Option<String>, | ||||||
| 
 | 
 | ||||||
|     /// Run interactive TUI or not
 |     /// Run interactive TUI or not
 | ||||||
|     #[arg(short, long, default_value_t = false)] |     #[arg(short, long, default_value_t = false)] | ||||||
|     interactive: bool, |     pub interactive: bool, | ||||||
| 
 | 
 | ||||||
|     /// Run all or nth, defaults to all
 |     /// Run all or nth, defaults to all
 | ||||||
|     #[arg(
 |     #[arg(
 | ||||||
| @ -31,15 +31,15 @@ pub struct Args { | |||||||
|         conflicts_with = "number", |         conflicts_with = "number", | ||||||
|         conflicts_with = "interactive" |         conflicts_with = "interactive" | ||||||
|     )] |     )] | ||||||
|     all: bool, |     pub all: bool, | ||||||
| 
 | 
 | ||||||
|     /// Run nth matching, zero indexed
 |     /// Run nth matching, zero indexed
 | ||||||
|     #[arg(short, long, default_value_t = 0, conflicts_with = "interactive")] |     #[arg(short, long, default_value_t = 0, conflicts_with = "interactive")] | ||||||
|     number: usize, |     pub number: usize, | ||||||
| 
 | 
 | ||||||
|     /// list scores, will also be affected by run filter
 |     /// list scores, will also be affected by run filter
 | ||||||
|     #[arg(short, long, default_value_t = false, conflicts_with = "interactive")] |     #[arg(short, long, default_value_t = false, conflicts_with = "interactive")] | ||||||
|     list: bool, |     pub list: bool, | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| fn maestro_scores_filter<T: Topology>( | fn maestro_scores_filter<T: Topology>( | ||||||
|  | |||||||
		Loading…
	
		Reference in New Issue
	
	Block a user