From 949c9a40bef575864c64c42a5e724e4b915eae08 Mon Sep 17 00:00:00 2001 From: wjro Date: Tue, 13 Jan 2026 12:09:56 -0500 Subject: [PATCH 01/13] feat: added cert manager capability as well as scores to install openshift subscription to community cert-manager operator --- examples/cert_manager/Cargo.toml | 19 ++++++ examples/cert_manager/src/main.rs | 26 ++++++++ .../topology/k8s_anywhere/k8s_anywhere.rs | 25 +++++++ .../src/modules/cert_manager/capability.rs | 18 +++++ harmony/src/modules/cert_manager/mod.rs | 3 + harmony/src/modules/cert_manager/operator.rs | 64 ++++++++++++++++++ harmony/src/modules/cert_manager/score_k8s.rs | 66 +++++++++++++++++++ 7 files changed, 221 insertions(+) create mode 100644 examples/cert_manager/Cargo.toml create mode 100644 examples/cert_manager/src/main.rs create mode 100644 harmony/src/modules/cert_manager/capability.rs create mode 100644 harmony/src/modules/cert_manager/operator.rs create mode 100644 harmony/src/modules/cert_manager/score_k8s.rs diff --git a/examples/cert_manager/Cargo.toml b/examples/cert_manager/Cargo.toml new file mode 100644 index 00000000..a67fcf83 --- /dev/null +++ b/examples/cert_manager/Cargo.toml @@ -0,0 +1,19 @@ +[package] +name = "cert_manager" +edition = "2024" +version.workspace = true +readme.workspace = true +license.workspace = true +publish = false + +[dependencies] +harmony = { path = "../../harmony" } +harmony_cli = { path = "../../harmony_cli" } +harmony_types = { path = "../../harmony_types" } +cidr = { workspace = true } +tokio = { workspace = true } +harmony_macros = { path = "../../harmony_macros" } +log = { workspace = true } +env_logger = { workspace = true } +url = { workspace = true } +assert_cmd = "2.0.16" diff --git a/examples/cert_manager/src/main.rs b/examples/cert_manager/src/main.rs new file mode 100644 index 00000000..ee0a2033 --- /dev/null +++ b/examples/cert_manager/src/main.rs @@ -0,0 +1,26 @@ +use harmony::{ + inventory::Inventory, + modules::{ + cert_manager::{ + capability::CertificateManagementConfig, score_k8s::CertificateManagementScore, + }, + postgresql::{PostgreSQLScore, capability::PostgreSQLConfig}, + }, + topology::K8sAnywhereTopology, +}; + +#[tokio::main] +async fn main() { + let cert_manager = CertificateManagementScore { + config: CertificateManagementConfig {}, + }; + + harmony_cli::run( + Inventory::autoload(), + K8sAnywhereTopology::from_env(), + vec![Box::new(cert_manager)], + None, + ) + .await + .unwrap(); +} diff --git a/harmony/src/domain/topology/k8s_anywhere/k8s_anywhere.rs b/harmony/src/domain/topology/k8s_anywhere/k8s_anywhere.rs index 22dfaad1..c259efff 100644 --- a/harmony/src/domain/topology/k8s_anywhere/k8s_anywhere.rs +++ b/harmony/src/domain/topology/k8s_anywhere/k8s_anywhere.rs @@ -17,6 +17,10 @@ use crate::{ interpret::InterpretStatus, inventory::Inventory, modules::{ + cert_manager::{ + capability::{CertificateManagement, CertificateManagementConfig}, + operator::CertManagerOperatorScore, + }, k3d::K3DInstallationScore, k8s::ingress::{K8sIngressScore, PathType}, monitoring::{ @@ -359,6 +363,27 @@ impl Serialize for K8sAnywhereTopology { } } +#[async_trait] +impl CertificateManagement for K8sAnywhereTopology { + async fn install( + &self, + config: &CertificateManagementConfig, + ) -> Result { + let cert_management_operator = CertManagerOperatorScore::default(); + + cert_management_operator + .interpret(&Inventory::empty(), self) + .await + .map_err(|e| PreparationError { msg: e.to_string() })?; + Ok(PreparationOutcome::Success { + details: format!( + "Installed cert-manager into ns: {}", + cert_management_operator.namespace + ), + }) + } +} + impl K8sAnywhereTopology { pub fn from_env() -> Self { Self { diff --git a/harmony/src/modules/cert_manager/capability.rs b/harmony/src/modules/cert_manager/capability.rs new file mode 100644 index 00000000..fffe5373 --- /dev/null +++ b/harmony/src/modules/cert_manager/capability.rs @@ -0,0 +1,18 @@ +use async_trait::async_trait; +use serde::Serialize; + +use crate::{ + interpret::Outcome, + topology::{PreparationError, PreparationOutcome}, +}; + +#[async_trait] +pub trait CertificateManagement: Send + Sync { + async fn install( + &self, + config: &CertificateManagementConfig, + ) -> Result; +} + +#[derive(Debug, Clone, Serialize)] +pub struct CertificateManagementConfig {} diff --git a/harmony/src/modules/cert_manager/mod.rs b/harmony/src/modules/cert_manager/mod.rs index 032439ea..3e4da4f3 100644 --- a/harmony/src/modules/cert_manager/mod.rs +++ b/harmony/src/modules/cert_manager/mod.rs @@ -1,3 +1,6 @@ +pub mod capability; pub mod cluster_issuer; mod helm; +pub mod operator; +pub mod score_k8s; pub use helm::*; diff --git a/harmony/src/modules/cert_manager/operator.rs b/harmony/src/modules/cert_manager/operator.rs new file mode 100644 index 00000000..e4112db2 --- /dev/null +++ b/harmony/src/modules/cert_manager/operator.rs @@ -0,0 +1,64 @@ +use kube::api::ObjectMeta; +use serde::Serialize; + +use crate::{ + interpret::Interpret, + modules::k8s::{ + apps::crd::{Subscription, SubscriptionSpec}, + resource::K8sResourceScore, + }, + score::Score, + topology::{K8sclient, Topology, k8s::K8sClient}, +}; + +/// Install the Cert-Manager Operator via RedHat Community Operators registry.redhat.io/redhat/community-operator-index:v4.19 +/// This Score creates a Subscription CR in the specified namespace + +#[derive(Debug, Clone, Serialize)] +pub struct CertManagerOperatorScore { + pub namespace: String, + pub channel: String, + pub install_plan_approval: String, + pub source: String, + pub source_namespace: String, +} + +impl Default for CertManagerOperatorScore { + fn default() -> Self { + Self { + namespace: "openshift-operators".to_string(), + channel: "stable".to_string(), + install_plan_approval: "Automatic".to_string(), + source: "community-operators".to_string(), + source_namespace: "openshift-marketplace".to_string(), + } + } +} + +impl Score for CertManagerOperatorScore { + fn name(&self) -> String { + "CertManagerOperatorScore".to_string() + } + + fn create_interpret(&self) -> Box> { + let metadata = ObjectMeta { + name: Some("cert-manager".to_string()), + namespace: Some(self.namespace.clone()), + ..ObjectMeta::default() + }; + + let spec = SubscriptionSpec { + channel: Some(self.channel.clone()), + config: None, + install_plan_approval: Some(self.install_plan_approval.clone()), + name: "cert-manager".to_string(), + source: self.source.clone(), + source_namespace: self.source_namespace.clone(), + starting_csv: None, + }; + + let subscription = Subscription { metadata, spec }; + + K8sResourceScore::single(subscription, Some(self.namespace.clone())).create_interpret() + } +} diff --git a/harmony/src/modules/cert_manager/score_k8s.rs b/harmony/src/modules/cert_manager/score_k8s.rs new file mode 100644 index 00000000..18e08264 --- /dev/null +++ b/harmony/src/modules/cert_manager/score_k8s.rs @@ -0,0 +1,66 @@ +use async_trait::async_trait; +use harmony_types::id::Id; +use serde::Serialize; + +use crate::{ + data::Version, + interpret::{Interpret, InterpretError, InterpretName, InterpretStatus, Outcome}, + inventory::Inventory, + modules::cert_manager::capability::{CertificateManagement, CertificateManagementConfig}, + score::Score, + topology::Topology, +}; + +#[derive(Debug, Clone, Serialize)] +pub struct CertificateManagementScore { + pub config: CertificateManagementConfig, +} + +impl Score for CertificateManagementScore { + fn name(&self) -> String { + "CertificateManagementScore".to_string() + } + + fn create_interpret(&self) -> Box> { + Box::new(CertificateManagementInterpret { + config: self.config.clone(), + }) + } +} + +#[derive(Debug)] +struct CertificateManagementInterpret { + config: CertificateManagementConfig, +} + +#[async_trait] +impl Interpret for CertificateManagementInterpret { + async fn execute( + &self, + inventory: &Inventory, + topology: &T, + ) -> Result { + let cert_management = topology + .install(&self.config) + .await + .map_err(|e| InterpretError::new(e.to_string()))?; + + Ok(Outcome::success(format!("Installed CertificateManagement"))) + } + + fn get_name(&self) -> InterpretName { + InterpretName::Custom("CertificateManagementInterpret") + } + + fn get_version(&self) -> Version { + todo!() + } + + fn get_status(&self) -> InterpretStatus { + todo!() + } + + fn get_children(&self) -> Vec { + todo!() + } +} -- 2.39.5 From 2153edc68ca4badef9d7f2801a5335583285c1be Mon Sep 17 00:00:00 2001 From: wjro Date: Tue, 13 Jan 2026 14:05:10 -0500 Subject: [PATCH 02/13] feat(cert-manager): added crds for cert-manager --- .../modules/cert_manager/crd/certificate.rs | 113 ++++++++++++++++++ .../cert_manager/crd/cluster_issuer.rs | 45 +++++++ .../src/modules/cert_manager/crd/issuer.rs | 44 +++++++ harmony/src/modules/cert_manager/crd/mod.rs | 63 ++++++++++ harmony/src/modules/cert_manager/mod.rs | 1 + 5 files changed, 266 insertions(+) create mode 100644 harmony/src/modules/cert_manager/crd/certificate.rs create mode 100644 harmony/src/modules/cert_manager/crd/cluster_issuer.rs create mode 100644 harmony/src/modules/cert_manager/crd/issuer.rs create mode 100644 harmony/src/modules/cert_manager/crd/mod.rs diff --git a/harmony/src/modules/cert_manager/crd/certificate.rs b/harmony/src/modules/cert_manager/crd/certificate.rs new file mode 100644 index 00000000..7c0866b7 --- /dev/null +++ b/harmony/src/modules/cert_manager/crd/certificate.rs @@ -0,0 +1,113 @@ +use kube::{CustomResource, api::ObjectMeta}; +use serde::{Deserialize, Serialize}; + +#[derive(CustomResource, Deserialize, Serialize, Clone, Debug)] +#[kube( + group = "cert-manager.io", + version = "v1", + kind = "Certificate", + plural = "certificates", + namespaced = true, + schema = "disabled" +)] +#[serde(rename_all = "camelCase")] +pub struct CertificateSpec { + /// Name of the Secret where the certificate will be stored + pub secret_name: String, + + /// Common Name (optional but often discouraged in favor of SANs) + #[serde(skip_serializing_if = "Option::is_none")] + pub common_name: Option, + + /// DNS Subject Alternative Names + #[serde(skip_serializing_if = "Option::is_none")] + pub dns_names: Option>, + + /// IP Subject Alternative Names + #[serde(skip_serializing_if = "Option::is_none")] + pub ip_addresses: Option>, + + /// Certificate duration (e.g. "2160h") + #[serde(skip_serializing_if = "Option::is_none")] + pub duration: Option, + + /// How long before expiry cert-manager should renew + #[serde(skip_serializing_if = "Option::is_none")] + pub renew_before: Option, + + /// Reference to the Issuer or ClusterIssuer + pub issuer_ref: IssuerRef, + + /// Is this a CA certificate + #[serde(skip_serializing_if = "Option::is_none")] + pub is_ca: Option, + + /// Private key configuration + #[serde(skip_serializing_if = "Option::is_none")] + pub private_key: Option, +} + +impl Default for Certificate { + fn default() -> Self { + Certificate { + metadata: ObjectMeta::default(), + spec: CertificateSpec::default(), + } + } +} + +impl Default for CertificateSpec { + fn default() -> Self { + Self { + secret_name: String::new(), + common_name: None, + dns_names: None, + ip_addresses: None, + duration: None, + renew_before: None, + issuer_ref: IssuerRef::default(), + is_ca: None, + private_key: None, + } + } +} + +#[derive(Deserialize, Serialize, Clone, Debug)] +#[serde(rename_all = "camelCase")] +pub struct IssuerRef { + pub name: String, + + /// Either "Issuer" or "ClusterIssuer" + #[serde(skip_serializing_if = "Option::is_none")] + pub kind: Option, + + #[serde(skip_serializing_if = "Option::is_none")] + pub group: Option, +} + +impl Default for IssuerRef { + fn default() -> Self { + Self { + name: String::new(), + kind: None, + group: None, + } + } +} + +#[derive(Deserialize, Serialize, Clone, Debug)] +#[serde(rename_all = "camelCase")] +pub struct PrivateKey { + /// RSA or ECDSA + #[serde(skip_serializing_if = "Option::is_none")] + pub algorithm: Option, + + /// Key size (e.g. 2048, 4096) + #[serde(skip_serializing_if = "Option::is_none")] + pub size: Option, + + /// Rotation policy: "Never" or "Always" + #[serde(skip_serializing_if = "Option::is_none")] + pub rotation_policy: Option, +} + diff --git a/harmony/src/modules/cert_manager/crd/cluster_issuer.rs b/harmony/src/modules/cert_manager/crd/cluster_issuer.rs new file mode 100644 index 00000000..49395489 --- /dev/null +++ b/harmony/src/modules/cert_manager/crd/cluster_issuer.rs @@ -0,0 +1,45 @@ +use kube::{CustomResource, api::ObjectMeta}; +use serde::{Deserialize, Serialize}; + +use crate::modules::cert_manager::crd::{AcmeIssuer, CaIssuer, SelfSignedIssuer}; + +#[derive(CustomResource, Deserialize, Serialize, Clone, Debug)] +#[kube( + group = "cert-manager.io", + version = "v1", + kind = "ClusterIssuer", + plural = "clusterissuers", + namespaced = false, + schema = "disabled" +)] +#[serde(rename_all = "camelCase")] +pub struct ClusterIssuerSpec { + #[serde(skip_serializing_if = "Option::is_none")] + pub self_signed: Option, + + #[serde(skip_serializing_if = "Option::is_none")] + pub ca: Option, + + #[serde(skip_serializing_if = "Option::is_none")] + pub acme: Option, +} + +impl Default for ClusterIssuer { + fn default() -> Self { + ClusterIssuer { + metadata: ObjectMeta::default(), + spec: ClusterIssuerSpec::default(), + } + } +} + +impl Default for ClusterIssuerSpec { + fn default() -> Self { + Self { + self_signed: None, + ca: None, + acme: None, + } + } +} + diff --git a/harmony/src/modules/cert_manager/crd/issuer.rs b/harmony/src/modules/cert_manager/crd/issuer.rs new file mode 100644 index 00000000..e4d57d33 --- /dev/null +++ b/harmony/src/modules/cert_manager/crd/issuer.rs @@ -0,0 +1,44 @@ +use kube::{CustomResource, api::ObjectMeta}; +use serde::{Deserialize, Serialize}; + +use crate::modules::cert_manager::crd::{AcmeIssuer, CaIssuer, SelfSignedIssuer}; + +#[derive(CustomResource, Deserialize, Serialize, Clone, Debug)] +#[kube( + group = "cert-manager.io", + version = "v1", + kind = "Issuer", + plural = "issuers", + namespaced = true, + schema = "disabled" +)] +#[serde(rename_all = "camelCase")] +pub struct IssuerSpec { + #[serde(skip_serializing_if = "Option::is_none")] + pub self_signed: Option, + + #[serde(skip_serializing_if = "Option::is_none")] + pub ca: Option, + + #[serde(skip_serializing_if = "Option::is_none")] + pub acme: Option, +} + +impl Default for Issuer { + fn default() -> Self { + Issuer { + metadata: ObjectMeta::default(), + spec: IssuerSpec::default(), + } + } +} + +impl Default for IssuerSpec { + fn default() -> Self { + Self { + self_signed: None, + ca: None, + acme: None, + } + } +} diff --git a/harmony/src/modules/cert_manager/crd/mod.rs b/harmony/src/modules/cert_manager/crd/mod.rs new file mode 100644 index 00000000..c9050b7d --- /dev/null +++ b/harmony/src/modules/cert_manager/crd/mod.rs @@ -0,0 +1,63 @@ +use serde::{Deserialize, Serialize}; + + +pub mod certificate; +pub mod issuer; +pub mod cluster_issuer; + +#[derive(Deserialize, Serialize, Clone, Debug)] +#[serde(rename_all = "camelCase")] +pub struct CaIssuer { + /// Secret containing `tls.crt` and `tls.key` + pub secret_name: String, +} + +#[derive(Deserialize, Serialize, Clone, Debug, Default)] +#[serde(rename_all = "camelCase")] +pub struct SelfSignedIssuer {} + +#[derive(Deserialize, Serialize, Clone, Debug)] +#[serde(rename_all = "camelCase")] +pub struct AcmeIssuer { + pub server: String, + pub email: String, + + /// Secret used to store the ACME account private key + pub private_key_secret_ref: SecretKeySelector, + + pub solvers: Vec, +} + +#[derive(Deserialize, Serialize, Clone, Debug)] +#[serde(rename_all = "camelCase")] +pub struct SecretKeySelector { + pub name: String, + pub key: String, +} + +#[derive(Deserialize, Serialize, Clone, Debug)] +#[serde(rename_all = "camelCase")] +pub struct AcmeSolver { + #[serde(skip_serializing_if = "Option::is_none")] + pub http01: Option, + + #[serde(skip_serializing_if = "Option::is_none")] + pub dns01: Option, +} + +#[derive(Deserialize, Serialize, Clone, Debug)] +#[serde(rename_all = "camelCase")] +pub struct Dns01Solver {} + +#[derive(Deserialize, Serialize, Clone, Debug)] +#[serde(rename_all = "camelCase")] +pub struct Http01Solver { + pub ingress: IngressSolver, +} + +#[derive(Deserialize, Serialize, Clone, Debug)] +#[serde(rename_all = "camelCase")] +pub struct IngressSolver { + #[serde(skip_serializing_if = "Option::is_none")] + pub class: Option, +} diff --git a/harmony/src/modules/cert_manager/mod.rs b/harmony/src/modules/cert_manager/mod.rs index 3e4da4f3..430252d2 100644 --- a/harmony/src/modules/cert_manager/mod.rs +++ b/harmony/src/modules/cert_manager/mod.rs @@ -3,4 +3,5 @@ pub mod cluster_issuer; mod helm; pub mod operator; pub mod score_k8s; +pub mod crd; pub use helm::*; -- 2.39.5 From dc421fa09974857101c7e986a2fe0140529d7679 Mon Sep 17 00:00:00 2001 From: wjro Date: Tue, 13 Jan 2026 15:43:58 -0500 Subject: [PATCH 03/13] wip: added scores and basic implentation to create certs and issuers --- examples/cert_manager/src/main.rs | 8 ++- .../topology/k8s_anywhere/k8s_anywhere.rs | 58 ++++++++++++++++++- .../src/modules/cert_manager/capability.rs | 40 +++++++++++-- harmony/src/modules/cert_manager/crd/mod.rs | 3 + .../cert_manager/crd/score_certificate.rs | 47 +++++++++++++++ .../cert_manager/crd/score_cluster_issuer.rs | 51 ++++++++++++++++ .../modules/cert_manager/crd/score_issuer.rs | 48 +++++++++++++++ 7 files changed, 248 insertions(+), 7 deletions(-) create mode 100644 harmony/src/modules/cert_manager/crd/score_certificate.rs create mode 100644 harmony/src/modules/cert_manager/crd/score_cluster_issuer.rs create mode 100644 harmony/src/modules/cert_manager/crd/score_issuer.rs diff --git a/examples/cert_manager/src/main.rs b/examples/cert_manager/src/main.rs index ee0a2033..df6484e2 100644 --- a/examples/cert_manager/src/main.rs +++ b/examples/cert_manager/src/main.rs @@ -12,7 +12,13 @@ use harmony::{ #[tokio::main] async fn main() { let cert_manager = CertificateManagementScore { - config: CertificateManagementConfig {}, + config: CertificateManagementConfig { + name: todo!(), + namespace: todo!(), + acme_issuer: todo!(), + ca_issuer: todo!(), + self_signed: todo!(), + }, }; harmony_cli::run( diff --git a/harmony/src/domain/topology/k8s_anywhere/k8s_anywhere.rs b/harmony/src/domain/topology/k8s_anywhere/k8s_anywhere.rs index c259efff..ca998477 100644 --- a/harmony/src/domain/topology/k8s_anywhere/k8s_anywhere.rs +++ b/harmony/src/domain/topology/k8s_anywhere/k8s_anywhere.rs @@ -18,7 +18,8 @@ use crate::{ inventory::Inventory, modules::{ cert_manager::{ - capability::{CertificateManagement, CertificateManagementConfig}, + capability::{CertificateManagement, CertificateManagementConfig, Issuer}, + crd::{score_certificate::CertificateScore, score_issuer::IssuerScore}, operator::CertManagerOperatorScore, }, k3d::K3DInstallationScore, @@ -382,6 +383,38 @@ impl CertificateManagement for K8sAnywhereTopology { ), }) } + + async fn ensure_ready( + &self, + config: &CertificateManagementConfig, + ) -> Result { + self.certificate_issuer_ready(Issuer::Issuer, config) + .await?; + Ok(PreparationOutcome::Success { + details: "issuer ready".to_string(), + }) + } + + async fn create_certificate( + &self, + cert_name: String, + config: &CertificateManagementConfig, + ) -> Result { + let cert = CertificateScore { + name: cert_name, + config: config.clone(), + }; + cert.interpret(&Inventory::empty(), self) + .await + .map_err(|e| PreparationError { msg: e.to_string() })?; + + Ok(PreparationOutcome::Success { + details: format!( + "Created cert into ns: {:#?}", + config.namespace.clone() + ), + }) + } } impl K8sAnywhereTopology { @@ -935,6 +968,29 @@ impl K8sAnywhereTopology { ), }) } + + async fn certificate_issuer_ready( + &self, + issuer: Issuer, + config: &CertificateManagementConfig, + ) -> Result { + match issuer { + Issuer::ClusterIssuer => todo!(), + + Issuer::Issuer => { + let issuer_score = IssuerScore { + config: config.clone(), + }; + issuer_score + .interpret(&Inventory::empty(), self) + .await + .map_err(|e| PreparationError { msg: e.to_string() })?; + Ok(PreparationOutcome::Success { + details: format!("issuer of kind {} is ready", issuer.to_string()), + }) + } + } + } } #[derive(Clone, Debug)] diff --git a/harmony/src/modules/cert_manager/capability.rs b/harmony/src/modules/cert_manager/capability.rs index fffe5373..bd0c921f 100644 --- a/harmony/src/modules/cert_manager/capability.rs +++ b/harmony/src/modules/cert_manager/capability.rs @@ -1,10 +1,7 @@ use async_trait::async_trait; use serde::Serialize; -use crate::{ - interpret::Outcome, - topology::{PreparationError, PreparationOutcome}, -}; +use crate::{modules::cert_manager::crd::{AcmeIssuer, CaIssuer}, topology::{PreparationError, PreparationOutcome}}; #[async_trait] pub trait CertificateManagement: Send + Sync { @@ -12,7 +9,40 @@ pub trait CertificateManagement: Send + Sync { &self, config: &CertificateManagementConfig, ) -> Result; + + async fn ensure_ready( + &self, + config: &CertificateManagementConfig, + ) -> Result; + + async fn create_certificate( + &self, + cert_name: String, + config: &CertificateManagementConfig, + ) -> Result; } #[derive(Debug, Clone, Serialize)] -pub struct CertificateManagementConfig {} +pub struct CertificateManagementConfig { + pub name: String, + pub namespace: Option, + pub acme_issuer: Option, + pub ca_issuer: Option, + pub self_signed: bool, +} + +#[derive(Serialize)] +pub enum Issuer { + ClusterIssuer, + Issuer, +} + +impl std::fmt::Display for Issuer { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Issuer::Issuer => f.write_str("Issuer"), + Issuer::ClusterIssuer => f.write_str("ClusterIssuer"), + } + } +} + diff --git a/harmony/src/modules/cert_manager/crd/mod.rs b/harmony/src/modules/cert_manager/crd/mod.rs index c9050b7d..70ea4693 100644 --- a/harmony/src/modules/cert_manager/crd/mod.rs +++ b/harmony/src/modules/cert_manager/crd/mod.rs @@ -4,6 +4,9 @@ use serde::{Deserialize, Serialize}; pub mod certificate; pub mod issuer; pub mod cluster_issuer; +//pub mod score_cluster_issuer; +pub mod score_issuer; +pub mod score_certificate; #[derive(Deserialize, Serialize, Clone, Debug)] #[serde(rename_all = "camelCase")] diff --git a/harmony/src/modules/cert_manager/crd/score_certificate.rs b/harmony/src/modules/cert_manager/crd/score_certificate.rs new file mode 100644 index 00000000..53c2a346 --- /dev/null +++ b/harmony/src/modules/cert_manager/crd/score_certificate.rs @@ -0,0 +1,47 @@ +use kube::api::ObjectMeta; +use serde::Serialize; + +use crate::{ + interpret::Interpret, + modules::{ + cert_manager::{ + capability::CertificateManagementConfig, + crd::certificate::{Certificate, CertificateSpec, IssuerRef}, + }, + k8s::resource::K8sResourceScore, + }, + score::Score, + topology::{K8sclient, Topology}, +}; + +#[derive(Debug, Clone, Serialize)] +pub struct CertificateScore { + pub name: String, + pub config: CertificateManagementConfig, +} + +impl Score for CertificateScore { + fn name(&self) -> String { + "CertificateScore".to_string() + } + + fn create_interpret(&self) -> Box> { + let cert = Certificate { + metadata: ObjectMeta { + name: Some(self.name.clone()), + namespace: self.config.namespace.clone(), + ..Default::default() + }, + spec: CertificateSpec { + secret_name: format!("{}-tls", self.name.clone()), + issuer_ref: IssuerRef { + name: self.config.name.clone(), + kind: Some("Issuer".into()), + group: Some("cert-manager.io".into()), + }, + ..Default::default() + }, + }; + K8sResourceScore::single(cert, self.config.namespace.clone()).create_interpret() + } +} diff --git a/harmony/src/modules/cert_manager/crd/score_cluster_issuer.rs b/harmony/src/modules/cert_manager/crd/score_cluster_issuer.rs new file mode 100644 index 00000000..5437fc7c --- /dev/null +++ b/harmony/src/modules/cert_manager/crd/score_cluster_issuer.rs @@ -0,0 +1,51 @@ +use kube::api::ObjectMeta; +use serde::Serialize; + +use crate::{ + interpret::Interpret, + modules::{ + cert_manager::crd::{ + AcmeIssuer, CaIssuer, SelfSignedIssuer, + cluster_issuer::{ClusterIssuer, ClusterIssuerSpec}, + }, + k8s::resource::K8sResourceScore, + }, + score::Score, + topology::{K8sclient, Topology}, +}; + +#[derive(Debug, Clone, Serialize)] +pub struct ClusterIssuerScore { + name: String, + acme_issuer: Option, + ca_issuer: Option, + self_signed: bool, +} + +impl Score for ClusterIssuerScore { + fn name(&self) -> String { + "ClusterIssuerScore".to_string() + } + + fn create_interpret(&self) -> Box> { + let metadata = ObjectMeta { + name: Some(self.name.clone()), + namespace: None, + ..ObjectMeta::default() + }; + + let spec = ClusterIssuerSpec { + acme: self.acme_issuer.clone(), + ca: self.ca_issuer.clone(), + self_signed: if self.self_signed { + Some(SelfSignedIssuer::default()) + } else { + None + }, + }; + + let cluster_issuer = ClusterIssuer { metadata, spec }; + + K8sResourceScore::single(cluster_issuer, None).create_interpret() + } +} diff --git a/harmony/src/modules/cert_manager/crd/score_issuer.rs b/harmony/src/modules/cert_manager/crd/score_issuer.rs new file mode 100644 index 00000000..05af62dd --- /dev/null +++ b/harmony/src/modules/cert_manager/crd/score_issuer.rs @@ -0,0 +1,48 @@ +use kube::api::ObjectMeta; +use serde::Serialize; + +use crate::{ + interpret::Interpret, + modules::{ + cert_manager::{capability::CertificateManagementConfig, crd::{ + AcmeIssuer, CaIssuer, SelfSignedIssuer, + issuer::{Issuer, IssuerSpec}, + }}, + k8s::resource::K8sResourceScore, + }, + score::Score, + topology::{K8sclient, Topology}, +}; + +#[derive(Debug, Clone, Serialize)] +pub struct IssuerScore { + pub config: CertificateManagementConfig, +} + +impl Score for IssuerScore { + fn name(&self) -> String { + "IssuerScore".to_string() + } + + fn create_interpret(&self) -> Box> { + let metadata = ObjectMeta { + name: Some(self.config.name.clone()), + namespace: self.config.namespace.clone(), + ..ObjectMeta::default() + }; + + let spec = IssuerSpec { + acme: self.config.acme_issuer.clone(), + ca: self.config.ca_issuer.clone(), + self_signed: if self.config.self_signed { + Some(SelfSignedIssuer::default()) + } else { + None + }, + }; + + let issuer = Issuer { metadata, spec }; + + K8sResourceScore::single(issuer, self.config.namespace.clone()).create_interpret() + } +} -- 2.39.5 From 25c5cd84fe0b65aa30b740cc7d62e0a9d1e497ec Mon Sep 17 00:00:00 2001 From: wjro Date: Wed, 14 Jan 2026 14:39:05 -0500 Subject: [PATCH 04/13] fix: added create_issuer fn to trait and its implementation is k8sanywhere --- .../topology/k8s_anywhere/k8s_anywhere.rs | 92 ++++++++++++------- .../src/modules/cert_manager/capability.rs | 29 +++--- .../modules/cert_manager/crd/certificate.rs | 1 - .../cert_manager/crd/cluster_issuer.rs | 1 - harmony/src/modules/cert_manager/crd/mod.rs | 5 +- .../cert_manager/crd/score_certificate.rs | 9 +- .../modules/cert_manager/crd/score_issuer.rs | 13 ++- harmony/src/modules/cert_manager/mod.rs | 2 +- 8 files changed, 88 insertions(+), 64 deletions(-) diff --git a/harmony/src/domain/topology/k8s_anywhere/k8s_anywhere.rs b/harmony/src/domain/topology/k8s_anywhere/k8s_anywhere.rs index ca998477..d6b86da0 100644 --- a/harmony/src/domain/topology/k8s_anywhere/k8s_anywhere.rs +++ b/harmony/src/domain/topology/k8s_anywhere/k8s_anywhere.rs @@ -18,7 +18,7 @@ use crate::{ inventory::Inventory, modules::{ cert_manager::{ - capability::{CertificateManagement, CertificateManagementConfig, Issuer}, + capability::{CertificateManagement, CertificateManagementConfig}, crd::{score_certificate::CertificateScore, score_issuer::IssuerScore}, operator::CertManagerOperatorScore, }, @@ -376,6 +376,7 @@ impl CertificateManagement for K8sAnywhereTopology { .interpret(&Inventory::empty(), self) .await .map_err(|e| PreparationError { msg: e.to_string() })?; + Ok(PreparationOutcome::Success { details: format!( "Installed cert-manager into ns: {}", @@ -388,31 +389,52 @@ impl CertificateManagement for K8sAnywhereTopology { &self, config: &CertificateManagementConfig, ) -> Result { - self.certificate_issuer_ready(Issuer::Issuer, config) - .await?; + todo!() + } + + async fn create_issuer( + &self, + issuer_name: String, + config: &CertificateManagementConfig, + ) -> Result { + let issuer_score = IssuerScore { + config: config.clone(), + }; + + issuer_score + .interpret(&Inventory::empty(), self) + .await + .map_err(|e| PreparationError { msg: e.to_string() })?; + Ok(PreparationOutcome::Success { - details: "issuer ready".to_string(), + details: format!("issuer of kind {} is ready", issuer_name), }) } async fn create_certificate( &self, cert_name: String, + issuer_name: String, config: &CertificateManagementConfig, ) -> Result { + self.certificate_issuer_ready( + issuer_name.clone(), + self.k8s_client().await.unwrap(), + config, + ) + .await?; + let cert = CertificateScore { - name: cert_name, + cert_name: cert_name, config: config.clone(), + issuer_name, }; cert.interpret(&Inventory::empty(), self) .await .map_err(|e| PreparationError { msg: e.to_string() })?; Ok(PreparationOutcome::Success { - details: format!( - "Created cert into ns: {:#?}", - config.namespace.clone() - ), + details: format!("Created cert into ns: {:#?}", config.namespace.clone()), }) } } @@ -436,6 +458,35 @@ impl K8sAnywhereTopology { } } + pub async fn certificate_issuer_ready( + &self, + issuer_name: String, + k8s_client: Arc, + config: &CertificateManagementConfig, + ) -> Result { + let ns = config.namespace.clone().ok_or_else(|| PreparationError { + msg: "namespace is required".to_string(), + })?; + + let gvk = GroupVersionKind { + group: "cert-manager.io".to_string(), + version: "v1".to_string(), + kind: "Issuer".to_string(), + }; + + match k8s_client + .get_resource_json_value(&issuer_name, Some(&ns), &gvk) + .await + { + Ok(_cert_issuer) => Ok(PreparationOutcome::Success { + details: format!("issuer of kind {} is ready", issuer_name), + }), + Err(e) => Err(PreparationError { + msg: format!("{} issuer {} not present", e.to_string(), issuer_name), + }), + } + } + pub async fn get_k8s_distribution(&self) -> Result<&KubernetesDistribution, PreparationError> { self.k8s_distribution .get_or_try_init(async || { @@ -968,29 +1019,6 @@ impl K8sAnywhereTopology { ), }) } - - async fn certificate_issuer_ready( - &self, - issuer: Issuer, - config: &CertificateManagementConfig, - ) -> Result { - match issuer { - Issuer::ClusterIssuer => todo!(), - - Issuer::Issuer => { - let issuer_score = IssuerScore { - config: config.clone(), - }; - issuer_score - .interpret(&Inventory::empty(), self) - .await - .map_err(|e| PreparationError { msg: e.to_string() })?; - Ok(PreparationOutcome::Success { - details: format!("issuer of kind {} is ready", issuer.to_string()), - }) - } - } - } } #[derive(Clone, Debug)] diff --git a/harmony/src/modules/cert_manager/capability.rs b/harmony/src/modules/cert_manager/capability.rs index bd0c921f..0c6940da 100644 --- a/harmony/src/modules/cert_manager/capability.rs +++ b/harmony/src/modules/cert_manager/capability.rs @@ -1,8 +1,12 @@ use async_trait::async_trait; use serde::Serialize; -use crate::{modules::cert_manager::crd::{AcmeIssuer, CaIssuer}, topology::{PreparationError, PreparationOutcome}}; +use crate::{ + modules::cert_manager::crd::{AcmeIssuer, CaIssuer}, + topology::{PreparationError, PreparationOutcome}, +}; +///TODO rust doc explaining issuer, certificate etc #[async_trait] pub trait CertificateManagement: Send + Sync { async fn install( @@ -15,9 +19,16 @@ pub trait CertificateManagement: Send + Sync { config: &CertificateManagementConfig, ) -> Result; + async fn create_issuer( + &self, + issuer_name: String, + config: &CertificateManagementConfig, + ) -> Result; + async fn create_certificate( &self, cert_name: String, + issuer_name: String, config: &CertificateManagementConfig, ) -> Result; } @@ -30,19 +41,3 @@ pub struct CertificateManagementConfig { pub ca_issuer: Option, pub self_signed: bool, } - -#[derive(Serialize)] -pub enum Issuer { - ClusterIssuer, - Issuer, -} - -impl std::fmt::Display for Issuer { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - Issuer::Issuer => f.write_str("Issuer"), - Issuer::ClusterIssuer => f.write_str("ClusterIssuer"), - } - } -} - diff --git a/harmony/src/modules/cert_manager/crd/certificate.rs b/harmony/src/modules/cert_manager/crd/certificate.rs index 7c0866b7..2375b0b2 100644 --- a/harmony/src/modules/cert_manager/crd/certificate.rs +++ b/harmony/src/modules/cert_manager/crd/certificate.rs @@ -110,4 +110,3 @@ pub struct PrivateKey { #[serde(skip_serializing_if = "Option::is_none")] pub rotation_policy: Option, } - diff --git a/harmony/src/modules/cert_manager/crd/cluster_issuer.rs b/harmony/src/modules/cert_manager/crd/cluster_issuer.rs index 49395489..0348972c 100644 --- a/harmony/src/modules/cert_manager/crd/cluster_issuer.rs +++ b/harmony/src/modules/cert_manager/crd/cluster_issuer.rs @@ -42,4 +42,3 @@ impl Default for ClusterIssuerSpec { } } } - diff --git a/harmony/src/modules/cert_manager/crd/mod.rs b/harmony/src/modules/cert_manager/crd/mod.rs index 70ea4693..7f8e6d1c 100644 --- a/harmony/src/modules/cert_manager/crd/mod.rs +++ b/harmony/src/modules/cert_manager/crd/mod.rs @@ -1,12 +1,11 @@ use serde::{Deserialize, Serialize}; - pub mod certificate; -pub mod issuer; pub mod cluster_issuer; +pub mod issuer; //pub mod score_cluster_issuer; -pub mod score_issuer; pub mod score_certificate; +pub mod score_issuer; #[derive(Deserialize, Serialize, Clone, Debug)] #[serde(rename_all = "camelCase")] diff --git a/harmony/src/modules/cert_manager/crd/score_certificate.rs b/harmony/src/modules/cert_manager/crd/score_certificate.rs index 53c2a346..9e512350 100644 --- a/harmony/src/modules/cert_manager/crd/score_certificate.rs +++ b/harmony/src/modules/cert_manager/crd/score_certificate.rs @@ -16,7 +16,8 @@ use crate::{ #[derive(Debug, Clone, Serialize)] pub struct CertificateScore { - pub name: String, + pub cert_name: String, + pub issuer_name: String, pub config: CertificateManagementConfig, } @@ -28,14 +29,14 @@ impl Score for CertificateScore { fn create_interpret(&self) -> Box> { let cert = Certificate { metadata: ObjectMeta { - name: Some(self.name.clone()), + name: Some(self.cert_name.clone()), namespace: self.config.namespace.clone(), ..Default::default() }, spec: CertificateSpec { - secret_name: format!("{}-tls", self.name.clone()), + secret_name: format!("{}-tls", self.cert_name.clone()), issuer_ref: IssuerRef { - name: self.config.name.clone(), + name: self.issuer_name.clone(), kind: Some("Issuer".into()), group: Some("cert-manager.io".into()), }, diff --git a/harmony/src/modules/cert_manager/crd/score_issuer.rs b/harmony/src/modules/cert_manager/crd/score_issuer.rs index 05af62dd..f30f155e 100644 --- a/harmony/src/modules/cert_manager/crd/score_issuer.rs +++ b/harmony/src/modules/cert_manager/crd/score_issuer.rs @@ -4,10 +4,13 @@ use serde::Serialize; use crate::{ interpret::Interpret, modules::{ - cert_manager::{capability::CertificateManagementConfig, crd::{ - AcmeIssuer, CaIssuer, SelfSignedIssuer, - issuer::{Issuer, IssuerSpec}, - }}, + cert_manager::{ + capability::CertificateManagementConfig, + crd::{ + SelfSignedIssuer, + issuer::{Issuer, IssuerSpec}, + }, + }, k8s::resource::K8sResourceScore, }, score::Score, @@ -26,7 +29,7 @@ impl Score for IssuerScore { fn create_interpret(&self) -> Box> { let metadata = ObjectMeta { - name: Some(self.config.name.clone()), + name: Some(format!("{}-issuer", self.config.namespace.clone().unwrap())), namespace: self.config.namespace.clone(), ..ObjectMeta::default() }; diff --git a/harmony/src/modules/cert_manager/mod.rs b/harmony/src/modules/cert_manager/mod.rs index 430252d2..9de3b9af 100644 --- a/harmony/src/modules/cert_manager/mod.rs +++ b/harmony/src/modules/cert_manager/mod.rs @@ -1,7 +1,7 @@ pub mod capability; pub mod cluster_issuer; +pub mod crd; mod helm; pub mod operator; pub mod score_k8s; -pub mod crd; pub use helm::*; -- 2.39.5 From f576effeca04591a43d3c55ce354a9c005dee567 Mon Sep 17 00:00:00 2001 From: wjro Date: Wed, 14 Jan 2026 16:18:59 -0500 Subject: [PATCH 05/13] feat: added working examples to add self signed issuer and self signed certificate. modified get_resource_json_value to be able to get cluster scoped operators --- examples/cert_manager/src/main.rs | 32 ++++++--- harmony/src/domain/topology/k8s.rs | 26 +++++-- .../topology/k8s_anywhere/k8s_anywhere.rs | 32 +++++++-- .../src/modules/cert_manager/capability.rs | 4 +- .../cert_manager/crd/score_certificate.rs | 1 + .../modules/cert_manager/crd/score_issuer.rs | 3 +- harmony/src/modules/cert_manager/mod.rs | 4 +- .../modules/cert_manager/score_create_cert.rs | 72 +++++++++++++++++++ .../cert_manager/score_create_issuer.rs | 66 +++++++++++++++++ .../{score_k8s.rs => score_operator.rs} | 7 +- 10 files changed, 217 insertions(+), 30 deletions(-) create mode 100644 harmony/src/modules/cert_manager/score_create_cert.rs create mode 100644 harmony/src/modules/cert_manager/score_create_issuer.rs rename harmony/src/modules/cert_manager/{score_k8s.rs => score_operator.rs} (90%) diff --git a/examples/cert_manager/src/main.rs b/examples/cert_manager/src/main.rs index df6484e2..03855940 100644 --- a/examples/cert_manager/src/main.rs +++ b/examples/cert_manager/src/main.rs @@ -2,7 +2,9 @@ use harmony::{ inventory::Inventory, modules::{ cert_manager::{ - capability::CertificateManagementConfig, score_k8s::CertificateManagementScore, + capability::CertificateManagementConfig, score_create_cert::CertificateCreationScore, + score_create_issuer::CertificateIssuerScore, + score_operator::CertificateManagementScore, }, postgresql::{PostgreSQLScore, capability::PostgreSQLConfig}, }, @@ -11,20 +13,32 @@ use harmony::{ #[tokio::main] async fn main() { + let config = CertificateManagementConfig { + namespace: Some("test".to_string()), + acme_issuer: None, + ca_issuer: None, + self_signed: true, + }; + let cert_manager = CertificateManagementScore { - config: CertificateManagementConfig { - name: todo!(), - namespace: todo!(), - acme_issuer: todo!(), - ca_issuer: todo!(), - self_signed: todo!(), - }, + config: config.clone(), + }; + + let issuer = CertificateIssuerScore { + config: config.clone(), + issuer_name: "test-self-signed-issuer".to_string(), + }; + + let cert = CertificateCreationScore { + config: config.clone(), + cert_name: "test-self-signed-cert".to_string(), + issuer_name: "test-self-signed-issuer".to_string(), }; harmony_cli::run( Inventory::autoload(), K8sAnywhereTopology::from_env(), - vec![Box::new(cert_manager)], + vec![Box::new(cert_manager), Box::new(issuer), Box::new(cert)], None, ) .await diff --git a/harmony/src/domain/topology/k8s.rs b/harmony/src/domain/topology/k8s.rs index ef34f3cf..bb786eb9 100644 --- a/harmony/src/domain/topology/k8s.rs +++ b/harmony/src/domain/topology/k8s.rs @@ -230,14 +230,26 @@ impl K8sClient { namespace: Option<&str>, gvk: &GroupVersionKind, ) -> Result { - let gvk = ApiResource::from_gvk(gvk); - let resource: Api = if let Some(ns) = namespace { - Api::namespaced_with(self.client.clone(), ns, &gvk) - } else { - Api::default_namespaced_with(self.client.clone(), &gvk) - }; + let api_resource = ApiResource::from_gvk(gvk); - resource.get(name).await + // 1. Try namespaced first (if a namespace was provided) + if let Some(ns) = namespace { + let api: Api = + Api::namespaced_with(self.client.clone(), ns, &api_resource); + + match api.get(name).await { + Ok(obj) => return Ok(obj), + Err(Error::Api(ae)) if ae.code == 404 => { + // fall through and try cluster-scoped + } + Err(e) => return Err(e), + } + } + + // 2. Fallback to cluster-scoped + let api: Api = Api::all_with(self.client.clone(), &api_resource); + + api.get(name).await } pub async fn get_secret_json_value( diff --git a/harmony/src/domain/topology/k8s_anywhere/k8s_anywhere.rs b/harmony/src/domain/topology/k8s_anywhere/k8s_anywhere.rs index d6b86da0..dc502367 100644 --- a/harmony/src/domain/topology/k8s_anywhere/k8s_anywhere.rs +++ b/harmony/src/domain/topology/k8s_anywhere/k8s_anywhere.rs @@ -366,10 +366,7 @@ impl Serialize for K8sAnywhereTopology { #[async_trait] impl CertificateManagement for K8sAnywhereTopology { - async fn install( - &self, - config: &CertificateManagementConfig, - ) -> Result { + async fn install(&self) -> Result { let cert_management_operator = CertManagerOperatorScore::default(); cert_management_operator @@ -385,11 +382,33 @@ impl CertificateManagement for K8sAnywhereTopology { }) } - async fn ensure_ready( + async fn ensure_certificate_management_ready( &self, config: &CertificateManagementConfig, ) -> Result { - todo!() + let k8s_client = self.k8s_client().await.unwrap(); + let gvk = GroupVersionKind { + group: "operators.coreos.com".to_string(), + version: "v1".to_string(), + kind: "Operator".to_string(), + }; + //TODO make this generic across k8s distributions using k8s family + match k8s_client + .get_resource_json_value( + "cert-manager.openshift-operators", + None, + &gvk, + ) + .await + { + Ok(_ready) => Ok(PreparationOutcome::Success { + details: "Certificate Management Ready".to_string(), + }), + Err(e) => { + debug!("{} operator not found", e.to_string()); + self.install().await + } + } } async fn create_issuer( @@ -398,6 +417,7 @@ impl CertificateManagement for K8sAnywhereTopology { config: &CertificateManagementConfig, ) -> Result { let issuer_score = IssuerScore { + issuer_name: issuer_name.clone(), config: config.clone(), }; diff --git a/harmony/src/modules/cert_manager/capability.rs b/harmony/src/modules/cert_manager/capability.rs index 0c6940da..0cad041d 100644 --- a/harmony/src/modules/cert_manager/capability.rs +++ b/harmony/src/modules/cert_manager/capability.rs @@ -11,10 +11,9 @@ use crate::{ pub trait CertificateManagement: Send + Sync { async fn install( &self, - config: &CertificateManagementConfig, ) -> Result; - async fn ensure_ready( + async fn ensure_certificate_management_ready( &self, config: &CertificateManagementConfig, ) -> Result; @@ -35,7 +34,6 @@ pub trait CertificateManagement: Send + Sync { #[derive(Debug, Clone, Serialize)] pub struct CertificateManagementConfig { - pub name: String, pub namespace: Option, pub acme_issuer: Option, pub ca_issuer: Option, diff --git a/harmony/src/modules/cert_manager/crd/score_certificate.rs b/harmony/src/modules/cert_manager/crd/score_certificate.rs index 9e512350..fb9a145a 100644 --- a/harmony/src/modules/cert_manager/crd/score_certificate.rs +++ b/harmony/src/modules/cert_manager/crd/score_certificate.rs @@ -40,6 +40,7 @@ impl Score for CertificateScore { kind: Some("Issuer".into()), group: Some("cert-manager.io".into()), }, + dns_names: Some(vec!["test.example.local".to_string()]), ..Default::default() }, }; diff --git a/harmony/src/modules/cert_manager/crd/score_issuer.rs b/harmony/src/modules/cert_manager/crd/score_issuer.rs index f30f155e..c858d6ef 100644 --- a/harmony/src/modules/cert_manager/crd/score_issuer.rs +++ b/harmony/src/modules/cert_manager/crd/score_issuer.rs @@ -19,6 +19,7 @@ use crate::{ #[derive(Debug, Clone, Serialize)] pub struct IssuerScore { + pub issuer_name: String, pub config: CertificateManagementConfig, } @@ -29,7 +30,7 @@ impl Score for IssuerScore { fn create_interpret(&self) -> Box> { let metadata = ObjectMeta { - name: Some(format!("{}-issuer", self.config.namespace.clone().unwrap())), + name: Some(self.issuer_name.clone()), namespace: self.config.namespace.clone(), ..ObjectMeta::default() }; diff --git a/harmony/src/modules/cert_manager/mod.rs b/harmony/src/modules/cert_manager/mod.rs index 9de3b9af..9b09876f 100644 --- a/harmony/src/modules/cert_manager/mod.rs +++ b/harmony/src/modules/cert_manager/mod.rs @@ -3,5 +3,7 @@ pub mod cluster_issuer; pub mod crd; mod helm; pub mod operator; -pub mod score_k8s; +pub mod score_operator; +pub mod score_create_cert; +pub mod score_create_issuer; pub use helm::*; diff --git a/harmony/src/modules/cert_manager/score_create_cert.rs b/harmony/src/modules/cert_manager/score_create_cert.rs new file mode 100644 index 00000000..ceb109d2 --- /dev/null +++ b/harmony/src/modules/cert_manager/score_create_cert.rs @@ -0,0 +1,72 @@ +use async_trait::async_trait; +use harmony_types::id::Id; +use serde::Serialize; + +use crate::{ + data::Version, + interpret::{Interpret, InterpretError, InterpretName, InterpretStatus, Outcome}, + inventory::Inventory, + modules::cert_manager::capability::{CertificateManagement, CertificateManagementConfig}, + score::Score, + topology::Topology, +}; + +#[derive(Debug, Clone, Serialize)] +pub struct CertificateCreationScore { + pub cert_name: String, + pub issuer_name: String, + pub config: CertificateManagementConfig, +} + +impl Score for CertificateCreationScore { + fn name(&self) -> String { + "CertificateCreationScore".to_string() + } + + fn create_interpret(&self) -> Box> { + Box::new(CertificateCreationInterpret { + cert_name: self.cert_name.clone(), + issuer_name: self.issuer_name.clone(), + config: self.config.clone(), + }) + } +} + +#[derive(Debug)] +struct CertificateCreationInterpret { + cert_name: String, + issuer_name: String, + config: CertificateManagementConfig, +} + +#[async_trait] +impl Interpret for CertificateCreationInterpret { + async fn execute( + &self, + inventory: &Inventory, + topology: &T, + ) -> Result { + let _certificate = topology + .create_certificate(self.cert_name.clone(), self.issuer_name.clone(), &self.config) + .await + .map_err(|e| InterpretError::new(e.to_string()))?; + + Ok(Outcome::success(format!("Installed CertificateManagement"))) + } + + fn get_name(&self) -> InterpretName { + InterpretName::Custom("CertificateManagementInterpret") + } + + fn get_version(&self) -> Version { + todo!() + } + + fn get_status(&self) -> InterpretStatus { + todo!() + } + + fn get_children(&self) -> Vec { + todo!() + } +} diff --git a/harmony/src/modules/cert_manager/score_create_issuer.rs b/harmony/src/modules/cert_manager/score_create_issuer.rs new file mode 100644 index 00000000..7ccc5482 --- /dev/null +++ b/harmony/src/modules/cert_manager/score_create_issuer.rs @@ -0,0 +1,66 @@ +use async_trait::async_trait; +use harmony_types::id::Id; +use log::debug; +use serde::Serialize; + +use crate::{data::Version, interpret::{Interpret, InterpretError, InterpretName, InterpretStatus, Outcome}, inventory::Inventory, modules::cert_manager::capability::{CertificateManagement, CertificateManagementConfig}, score::Score, topology::Topology}; + + + +#[derive(Debug, Clone, Serialize)] +pub struct CertificateIssuerScore { + pub issuer_name: String, + pub config: CertificateManagementConfig, +} + +impl Score for CertificateIssuerScore { + fn name(&self) -> String { + "CertificateIssuerScore".to_string() + } + + fn create_interpret(&self) -> Box> { + Box::new(CertificateIssuerInterpret { + config: self.config.clone(), + issuer_name: self.issuer_name.clone(), + }) + } +} + +#[derive(Debug)] +struct CertificateIssuerInterpret { + config: CertificateManagementConfig, + issuer_name: String, +} + +#[async_trait] +impl Interpret for CertificateIssuerInterpret { + async fn execute( + &self, + inventory: &Inventory, + topology: &T, + ) -> Result { + debug!("issuer name: {}", self.issuer_name.clone()); + let _cert_issuer = topology + .create_issuer(self.issuer_name.clone(), &self.config) + .await + .map_err(|e| InterpretError::new(e.to_string()))?; + + Ok(Outcome::success(format!("Installed CertificateManagement"))) + } + + fn get_name(&self) -> InterpretName { + InterpretName::Custom("CertificateManagementInterpret") + } + + fn get_version(&self) -> Version { + todo!() + } + + fn get_status(&self) -> InterpretStatus { + todo!() + } + + fn get_children(&self) -> Vec { + todo!() + } +} diff --git a/harmony/src/modules/cert_manager/score_k8s.rs b/harmony/src/modules/cert_manager/score_operator.rs similarity index 90% rename from harmony/src/modules/cert_manager/score_k8s.rs rename to harmony/src/modules/cert_manager/score_operator.rs index 18e08264..61c24560 100644 --- a/harmony/src/modules/cert_manager/score_k8s.rs +++ b/harmony/src/modules/cert_manager/score_operator.rs @@ -40,12 +40,13 @@ impl Interpret for CertificateManagement inventory: &Inventory, topology: &T, ) -> Result { - let cert_management = topology - .install(&self.config) + + topology + .ensure_certificate_management_ready(&self.config) .await .map_err(|e| InterpretError::new(e.to_string()))?; - Ok(Outcome::success(format!("Installed CertificateManagement"))) + Ok(Outcome::success(format!("CertificateManagement is ready"))) } fn get_name(&self) -> InterpretName { -- 2.39.5 From f319f74edf870c780aed76bbc864cfc10a0346e7 Mon Sep 17 00:00:00 2001 From: wjro Date: Wed, 14 Jan 2026 16:19:56 -0500 Subject: [PATCH 06/13] cargo fmt --- .../src/domain/topology/k8s_anywhere/k8s_anywhere.rs | 6 +----- harmony/src/modules/cert_manager/capability.rs | 4 +--- harmony/src/modules/cert_manager/mod.rs | 2 +- harmony/src/modules/cert_manager/score_create_cert.rs | 6 +++++- .../src/modules/cert_manager/score_create_issuer.rs | 11 ++++++++--- harmony/src/modules/cert_manager/score_operator.rs | 1 - 6 files changed, 16 insertions(+), 14 deletions(-) diff --git a/harmony/src/domain/topology/k8s_anywhere/k8s_anywhere.rs b/harmony/src/domain/topology/k8s_anywhere/k8s_anywhere.rs index dc502367..4760b2e6 100644 --- a/harmony/src/domain/topology/k8s_anywhere/k8s_anywhere.rs +++ b/harmony/src/domain/topology/k8s_anywhere/k8s_anywhere.rs @@ -394,11 +394,7 @@ impl CertificateManagement for K8sAnywhereTopology { }; //TODO make this generic across k8s distributions using k8s family match k8s_client - .get_resource_json_value( - "cert-manager.openshift-operators", - None, - &gvk, - ) + .get_resource_json_value("cert-manager.openshift-operators", None, &gvk) .await { Ok(_ready) => Ok(PreparationOutcome::Success { diff --git a/harmony/src/modules/cert_manager/capability.rs b/harmony/src/modules/cert_manager/capability.rs index 0cad041d..a2806601 100644 --- a/harmony/src/modules/cert_manager/capability.rs +++ b/harmony/src/modules/cert_manager/capability.rs @@ -9,9 +9,7 @@ use crate::{ ///TODO rust doc explaining issuer, certificate etc #[async_trait] pub trait CertificateManagement: Send + Sync { - async fn install( - &self, - ) -> Result; + async fn install(&self) -> Result; async fn ensure_certificate_management_ready( &self, diff --git a/harmony/src/modules/cert_manager/mod.rs b/harmony/src/modules/cert_manager/mod.rs index 9b09876f..d1ff660e 100644 --- a/harmony/src/modules/cert_manager/mod.rs +++ b/harmony/src/modules/cert_manager/mod.rs @@ -3,7 +3,7 @@ pub mod cluster_issuer; pub mod crd; mod helm; pub mod operator; -pub mod score_operator; pub mod score_create_cert; pub mod score_create_issuer; +pub mod score_operator; pub use helm::*; diff --git a/harmony/src/modules/cert_manager/score_create_cert.rs b/harmony/src/modules/cert_manager/score_create_cert.rs index ceb109d2..23e761b5 100644 --- a/harmony/src/modules/cert_manager/score_create_cert.rs +++ b/harmony/src/modules/cert_manager/score_create_cert.rs @@ -47,7 +47,11 @@ impl Interpret for CertificateCreationIn topology: &T, ) -> Result { let _certificate = topology - .create_certificate(self.cert_name.clone(), self.issuer_name.clone(), &self.config) + .create_certificate( + self.cert_name.clone(), + self.issuer_name.clone(), + &self.config, + ) .await .map_err(|e| InterpretError::new(e.to_string()))?; diff --git a/harmony/src/modules/cert_manager/score_create_issuer.rs b/harmony/src/modules/cert_manager/score_create_issuer.rs index 7ccc5482..447ddf67 100644 --- a/harmony/src/modules/cert_manager/score_create_issuer.rs +++ b/harmony/src/modules/cert_manager/score_create_issuer.rs @@ -3,9 +3,14 @@ use harmony_types::id::Id; use log::debug; use serde::Serialize; -use crate::{data::Version, interpret::{Interpret, InterpretError, InterpretName, InterpretStatus, Outcome}, inventory::Inventory, modules::cert_manager::capability::{CertificateManagement, CertificateManagementConfig}, score::Score, topology::Topology}; - - +use crate::{ + data::Version, + interpret::{Interpret, InterpretError, InterpretName, InterpretStatus, Outcome}, + inventory::Inventory, + modules::cert_manager::capability::{CertificateManagement, CertificateManagementConfig}, + score::Score, + topology::Topology, +}; #[derive(Debug, Clone, Serialize)] pub struct CertificateIssuerScore { diff --git a/harmony/src/modules/cert_manager/score_operator.rs b/harmony/src/modules/cert_manager/score_operator.rs index 61c24560..b13e0854 100644 --- a/harmony/src/modules/cert_manager/score_operator.rs +++ b/harmony/src/modules/cert_manager/score_operator.rs @@ -40,7 +40,6 @@ impl Interpret for CertificateManagement inventory: &Inventory, topology: &T, ) -> Result { - topology .ensure_certificate_management_ready(&self.config) .await -- 2.39.5 From 8f111bcb8b1413dcd102ac3700751228eb89ad59 Mon Sep 17 00:00:00 2001 From: wjro Date: Fri, 16 Jan 2026 13:16:06 -0500 Subject: [PATCH 07/13] feat: added fn get_ca_cert to trait certificateManagement --- .../topology/k8s_anywhere/k8s_anywhere.rs | 33 +++++++++++++++++++ .../src/modules/cert_manager/capability.rs | 6 ++++ 2 files changed, 39 insertions(+) diff --git a/harmony/src/domain/topology/k8s_anywhere/k8s_anywhere.rs b/harmony/src/domain/topology/k8s_anywhere/k8s_anywhere.rs index 4760b2e6..0476ac35 100644 --- a/harmony/src/domain/topology/k8s_anywhere/k8s_anywhere.rs +++ b/harmony/src/domain/topology/k8s_anywhere/k8s_anywhere.rs @@ -453,6 +453,39 @@ impl CertificateManagement for K8sAnywhereTopology { details: format!("Created cert into ns: {:#?}", config.namespace.clone()), }) } + + async fn get_ca_certificate( + &self, + cert_name: String, + config: &CertificateManagementConfig, + ) -> Result { + let namespace = config.namespace.clone().unwrap(); + + let client = self.k8s_client().await.unwrap(); + + let secret = client + .get_secret_json_value(&cert_name, Some(&namespace)) + .await? + .data; + + let ca_cert = secret + .get("data") + .ok_or_else(|| PreparationError { + msg: format!("failed to get data from secret {}", cert_name), + })? + .get("ca.crt") + .ok_or_else(|| PreparationError { + msg: format!("failed to get ca.crt from secret {}", cert_name), + })?; + + trace!("{:#?}", ca_cert.clone()); + + let cert: String = serde_json::from_value(ca_cert.clone()) + .map_err(|e| PreparationError { msg: e.to_string() })?; + + trace!("{:#?}", cert.clone()); + Ok(cert) + } } impl K8sAnywhereTopology { diff --git a/harmony/src/modules/cert_manager/capability.rs b/harmony/src/modules/cert_manager/capability.rs index a2806601..f39f6e6d 100644 --- a/harmony/src/modules/cert_manager/capability.rs +++ b/harmony/src/modules/cert_manager/capability.rs @@ -28,6 +28,12 @@ pub trait CertificateManagement: Send + Sync { issuer_name: String, config: &CertificateManagementConfig, ) -> Result; + + async fn get_ca_certificate( + &self, + cert_name: String, + config: &CertificateManagementConfig, + ) -> Result; } #[derive(Debug, Clone, Serialize)] -- 2.39.5 From c6642db6fb3c039e35778b05d9bfe5125fdc9a0a Mon Sep 17 00:00:00 2001 From: wjro Date: Fri, 16 Jan 2026 13:39:10 -0500 Subject: [PATCH 08/13] fix: modified k8sanywhere implentation of get_ca_cert to use the kubernetes certificate name to find its respective secret and ca.crt --- .../topology/k8s_anywhere/k8s_anywhere.rs | 41 +++++++++++++++---- 1 file changed, 33 insertions(+), 8 deletions(-) diff --git a/harmony/src/domain/topology/k8s_anywhere/k8s_anywhere.rs b/harmony/src/domain/topology/k8s_anywhere/k8s_anywhere.rs index 0476ac35..93570abe 100644 --- a/harmony/src/domain/topology/k8s_anywhere/k8s_anywhere.rs +++ b/harmony/src/domain/topology/k8s_anywhere/k8s_anywhere.rs @@ -460,31 +460,56 @@ impl CertificateManagement for K8sAnywhereTopology { config: &CertificateManagementConfig, ) -> Result { let namespace = config.namespace.clone().unwrap(); - + let certificate_gvk = GroupVersionKind { + group: "cert-manager.io".to_string(), + version: "v1".to_string(), + kind: "Certificate".to_string(), + }; let client = self.k8s_client().await.unwrap(); + let certificate_data = client + .get_resource_json_value(&cert_name, Some(&namespace), &certificate_gvk) + .await? + .data; + + trace!("Certificate Data {:#?}", certificate_data); + + let secret_name = certificate_data + .get("spec") + .ok_or_else(|| PreparationError { + msg: format!("failed to get spec from Certificate {}", cert_name), + })? + .get("secretName") + .ok_or_else(|| PreparationError { + msg: format!("failed to get secretName from Certificate {}", cert_name), + })?; + + trace!("Secret Name {:#?}", secret_name); + + let secret_name: String = serde_json::from_value(secret_name.clone()) + .map_err(|e| PreparationError { msg: e.to_string() })?; let secret = client - .get_secret_json_value(&cert_name, Some(&namespace)) + .get_secret_json_value(&secret_name, Some(&namespace)) .await? .data; let ca_cert = secret .get("data") .ok_or_else(|| PreparationError { - msg: format!("failed to get data from secret {}", cert_name), + msg: format!("failed to get data from secret {}", secret_name), })? .get("ca.crt") .ok_or_else(|| PreparationError { - msg: format!("failed to get ca.crt from secret {}", cert_name), + msg: format!("failed to get ca.crt from secret {}", secret_name), })?; - trace!("{:#?}", ca_cert.clone()); + trace!("ca.crt {:#?}", ca_cert.clone()); - let cert: String = serde_json::from_value(ca_cert.clone()) + let ca_cert: String = serde_json::from_value(ca_cert.clone()) .map_err(|e| PreparationError { msg: e.to_string() })?; - trace!("{:#?}", cert.clone()); - Ok(cert) + trace!("ca.crt string {:#?}", ca_cert.clone()); + Ok(ca_cert) } } -- 2.39.5 From 731d59c8b0e13c37d6d3bdea088b396e5241e62f Mon Sep 17 00:00:00 2001 From: wjro Date: Mon, 19 Jan 2026 11:37:47 -0500 Subject: [PATCH 09/13] fix: modified trait to use other return types, modified trait function name to be ensure ready, use rust CRD definitions rather than constructing gvk for certificateManagement trait function in k8sanywhere --- examples/cert_manager/src/main.rs | 10 +- .../topology/k8s_anywhere/k8s_anywhere.rs | 199 +++++++++--------- .../src/modules/cert_manager/capability.rs | 15 +- .../modules/cert_manager/score_create_cert.rs | 6 + .../modules/cert_manager/score_operator.rs | 3 +- 5 files changed, 118 insertions(+), 115 deletions(-) diff --git a/examples/cert_manager/src/main.rs b/examples/cert_manager/src/main.rs index 03855940..1d0c3acd 100644 --- a/examples/cert_manager/src/main.rs +++ b/examples/cert_manager/src/main.rs @@ -1,12 +1,8 @@ use harmony::{ inventory::Inventory, - modules::{ - cert_manager::{ - capability::CertificateManagementConfig, score_create_cert::CertificateCreationScore, - score_create_issuer::CertificateIssuerScore, - score_operator::CertificateManagementScore, - }, - postgresql::{PostgreSQLScore, capability::PostgreSQLConfig}, + modules::cert_manager::{ + capability::CertificateManagementConfig, score_create_cert::CertificateCreationScore, + score_create_issuer::CertificateIssuerScore, score_operator::CertificateManagementScore, }, topology::K8sAnywhereTopology, }; diff --git a/harmony/src/domain/topology/k8s_anywhere/k8s_anywhere.rs b/harmony/src/domain/topology/k8s_anywhere/k8s_anywhere.rs index 93570abe..ef480151 100644 --- a/harmony/src/domain/topology/k8s_anywhere/k8s_anywhere.rs +++ b/harmony/src/domain/topology/k8s_anywhere/k8s_anywhere.rs @@ -14,16 +14,24 @@ use tokio::sync::OnceCell; use crate::{ executors::ExecutorError, - interpret::InterpretStatus, + interpret::{InterpretStatus, Outcome}, inventory::Inventory, modules::{ cert_manager::{ capability::{CertificateManagement, CertificateManagementConfig}, - crd::{score_certificate::CertificateScore, score_issuer::IssuerScore}, + crd::{ + certificate::Certificate, + issuer::{Issuer, IssuerSpec}, + score_certificate::CertificateScore, + score_issuer::IssuerScore, + }, operator::CertManagerOperatorScore, }, k3d::K3DInstallationScore, - k8s::ingress::{K8sIngressScore, PathType}, + k8s::{ + apps::crd::Subscription, + ingress::{K8sIngressScore, PathType}, + }, monitoring::{ grafana::{grafana::Grafana, helm::helm_grafana::grafana_helm_chart_score}, kube_prometheus::crd::{ @@ -366,44 +374,36 @@ impl Serialize for K8sAnywhereTopology { #[async_trait] impl CertificateManagement for K8sAnywhereTopology { - async fn install(&self) -> Result { + async fn install(&self) -> Result { let cert_management_operator = CertManagerOperatorScore::default(); cert_management_operator .interpret(&Inventory::empty(), self) .await - .map_err(|e| PreparationError { msg: e.to_string() })?; + .map_err(|e| ExecutorError::UnexpectedError(e.to_string()))?; - Ok(PreparationOutcome::Success { - details: format!( - "Installed cert-manager into ns: {}", - cert_management_operator.namespace - ), - }) + Ok(Outcome::success(format!( + "Installed cert-manager into ns: {}", + cert_management_operator.namespace + ))) } - async fn ensure_certificate_management_ready( + async fn ensure_ready( &self, config: &CertificateManagementConfig, - ) -> Result { + ) -> Result { let k8s_client = self.k8s_client().await.unwrap(); - let gvk = GroupVersionKind { - group: "operators.coreos.com".to_string(), - version: "v1".to_string(), - kind: "Operator".to_string(), - }; - //TODO make this generic across k8s distributions using k8s family + match k8s_client - .get_resource_json_value("cert-manager.openshift-operators", None, &gvk) + .get_resource::("cert-manager", Some("openshift-operators")) .await + .map_err(|e| ExecutorError::UnexpectedError(format!("{}", e)))? { - Ok(_ready) => Ok(PreparationOutcome::Success { - details: "Certificate Management Ready".to_string(), - }), - Err(e) => { - debug!("{} operator not found", e.to_string()); - self.install().await + Some(subscription) => { + trace!("subscription {:#?}", subscription,); + Ok(Outcome::success(format!("Certificate Management Ready",))) } + None => self.install().await, } } @@ -411,7 +411,7 @@ impl CertificateManagement for K8sAnywhereTopology { &self, issuer_name: String, config: &CertificateManagementConfig, - ) -> Result { + ) -> Result { let issuer_score = IssuerScore { issuer_name: issuer_name.clone(), config: config.clone(), @@ -420,11 +420,12 @@ impl CertificateManagement for K8sAnywhereTopology { issuer_score .interpret(&Inventory::empty(), self) .await - .map_err(|e| PreparationError { msg: e.to_string() })?; + .map_err(|e| ExecutorError::UnexpectedError(e.to_string()))?; - Ok(PreparationOutcome::Success { - details: format!("issuer of kind {} is ready", issuer_name), - }) + Ok(Outcome::success(format!( + "issuer of kind {} is ready", + issuer_name + ))) } async fn create_certificate( @@ -432,7 +433,7 @@ impl CertificateManagement for K8sAnywhereTopology { cert_name: String, issuer_name: String, config: &CertificateManagementConfig, - ) -> Result { + ) -> Result { self.certificate_issuer_ready( issuer_name.clone(), self.k8s_client().await.unwrap(), @@ -447,69 +448,66 @@ impl CertificateManagement for K8sAnywhereTopology { }; cert.interpret(&Inventory::empty(), self) .await - .map_err(|e| PreparationError { msg: e.to_string() })?; + .map_err(|e| ExecutorError::UnexpectedError(e.to_string()))?; - Ok(PreparationOutcome::Success { - details: format!("Created cert into ns: {:#?}", config.namespace.clone()), - }) + Ok(Outcome::success(format!( + "Created cert into ns: {:#?}", + config.namespace.clone() + ))) } async fn get_ca_certificate( &self, cert_name: String, config: &CertificateManagementConfig, - ) -> Result { + ) -> Result { let namespace = config.namespace.clone().unwrap(); - let certificate_gvk = GroupVersionKind { - group: "cert-manager.io".to_string(), - version: "v1".to_string(), - kind: "Certificate".to_string(), - }; + let client = self.k8s_client().await.unwrap(); - let certificate_data = client - .get_resource_json_value(&cert_name, Some(&namespace), &certificate_gvk) - .await? - .data; - trace!("Certificate Data {:#?}", certificate_data); + if let Some(certificate) = client + .get_resource::(&cert_name, Some(&namespace)) + .await + .map_err(|e| ExecutorError::UnexpectedError(format!("{}", e)))? + { + let secret_name = certificate.spec.secret_name.clone(); - let secret_name = certificate_data - .get("spec") - .ok_or_else(|| PreparationError { - msg: format!("failed to get spec from Certificate {}", cert_name), - })? - .get("secretName") - .ok_or_else(|| PreparationError { - msg: format!("failed to get secretName from Certificate {}", cert_name), - })?; + trace!("Secret Name {:#?}", secret_name); + if let Some(secret) = client + .get_resource::(&secret_name, Some(&namespace)) + .await + .map_err(|e| { + ExecutorError::UnexpectedError(format!( + "secret {} not found in namespace {}: {}", + secret_name, namespace, e + )) + })? + { + let ca_cert = secret + .data + .as_ref() + .and_then(|d| d.get("ca.crt")) + .ok_or_else(|| { + ExecutorError::UnexpectedError("Secret missing key 'ca.crt'".into()) + })?; - trace!("Secret Name {:#?}", secret_name); + let ca_cert = String::from_utf8(ca_cert.0.clone()).map_err(|_| { + ExecutorError::UnexpectedError("ca.crt is not valid UTF-8".into()) + })?; - let secret_name: String = serde_json::from_value(secret_name.clone()) - .map_err(|e| PreparationError { msg: e.to_string() })?; - - let secret = client - .get_secret_json_value(&secret_name, Some(&namespace)) - .await? - .data; - - let ca_cert = secret - .get("data") - .ok_or_else(|| PreparationError { - msg: format!("failed to get data from secret {}", secret_name), - })? - .get("ca.crt") - .ok_or_else(|| PreparationError { - msg: format!("failed to get ca.crt from secret {}", secret_name), - })?; - - trace!("ca.crt {:#?}", ca_cert.clone()); - - let ca_cert: String = serde_json::from_value(ca_cert.clone()) - .map_err(|e| PreparationError { msg: e.to_string() })?; - - trace!("ca.crt string {:#?}", ca_cert.clone()); - Ok(ca_cert) + return Ok(ca_cert); + } else { + Err(ExecutorError::UnexpectedError(format!( + "Error getting secret associated with cert_name: {}", + cert_name + ))) + } + } else { + return Err(ExecutorError::UnexpectedError(format!( + "Certificate {} not found in namespace {}", + cert_name, namespace + ))); + } } } @@ -537,27 +535,30 @@ impl K8sAnywhereTopology { issuer_name: String, k8s_client: Arc, config: &CertificateManagementConfig, - ) -> Result { - let ns = config.namespace.clone().ok_or_else(|| PreparationError { - msg: "namespace is required".to_string(), - })?; - - let gvk = GroupVersionKind { - group: "cert-manager.io".to_string(), - version: "v1".to_string(), - kind: "Issuer".to_string(), - }; + ) -> Result { + let ns = config + .namespace + .clone() + .ok_or_else(|| ExecutorError::UnexpectedError("namespace is required".to_string()))?; match k8s_client - .get_resource_json_value(&issuer_name, Some(&ns), &gvk) + .get_resource::(&issuer_name, Some(&ns)) .await { - Ok(_cert_issuer) => Ok(PreparationOutcome::Success { - details: format!("issuer of kind {} is ready", issuer_name), - }), - Err(e) => Err(PreparationError { - msg: format!("{} issuer {} not present", e.to_string(), issuer_name), - }), + Ok(Some(_cert_issuer)) => Ok(Outcome::success(format!( + "issuer of kind {} is ready", + issuer_name + ))), + + Ok(None) => Err(ExecutorError::UnexpectedError(format!( + "Issuer {} not present in namespace {}", + issuer_name, ns + ))), + + Err(e) => Err(ExecutorError::UnexpectedError(format!( + "Failed to fetch Issuer {}: {}", + issuer_name, e + ))), } } diff --git a/harmony/src/modules/cert_manager/capability.rs b/harmony/src/modules/cert_manager/capability.rs index f39f6e6d..0610a726 100644 --- a/harmony/src/modules/cert_manager/capability.rs +++ b/harmony/src/modules/cert_manager/capability.rs @@ -2,38 +2,39 @@ use async_trait::async_trait; use serde::Serialize; use crate::{ + executors::ExecutorError, + interpret::Outcome, modules::cert_manager::crd::{AcmeIssuer, CaIssuer}, - topology::{PreparationError, PreparationOutcome}, }; ///TODO rust doc explaining issuer, certificate etc #[async_trait] pub trait CertificateManagement: Send + Sync { - async fn install(&self) -> Result; + async fn install(&self) -> Result; - async fn ensure_certificate_management_ready( + async fn ensure_ready( &self, config: &CertificateManagementConfig, - ) -> Result; + ) -> Result; async fn create_issuer( &self, issuer_name: String, config: &CertificateManagementConfig, - ) -> Result; + ) -> Result; async fn create_certificate( &self, cert_name: String, issuer_name: String, config: &CertificateManagementConfig, - ) -> Result; + ) -> Result; async fn get_ca_certificate( &self, cert_name: String, config: &CertificateManagementConfig, - ) -> Result; + ) -> Result; } #[derive(Debug, Clone, Serialize)] diff --git a/harmony/src/modules/cert_manager/score_create_cert.rs b/harmony/src/modules/cert_manager/score_create_cert.rs index 23e761b5..8f3a21b6 100644 --- a/harmony/src/modules/cert_manager/score_create_cert.rs +++ b/harmony/src/modules/cert_manager/score_create_cert.rs @@ -1,5 +1,6 @@ use async_trait::async_trait; use harmony_types::id::Id; +use log::trace; use serde::Serialize; use crate::{ @@ -55,6 +56,11 @@ impl Interpret for CertificateCreationIn .await .map_err(|e| InterpretError::new(e.to_string()))?; + let ca_cert = topology + .get_ca_certificate("test-self-signed-cert".to_string(), &self.config) + .await?; + trace!("cacert: {}", ca_cert); + Ok(Outcome::success(format!("Installed CertificateManagement"))) } diff --git a/harmony/src/modules/cert_manager/score_operator.rs b/harmony/src/modules/cert_manager/score_operator.rs index b13e0854..ec783266 100644 --- a/harmony/src/modules/cert_manager/score_operator.rs +++ b/harmony/src/modules/cert_manager/score_operator.rs @@ -40,8 +40,7 @@ impl Interpret for CertificateManagement inventory: &Inventory, topology: &T, ) -> Result { - topology - .ensure_certificate_management_ready(&self.config) + let _cert_management = &CertificateManagement::ensure_ready(topology, &self.config) .await .map_err(|e| InterpretError::new(e.to_string()))?; -- 2.39.5 From 54a53fa982f18fc46774178236313eb56ee28a53 Mon Sep 17 00:00:00 2001 From: wjro Date: Mon, 19 Jan 2026 12:48:47 -0500 Subject: [PATCH 10/13] fix: modified score names for better clarity --- examples/cert_manager/src/main.rs | 6 +++--- .../domain/topology/k8s_anywhere/k8s_anywhere.rs | 10 ++++------ harmony/src/modules/cert_manager/crd/mod.rs | 4 ++-- ...re_certificate.rs => score_k8s_certificate.rs} | 4 ++-- ...ster_issuer.rs => score_k8s_cluster_issuer.rs} | 0 .../crd/{score_issuer.rs => score_k8s_issuer.rs} | 4 ++-- harmony/src/modules/cert_manager/mod.rs | 6 +++--- ...score_operator.rs => score_cert_management.rs} | 0 ...{score_create_cert.rs => score_certificate.rs} | 15 +++++---------- .../{score_create_issuer.rs => score_issuer.rs} | 0 10 files changed, 21 insertions(+), 28 deletions(-) rename harmony/src/modules/cert_manager/crd/{score_certificate.rs => score_k8s_certificate.rs} (93%) rename harmony/src/modules/cert_manager/crd/{score_cluster_issuer.rs => score_k8s_cluster_issuer.rs} (100%) rename harmony/src/modules/cert_manager/crd/{score_issuer.rs => score_k8s_issuer.rs} (93%) rename harmony/src/modules/cert_manager/{score_operator.rs => score_cert_management.rs} (100%) rename harmony/src/modules/cert_manager/{score_create_cert.rs => score_certificate.rs} (84%) rename harmony/src/modules/cert_manager/{score_create_issuer.rs => score_issuer.rs} (100%) diff --git a/examples/cert_manager/src/main.rs b/examples/cert_manager/src/main.rs index 1d0c3acd..7ab3ce18 100644 --- a/examples/cert_manager/src/main.rs +++ b/examples/cert_manager/src/main.rs @@ -1,8 +1,8 @@ use harmony::{ inventory::Inventory, modules::cert_manager::{ - capability::CertificateManagementConfig, score_create_cert::CertificateCreationScore, - score_create_issuer::CertificateIssuerScore, score_operator::CertificateManagementScore, + capability::CertificateManagementConfig, score_cert_management::CertificateManagementScore, + score_certificate::CertificateScore, score_issuer::CertificateIssuerScore, }, topology::K8sAnywhereTopology, }; @@ -25,7 +25,7 @@ async fn main() { issuer_name: "test-self-signed-issuer".to_string(), }; - let cert = CertificateCreationScore { + let cert = CertificateScore { config: config.clone(), cert_name: "test-self-signed-cert".to_string(), issuer_name: "test-self-signed-issuer".to_string(), diff --git a/harmony/src/domain/topology/k8s_anywhere/k8s_anywhere.rs b/harmony/src/domain/topology/k8s_anywhere/k8s_anywhere.rs index ef480151..14e1ac27 100644 --- a/harmony/src/domain/topology/k8s_anywhere/k8s_anywhere.rs +++ b/harmony/src/domain/topology/k8s_anywhere/k8s_anywhere.rs @@ -20,10 +20,8 @@ use crate::{ cert_manager::{ capability::{CertificateManagement, CertificateManagementConfig}, crd::{ - certificate::Certificate, - issuer::{Issuer, IssuerSpec}, - score_certificate::CertificateScore, - score_issuer::IssuerScore, + certificate::Certificate, issuer::Issuer, + score_k8s_certificate::K8sCertificateScore, score_k8s_issuer::K8sIssuerScore, }, operator::CertManagerOperatorScore, }, @@ -412,7 +410,7 @@ impl CertificateManagement for K8sAnywhereTopology { issuer_name: String, config: &CertificateManagementConfig, ) -> Result { - let issuer_score = IssuerScore { + let issuer_score = K8sIssuerScore { issuer_name: issuer_name.clone(), config: config.clone(), }; @@ -441,7 +439,7 @@ impl CertificateManagement for K8sAnywhereTopology { ) .await?; - let cert = CertificateScore { + let cert = K8sCertificateScore { cert_name: cert_name, config: config.clone(), issuer_name, diff --git a/harmony/src/modules/cert_manager/crd/mod.rs b/harmony/src/modules/cert_manager/crd/mod.rs index 7f8e6d1c..7725c1ab 100644 --- a/harmony/src/modules/cert_manager/crd/mod.rs +++ b/harmony/src/modules/cert_manager/crd/mod.rs @@ -4,8 +4,8 @@ pub mod certificate; pub mod cluster_issuer; pub mod issuer; //pub mod score_cluster_issuer; -pub mod score_certificate; -pub mod score_issuer; +pub mod score_k8s_certificate; +pub mod score_k8s_issuer; #[derive(Deserialize, Serialize, Clone, Debug)] #[serde(rename_all = "camelCase")] diff --git a/harmony/src/modules/cert_manager/crd/score_certificate.rs b/harmony/src/modules/cert_manager/crd/score_k8s_certificate.rs similarity index 93% rename from harmony/src/modules/cert_manager/crd/score_certificate.rs rename to harmony/src/modules/cert_manager/crd/score_k8s_certificate.rs index fb9a145a..adc7cc48 100644 --- a/harmony/src/modules/cert_manager/crd/score_certificate.rs +++ b/harmony/src/modules/cert_manager/crd/score_k8s_certificate.rs @@ -15,13 +15,13 @@ use crate::{ }; #[derive(Debug, Clone, Serialize)] -pub struct CertificateScore { +pub struct K8sCertificateScore { pub cert_name: String, pub issuer_name: String, pub config: CertificateManagementConfig, } -impl Score for CertificateScore { +impl Score for K8sCertificateScore { fn name(&self) -> String { "CertificateScore".to_string() } diff --git a/harmony/src/modules/cert_manager/crd/score_cluster_issuer.rs b/harmony/src/modules/cert_manager/crd/score_k8s_cluster_issuer.rs similarity index 100% rename from harmony/src/modules/cert_manager/crd/score_cluster_issuer.rs rename to harmony/src/modules/cert_manager/crd/score_k8s_cluster_issuer.rs diff --git a/harmony/src/modules/cert_manager/crd/score_issuer.rs b/harmony/src/modules/cert_manager/crd/score_k8s_issuer.rs similarity index 93% rename from harmony/src/modules/cert_manager/crd/score_issuer.rs rename to harmony/src/modules/cert_manager/crd/score_k8s_issuer.rs index c858d6ef..213da588 100644 --- a/harmony/src/modules/cert_manager/crd/score_issuer.rs +++ b/harmony/src/modules/cert_manager/crd/score_k8s_issuer.rs @@ -18,12 +18,12 @@ use crate::{ }; #[derive(Debug, Clone, Serialize)] -pub struct IssuerScore { +pub struct K8sIssuerScore { pub issuer_name: String, pub config: CertificateManagementConfig, } -impl Score for IssuerScore { +impl Score for K8sIssuerScore { fn name(&self) -> String { "IssuerScore".to_string() } diff --git a/harmony/src/modules/cert_manager/mod.rs b/harmony/src/modules/cert_manager/mod.rs index d1ff660e..46c1cb46 100644 --- a/harmony/src/modules/cert_manager/mod.rs +++ b/harmony/src/modules/cert_manager/mod.rs @@ -3,7 +3,7 @@ pub mod cluster_issuer; pub mod crd; mod helm; pub mod operator; -pub mod score_create_cert; -pub mod score_create_issuer; -pub mod score_operator; +pub mod score_cert_management; +pub mod score_certificate; +pub mod score_issuer; pub use helm::*; diff --git a/harmony/src/modules/cert_manager/score_operator.rs b/harmony/src/modules/cert_manager/score_cert_management.rs similarity index 100% rename from harmony/src/modules/cert_manager/score_operator.rs rename to harmony/src/modules/cert_manager/score_cert_management.rs diff --git a/harmony/src/modules/cert_manager/score_create_cert.rs b/harmony/src/modules/cert_manager/score_certificate.rs similarity index 84% rename from harmony/src/modules/cert_manager/score_create_cert.rs rename to harmony/src/modules/cert_manager/score_certificate.rs index 8f3a21b6..19cf7f13 100644 --- a/harmony/src/modules/cert_manager/score_create_cert.rs +++ b/harmony/src/modules/cert_manager/score_certificate.rs @@ -13,19 +13,19 @@ use crate::{ }; #[derive(Debug, Clone, Serialize)] -pub struct CertificateCreationScore { +pub struct CertificateScore { pub cert_name: String, pub issuer_name: String, pub config: CertificateManagementConfig, } -impl Score for CertificateCreationScore { +impl Score for CertificateScore { fn name(&self) -> String { "CertificateCreationScore".to_string() } fn create_interpret(&self) -> Box> { - Box::new(CertificateCreationInterpret { + Box::new(CertificateInterpret { cert_name: self.cert_name.clone(), issuer_name: self.issuer_name.clone(), config: self.config.clone(), @@ -34,14 +34,14 @@ impl Score for CertificateCreationScore } #[derive(Debug)] -struct CertificateCreationInterpret { +struct CertificateInterpret { cert_name: String, issuer_name: String, config: CertificateManagementConfig, } #[async_trait] -impl Interpret for CertificateCreationInterpret { +impl Interpret for CertificateInterpret { async fn execute( &self, inventory: &Inventory, @@ -56,11 +56,6 @@ impl Interpret for CertificateCreationIn .await .map_err(|e| InterpretError::new(e.to_string()))?; - let ca_cert = topology - .get_ca_certificate("test-self-signed-cert".to_string(), &self.config) - .await?; - trace!("cacert: {}", ca_cert); - Ok(Outcome::success(format!("Installed CertificateManagement"))) } diff --git a/harmony/src/modules/cert_manager/score_create_issuer.rs b/harmony/src/modules/cert_manager/score_issuer.rs similarity index 100% rename from harmony/src/modules/cert_manager/score_create_issuer.rs rename to harmony/src/modules/cert_manager/score_issuer.rs -- 2.39.5 From c3ac0bafad0083c1f20c923e5ca519e900740b97 Mon Sep 17 00:00:00 2001 From: Jean-Gabriel Gill-Couture Date: Tue, 20 Jan 2026 12:11:48 -0500 Subject: [PATCH 11/13] lint: Remove useless variable assignment --- harmony/src/modules/cert_manager/score_cert_management.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/harmony/src/modules/cert_manager/score_cert_management.rs b/harmony/src/modules/cert_manager/score_cert_management.rs index ec783266..23d30df0 100644 --- a/harmony/src/modules/cert_manager/score_cert_management.rs +++ b/harmony/src/modules/cert_manager/score_cert_management.rs @@ -37,10 +37,10 @@ struct CertificateManagementInterpret { impl Interpret for CertificateManagementInterpret { async fn execute( &self, - inventory: &Inventory, + _inventory: &Inventory, topology: &T, ) -> Result { - let _cert_management = &CertificateManagement::ensure_ready(topology, &self.config) + CertificateManagement::ensure_ready(topology, &self.config) .await .map_err(|e| InterpretError::new(e.to_string()))?; -- 2.39.5 From ab33aba776af179a2989db8add71036a266ba40d Mon Sep 17 00:00:00 2001 From: wjro Date: Tue, 20 Jan 2026 13:21:24 -0500 Subject: [PATCH 12/13] fix: moved cert management ensure ready to k8sanywhere --- examples/cert_manager/src/main.rs | 6 +---- harmony/src/domain/topology/k8s.rs | 26 +++++-------------- .../topology/k8s_anywhere/k8s_anywhere.rs | 19 +++++++++----- .../src/modules/cert_manager/capability.rs | 5 +--- .../cert_manager/crd/score_k8s_certificate.rs | 4 +-- .../cert_manager/crd/score_k8s_issuer.rs | 4 +-- .../cert_manager/score_cert_management.rs | 14 +++------- harmony/src/modules/mod.rs | 1 + 8 files changed, 30 insertions(+), 49 deletions(-) diff --git a/examples/cert_manager/src/main.rs b/examples/cert_manager/src/main.rs index 7ab3ce18..3403bfb1 100644 --- a/examples/cert_manager/src/main.rs +++ b/examples/cert_manager/src/main.rs @@ -16,10 +16,6 @@ async fn main() { self_signed: true, }; - let cert_manager = CertificateManagementScore { - config: config.clone(), - }; - let issuer = CertificateIssuerScore { config: config.clone(), issuer_name: "test-self-signed-issuer".to_string(), @@ -34,7 +30,7 @@ async fn main() { harmony_cli::run( Inventory::autoload(), K8sAnywhereTopology::from_env(), - vec![Box::new(cert_manager), Box::new(issuer), Box::new(cert)], + vec![Box::new(issuer), Box::new(cert)], None, ) .await diff --git a/harmony/src/domain/topology/k8s.rs b/harmony/src/domain/topology/k8s.rs index bb786eb9..ef34f3cf 100644 --- a/harmony/src/domain/topology/k8s.rs +++ b/harmony/src/domain/topology/k8s.rs @@ -230,26 +230,14 @@ impl K8sClient { namespace: Option<&str>, gvk: &GroupVersionKind, ) -> Result { - let api_resource = ApiResource::from_gvk(gvk); + let gvk = ApiResource::from_gvk(gvk); + let resource: Api = if let Some(ns) = namespace { + Api::namespaced_with(self.client.clone(), ns, &gvk) + } else { + Api::default_namespaced_with(self.client.clone(), &gvk) + }; - // 1. Try namespaced first (if a namespace was provided) - if let Some(ns) = namespace { - let api: Api = - Api::namespaced_with(self.client.clone(), ns, &api_resource); - - match api.get(name).await { - Ok(obj) => return Ok(obj), - Err(Error::Api(ae)) if ae.code == 404 => { - // fall through and try cluster-scoped - } - Err(e) => return Err(e), - } - } - - // 2. Fallback to cluster-scoped - let api: Api = Api::all_with(self.client.clone(), &api_resource); - - api.get(name).await + resource.get(name).await } pub async fn get_secret_json_value( diff --git a/harmony/src/domain/topology/k8s_anywhere/k8s_anywhere.rs b/harmony/src/domain/topology/k8s_anywhere/k8s_anywhere.rs index 14e1ac27..fe232552 100644 --- a/harmony/src/domain/topology/k8s_anywhere/k8s_anywhere.rs +++ b/harmony/src/domain/topology/k8s_anywhere/k8s_anywhere.rs @@ -21,9 +21,11 @@ use crate::{ capability::{CertificateManagement, CertificateManagementConfig}, crd::{ certificate::Certificate, issuer::Issuer, - score_k8s_certificate::K8sCertificateScore, score_k8s_issuer::K8sIssuerScore, + score_k8s_certificate::K8sCertManagerCertificateScore, + score_k8s_issuer::K8sCertManagerIssuerScore, }, operator::CertManagerOperatorScore, + score_cert_management::CertificateManagementScore, }, k3d::K3DInstallationScore, k8s::{ @@ -386,10 +388,7 @@ impl CertificateManagement for K8sAnywhereTopology { ))) } - async fn ensure_ready( - &self, - config: &CertificateManagementConfig, - ) -> Result { + async fn ensure_ready(&self) -> Result { let k8s_client = self.k8s_client().await.unwrap(); match k8s_client @@ -410,7 +409,7 @@ impl CertificateManagement for K8sAnywhereTopology { issuer_name: String, config: &CertificateManagementConfig, ) -> Result { - let issuer_score = K8sIssuerScore { + let issuer_score = K8sCertManagerIssuerScore { issuer_name: issuer_name.clone(), config: config.clone(), }; @@ -439,7 +438,7 @@ impl CertificateManagement for K8sAnywhereTopology { ) .await?; - let cert = K8sCertificateScore { + let cert = K8sCertManagerCertificateScore { cert_name: cert_name, config: config.clone(), issuer_name, @@ -1246,6 +1245,12 @@ impl Topology for K8sAnywhereTopology { .await .map_err(PreparationError::new)?; + let cert_mgmt = CertificateManagementScore {}; + cert_mgmt + .interpret(&Inventory::empty(), self) + .await + .map_err(|e| PreparationError::new(format!("{}", e)))?; + match self.is_helm_available() { Ok(()) => Ok(PreparationOutcome::Success { details: format!("{} + helm available", k8s_state.message.clone()), diff --git a/harmony/src/modules/cert_manager/capability.rs b/harmony/src/modules/cert_manager/capability.rs index 0610a726..979b27f7 100644 --- a/harmony/src/modules/cert_manager/capability.rs +++ b/harmony/src/modules/cert_manager/capability.rs @@ -12,10 +12,7 @@ use crate::{ pub trait CertificateManagement: Send + Sync { async fn install(&self) -> Result; - async fn ensure_ready( - &self, - config: &CertificateManagementConfig, - ) -> Result; + async fn ensure_ready(&self) -> Result; async fn create_issuer( &self, diff --git a/harmony/src/modules/cert_manager/crd/score_k8s_certificate.rs b/harmony/src/modules/cert_manager/crd/score_k8s_certificate.rs index adc7cc48..a1600b6e 100644 --- a/harmony/src/modules/cert_manager/crd/score_k8s_certificate.rs +++ b/harmony/src/modules/cert_manager/crd/score_k8s_certificate.rs @@ -15,13 +15,13 @@ use crate::{ }; #[derive(Debug, Clone, Serialize)] -pub struct K8sCertificateScore { +pub struct K8sCertManagerCertificateScore { pub cert_name: String, pub issuer_name: String, pub config: CertificateManagementConfig, } -impl Score for K8sCertificateScore { +impl Score for K8sCertManagerCertificateScore { fn name(&self) -> String { "CertificateScore".to_string() } diff --git a/harmony/src/modules/cert_manager/crd/score_k8s_issuer.rs b/harmony/src/modules/cert_manager/crd/score_k8s_issuer.rs index 213da588..750a017f 100644 --- a/harmony/src/modules/cert_manager/crd/score_k8s_issuer.rs +++ b/harmony/src/modules/cert_manager/crd/score_k8s_issuer.rs @@ -18,12 +18,12 @@ use crate::{ }; #[derive(Debug, Clone, Serialize)] -pub struct K8sIssuerScore { +pub struct K8sCertManagerIssuerScore { pub issuer_name: String, pub config: CertificateManagementConfig, } -impl Score for K8sIssuerScore { +impl Score for K8sCertManagerIssuerScore { fn name(&self) -> String { "IssuerScore".to_string() } diff --git a/harmony/src/modules/cert_manager/score_cert_management.rs b/harmony/src/modules/cert_manager/score_cert_management.rs index 23d30df0..fefd46cd 100644 --- a/harmony/src/modules/cert_manager/score_cert_management.rs +++ b/harmony/src/modules/cert_manager/score_cert_management.rs @@ -12,9 +12,7 @@ use crate::{ }; #[derive(Debug, Clone, Serialize)] -pub struct CertificateManagementScore { - pub config: CertificateManagementConfig, -} +pub struct CertificateManagementScore {} impl Score for CertificateManagementScore { fn name(&self) -> String { @@ -22,16 +20,12 @@ impl Score for CertificateManagementScor } fn create_interpret(&self) -> Box> { - Box::new(CertificateManagementInterpret { - config: self.config.clone(), - }) + Box::new(CertificateManagementInterpret {}) } } #[derive(Debug)] -struct CertificateManagementInterpret { - config: CertificateManagementConfig, -} +struct CertificateManagementInterpret {} #[async_trait] impl Interpret for CertificateManagementInterpret { @@ -40,7 +34,7 @@ impl Interpret for CertificateManagement _inventory: &Inventory, topology: &T, ) -> Result { - CertificateManagement::ensure_ready(topology, &self.config) + CertificateManagement::ensure_ready(topology) .await .map_err(|e| InterpretError::new(e.to_string()))?; diff --git a/harmony/src/modules/mod.rs b/harmony/src/modules/mod.rs index c845fb10..3fa69469 100644 --- a/harmony/src/modules/mod.rs +++ b/harmony/src/modules/mod.rs @@ -13,6 +13,7 @@ pub mod k8s; pub mod lamp; pub mod load_balancer; pub mod monitoring; +pub mod nats; pub mod network; pub mod okd; pub mod opnsense; -- 2.39.5 From 1e98100ed474353759f8acf121d82679eca610ef Mon Sep 17 00:00:00 2001 From: wjro Date: Tue, 20 Jan 2026 13:43:52 -0500 Subject: [PATCH 13/13] fix: mod.rs --- harmony/src/modules/mod.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/harmony/src/modules/mod.rs b/harmony/src/modules/mod.rs index 3fa69469..c845fb10 100644 --- a/harmony/src/modules/mod.rs +++ b/harmony/src/modules/mod.rs @@ -13,7 +13,6 @@ pub mod k8s; pub mod lamp; pub mod load_balancer; pub mod monitoring; -pub mod nats; pub mod network; pub mod okd; pub mod opnsense; -- 2.39.5