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..3403bfb1 --- /dev/null +++ b/examples/cert_manager/src/main.rs @@ -0,0 +1,38 @@ +use harmony::{ + inventory::Inventory, + modules::cert_manager::{ + capability::CertificateManagementConfig, score_cert_management::CertificateManagementScore, + score_certificate::CertificateScore, score_issuer::CertificateIssuerScore, + }, + topology::K8sAnywhereTopology, +}; + +#[tokio::main] +async fn main() { + let config = CertificateManagementConfig { + namespace: Some("test".to_string()), + acme_issuer: None, + ca_issuer: None, + self_signed: true, + }; + + let issuer = CertificateIssuerScore { + config: config.clone(), + issuer_name: "test-self-signed-issuer".to_string(), + }; + + let cert = CertificateScore { + 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(issuer), Box::new(cert)], + 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..fe232552 100644 --- a/harmony/src/domain/topology/k8s_anywhere/k8s_anywhere.rs +++ b/harmony/src/domain/topology/k8s_anywhere/k8s_anywhere.rs @@ -14,11 +14,24 @@ use tokio::sync::OnceCell; use crate::{ executors::ExecutorError, - interpret::InterpretStatus, + interpret::{InterpretStatus, Outcome}, inventory::Inventory, modules::{ + cert_manager::{ + capability::{CertificateManagement, CertificateManagementConfig}, + crd::{ + certificate::Certificate, issuer::Issuer, + score_k8s_certificate::K8sCertManagerCertificateScore, + score_k8s_issuer::K8sCertManagerIssuerScore, + }, + operator::CertManagerOperatorScore, + score_cert_management::CertificateManagementScore, + }, 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::{ @@ -359,6 +372,142 @@ impl Serialize for K8sAnywhereTopology { } } +#[async_trait] +impl CertificateManagement for K8sAnywhereTopology { + async fn install(&self) -> Result { + let cert_management_operator = CertManagerOperatorScore::default(); + + cert_management_operator + .interpret(&Inventory::empty(), self) + .await + .map_err(|e| ExecutorError::UnexpectedError(e.to_string()))?; + + Ok(Outcome::success(format!( + "Installed cert-manager into ns: {}", + cert_management_operator.namespace + ))) + } + + async fn ensure_ready(&self) -> Result { + let k8s_client = self.k8s_client().await.unwrap(); + + match k8s_client + .get_resource::("cert-manager", Some("openshift-operators")) + .await + .map_err(|e| ExecutorError::UnexpectedError(format!("{}", e)))? + { + Some(subscription) => { + trace!("subscription {:#?}", subscription,); + Ok(Outcome::success(format!("Certificate Management Ready",))) + } + None => self.install().await, + } + } + + async fn create_issuer( + &self, + issuer_name: String, + config: &CertificateManagementConfig, + ) -> Result { + let issuer_score = K8sCertManagerIssuerScore { + issuer_name: issuer_name.clone(), + config: config.clone(), + }; + + issuer_score + .interpret(&Inventory::empty(), self) + .await + .map_err(|e| ExecutorError::UnexpectedError(e.to_string()))?; + + Ok(Outcome::success(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 = K8sCertManagerCertificateScore { + cert_name: cert_name, + config: config.clone(), + issuer_name, + }; + cert.interpret(&Inventory::empty(), self) + .await + .map_err(|e| ExecutorError::UnexpectedError(e.to_string()))?; + + Ok(Outcome::success(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(); + + 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(); + + 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()) + })?; + + let ca_cert = String::from_utf8(ca_cert.0.clone()).map_err(|_| { + ExecutorError::UnexpectedError("ca.crt is not valid UTF-8".into()) + })?; + + 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 + ))); + } + } +} + impl K8sAnywhereTopology { pub fn from_env() -> Self { Self { @@ -378,6 +527,38 @@ 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(|| ExecutorError::UnexpectedError("namespace is required".to_string()))?; + + match k8s_client + .get_resource::(&issuer_name, Some(&ns)) + .await + { + 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 + ))), + } + } + pub async fn get_k8s_distribution(&self) -> Result<&KubernetesDistribution, PreparationError> { self.k8s_distribution .get_or_try_init(async || { @@ -1064,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 new file mode 100644 index 00000000..979b27f7 --- /dev/null +++ b/harmony/src/modules/cert_manager/capability.rs @@ -0,0 +1,43 @@ +use async_trait::async_trait; +use serde::Serialize; + +use crate::{ + executors::ExecutorError, + interpret::Outcome, + modules::cert_manager::crd::{AcmeIssuer, CaIssuer}, +}; + +///TODO rust doc explaining issuer, certificate etc +#[async_trait] +pub trait CertificateManagement: Send + Sync { + async fn install(&self) -> Result; + + async fn ensure_ready(&self) -> 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; + + async fn get_ca_certificate( + &self, + cert_name: String, + config: &CertificateManagementConfig, + ) -> Result; +} + +#[derive(Debug, Clone, Serialize)] +pub struct CertificateManagementConfig { + pub namespace: Option, + pub acme_issuer: Option, + pub ca_issuer: Option, + pub self_signed: bool, +} 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..2375b0b2 --- /dev/null +++ b/harmony/src/modules/cert_manager/crd/certificate.rs @@ -0,0 +1,112 @@ +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..0348972c --- /dev/null +++ b/harmony/src/modules/cert_manager/crd/cluster_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 = "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..7725c1ab --- /dev/null +++ b/harmony/src/modules/cert_manager/crd/mod.rs @@ -0,0 +1,65 @@ +use serde::{Deserialize, Serialize}; + +pub mod certificate; +pub mod cluster_issuer; +pub mod issuer; +//pub mod score_cluster_issuer; +pub mod score_k8s_certificate; +pub mod score_k8s_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/crd/score_k8s_certificate.rs b/harmony/src/modules/cert_manager/crd/score_k8s_certificate.rs new file mode 100644 index 00000000..a1600b6e --- /dev/null +++ b/harmony/src/modules/cert_manager/crd/score_k8s_certificate.rs @@ -0,0 +1,49 @@ +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 K8sCertManagerCertificateScore { + pub cert_name: String, + pub issuer_name: String, + pub config: CertificateManagementConfig, +} + +impl Score for K8sCertManagerCertificateScore { + fn name(&self) -> String { + "CertificateScore".to_string() + } + + fn create_interpret(&self) -> Box> { + let cert = Certificate { + metadata: ObjectMeta { + name: Some(self.cert_name.clone()), + namespace: self.config.namespace.clone(), + ..Default::default() + }, + spec: CertificateSpec { + secret_name: format!("{}-tls", self.cert_name.clone()), + issuer_ref: IssuerRef { + name: self.issuer_name.clone(), + kind: Some("Issuer".into()), + group: Some("cert-manager.io".into()), + }, + dns_names: Some(vec!["test.example.local".to_string()]), + ..Default::default() + }, + }; + K8sResourceScore::single(cert, self.config.namespace.clone()).create_interpret() + } +} diff --git a/harmony/src/modules/cert_manager/crd/score_k8s_cluster_issuer.rs b/harmony/src/modules/cert_manager/crd/score_k8s_cluster_issuer.rs new file mode 100644 index 00000000..5437fc7c --- /dev/null +++ b/harmony/src/modules/cert_manager/crd/score_k8s_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_k8s_issuer.rs b/harmony/src/modules/cert_manager/crd/score_k8s_issuer.rs new file mode 100644 index 00000000..750a017f --- /dev/null +++ b/harmony/src/modules/cert_manager/crd/score_k8s_issuer.rs @@ -0,0 +1,52 @@ +use kube::api::ObjectMeta; +use serde::Serialize; + +use crate::{ + interpret::Interpret, + modules::{ + cert_manager::{ + capability::CertificateManagementConfig, + crd::{ + SelfSignedIssuer, + issuer::{Issuer, IssuerSpec}, + }, + }, + k8s::resource::K8sResourceScore, + }, + score::Score, + topology::{K8sclient, Topology}, +}; + +#[derive(Debug, Clone, Serialize)] +pub struct K8sCertManagerIssuerScore { + pub issuer_name: String, + pub config: CertificateManagementConfig, +} + +impl Score for K8sCertManagerIssuerScore { + fn name(&self) -> String { + "IssuerScore".to_string() + } + + fn create_interpret(&self) -> Box> { + let metadata = ObjectMeta { + name: Some(self.issuer_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() + } +} diff --git a/harmony/src/modules/cert_manager/mod.rs b/harmony/src/modules/cert_manager/mod.rs index 032439ea..46c1cb46 100644 --- a/harmony/src/modules/cert_manager/mod.rs +++ b/harmony/src/modules/cert_manager/mod.rs @@ -1,3 +1,9 @@ +pub mod capability; pub mod cluster_issuer; +pub mod crd; mod helm; +pub mod 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/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_cert_management.rs b/harmony/src/modules/cert_manager/score_cert_management.rs new file mode 100644 index 00000000..fefd46cd --- /dev/null +++ b/harmony/src/modules/cert_manager/score_cert_management.rs @@ -0,0 +1,59 @@ +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 {} + +impl Score for CertificateManagementScore { + fn name(&self) -> String { + "CertificateManagementScore".to_string() + } + + fn create_interpret(&self) -> Box> { + Box::new(CertificateManagementInterpret {}) + } +} + +#[derive(Debug)] +struct CertificateManagementInterpret {} + +#[async_trait] +impl Interpret for CertificateManagementInterpret { + async fn execute( + &self, + _inventory: &Inventory, + topology: &T, + ) -> Result { + CertificateManagement::ensure_ready(topology) + .await + .map_err(|e| InterpretError::new(e.to_string()))?; + + Ok(Outcome::success(format!("CertificateManagement is ready"))) + } + + 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_certificate.rs b/harmony/src/modules/cert_manager/score_certificate.rs new file mode 100644 index 00000000..19cf7f13 --- /dev/null +++ b/harmony/src/modules/cert_manager/score_certificate.rs @@ -0,0 +1,77 @@ +use async_trait::async_trait; +use harmony_types::id::Id; +use log::trace; +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 CertificateScore { + pub cert_name: String, + pub issuer_name: String, + pub config: CertificateManagementConfig, +} + +impl Score for CertificateScore { + fn name(&self) -> String { + "CertificateCreationScore".to_string() + } + + fn create_interpret(&self) -> Box> { + Box::new(CertificateInterpret { + cert_name: self.cert_name.clone(), + issuer_name: self.issuer_name.clone(), + config: self.config.clone(), + }) + } +} + +#[derive(Debug)] +struct CertificateInterpret { + cert_name: String, + issuer_name: String, + config: CertificateManagementConfig, +} + +#[async_trait] +impl Interpret for CertificateInterpret { + 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_issuer.rs b/harmony/src/modules/cert_manager/score_issuer.rs new file mode 100644 index 00000000..447ddf67 --- /dev/null +++ b/harmony/src/modules/cert_manager/score_issuer.rs @@ -0,0 +1,71 @@ +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!() + } +}