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