From 3e14ebd62c6ffd6a30567fad6b488ce3b63ff459 Mon Sep 17 00:00:00 2001 From: Jean-Gabriel Gill-Couture Date: Wed, 10 Dec 2025 22:55:08 -0500 Subject: [PATCH 1/2] feat: cnpg operator score --- harmony/src/modules/k8s/apps/crd/mod.rs | 2 + .../crd/subscriptions_operators_coreos_com.rs | 68 +++++++++++++++++++ harmony/src/modules/postgresql/mod.rs | 2 + harmony/src/modules/postgresql/operator.rs | 66 ++++++++++++++++++ 4 files changed, 138 insertions(+) create mode 100644 harmony/src/modules/k8s/apps/crd/subscriptions_operators_coreos_com.rs create mode 100644 harmony/src/modules/postgresql/operator.rs diff --git a/harmony/src/modules/k8s/apps/crd/mod.rs b/harmony/src/modules/k8s/apps/crd/mod.rs index e83b27b..e45f172 100644 --- a/harmony/src/modules/k8s/apps/crd/mod.rs +++ b/harmony/src/modules/k8s/apps/crd/mod.rs @@ -1,2 +1,4 @@ mod catalogsources_operators_coreos_com; pub use catalogsources_operators_coreos_com::*; +mod subscriptions_operators_coreos_com; +pub use subscriptions_operators_coreos_com::*; diff --git a/harmony/src/modules/k8s/apps/crd/subscriptions_operators_coreos_com.rs b/harmony/src/modules/k8s/apps/crd/subscriptions_operators_coreos_com.rs new file mode 100644 index 0000000..981cd09 --- /dev/null +++ b/harmony/src/modules/k8s/apps/crd/subscriptions_operators_coreos_com.rs @@ -0,0 +1,68 @@ +use k8s_openapi::apimachinery::pkg::apis::meta::v1::ObjectMeta; +use kube::CustomResource; +use serde::{Deserialize, Serialize}; + +#[derive(CustomResource, Deserialize, Serialize, Clone, Debug)] +#[kube( + group = "operators.coreos.com", + version = "v1alpha1", + kind = "Subscription", + plural = "subscriptions", + namespaced = true, + schema = "disabled" +)] +#[serde(rename_all = "camelCase")] +pub struct SubscriptionSpec { + #[serde(skip_serializing_if = "Option::is_none")] + pub channel: Option, + + #[serde(skip_serializing_if = "Option::is_none")] + pub config: Option, + + #[serde(skip_serializing_if = "Option::is_none")] + pub install_plan_approval: Option, + + pub name: String, + + pub source: String, + + pub source_namespace: String, + + #[serde(skip_serializing_if = "Option::is_none")] + pub starting_csv: Option, +} +#[derive(Deserialize, Serialize, Clone, Debug)] +#[serde(rename_all = "camelCase")] +pub struct SubscriptionConfig { + #[serde(skip_serializing_if = "Option::is_none")] + pub env: Option>, + + #[serde(skip_serializing_if = "Option::is_none")] + pub node_selector: Option>, + + #[serde(skip_serializing_if = "Option::is_none")] + pub tolerations: Option>, +} + +impl Default for Subscription { + fn default() -> Self { + Subscription { + metadata: ObjectMeta::default(), + spec: SubscriptionSpec::default(), + } + } +} + +impl Default for SubscriptionSpec { + fn default() -> SubscriptionSpec { + SubscriptionSpec { + name: String::new(), + source: String::new(), + source_namespace: String::new(), + channel: None, + config: None, + install_plan_approval: None, + starting_csv: None, + } + } +} diff --git a/harmony/src/modules/postgresql/mod.rs b/harmony/src/modules/postgresql/mod.rs index fd47d6f..539a298 100644 --- a/harmony/src/modules/postgresql/mod.rs +++ b/harmony/src/modules/postgresql/mod.rs @@ -2,3 +2,5 @@ pub mod capability; mod score; pub mod failover; +mod operator; +pub use operator::*; diff --git a/harmony/src/modules/postgresql/operator.rs b/harmony/src/modules/postgresql/operator.rs new file mode 100644 index 0000000..4f442e6 --- /dev/null +++ b/harmony/src/modules/postgresql/operator.rs @@ -0,0 +1,66 @@ +use k8s_openapi::apimachinery::pkg::apis::meta::v1::ObjectMeta; +use serde::Serialize; + +use crate::interpret::Interpret; +use crate::modules::k8s::apps::crd::{Subscription, SubscriptionSpec}; +use crate::modules::k8s::resource::K8sResourceScore; +use crate::score::Score; +use crate::topology::{K8sclient, Topology}; + +#[derive(Debug, Clone, Serialize)] +pub struct CloudNativePgOperatorScore { + pub namespace: String, + pub channel: String, + pub install_plan_approval: String, + pub source: String, + pub source_namespace: String, +} + +impl Default for CloudNativePgOperatorScore { + fn default() -> Self { + Self { + namespace: "cnpg-system".to_string(), + channel: "stable".to_string(), + install_plan_approval: "Automatic".to_string(), + source: "operatorhubio-catalog".to_string(), + source_namespace: "openshift-marketplace".to_string(), + } + } +} + +impl CloudNativePgOperatorScore { + pub fn new(namespace: &str) -> Self { + Self { + namespace: namespace.to_string(), + ..Default::default() + } + } +} + +impl Score for CloudNativePgOperatorScore { + fn create_interpret(&self) -> Box> { + let metadata = ObjectMeta { + name: Some("cloudnative-pg".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: "cloudnative-pg".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() + } + + fn name(&self) -> String { + format!("CloudNativePgOperatorScore({})", self.namespace) + } +} -- 2.39.5 From f242aafebb799b2807434a503f79aac385d97cc7 Mon Sep 17 00:00:00 2001 From: Jean-Gabriel Gill-Couture Date: Thu, 11 Dec 2025 12:18:28 -0500 Subject: [PATCH 2/2] feat: Subscription for cnpg-operator fixed default values, tested and added to operatorhub example. --- examples/operatorhub_catalog/src/main.rs | 8 ++--- harmony/src/modules/postgresql/operator.rs | 40 ++++++++++++++++++++-- 2 files changed, 42 insertions(+), 6 deletions(-) diff --git a/examples/operatorhub_catalog/src/main.rs b/examples/operatorhub_catalog/src/main.rs index 66ba72a..8e35024 100644 --- a/examples/operatorhub_catalog/src/main.rs +++ b/examples/operatorhub_catalog/src/main.rs @@ -2,19 +2,19 @@ use std::str::FromStr; use harmony::{ inventory::Inventory, - modules::{k8s::apps::OperatorHubCatalogSourceScore, tenant::TenantScore}, - topology::{K8sAnywhereTopology, tenant::TenantConfig}, + modules::{k8s::apps::OperatorHubCatalogSourceScore, postgresql::CloudNativePgOperatorScore}, + topology::K8sAnywhereTopology, }; -use harmony_types::id::Id; #[tokio::main] async fn main() { let operatorhub_catalog = OperatorHubCatalogSourceScore::default(); + let cnpg_operator = CloudNativePgOperatorScore::default(); harmony_cli::run( Inventory::autoload(), K8sAnywhereTopology::from_env(), - vec![Box::new(operatorhub_catalog)], + vec![Box::new(operatorhub_catalog), Box::new(cnpg_operator)], None, ) .await diff --git a/harmony/src/modules/postgresql/operator.rs b/harmony/src/modules/postgresql/operator.rs index 4f442e6..d908361 100644 --- a/harmony/src/modules/postgresql/operator.rs +++ b/harmony/src/modules/postgresql/operator.rs @@ -7,6 +7,42 @@ use crate::modules::k8s::resource::K8sResourceScore; use crate::score::Score; use crate::topology::{K8sclient, Topology}; +/// Install the CloudNativePg (CNPG) Operator via an OperatorHub `Subscription`. +/// +/// This Score creates a a `Subscription` Custom Resource in the specified namespace. +/// +/// The default implementation pulls the `cloudnative-pg` operator from the +/// `operatorhubio-catalog` source. +/// +/// # Goals +/// - Deploy the CNPG Operator to manage PostgreSQL clusters in OpenShift/OKD environments. +/// +/// # Usage +/// ``` +/// use harmony::modules::postgresql::CloudNativePgOperatorScore; +/// let score = CloudNativePgOperatorScore::default(); +/// ``` +/// +/// Or, you can take control of most relevant fiedls this way : +/// +/// ``` +/// use harmony::modules::postgresql::CloudNativePgOperatorScore; +/// +/// let score = CloudNativePgOperatorScore { +/// namespace: "custom-cnpg-namespace".to_string(), +/// channel: "unstable-i-want-bleedingedge-v498437".to_string(), +/// install_plan_approval: "Manual".to_string(), +/// source: "operatorhubio-catalog-but-different".to_string(), +/// source_namespace: "i-customize-everything-marketplace".to_string(), +/// }; +/// ``` +/// +/// # Limitations +/// - **OperatorHub dependency**: Requires OperatorHub catalog sources (e.g., `operatorhubio-catalog` in `openshift-marketplace`). +/// - **OKD/OpenShift assumption**: Catalog/source names and namespaces are hardcoded for OKD-like setups; adjust for upstream OpenShift. +/// - **Hardcoded values in Default implementation**: Operator name (`cloudnative-pg`), channel (`stable-v1`), automatic install plan approval. +/// - **No config options**: Does not support custom `SubscriptionConfig` (env vars, node selectors, tolerations). +/// - **Single namespace**: Targets one namespace per score instance. #[derive(Debug, Clone, Serialize)] pub struct CloudNativePgOperatorScore { pub namespace: String, @@ -19,8 +55,8 @@ pub struct CloudNativePgOperatorScore { impl Default for CloudNativePgOperatorScore { fn default() -> Self { Self { - namespace: "cnpg-system".to_string(), - channel: "stable".to_string(), + namespace: "openshift-operators".to_string(), + channel: "stable-v1".to_string(), install_plan_approval: "Automatic".to_string(), source: "operatorhubio-catalog".to_string(), source_namespace: "openshift-marketplace".to_string(), -- 2.39.5