diff --git a/Cargo.lock b/Cargo.lock index ae52051..69551f8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1181,6 +1181,16 @@ dependencies = [ "percent-encoding", ] +[[package]] +name = "fqdn" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0f5d7f7b3eed2f771fc7f6fcb651f9560d7b0c483d75876082acb4649d266b3" +dependencies = [ + "punycode", + "serde", +] + [[package]] name = "funty" version = "2.0.0" @@ -1407,6 +1417,7 @@ dependencies = [ "dockerfile_builder", "email_address", "env_logger", + "fqdn", "harmony_macros", "harmony_types", "helm-wrapper-rs", @@ -3026,6 +3037,12 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "punycode" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e9e1dcb320d6839f6edb64f7a4a59d39b30480d4d1765b56873f7c858538a5fe" + [[package]] name = "quote" version = "1.0.40" diff --git a/examples/lamp/src/main.rs b/examples/lamp/src/main.rs index 14180cc..9d486eb 100644 --- a/examples/lamp/src/main.rs +++ b/examples/lamp/src/main.rs @@ -3,7 +3,9 @@ use harmony::{ inventory::Inventory, maestro::Maestro, modules::{ + { lamp::{LAMPConfig, LAMPScore}, + }, monitoring::monitoring_alerting::MonitoringAlertingStackScore, }, topology::{K8sAnywhereTopology, Url}, diff --git a/harmony/Cargo.toml b/harmony/Cargo.toml index cb9b001..70cf00a 100644 --- a/harmony/Cargo.toml +++ b/harmony/Cargo.toml @@ -40,3 +40,11 @@ dockerfile_builder = "0.1.5" temp-file = "0.1.9" convert_case.workspace = true email_address = "0.2.9" +fqdn = { version = "0.4.6", features = [ + "domain-label-cannot-start-or-end-with-hyphen", + "domain-label-length-limited-to-63", + "domain-name-without-special-chars", + "domain-name-length-limited-to-255", + "punycode", + "serde", +] } diff --git a/harmony/src/modules/k8s/ingress.rs b/harmony/src/modules/k8s/ingress.rs new file mode 100644 index 0000000..883d721 --- /dev/null +++ b/harmony/src/modules/k8s/ingress.rs @@ -0,0 +1,98 @@ +use harmony_macros::ingress_path; +use k8s_openapi::api::networking::v1::Ingress; +use serde::Serialize; +use serde_json::json; + +use crate::{ + interpret::Interpret, + score::Score, + topology::{K8sclient, Topology}, +}; + +use super::resource::{K8sResourceInterpret, K8sResourceScore}; + +#[derive(Debug, Clone, Serialize)] +pub enum PathType { + ImplementationSpecific, + Exact, + Prefix, +} + +impl PathType { + fn as_str(&self) -> &'static str { + match self { + PathType::ImplementationSpecific => "ImplementationSpecific", + PathType::Exact => "Exact", + PathType::Prefix => "Prefix", + } + } +} + +type IngressPath = String; + +#[derive(Debug, Clone, Serialize)] +pub struct K8sIngressScore { + pub name: fqdn::FQDN, + pub host: fqdn::FQDN, + pub backend_service: fqdn::FQDN, + pub port: u16, + pub path: Option, + pub path_type: Option, + pub namespace: Option, +} + +impl Score for K8sIngressScore { + fn create_interpret(&self) -> Box> { + let path = match self.path.clone() { + Some(p) => p, + None => ingress_path!("/"), + }; + + let path_type = match self.path_type.clone() { + Some(p) => p, + None => PathType::Prefix, + }; + + let ingress = json!( + { + "metadata": { + "name": self.name + }, + "spec": { + "rules": [ + { "host": self.host, + "http": { + "paths": [ + { + "path": path, + "pathType": path_type.as_str(), + "backend": [ + { + "service": self.backend_service, + "port": self.port + } + ] + } + ] + } + } + ] + } + } + ); + + let ingress: Ingress = serde_json::from_value(ingress).unwrap(); + Box::new(K8sResourceInterpret { + score: K8sResourceScore::single( + ingress.clone(), + self.namespace + .clone() + .map(|f| f.as_c_str().to_str().unwrap().to_string()), + ), + }) + } + + fn name(&self) -> String { + format!("{} K8sIngressScore", self.name) + } +} diff --git a/harmony/src/modules/k8s/mod.rs b/harmony/src/modules/k8s/mod.rs index 97e238f..90781f9 100644 --- a/harmony/src/modules/k8s/mod.rs +++ b/harmony/src/modules/k8s/mod.rs @@ -1,3 +1,4 @@ pub mod deployment; +pub mod ingress; pub mod namespace; pub mod resource; diff --git a/harmony/src/modules/lamp.rs b/harmony/src/modules/lamp.rs index 637ba92..3c5c439 100644 --- a/harmony/src/modules/lamp.rs +++ b/harmony/src/modules/lamp.rs @@ -1,6 +1,8 @@ use convert_case::{Case, Casing}; use dockerfile_builder::instruction::{CMD, COPY, ENV, EXPOSE, FROM, RUN, WORKDIR}; use dockerfile_builder::{Dockerfile, instruction_builder::EnvBuilder}; +use fqdn::fqdn; +use harmony_macros::ingress_path; use non_blank_string_rs::NonBlankString; use serde_json::json; use std::collections::HashMap; @@ -13,6 +15,7 @@ use log::{debug, info}; use serde::Serialize; use crate::config::{REGISTRY_PROJECT, REGISTRY_URL}; +use crate::modules::k8s::ingress::K8sIngressScore; use crate::topology::HelmCommand; use crate::{ data::{Id, Version}, @@ -132,6 +135,29 @@ impl Interpret for LAMPInterpret { info!("LAMP deployment_score {deployment_score:?}"); + let lamp_ingress = K8sIngressScore { + name: fqdn!("lamp-ingress"), + host: fqdn!("test"), + backend_service: fqdn!( + >::name(&self.score) + .to_case(Case::Kebab) + .as_str() + ), + port: 8080, + path: Some(ingress_path!("/")), + path_type: None, + namespace: self + .get_namespace() + .map(|nbs| fqdn!(nbs.to_string().as_str())), + }; + + lamp_ingress + .create_interpret() + .execute(inventory, topology) + .await?; + + info!("LAMP lamp_ingress {lamp_ingress:?}"); + Ok(Outcome::success( "Successfully deployed LAMP Stack!".to_string(), )) diff --git a/harmony_macros/src/lib.rs b/harmony_macros/src/lib.rs index 2cc4c37..7e9ee47 100644 --- a/harmony_macros/src/lib.rs +++ b/harmony_macros/src/lib.rs @@ -116,3 +116,19 @@ pub fn yaml(input: TokenStream) -> TokenStream { } .into() } + +/// Verify that a string is a valid(ish) ingress path +/// Panics if path does not start with `/` +#[proc_macro] +pub fn ingress_path(input: TokenStream) -> TokenStream { + let input = parse_macro_input!(input as LitStr); + let path_str = input.value(); + + match path_str.starts_with("/") { + true => { + let expanded = quote! {(#path_str.to_string()) }; + return TokenStream::from(expanded); + } + false => panic!("Invalid ingress path"), + } +}