Merge pull request 'feat: Introduce Application trait, not too sure how it will evolve but it makes sense, at the very least to identify the Application, also some minor refactoring' (#73) from feat/applicationTrait into master
Some checks failed
Run Check Script / check (push) Failing after 50s
Compile and package harmony_composer / package_harmony_composer (push) Has been cancelled

Reviewed-on: https://git.nationtech.io/NationTech/harmony/pulls/73
This commit is contained in:
johnride 2025-07-02 15:25:26 +00:00
commit 2ff3f4afa9
7 changed files with 107 additions and 46 deletions

View File

@ -21,6 +21,7 @@ pub enum InterpretName {
OPNSense, OPNSense,
K3dInstallation, K3dInstallation,
TenantInterpret, TenantInterpret,
Application,
} }
impl std::fmt::Display for InterpretName { impl std::fmt::Display for InterpretName {
@ -37,6 +38,7 @@ impl std::fmt::Display for InterpretName {
InterpretName::OPNSense => f.write_str("OPNSense"), InterpretName::OPNSense => f.write_str("OPNSense"),
InterpretName::K3dInstallation => f.write_str("K3dInstallation"), InterpretName::K3dInstallation => f.write_str("K3dInstallation"),
InterpretName::TenantInterpret => f.write_str("Tenant"), InterpretName::TenantInterpret => f.write_str("Tenant"),
InterpretName::Application => f.write_str("Application"),
} }
} }
} }

View File

@ -34,6 +34,17 @@ pub struct Inventory {
} }
impl Inventory { impl Inventory {
pub fn empty() -> Self {
Self {
location: Location::new("Empty".to_string(), "location".to_string()),
switch: vec![],
firewall: vec![],
worker_host: vec![],
storage_host: vec![],
control_plane_host: vec![],
}
}
pub fn autoload() -> Self { pub fn autoload() -> Self {
Self { Self {
location: Location::test_building(), location: Location::test_building(),

View File

@ -1,7 +1,14 @@
use async_trait::async_trait; use async_trait::async_trait;
use log::info; use log::info;
use serde_json::Value;
use crate::{modules::application::ApplicationFeature, topology::Topology}; use crate::{
data::Version,
inventory::Inventory,
modules::{application::ApplicationFeature, helm::chart::HelmChartScore},
score::Score,
topology::{HelmCommand, Topology, Url},
};
/// ContinuousDelivery in Harmony provides this functionality : /// ContinuousDelivery in Harmony provides this functionality :
/// ///
@ -34,9 +41,44 @@ use crate::{modules::application::ApplicationFeature, topology::Topology};
pub struct ContinuousDelivery {} pub struct ContinuousDelivery {}
#[async_trait] #[async_trait]
impl<T: Topology + 'static> ApplicationFeature<T> for ContinuousDelivery { impl<T: Topology + HelmCommand + 'static> ApplicationFeature<T> for ContinuousDelivery {
async fn ensure_installed(&self, _topology: &T) -> Result<(), String> { async fn ensure_installed(&self, topology: &T) -> Result<(), String> {
info!("Installing ContinuousDelivery feature"); info!("Installing ContinuousDelivery feature");
todo!() let cd_server = HelmChartScore {
namespace: todo!(
"ArgoCD Helm chart with proper understanding of Tenant, see how Will did it for Monitoring for now"
),
release_name: todo!("argocd helm chart whatever"),
chart_name: todo!(),
chart_version: todo!(),
values_overrides: todo!(),
values_yaml: todo!(),
create_namespace: todo!(),
install_only: todo!(),
repository: todo!(),
};
let interpret = cd_server.create_interpret();
interpret.execute(&Inventory::empty(), topology);
todo!("1. Create ArgoCD score that installs argo using helm chart, see if Taha's already done it
2. Package app (docker image, helm chart)
3. Push to registry if staging or prod
4. Poke Argo
5. Ensure app is up")
}
fn name(&self) -> String {
"ContinuousDelivery".to_string()
} }
} }
/// For now this is entirely bound to K8s / ArgoCD, will have to be revisited when we support
/// more CD systems
pub struct CDApplicationConfig {
version: Version,
helm_chart_url: Url,
values_overrides: Value,
}
pub trait ContinuousDeliveryApplication {
fn get_config(&self) -> CDApplicationConfig;
}

View File

@ -36,4 +36,7 @@ impl<T: Topology + K8sclient + 'static> ApplicationFeature<T> for PublicEndpoint
); );
todo!() todo!()
} }
fn name(&self) -> String {
"PublicEndpoint".to_string()
}
} }

