feat: PostgreSQLScore happy path using cnpg operator
Some checks failed
Run Check Script / check (pull_request) Failing after 37s
Some checks failed
Run Check Script / check (pull_request) Failing after 37s
This commit is contained in:
57
harmony/src/modules/postgresql/cnpg/crd.rs
Normal file
57
harmony/src/modules/postgresql/cnpg/crd.rs
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
use kube::{api::ObjectMeta, CustomResource};
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
#[derive(CustomResource, Deserialize, Serialize, Clone, Debug)]
|
||||||
|
#[kube(
|
||||||
|
group = "postgresql.cnpg.io",
|
||||||
|
version = "v1",
|
||||||
|
kind = "Cluster",
|
||||||
|
plural = "clusters",
|
||||||
|
namespaced = true,
|
||||||
|
schema = "disabled"
|
||||||
|
)]
|
||||||
|
pub struct ClusterSpec {
|
||||||
|
pub instances: i32,
|
||||||
|
pub image_name: Option<String>,
|
||||||
|
pub storage: Storage,
|
||||||
|
pub bootstrap: Bootstrap,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for Cluster {
|
||||||
|
fn default() -> Self {
|
||||||
|
Cluster {
|
||||||
|
metadata: ObjectMeta::default(),
|
||||||
|
spec: ClusterSpec::default(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for ClusterSpec {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
instances: 1,
|
||||||
|
image_name: None,
|
||||||
|
storage: Storage::default(),
|
||||||
|
bootstrap: Bootstrap::default(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize, Serialize, Clone, Debug, Default)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
pub struct Storage {
|
||||||
|
pub size: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize, Serialize, Clone, Debug, Default)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
pub struct Bootstrap {
|
||||||
|
pub initdb: Initdb,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize, Serialize, Clone, Debug, Default)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
pub struct Initdb {
|
||||||
|
pub database: String,
|
||||||
|
pub owner: String,
|
||||||
|
}
|
||||||
2
harmony/src/modules/postgresql/cnpg/mod.rs
Normal file
2
harmony/src/modules/postgresql/cnpg/mod.rs
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
mod crd;
|
||||||
|
pub use crd::*;
|
||||||
@@ -1,6 +1,9 @@
|
|||||||
pub mod capability;
|
pub mod capability;
|
||||||
mod score;
|
mod score;
|
||||||
|
pub use score::*;
|
||||||
|
|
||||||
pub mod failover;
|
pub mod failover;
|
||||||
mod operator;
|
mod operator;
|
||||||
pub use operator::*;
|
pub use operator::*;
|
||||||
|
|
||||||
|
pub mod cnpg;
|
||||||
|
|||||||
@@ -1,88 +1,87 @@
|
|||||||
use crate::{
|
|
||||||
domain::{data::Version, interpret::InterpretStatus},
|
|
||||||
interpret::{Interpret, InterpretError, InterpretName, Outcome},
|
|
||||||
inventory::Inventory,
|
|
||||||
modules::postgresql::capability::PostgreSQL,
|
|
||||||
score::Score,
|
|
||||||
topology::Topology,
|
|
||||||
};
|
|
||||||
|
|
||||||
use super::capability::*;
|
|
||||||
|
|
||||||
use harmony_types::id::Id;
|
|
||||||
|
|
||||||
use async_trait::async_trait;
|
|
||||||
use log::info;
|
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
|
|
||||||
#[derive(Clone, Debug, Serialize)]
|
use crate::interpret::Interpret;
|
||||||
|
use crate::modules::k8s::resource::K8sResourceScore;
|
||||||
|
use crate::modules::postgresql::cnpg::{Bootstrap, Cluster, ClusterSpec, Initdb, Storage};
|
||||||
|
use crate::score::Score;
|
||||||
|
use crate::topology::{K8sclient, Topology};
|
||||||
|
use k8s_openapi::apimachinery::pkg::apis::meta::v1::ObjectMeta;
|
||||||
|
|
||||||
|
/// Deploys an opinionated, highly available PostgreSQL cluster managed by CNPG.
|
||||||
|
///
|
||||||
|
/// # Goals
|
||||||
|
/// - Production-ready Postgres HA (3 instances), persistent storage, app DB.
|
||||||
|
///
|
||||||
|
/// # Usage
|
||||||
|
/// ```
|
||||||
|
/// use harmony::modules::postgresql::PostgreSQLScore;
|
||||||
|
/// let score = PostgreSQLScore::new("my-app-ns");
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// # Limitations (Happy Path)
|
||||||
|
/// - Requires CNPG operator installed (use CloudNativePgOperatorScore).
|
||||||
|
/// - No backups, monitoring, extensions configured.
|
||||||
|
///
|
||||||
|
/// TODO : refactor this to declare a clean dependency on cnpg operator. Then cnpg operator will
|
||||||
|
/// self-deploy either using operatorhub or helm chart depending on k8s flavor. This is cnpg
|
||||||
|
/// specific behavior
|
||||||
|
#[derive(Debug, Clone, Serialize)]
|
||||||
pub struct PostgreSQLScore {
|
pub struct PostgreSQLScore {
|
||||||
config: PostgreSQLConfig,
|
pub namespace: String,
|
||||||
|
pub instances: i32,
|
||||||
|
pub storage_size: String,
|
||||||
|
pub image_name: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
impl Default for PostgreSQLScore {
|
||||||
pub struct PostgreSQLInterpret {
|
fn default() -> Self {
|
||||||
config: PostgreSQLConfig,
|
|
||||||
version: Version,
|
|
||||||
status: InterpretStatus,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl PostgreSQLInterpret {
|
|
||||||
pub fn new(config: PostgreSQLConfig) -> Self {
|
|
||||||
let version = Version::from("1.0.0").expect("Version should be valid");
|
|
||||||
Self {
|
Self {
|
||||||
config,
|
namespace: "default".to_string(),
|
||||||
version,
|
instances: 1,
|
||||||
status: InterpretStatus::QUEUED,
|
storage_size: "1Gi".to_string(),
|
||||||
|
image_name: None, // This lets cnpg use its default image
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T: Topology + PostgreSQL> Score<T> for PostgreSQLScore {
|
impl PostgreSQLScore {
|
||||||
fn name(&self) -> String {
|
pub fn new(namespace: &str) -> Self {
|
||||||
"PostgreSQLScore".to_string()
|
Self {
|
||||||
|
namespace: namespace.to_string(),
|
||||||
|
..Default::default()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: Topology + K8sclient> Score<T> for PostgreSQLScore {
|
||||||
fn create_interpret(&self) -> Box<dyn Interpret<T>> {
|
fn create_interpret(&self) -> Box<dyn Interpret<T>> {
|
||||||
Box::new(PostgreSQLInterpret::new(self.config.clone()))
|
let metadata = ObjectMeta {
|
||||||
}
|
name: Some("postgres".to_string()),
|
||||||
}
|
namespace: Some(self.namespace.clone()),
|
||||||
|
..ObjectMeta::default()
|
||||||
#[async_trait]
|
};
|
||||||
impl<T: Topology + PostgreSQL> Interpret<T> for PostgreSQLInterpret {
|
|
||||||
fn get_name(&self) -> InterpretName {
|
let spec = ClusterSpec {
|
||||||
InterpretName::Custom("PostgreSQLInterpret")
|
instances: self.instances,
|
||||||
}
|
image_name: self.image_name.clone(),
|
||||||
|
storage: Storage {
|
||||||
fn get_version(&self) -> crate::domain::data::Version {
|
size: self.storage_size.clone(),
|
||||||
self.version.clone()
|
},
|
||||||
}
|
bootstrap: Bootstrap {
|
||||||
|
initdb: Initdb {
|
||||||
fn get_status(&self) -> InterpretStatus {
|
database: "app".to_string(),
|
||||||
self.status.clone()
|
owner: "app".to_string(),
|
||||||
}
|
},
|
||||||
|
},
|
||||||
fn get_children(&self) -> Vec<Id> {
|
..ClusterSpec::default()
|
||||||
todo!()
|
};
|
||||||
}
|
|
||||||
|
let cluster = Cluster { metadata, spec };
|
||||||
async fn execute(
|
|
||||||
&self,
|
K8sResourceScore::single(cluster, Some(self.namespace.clone())).create_interpret()
|
||||||
_inventory: &Inventory,
|
}
|
||||||
topology: &T,
|
|
||||||
) -> Result<Outcome, InterpretError> {
|
fn name(&self) -> String {
|
||||||
info!(
|
format!("PostgreSQLScore({})", self.namespace)
|
||||||
"Executing PostgreSQLInterpret with config {:?}",
|
|
||||||
self.config
|
|
||||||
);
|
|
||||||
|
|
||||||
let cluster_name = topology
|
|
||||||
.deploy(&self.config)
|
|
||||||
.await
|
|
||||||
.map_err(|e| InterpretError::from(e))?;
|
|
||||||
|
|
||||||
Ok(Outcome::success(format!(
|
|
||||||
"Deployed PostgreSQL cluster `{cluster_name}`"
|
|
||||||
)))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user