diff --git a/examples/cert_manager/src/main.rs b/examples/cert_manager/src/main.rs index df6484e..0385594 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 ef34f3c..bb786eb 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 d6b86da..dc50236 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 0c6940d..0cad041 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 9e51235..fb9a145 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 f30f155..c858d6e 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 9de3b9a..9b09876 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 0000000..ceb109d --- /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 0000000..7ccc548 --- /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 18e0826..61c2456 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 {