diff --git a/examples/cert_manager/src/main.rs b/examples/cert_manager/src/main.rs index 0385594..1d0c3ac 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 93570ab..ef48015 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 f39f6e6..0610a72 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 23e761b..8f3a21b 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 b13e085..ec78326 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()))?;