View File

@ -15,4 +15,7 @@ impl<T: Topology + HelmCommand + 'static> ApplicationFeature<T> for Monitoring {
info!("Ensuring monitoring is available for application"); info!("Ensuring monitoring is available for application");
todo!("create and execute k8s prometheus score, depends on Will's work") todo!("create and execute k8s prometheus score, depends on Will's work")
} }
fn name(&self) -> String {
"Monitoring".to_string()
}
} }

View File

@ -1,9 +1,11 @@
mod feature;
pub mod features; pub mod features;
mod rust; mod rust;
pub use feature::*;
use log::info;
pub use rust::*; pub use rust::*;
use async_trait::async_trait; use async_trait::async_trait;
use serde::Serialize;
use crate::{ use crate::{
data::{Id, Version}, data::{Id, Version},
@ -12,9 +14,14 @@ use crate::{
topology::Topology, topology::Topology,
}; };
pub trait Application: std::fmt::Debug + Send + Sync {
fn name(&self) -> String;
}
#[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>,
} }
#[async_trait] #[async_trait]
@ -24,7 +31,21 @@ impl<T: Topology + std::fmt::Debug> Interpret<T> for ApplicationInterpret<T> {
_inventory: &Inventory, _inventory: &Inventory,
topology: &T, topology: &T,
) -> Result<Outcome, InterpretError> { ) -> Result<Outcome, InterpretError> {
let app_name = self.application.name();
info!(
"Preparing {} features [{}] for application {app_name}",
self.features.len(),
self.features
.iter()
.map(|f| f.name())
.collect::<Vec<String>>()
.join(", ")
);
for feature in self.features.iter() { for feature in self.features.iter() {
info!(
"Installing feature {} for application {app_name}",
feature.name()
);
let _ = match feature.ensure_installed(topology).await { let _ = match feature.ensure_installed(topology).await {
Ok(()) => (), Ok(()) => (),
Err(msg) => { Err(msg) => {
@ -34,15 +55,17 @@ impl<T: Topology + std::fmt::Debug> Interpret<T> for ApplicationInterpret<T> {
} }
}; };
} }
todo!("Do I need to do anything more than this here??") todo!(
"Do I need to do anything more than this here?? I feel like the Application trait itself should expose something like ensure_ready but its becoming redundant. We'll see as this evolves."
)
} }
fn get_name(&self) -> InterpretName { fn get_name(&self) -> InterpretName {
todo!() InterpretName::Application
} }
fn get_version(&self) -> Version { fn get_version(&self) -> Version {
todo!() Version::from("1.0.0").unwrap()
} }
fn get_status(&self) -> InterpretStatus { fn get_status(&self) -> InterpretStatus {
@ -53,40 +76,3 @@ impl<T: Topology + std::fmt::Debug> Interpret<T> for ApplicationInterpret<T> {
todo!() 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 + ApplicationFeatureClone<T>
{
async fn ensure_installed(&self, topology: &T) -> Result<(), String>;
}
trait ApplicationFeatureClone<T: Topology> {
fn clone_box(&self) -> Box<dyn ApplicationFeature<T>>;
}
impl<A, T: Topology> ApplicationFeatureClone<T> for A
where
A: ApplicationFeature<T> + Clone + 'static,
{
fn clone_box(&self) -> Box<dyn ApplicationFeature<T>> {
Box::new(self.clone())
}
}
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 {
self.clone_box()
}
}

View File

@ -5,7 +5,7 @@ use crate::{
topology::{Topology, Url}, topology::{Topology, Url},
}; };
use super::{ApplicationFeature, ApplicationInterpret, features::ContinuousDelivery}; use super::{Application, ApplicationFeature, ApplicationInterpret};
#[derive(Debug, Serialize, Clone)] #[derive(Debug, Serialize, Clone)]
pub struct RustWebappScore<T: Topology + Clone + Serialize> { pub struct RustWebappScore<T: Topology + Clone + Serialize> {
@ -18,6 +18,9 @@ impl<T: Topology + std::fmt::Debug + Clone + Serialize + 'static> Score<T> for R
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 {
name: self.name.clone(),
}),
}) })
} }
@ -25,3 +28,14 @@ impl<T: Topology + std::fmt::Debug + Clone + Serialize + 'static> Score<T> for R
format!("{}-RustWebapp", self.name) format!("{}-RustWebapp", self.name)
} }
} }
#[derive(Debug)]
struct RustWebapp {
name: String,
}
impl Application for RustWebapp {
fn name(&self) -> String {
self.name.clone()
}
}