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) } } -