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_operator::CertificateManagementScore,
score_create_issuer::CertificateIssuerScore,
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))
.await
.map_err(|e| ExecutorError::UnexpectedError(format!("{}", e)))?
{
let secret_name = certificate.spec.secret_name.clone();
let secret_name = certificate_data trace!("Secret Name {:#?}", secret_name);
.get("spec") if let Some(secret) = client
.ok_or_else(|| PreparationError { .get_resource::<Secret>(&secret_name, Some(&namespace))
msg: format!("failed to get spec from Certificate {}", cert_name), .await
})? .map_err(|e| {
.get("secretName") ExecutorError::UnexpectedError(format!(
.ok_or_else(|| PreparationError { "secret {} not found in namespace {}: {}",
msg: format!("failed to get secretName from Certificate {}", cert_name), 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()) return Ok(ca_cert);
.map_err(|e| PreparationError { msg: e.to_string() })?; } else {
Err(ExecutorError::UnexpectedError(format!(
let secret = client "Error getting secret associated with cert_name: {}",
.get_secret_json_value(&secret_name, Some(&namespace)) cert_name
.await? )))
.data; }
} else {
let ca_cert = secret return Err(ExecutorError::UnexpectedError(format!(
.get("data") "Certificate {} not found in namespace {}",
.ok_or_else(|| PreparationError { cert_name, namespace
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)
} }
} }
@@ -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()))?;