feat: Application module architecture and placeholder features #70
@ -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<Self, Error> {
|
||||
Ok(Self {
|
||||
|
||||
@ -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<K8sClient>,
|
||||
_source: K8sSource,
|
||||
message: String,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
#[derive(Debug, Clone)]
|
||||
enum K8sSource {
|
||||
LocalK3d,
|
||||
Kubeconfig,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct K8sAnywhereTopology {
|
||||
k8s_state: OnceCell<Option<K8sState>>,
|
||||
tenant_manager: OnceCell<K8sTenantManager>,
|
||||
config: K8sAnywhereConfig,
|
||||
k8s_state: Arc<OnceCell<Option<K8sState>>>,
|
||||
tenant_manager: Arc<OnceCell<K8sTenantManager>>,
|
||||
config: Arc<K8sAnywhereConfig>,
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
@ -55,20 +58,28 @@ 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 {
|
||||
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
|
||||
|
||||
@ -22,7 +22,7 @@ use serde_json::json;
|
||||
|
||||
use super::{TenantConfig, TenantManager};
|
||||
|
||||
#[derive(new)]
|
||||
#[derive(new, Clone, Debug)]
|
||||
pub struct K8sTenantManager {
|
||||
k8s_client: Arc<K8sClient>,
|
||||
}
|
||||
|
||||
@ -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 <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!()
|
||||
}
|
||||
|
||||
@ -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<box dyn Score<T>>` instead of `features:
|
||||
// Vec<box dyn ApplicationFeature>>` ?
|
||||
//
|
||||
// Let's unpack this :
|
||||
//
|
||||
// RustWebappScore<T: Topology> {
|
||||
// features: Vec<box dyn Score<T>>,
|
||||
// }
|
||||
//
|
||||
// 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<T: Topology + Clone + Serialize> {
|
||||
// features: Vec<box dyn Score<T>>,
|
||||
// }
|
||||
//
|
||||
// Oh right not quite because it is `dyn`.
|
||||
//
|
||||
|
||||
@ -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<T: Topology + 'static> ApplicationFeature<T> 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<bool, String> {
|
||||
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 <T: Topology + 'static> ApplicationFeature<T> for ContinuousDelivery {
|
||||
async fn ensure_installed(&self, _topology: &T) -> Result<(), String> {
|
||||
info!("Installing ContinuousDelivery feature");
|
||||
todo!()
|
||||
}
|
||||
|
||||
async fn is_installed(&self) -> Result<bool, String> {
|
||||
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 <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")
|
||||
}
|
||||
|
||||
async fn is_installed(&self) -> Result<bool, String> {
|
||||
todo!()
|
||||
}
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
mod rust;
|
||||
pub mod features;
|
||||
mod rust;
|
||||
pub use rust::*;
|
||||
|
||||
use async_trait::async_trait;
|
||||
@ -32,13 +32,17 @@ impl<T: Topology> Score<T> for GoApplicationScore {
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct ApplicationInterpret {
|
||||
features: Vec<Box<dyn ApplicationFeature>>,
|
||||
pub struct ApplicationInterpret<T: Topology + std::fmt::Debug> {
|
||||
features: Vec<Box<dyn ApplicationFeature<T>>>,
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl<T: Topology> Interpret<T> for ApplicationInterpret {
|
||||
async fn execute(&self, _inventory: &Inventory, _topology: &T) -> Result<Outcome, InterpretError> {
|
||||
impl<T: Topology + std::fmt::Debug> Interpret<T> for ApplicationInterpret<T> {
|
||||
async fn execute(
|
||||
&self,
|
||||
_inventory: &Inventory,
|
||||
_topology: &T,
|
||||
) -> Result<Outcome, InterpretError> {
|
||||
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<T: Topology>: std::fmt::Debug + Send + Sync {
|
||||
async fn ensure_installed(&self, topology: &T) -> Result<(), String>;
|
||||
|
letian marked this conversation as resolved
Outdated
|
||||
async fn is_installed(&self) -> Result<bool, String>;
|
||||
async fn uninstall(&self) -> Result<(), String>;
|
||||
|
letian marked this conversation as resolved
Outdated
letian
commented
`is_installed` and `uninstall` seem unnecessary for now
|
||||
}
|
||||
|
||||
impl Serialize for Box<dyn ApplicationFeature> {
|
||||
impl<T: Topology> Serialize for Box<dyn ApplicationFeature<T>> {
|
||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: serde::Serializer,
|
||||
@ -79,7 +83,7 @@ impl Serialize for Box<dyn ApplicationFeature> {
|
||||
}
|
||||
}
|
||||
|
||||
impl Clone for Box<dyn ApplicationFeature> {
|
||||
impl<T: Topology> Clone for Box<dyn ApplicationFeature<T>> {
|
||||
fn clone(&self) -> Self {
|
||||
todo!()
|
||||
}
|
||||
@ -89,8 +93,8 @@ impl Clone for Box<dyn ApplicationFeature> {
|
||||
pub struct BackupFeature;
|
||||
|
||||
#[async_trait]
|
||||
impl ApplicationFeature for BackupFeature {
|
||||
async fn ensure_installed(&self) -> Result<(), String> {
|
||||
impl <T: Topology > ApplicationFeature<T> for BackupFeature {
|
||||
async fn ensure_installed(&self, _topology: &T) -> Result<(), String> {
|
||||
todo!()
|
||||
}
|
||||
|
||||
|
||||
@ -8,13 +8,13 @@ use crate::{
|
||||
use super::{ApplicationFeature, ApplicationInterpret};
|
||||
|
||||
#[derive(Debug, Serialize, Clone)]
|
||||
pub struct RustWebappScore {
|
||||
pub struct RustWebappScore<T: Topology + Clone + Serialize> {
|
||||
pub name: String,
|
||||
pub domain: Url,
|
||||
pub features: Vec<Box<dyn ApplicationFeature>>,
|
||||
pub features: Vec<Box<dyn ApplicationFeature<T>>>,
|
||||
}
|
||||
|
||||
impl<T: Topology> Score<T> for RustWebappScore {
|
||||
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!() })
|
||||
}
|
||||
@ -23,4 +23,3 @@ impl<T: Topology> Score<T> for RustWebappScore {
|
||||
format!("{}-RustWebapp", self.name)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user
naming could be revisited, but good enough for now