feat: added working examples to add self signed issuer and self signed certificate. modified get_resource_json_value to be able to get cluster scoped operators

This commit is contained in:
2026-01-14 16:18:59 -05:00
parent 25c5cd84fe
commit f576effeca
10 changed files with 217 additions and 30 deletions

View File

@@ -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

View File

@@ -230,14 +230,26 @@ impl K8sClient {
namespace: Option<&str>,
gvk: &GroupVersionKind,
) -> Result<DynamicObject, Error> {
let gvk = ApiResource::from_gvk(gvk);
let resource: Api<DynamicObject> = 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<DynamicObject> =
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<DynamicObject> = Api::all_with(self.client.clone(), &api_resource);
api.get(name).await
}
pub async fn get_secret_json_value(

View File

@@ -366,10 +366,7 @@ impl Serialize for K8sAnywhereTopology {
#[async_trait]
impl CertificateManagement for K8sAnywhereTopology {
async fn install(
&self,
config: &CertificateManagementConfig,
) -> Result<PreparationOutcome, PreparationError> {
async fn install(&self) -> Result<PreparationOutcome, PreparationError> {
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<PreparationOutcome, PreparationError> {
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<PreparationOutcome, PreparationError> {
let issuer_score = IssuerScore {
issuer_name: issuer_name.clone(),
config: config.clone(),
};

View File

@@ -11,10 +11,9 @@ use crate::{
pub trait CertificateManagement: Send + Sync {
async fn install(
&self,
config: &CertificateManagementConfig,
) -> Result<PreparationOutcome, PreparationError>;
async fn ensure_ready(
async fn ensure_certificate_management_ready(
&self,
config: &CertificateManagementConfig,
) -> Result<PreparationOutcome, PreparationError>;
@@ -35,7 +34,6 @@ pub trait CertificateManagement: Send + Sync {
#[derive(Debug, Clone, Serialize)]
pub struct CertificateManagementConfig {
pub name: String,
pub namespace: Option<String>,
pub acme_issuer: Option<AcmeIssuer>,
pub ca_issuer: Option<CaIssuer>,

View File

@@ -40,6 +40,7 @@ impl<T: Topology + K8sclient> Score<T> for CertificateScore {
kind: Some("Issuer".into()),
group: Some("cert-manager.io".into()),
},
dns_names: Some(vec!["test.example.local".to_string()]),
..Default::default()
},
};

View File

@@ -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<T: Topology + K8sclient> Score<T> for IssuerScore {
fn create_interpret(&self) -> Box<dyn Interpret<T>> {
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()
};

View File

@@ -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::*;

View File

@@ -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<T: Topology + CertificateManagement> Score<T> for CertificateCreationScore {
fn name(&self) -> String {
"CertificateCreationScore".to_string()
}
fn create_interpret(&self) -> Box<dyn Interpret<T>> {
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<T: Topology + CertificateManagement> Interpret<T> for CertificateCreationInterpret {
async fn execute(
&self,
inventory: &Inventory,
topology: &T,
) -> Result<Outcome, InterpretError> {
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<Id> {
todo!()
}
}

View File

@@ -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<T: Topology + CertificateManagement> Score<T> for CertificateIssuerScore {
fn name(&self) -> String {
"CertificateIssuerScore".to_string()
}
fn create_interpret(&self) -> Box<dyn Interpret<T>> {
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<T: Topology + CertificateManagement> Interpret<T> for CertificateIssuerInterpret {
async fn execute(
&self,
inventory: &Inventory,
topology: &T,
) -> Result<Outcome, InterpretError> {
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<Id> {
todo!()
}
}

View File

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