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

This commit is contained in:
2026-01-19 11:37:47 -05:00
parent c6642db6fb
commit 731d59c8b0
5 changed files with 118 additions and 115 deletions

View File

@@ -1,12 +1,8 @@
use harmony::{ use harmony::{
inventory::Inventory, inventory::Inventory,
modules::{ modules::cert_manager::{
cert_manager::{
capability::CertificateManagementConfig, score_create_cert::CertificateCreationScore, capability::CertificateManagementConfig, score_create_cert::CertificateCreationScore,
score_create_issuer::CertificateIssuerScore, score_create_issuer::CertificateIssuerScore, score_operator::CertificateManagementScore,
score_operator::CertificateManagementScore,
},
postgresql::{PostgreSQLScore, capability::PostgreSQLConfig},
}, },
topology::K8sAnywhereTopology, topology::K8sAnywhereTopology,
}; };

View File

@@ -14,16 +14,24 @@ use tokio::sync::OnceCell;
use crate::{ use crate::{
executors::ExecutorError, executors::ExecutorError,
interpret::InterpretStatus, interpret::{InterpretStatus, Outcome},
inventory::Inventory, inventory::Inventory,
modules::{ modules::{
cert_manager::{ cert_manager::{
capability::{CertificateManagement, CertificateManagementConfig}, capability::{CertificateManagement, CertificateManagementConfig},
crd::{score_certificate::CertificateScore, score_issuer::IssuerScore}, crd::{
certificate::Certificate,
issuer::{Issuer, IssuerSpec},
score_certificate::CertificateScore,
score_issuer::IssuerScore,
},
operator::CertManagerOperatorScore, operator::CertManagerOperatorScore,
}, },
k3d::K3DInstallationScore, k3d::K3DInstallationScore,
k8s::ingress::{K8sIngressScore, PathType}, k8s::{
apps::crd::Subscription,
ingress::{K8sIngressScore, PathType},
},
monitoring::{ monitoring::{
grafana::{grafana::Grafana, helm::helm_grafana::grafana_helm_chart_score}, grafana::{grafana::Grafana, helm::helm_grafana::grafana_helm_chart_score},
kube_prometheus::crd::{ kube_prometheus::crd::{
@@ -366,44 +374,36 @@ impl Serialize for K8sAnywhereTopology {
#[async_trait] #[async_trait]
impl CertificateManagement for K8sAnywhereTopology { impl CertificateManagement for K8sAnywhereTopology {
async fn install(&self) -> Result<PreparationOutcome, PreparationError> { async fn install(&self) -> Result<Outcome, ExecutorError> {
let cert_management_operator = CertManagerOperatorScore::default(); let cert_management_operator = CertManagerOperatorScore::default();
cert_management_operator cert_management_operator
.interpret(&Inventory::empty(), self) .interpret(&Inventory::empty(), self)
.await .await
.map_err(|e| PreparationError { msg: e.to_string() })?; .map_err(|e| ExecutorError::UnexpectedError(e.to_string()))?;
Ok(PreparationOutcome::Success { Ok(Outcome::success(format!(
details: format!(
"Installed cert-manager into ns: {}", "Installed cert-manager into ns: {}",
cert_management_operator.namespace cert_management_operator.namespace
), )))
})
} }
async fn ensure_certificate_management_ready( async fn ensure_ready(
&self, &self,
config: &CertificateManagementConfig, config: &CertificateManagementConfig,
) -> Result<PreparationOutcome, PreparationError> { ) -> Result<Outcome, ExecutorError> {
let k8s_client = self.k8s_client().await.unwrap(); 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 match k8s_client
.get_resource_json_value("cert-manager.openshift-operators", None, &gvk) .get_resource::<Subscription>("cert-manager", Some("openshift-operators"))
.await .await
.map_err(|e| ExecutorError::UnexpectedError(format!("{}", e)))?
{ {
Ok(_ready) => Ok(PreparationOutcome::Success { Some(subscription) => {
details: "Certificate Management Ready".to_string(), trace!("subscription {:#?}", subscription,);
}), Ok(Outcome::success(format!("Certificate Management Ready",)))
Err(e) => {
debug!("{} operator not found", e.to_string());
self.install().await
} }
None => self.install().await,
} }
} }
@@ -411,7 +411,7 @@ impl CertificateManagement for K8sAnywhereTopology {
&self, &self,
issuer_name: String, issuer_name: String,
config: &CertificateManagementConfig, config: &CertificateManagementConfig,
) -> Result<PreparationOutcome, PreparationError> { ) -> Result<Outcome, ExecutorError> {
let issuer_score = IssuerScore { let issuer_score = IssuerScore {
issuer_name: issuer_name.clone(), issuer_name: issuer_name.clone(),
config: config.clone(), config: config.clone(),
@@ -420,11 +420,12 @@ impl CertificateManagement for K8sAnywhereTopology {
issuer_score issuer_score
.interpret(&Inventory::empty(), self) .interpret(&Inventory::empty(), self)
.await .await
.map_err(|e| PreparationError { msg: e.to_string() })?; .map_err(|e| ExecutorError::UnexpectedError(e.to_string()))?;
Ok(PreparationOutcome::Success { Ok(Outcome::success(format!(
details: format!("issuer of kind {} is ready", issuer_name), "issuer of kind {} is ready",
}) issuer_name
)))
} }
async fn create_certificate( async fn create_certificate(
@@ -432,7 +433,7 @@ impl CertificateManagement for K8sAnywhereTopology {
cert_name: String, cert_name: String,
issuer_name: String, issuer_name: String,
config: &CertificateManagementConfig, config: &CertificateManagementConfig,
) -> Result<PreparationOutcome, PreparationError> { ) -> Result<Outcome, ExecutorError> {
self.certificate_issuer_ready( self.certificate_issuer_ready(
issuer_name.clone(), issuer_name.clone(),
self.k8s_client().await.unwrap(), self.k8s_client().await.unwrap(),
@@ -447,69 +448,66 @@ impl CertificateManagement for K8sAnywhereTopology {
}; };
cert.interpret(&Inventory::empty(), self) cert.interpret(&Inventory::empty(), self)
.await .await
.map_err(|e| PreparationError { msg: e.to_string() })?; .map_err(|e| ExecutorError::UnexpectedError(e.to_string()))?;
Ok(PreparationOutcome::Success { Ok(Outcome::success(format!(
details: format!("Created cert into ns: {:#?}", config.namespace.clone()), "Created cert into ns: {:#?}",
}) config.namespace.clone()
)))
} }
async fn get_ca_certificate( async fn get_ca_certificate(
&self, &self,
cert_name: String, cert_name: String,
config: &CertificateManagementConfig, config: &CertificateManagementConfig,
) -> Result<String, PreparationError> { ) -> Result<String, ExecutorError> {
let namespace = config.namespace.clone().unwrap(); 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 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::<Certificate>(&cert_name, Some(&namespace))
let secret_name = certificate_data .await
.get("spec") .map_err(|e| ExecutorError::UnexpectedError(format!("{}", e)))?
.ok_or_else(|| PreparationError { {
msg: format!("failed to get spec from Certificate {}", cert_name), let secret_name = certificate.spec.secret_name.clone();
})?
.get("secretName")
.ok_or_else(|| PreparationError {
msg: format!("failed to get secretName from Certificate {}", cert_name),
})?;
trace!("Secret Name {:#?}", secret_name); trace!("Secret Name {:#?}", secret_name);
if let Some(secret) = client
let secret_name: String = serde_json::from_value(secret_name.clone()) .get_resource::<Secret>(&secret_name, Some(&namespace))
.map_err(|e| PreparationError { msg: e.to_string() })?; .await
.map_err(|e| {
let secret = client ExecutorError::UnexpectedError(format!(
.get_secret_json_value(&secret_name, Some(&namespace)) "secret {} not found in namespace {}: {}",
.await? secret_name, namespace, e
.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 { let ca_cert = secret
msg: format!("failed to get ca.crt from secret {}", secret_name), .data
.as_ref()
.and_then(|d| d.get("ca.crt"))
.ok_or_else(|| {
ExecutorError::UnexpectedError("Secret missing key 'ca.crt'".into())
})?; })?;
trace!("ca.crt {:#?}", ca_cert.clone()); let ca_cert = String::from_utf8(ca_cert.0.clone()).map_err(|_| {
ExecutorError::UnexpectedError("ca.crt is not valid UTF-8".into())
})?;
let ca_cert: String = serde_json::from_value(ca_cert.clone()) return Ok(ca_cert);
.map_err(|e| PreparationError { msg: e.to_string() })?; } else {
Err(ExecutorError::UnexpectedError(format!(
trace!("ca.crt string {:#?}", ca_cert.clone()); "Error getting secret associated with cert_name: {}",
Ok(ca_cert) 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, issuer_name: String,
k8s_client: Arc<K8sClient>, k8s_client: Arc<K8sClient>,
config: &CertificateManagementConfig, config: &CertificateManagementConfig,
) -> Result<PreparationOutcome, PreparationError> { ) -> Result<Outcome, ExecutorError> {
let ns = config.namespace.clone().ok_or_else(|| PreparationError { let ns = config
msg: "namespace is required".to_string(), .namespace
})?; .clone()
.ok_or_else(|| ExecutorError::UnexpectedError("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 match k8s_client
.get_resource_json_value(&issuer_name, Some(&ns), &gvk) .get_resource::<Issuer>(&issuer_name, Some(&ns))
.await .await
{ {
Ok(_cert_issuer) => Ok(PreparationOutcome::Success { Ok(Some(_cert_issuer)) => Ok(Outcome::success(format!(
details: format!("issuer of kind {} is ready", issuer_name), "issuer of kind {} is ready",
}), issuer_name
Err(e) => Err(PreparationError { ))),
msg: format!("{} issuer {} not present", e.to_string(), 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
))),
} }
} }

View File

@@ -2,38 +2,39 @@ use async_trait::async_trait;
use serde::Serialize; use serde::Serialize;
use crate::{ use crate::{
executors::ExecutorError,
interpret::Outcome,
modules::cert_manager::crd::{AcmeIssuer, CaIssuer}, modules::cert_manager::crd::{AcmeIssuer, CaIssuer},
topology::{PreparationError, PreparationOutcome},
}; };
///TODO rust doc explaining issuer, certificate etc ///TODO rust doc explaining issuer, certificate etc
#[async_trait] #[async_trait]
pub trait CertificateManagement: Send + Sync { pub trait CertificateManagement: Send + Sync {
async fn install(&self) -> Result<PreparationOutcome, PreparationError>; async fn install(&self) -> Result<Outcome, ExecutorError>;
async fn ensure_certificate_management_ready( async fn ensure_ready(
&self, &self,
config: &CertificateManagementConfig, config: &CertificateManagementConfig,
) -> Result<PreparationOutcome, PreparationError>; ) -> Result<Outcome, ExecutorError>;
async fn create_issuer( async fn create_issuer(
&self, &self,
issuer_name: String, issuer_name: String,
config: &CertificateManagementConfig, config: &CertificateManagementConfig,
) -> Result<PreparationOutcome, PreparationError>; ) -> Result<Outcome, ExecutorError>;
async fn create_certificate( async fn create_certificate(
&self, &self,
cert_name: String, cert_name: String,
issuer_name: String, issuer_name: String,
config: &CertificateManagementConfig, config: &CertificateManagementConfig,
) -> Result<PreparationOutcome, PreparationError>; ) -> Result<Outcome, ExecutorError>;
async fn get_ca_certificate( async fn get_ca_certificate(
&self, &self,
cert_name: String, cert_name: String,
config: &CertificateManagementConfig, config: &CertificateManagementConfig,
) -> Result<String, PreparationError>; ) -> Result<String, ExecutorError>;
} }
#[derive(Debug, Clone, Serialize)] #[derive(Debug, Clone, Serialize)]

View File

@@ -1,5 +1,6 @@
use async_trait::async_trait; use async_trait::async_trait;
use harmony_types::id::Id; use harmony_types::id::Id;
use log::trace;
use serde::Serialize; use serde::Serialize;
use crate::{ use crate::{
@@ -55,6 +56,11 @@ impl<T: Topology + CertificateManagement> Interpret<T> for CertificateCreationIn
.await .await
.map_err(|e| InterpretError::new(e.to_string()))?; .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"))) Ok(Outcome::success(format!("Installed CertificateManagement")))
} }

View File

@@ -40,8 +40,7 @@ impl<T: Topology + CertificateManagement> Interpret<T> for CertificateManagement
inventory: &Inventory, inventory: &Inventory,
topology: &T, topology: &T,
) -> Result<Outcome, InterpretError> { ) -> Result<Outcome, InterpretError> {
topology let _cert_management = &CertificateManagement::ensure_ready(topology, &self.config)
.ensure_certificate_management_ready(&self.config)
.await .await
.map_err(|e| InterpretError::new(e.to_string()))?; .map_err(|e| InterpretError::new(e.to_string()))?;