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::{
inventory::Inventory,
modules::{
cert_manager::{
modules::cert_manager::{
capability::CertificateManagementConfig, score_create_cert::CertificateCreationScore,
score_create_issuer::CertificateIssuerScore,
score_operator::CertificateManagementScore,
},
postgresql::{PostgreSQLScore, capability::PostgreSQLConfig},
score_create_issuer::CertificateIssuerScore, score_operator::CertificateManagementScore,
},
topology::K8sAnywhereTopology,
};

View File

@@ -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<PreparationOutcome, PreparationError> {
async fn install(&self) -> Result<Outcome, ExecutorError> {
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!(
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<PreparationOutcome, PreparationError> {
) -> Result<Outcome, ExecutorError> {
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::<Subscription>("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<PreparationOutcome, PreparationError> {
) -> Result<Outcome, ExecutorError> {
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<PreparationOutcome, PreparationError> {
) -> Result<Outcome, ExecutorError> {
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<String, PreparationError> {
) -> Result<String, ExecutorError> {
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);
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),
})?;
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();
trace!("Secret Name {:#?}", secret_name);
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),
if let Some(secret) = client
.get_resource::<Secret>(&secret_name, Some(&namespace))
.await
.map_err(|e| {
ExecutorError::UnexpectedError(format!(
"secret {} not found in namespace {}: {}",
secret_name, namespace, e
))
})?
.get("ca.crt")
.ok_or_else(|| PreparationError {
msg: format!("failed to get ca.crt from secret {}", secret_name),
{
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!("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())
.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<K8sClient>,
config: &CertificateManagementConfig,
) -> Result<PreparationOutcome, PreparationError> {
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<Outcome, ExecutorError> {
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>(&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
))),
}
}

View File

@@ -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<PreparationOutcome, PreparationError>;
async fn install(&self) -> Result<Outcome, ExecutorError>;
async fn ensure_certificate_management_ready(
async fn ensure_ready(
&self,
config: &CertificateManagementConfig,
) -> Result<PreparationOutcome, PreparationError>;
) -> Result<Outcome, ExecutorError>;
async fn create_issuer(
&self,
issuer_name: String,
config: &CertificateManagementConfig,
) -> Result<PreparationOutcome, PreparationError>;
) -> Result<Outcome, ExecutorError>;
async fn create_certificate(
&self,
cert_name: String,
issuer_name: String,
config: &CertificateManagementConfig,
) -> Result<PreparationOutcome, PreparationError>;
) -> Result<Outcome, ExecutorError>;
async fn get_ca_certificate(
&self,
cert_name: String,
config: &CertificateManagementConfig,
) -> Result<String, PreparationError>;
) -> Result<String, ExecutorError>;
}
#[derive(Debug, Clone, Serialize)]

View File

@@ -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<T: Topology + CertificateManagement> Interpret<T> 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")))
}

View File

@@ -40,8 +40,7 @@ impl<T: Topology + CertificateManagement> Interpret<T> for CertificateManagement
inventory: &Inventory,
topology: &T,
) -> Result<Outcome, InterpretError> {
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()))?;