feat: Postgresql score based on the postgres capability now. true infrastructure abstraction!
Some checks failed
Run Check Script / check (pull_request) Failing after 33s
Some checks failed
Run Check Script / check (pull_request) Failing after 33s
This commit is contained in:
@@ -56,7 +56,7 @@ enum ExecutionMode {
|
|||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct BrocadeInfo {
|
pub struct BrocadeInfo {
|
||||||
os: BrocadeOs,
|
os: BrocadeOs,
|
||||||
version: String,
|
_version: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
@@ -263,7 +263,7 @@ async fn get_brocade_info(session: &mut BrocadeSession) -> Result<BrocadeInfo, E
|
|||||||
|
|
||||||
return Ok(BrocadeInfo {
|
return Ok(BrocadeInfo {
|
||||||
os: BrocadeOs::NetworkOperatingSystem,
|
os: BrocadeOs::NetworkOperatingSystem,
|
||||||
version,
|
_version: version,
|
||||||
});
|
});
|
||||||
} else if output.contains("ICX") {
|
} else if output.contains("ICX") {
|
||||||
let re = Regex::new(r"(?m)^\s*SW: Version\s*(?P<version>[a-zA-Z0-9.\-]+)")
|
let re = Regex::new(r"(?m)^\s*SW: Version\s*(?P<version>[a-zA-Z0-9.\-]+)")
|
||||||
@@ -276,7 +276,7 @@ async fn get_brocade_info(session: &mut BrocadeSession) -> Result<BrocadeInfo, E
|
|||||||
|
|
||||||
return Ok(BrocadeInfo {
|
return Ok(BrocadeInfo {
|
||||||
os: BrocadeOs::FastIron,
|
os: BrocadeOs::FastIron,
|
||||||
version,
|
_version: version,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
use std::{collections::HashMap, str::FromStr};
|
use std::str::FromStr;
|
||||||
|
|
||||||
use harmony::{
|
use harmony::{
|
||||||
inventory::Inventory,
|
inventory::Inventory,
|
||||||
|
|||||||
@@ -1,14 +1,18 @@
|
|||||||
use harmony::{
|
use harmony::{
|
||||||
inventory::Inventory, modules::postgresql::PostgreSQLScore, topology::K8sAnywhereTopology,
|
inventory::Inventory,
|
||||||
|
modules::postgresql::{PostgreSQLScore, capability::PostgreSQLConfig},
|
||||||
|
topology::K8sAnywhereTopology,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[tokio::main]
|
#[tokio::main]
|
||||||
async fn main() {
|
async fn main() {
|
||||||
let postgresql = PostgreSQLScore {
|
let postgresql = PostgreSQLScore {
|
||||||
name: "harmony-postgres-example".to_string(), // Override default name
|
config: PostgreSQLConfig {
|
||||||
namespace: "harmony-postgres-example".to_string(),
|
cluster_name: "harmony-postgres-example".to_string(), // Override default name
|
||||||
..Default::default() // Use harmony defaults, they are based on CNPG's default values :
|
namespace: "harmony-postgres-example".to_string(),
|
||||||
// "default" namespace, 1 instance, 1Gi storage
|
..Default::default() // Use harmony defaults, they are based on CNPG's default values :
|
||||||
|
// "default" namespace, 1 instance, 1Gi storage
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
harmony_cli::run(
|
harmony_cli::run(
|
||||||
|
|||||||
@@ -1,17 +1,22 @@
|
|||||||
use harmony::{
|
use harmony::{
|
||||||
inventory::Inventory,
|
inventory::Inventory,
|
||||||
modules::postgresql::{PostgreSQLConnectionScore, PostgreSQLScore, PublicPostgreSQLScore},
|
modules::postgresql::{
|
||||||
|
K8sPostgreSQLScore, PostgreSQLConnectionScore, PublicPostgreSQLScore,
|
||||||
|
capability::PostgreSQLConfig,
|
||||||
|
},
|
||||||
topology::K8sAnywhereTopology,
|
topology::K8sAnywhereTopology,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[tokio::main]
|
#[tokio::main]
|
||||||
async fn main() {
|
async fn main() {
|
||||||
let postgres = PublicPostgreSQLScore {
|
let postgres = PublicPostgreSQLScore {
|
||||||
postgres_score: PostgreSQLScore {
|
postgres_score: K8sPostgreSQLScore {
|
||||||
name: "harmony-postgres-example".to_string(), // Override default name
|
config: PostgreSQLConfig {
|
||||||
namespace: "harmony-public-postgres".to_string(),
|
cluster_name: "harmony-postgres-example".to_string(), // Override default name
|
||||||
..Default::default() // Use harmony defaults, they are based on CNPG's default values :
|
namespace: "harmony-public-postgres".to_string(),
|
||||||
// "default" namespace, 1 instance, 1Gi storage
|
..Default::default() // Use harmony defaults, they are based on CNPG's default values :
|
||||||
|
// 1 instance, 1Gi storage
|
||||||
|
},
|
||||||
},
|
},
|
||||||
hostname: "postgrestest.sto1.nationtech.io".to_string(),
|
hostname: "postgrestest.sto1.nationtech.io".to_string(),
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,16 +1,27 @@
|
|||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
modules::postgresql::capability::{
|
interpret::Outcome,
|
||||||
PostgreSQL, PostgreSQLConfig, PostgreSQLEndpoint, ReplicationCerts,
|
inventory::Inventory,
|
||||||
|
modules::postgresql::{
|
||||||
|
K8sPostgreSQLScore,
|
||||||
|
capability::{PostgreSQL, PostgreSQLConfig, PostgreSQLEndpoint, ReplicationCerts},
|
||||||
},
|
},
|
||||||
|
score::Score,
|
||||||
topology::K8sAnywhereTopology,
|
topology::K8sAnywhereTopology,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
impl PostgreSQL for K8sAnywhereTopology {
|
impl PostgreSQL for K8sAnywhereTopology {
|
||||||
async fn deploy(&self, config: &PostgreSQLConfig) -> Result<String, String> {
|
async fn deploy(&self, config: &PostgreSQLConfig) -> Result<String, String> {
|
||||||
todo!()
|
K8sPostgreSQLScore {
|
||||||
|
config: config.clone(),
|
||||||
|
}
|
||||||
|
.interpret(&Inventory::empty(), self)
|
||||||
|
.await
|
||||||
|
.map_err(|e| format!("Failed to deploy k8s postgresql : {e}"))?;
|
||||||
|
|
||||||
|
Ok(config.cluster_name.clone())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Extracts PostgreSQL-specific replication certs (PEM format) from a deployed primary cluster.
|
/// Extracts PostgreSQL-specific replication certs (PEM format) from a deployed primary cluster.
|
||||||
|
|||||||
@@ -1,11 +1,9 @@
|
|||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
use harmony_macros::hurl;
|
use harmony_macros::hurl;
|
||||||
use kube::{Api, api::GroupVersionKind};
|
use kube::api::GroupVersionKind;
|
||||||
use log::{debug, warn};
|
|
||||||
use non_blank_string_rs::NonBlankString;
|
use non_blank_string_rs::NonBlankString;
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
use serde::de::DeserializeOwned;
|
use std::{str::FromStr, sync::Arc};
|
||||||
use std::{process::Command, str::FromStr, sync::Arc};
|
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
data::Version,
|
data::Version,
|
||||||
@@ -13,10 +11,7 @@ use crate::{
|
|||||||
inventory::Inventory,
|
inventory::Inventory,
|
||||||
modules::helm::chart::{HelmChartScore, HelmRepository},
|
modules::helm::chart::{HelmChartScore, HelmRepository},
|
||||||
score::Score,
|
score::Score,
|
||||||
topology::{
|
topology::{HelmCommand, K8sclient, Topology, ingress::Ingress, k8s::K8sClient},
|
||||||
HelmCommand, K8sclient, PreparationError, PreparationOutcome, Topology, ingress::Ingress,
|
|
||||||
k8s::K8sClient,
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
use harmony_types::id::Id;
|
use harmony_types::id::Id;
|
||||||
|
|
||||||
|
|||||||
@@ -30,6 +30,21 @@ pub struct PostgreSQLConfig {
|
|||||||
pub instances: u32,
|
pub instances: u32,
|
||||||
pub storage_size: StorageSize,
|
pub storage_size: StorageSize,
|
||||||
pub role: PostgreSQLClusterRole,
|
pub role: PostgreSQLClusterRole,
|
||||||
|
/// **Note :** on OpenShfit based clusters, the namespace `default` has security
|
||||||
|
/// settings incompatible with the default CNPG behavior.
|
||||||
|
pub namespace: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for PostgreSQLConfig {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
cluster_name: "harmony-pg".to_string(),
|
||||||
|
instances: 1,
|
||||||
|
storage_size: StorageSize::gi(1),
|
||||||
|
role: PostgreSQLClusterRole::Primary,
|
||||||
|
namespace: "harmony".to_string(),
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, Serialize)]
|
#[derive(Clone, Debug, Serialize)]
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ use serde::{Deserialize, Serialize};
|
|||||||
)]
|
)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct ClusterSpec {
|
pub struct ClusterSpec {
|
||||||
pub instances: i32,
|
pub instances: u32,
|
||||||
pub image_name: Option<String>,
|
pub image_name: Option<String>,
|
||||||
pub storage: Storage,
|
pub storage: Storage,
|
||||||
pub bootstrap: Bootstrap,
|
pub bootstrap: Bootstrap,
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ use log::debug;
|
|||||||
use log::info;
|
use log::info;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
|
||||||
|
use crate::interpret::Outcome;
|
||||||
use crate::{
|
use crate::{
|
||||||
modules::postgresql::capability::{
|
modules::postgresql::capability::{
|
||||||
BootstrapConfig, BootstrapStrategy, ExternalClusterConfig, PostgreSQL,
|
BootstrapConfig, BootstrapStrategy, ExternalClusterConfig, PostgreSQL,
|
||||||
@@ -25,6 +26,7 @@ impl<T: PostgreSQL> PostgreSQL for FailoverTopology<T> {
|
|||||||
instances: config.instances,
|
instances: config.instances,
|
||||||
storage_size: config.storage_size.clone(),
|
storage_size: config.storage_size.clone(),
|
||||||
role: PostgreSQLClusterRole::Primary,
|
role: PostgreSQLClusterRole::Primary,
|
||||||
|
namespace: config.namespace.clone(),
|
||||||
};
|
};
|
||||||
|
|
||||||
info!(
|
info!(
|
||||||
@@ -91,6 +93,7 @@ impl<T: PostgreSQL> PostgreSQL for FailoverTopology<T> {
|
|||||||
instances: config.instances,
|
instances: config.instances,
|
||||||
storage_size: config.storage_size.clone(),
|
storage_size: config.storage_size.clone(),
|
||||||
role: PostgreSQLClusterRole::Replica(replica_cluster_config),
|
role: PostgreSQLClusterRole::Replica(replica_cluster_config),
|
||||||
|
namespace: config.namespace.clone(),
|
||||||
};
|
};
|
||||||
|
|
||||||
info!(
|
info!(
|
||||||
@@ -102,7 +105,7 @@ impl<T: PostgreSQL> PostgreSQL for FailoverTopology<T> {
|
|||||||
|
|
||||||
info!(
|
info!(
|
||||||
"Replica cluster '{}' deployed successfully; failover topology '{}' ready",
|
"Replica cluster '{}' deployed successfully; failover topology '{}' ready",
|
||||||
replica_config.cluster_name, config.cluster_name
|
replica_config.cluster_name, replica_config.cluster_name
|
||||||
);
|
);
|
||||||
|
|
||||||
Ok(primary_cluster_name)
|
Ok(primary_cluster_name)
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
pub mod capability;
|
pub mod capability;
|
||||||
mod score;
|
mod score_k8s;
|
||||||
mod score_connect;
|
mod score_connect;
|
||||||
pub use score_connect::*;
|
pub use score_connect::*;
|
||||||
pub use score::*;
|
pub use score_k8s::*;
|
||||||
mod score_public;
|
mod score_public;
|
||||||
pub use score_public::*;
|
pub use score_public::*;
|
||||||
|
|
||||||
@@ -10,4 +10,7 @@ pub mod failover;
|
|||||||
mod operator;
|
mod operator;
|
||||||
pub use operator::*;
|
pub use operator::*;
|
||||||
|
|
||||||
|
mod score;
|
||||||
|
pub use score::*;
|
||||||
|
|
||||||
pub mod cnpg;
|
pub mod cnpg;
|
||||||
|
|||||||
@@ -1,51 +1,42 @@
|
|||||||
|
use async_trait::async_trait;
|
||||||
|
use harmony_types::id::Id;
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
|
|
||||||
use crate::interpret::Interpret;
|
use crate::data::Version;
|
||||||
use crate::modules::k8s::resource::K8sResourceScore;
|
use crate::interpret::{Interpret, InterpretError, InterpretName, InterpretStatus, Outcome};
|
||||||
use crate::modules::postgresql::cnpg::{Bootstrap, Cluster, ClusterSpec, Initdb, Storage};
|
use crate::inventory::Inventory;
|
||||||
|
use crate::modules::postgresql::capability::{PostgreSQL, PostgreSQLConfig};
|
||||||
use crate::score::Score;
|
use crate::score::Score;
|
||||||
use crate::topology::{K8sclient, Topology};
|
use crate::topology::Topology;
|
||||||
use k8s_openapi::apimachinery::pkg::apis::meta::v1::ObjectMeta;
|
|
||||||
|
|
||||||
/// Deploys an opinionated, highly available PostgreSQL cluster managed by CNPG.
|
/// High-level, infrastructure-agnostic PostgreSQL deployment score.
|
||||||
///
|
///
|
||||||
/// # Goals
|
/// Delegates to the Topology's PostgreSQL capability implementation,
|
||||||
/// - Production-ready Postgres HA (3 instances), persistent storage, app DB.
|
/// allowing flexibility in deployment strategy (k8s/CNPG, cloud-managed, etc.).
|
||||||
///
|
///
|
||||||
/// # Usage
|
/// # Usage
|
||||||
/// ```
|
/// ```
|
||||||
/// use harmony::modules::postgresql::PostgreSQLScore;
|
/// use harmony::modules::postgresql::PostgreSQLScore;
|
||||||
/// let score = PostgreSQLScore::new("my-app-ns");
|
/// let score = PostgreSQLScore::new("harmony");
|
||||||
/// ```
|
/// ```
|
||||||
///
|
///
|
||||||
/// # Limitations (Happy Path)
|
/// # Design
|
||||||
/// - Requires CNPG operator installed (use CloudNativePgOperatorScore).
|
/// - PostgreSQLScore: High-level, relies on Topology's PostgreSQL implementation
|
||||||
/// - No backups, monitoring, extensions configured.
|
/// - Topology implements PostgreSQL capability (decoupled from score)
|
||||||
|
/// - K8s topologies use K8sPostgreSQLScore internally for CNPG deployment
|
||||||
///
|
///
|
||||||
/// TODO : refactor this to declare a clean dependency on cnpg operator. Then cnpg operator will
|
/// This layered approach gives users choice:
|
||||||
/// self-deploy either using operatorhub or helm chart depending on k8s flavor. This is cnpg
|
/// - Use PostgreSQLScore for portability across topologies
|
||||||
/// specific behavior
|
/// - Use K8sPostgreSQLScore directly for k8s-specific control
|
||||||
#[derive(Debug, Clone, Serialize)]
|
#[derive(Debug, Clone, Serialize)]
|
||||||
pub struct PostgreSQLScore {
|
pub struct PostgreSQLScore {
|
||||||
pub name: String,
|
pub config: PostgreSQLConfig,
|
||||||
/// **Note :** on OpenShfit based clusters, the namespace `default` has security
|
|
||||||
/// settings incompatible with the default CNPG behavior.
|
|
||||||
pub namespace: String,
|
|
||||||
pub instances: i32,
|
|
||||||
pub storage_size: String,
|
|
||||||
pub image_name: Option<String>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for PostgreSQLScore {
|
impl Default for PostgreSQLScore {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self {
|
Self {
|
||||||
name: "harmony-pg".to_string(),
|
config: PostgreSQLConfig::default(),
|
||||||
// We are using the namespace harmony by default since some clusters (openshift family)
|
|
||||||
// have incompatible configuration of the default namespace with cnpg
|
|
||||||
namespace: "harmony".to_string(),
|
|
||||||
instances: 1,
|
|
||||||
storage_size: "1Gi".to_string(),
|
|
||||||
image_name: None, // This lets cnpg use its default image
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -53,41 +44,63 @@ impl Default for PostgreSQLScore {
|
|||||||
impl PostgreSQLScore {
|
impl PostgreSQLScore {
|
||||||
pub fn new(namespace: &str) -> Self {
|
pub fn new(namespace: &str) -> Self {
|
||||||
Self {
|
Self {
|
||||||
namespace: namespace.to_string(),
|
config: PostgreSQLConfig {
|
||||||
..Default::default()
|
namespace: namespace.to_string(),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T: Topology + K8sclient> Score<T> for PostgreSQLScore {
|
impl<T: Topology + PostgreSQL + Send + Sync> Score<T> for PostgreSQLScore {
|
||||||
fn create_interpret(&self) -> Box<dyn Interpret<T>> {
|
fn create_interpret(&self) -> Box<dyn Interpret<T>> {
|
||||||
let metadata = ObjectMeta {
|
Box::new(PostgreSQLInterpret {
|
||||||
name: Some(self.name.clone()),
|
config: self.config.clone(),
|
||||||
namespace: Some(self.namespace.clone()),
|
})
|
||||||
..ObjectMeta::default()
|
|
||||||
};
|
|
||||||
|
|
||||||
let spec = ClusterSpec {
|
|
||||||
instances: self.instances,
|
|
||||||
image_name: self.image_name.clone(),
|
|
||||||
storage: Storage {
|
|
||||||
size: self.storage_size.clone(),
|
|
||||||
},
|
|
||||||
bootstrap: Bootstrap {
|
|
||||||
initdb: Initdb {
|
|
||||||
database: "app".to_string(),
|
|
||||||
owner: "app".to_string(),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
..ClusterSpec::default()
|
|
||||||
};
|
|
||||||
|
|
||||||
let cluster = Cluster { metadata, spec };
|
|
||||||
|
|
||||||
K8sResourceScore::single(cluster, Some(self.namespace.clone())).create_interpret()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn name(&self) -> String {
|
fn name(&self) -> String {
|
||||||
format!("PostgreSQLScore({})", self.namespace)
|
format!(
|
||||||
|
"PostgreSQLScore({}:{})",
|
||||||
|
self.config.namespace, self.config.cluster_name
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Interpret implementation that delegates to Topology's PostgreSQL capability.
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
struct PostgreSQLInterpret {
|
||||||
|
config: PostgreSQLConfig,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
|
impl<T: Topology + PostgreSQL + Send + Sync> Interpret<T> for PostgreSQLInterpret {
|
||||||
|
fn get_name(&self) -> InterpretName {
|
||||||
|
InterpretName::Custom("PostgreSQLInterpret")
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_version(&self) -> Version {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_status(&self) -> InterpretStatus {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_children(&self) -> Vec<Id> {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn execute(&self, _inventory: &Inventory, topo: &T) -> Result<Outcome, InterpretError> {
|
||||||
|
// Delegate to topology's PostgreSQL capability
|
||||||
|
let cluster_name = topo
|
||||||
|
.deploy(&self.config)
|
||||||
|
.await
|
||||||
|
.map_err(|e| InterpretError::new(e))?;
|
||||||
|
|
||||||
|
Ok(Outcome::success(format!(
|
||||||
|
"PostgreSQL cluster '{}' deployed in namespace '{}'",
|
||||||
|
cluster_name, self.config.namespace
|
||||||
|
)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
80
harmony/src/modules/postgresql/score_k8s.rs
Normal file
80
harmony/src/modules/postgresql/score_k8s.rs
Normal file
@@ -0,0 +1,80 @@
|
|||||||
|
use serde::Serialize;
|
||||||
|
|
||||||
|
use crate::interpret::Interpret;
|
||||||
|
use crate::modules::k8s::resource::K8sResourceScore;
|
||||||
|
use crate::modules::postgresql::capability::PostgreSQLConfig;
|
||||||
|
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.
|
||||||
|
///
|
||||||
|
/// # 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 K8sPostgreSQLScore {
|
||||||
|
pub config: PostgreSQLConfig,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for K8sPostgreSQLScore {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
config: PostgreSQLConfig::default(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl K8sPostgreSQLScore {
|
||||||
|
pub fn new(namespace: &str) -> Self {
|
||||||
|
Self {
|
||||||
|
config: PostgreSQLConfig {
|
||||||
|
namespace: namespace.to_string(),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: Topology + K8sclient> Score<T> for K8sPostgreSQLScore {
|
||||||
|
fn create_interpret(&self) -> Box<dyn Interpret<T>> {
|
||||||
|
let metadata = ObjectMeta {
|
||||||
|
name: Some(self.config.cluster_name.clone()),
|
||||||
|
namespace: Some(self.config.namespace.clone()),
|
||||||
|
..ObjectMeta::default()
|
||||||
|
};
|
||||||
|
|
||||||
|
let spec = ClusterSpec {
|
||||||
|
instances: self.config.instances,
|
||||||
|
storage: Storage {
|
||||||
|
size: self.config.storage_size.to_string(),
|
||||||
|
},
|
||||||
|
bootstrap: Bootstrap {
|
||||||
|
initdb: Initdb {
|
||||||
|
database: "app".to_string(),
|
||||||
|
owner: "app".to_string(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
..ClusterSpec::default()
|
||||||
|
};
|
||||||
|
|
||||||
|
let cluster = Cluster { metadata, spec };
|
||||||
|
|
||||||
|
K8sResourceScore::single(cluster, Some(self.config.namespace.clone())).create_interpret()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn name(&self) -> String {
|
||||||
|
format!("PostgreSQLScore({})", self.config.namespace)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -6,7 +6,7 @@ use crate::data::Version;
|
|||||||
use crate::domain::topology::router::{TlsRoute, TlsRouter};
|
use crate::domain::topology::router::{TlsRoute, TlsRouter};
|
||||||
use crate::interpret::{Interpret, InterpretError, InterpretName, InterpretStatus, Outcome};
|
use crate::interpret::{Interpret, InterpretError, InterpretName, InterpretStatus, Outcome};
|
||||||
use crate::inventory::Inventory;
|
use crate::inventory::Inventory;
|
||||||
use crate::modules::postgresql::PostgreSQLScore;
|
use crate::modules::postgresql::K8sPostgreSQLScore;
|
||||||
use crate::score::Score;
|
use crate::score::Score;
|
||||||
use crate::topology::{K8sclient, Topology};
|
use crate::topology::{K8sclient, Topology};
|
||||||
|
|
||||||
@@ -23,7 +23,7 @@ use crate::topology::{K8sclient, Topology};
|
|||||||
#[derive(Debug, Clone, Serialize)]
|
#[derive(Debug, Clone, Serialize)]
|
||||||
pub struct PublicPostgreSQLScore {
|
pub struct PublicPostgreSQLScore {
|
||||||
/// Inner non-public Postgres cluster config.
|
/// Inner non-public Postgres cluster config.
|
||||||
pub postgres_score: PostgreSQLScore,
|
pub postgres_score: K8sPostgreSQLScore,
|
||||||
/// Public hostname for RW TLS passthrough (port 443 → cluster-rw:5432).
|
/// Public hostname for RW TLS passthrough (port 443 → cluster-rw:5432).
|
||||||
pub hostname: String,
|
pub hostname: String,
|
||||||
}
|
}
|
||||||
@@ -31,7 +31,7 @@ pub struct PublicPostgreSQLScore {
|
|||||||
impl PublicPostgreSQLScore {
|
impl PublicPostgreSQLScore {
|
||||||
pub fn new(namespace: &str, hostname: &str) -> Self {
|
pub fn new(namespace: &str, hostname: &str) -> Self {
|
||||||
Self {
|
Self {
|
||||||
postgres_score: PostgreSQLScore::new(namespace),
|
postgres_score: K8sPostgreSQLScore::new(namespace),
|
||||||
hostname: hostname.to_string(),
|
hostname: hostname.to_string(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -39,9 +39,9 @@ impl PublicPostgreSQLScore {
|
|||||||
|
|
||||||
impl<T: Topology + K8sclient + TlsRouter + Send + Sync> Score<T> for PublicPostgreSQLScore {
|
impl<T: Topology + K8sclient + TlsRouter + Send + Sync> Score<T> for PublicPostgreSQLScore {
|
||||||
fn create_interpret(&self) -> Box<dyn Interpret<T>> {
|
fn create_interpret(&self) -> Box<dyn Interpret<T>> {
|
||||||
let rw_backend = format!("{}-rw", self.postgres_score.name);
|
let rw_backend = format!("{}-rw", self.postgres_score.config.cluster_name);
|
||||||
let tls_route = TlsRoute {
|
let tls_route = TlsRoute {
|
||||||
namespace: self.postgres_score.namespace.clone(),
|
namespace: self.postgres_score.config.namespace.clone(),
|
||||||
hostname: self.hostname.clone(),
|
hostname: self.hostname.clone(),
|
||||||
backend: rw_backend,
|
backend: rw_backend,
|
||||||
target_port: 5432,
|
target_port: 5432,
|
||||||
@@ -56,7 +56,7 @@ impl<T: Topology + K8sclient + TlsRouter + Send + Sync> Score<T> for PublicPostg
|
|||||||
fn name(&self) -> String {
|
fn name(&self) -> String {
|
||||||
format!(
|
format!(
|
||||||
"PublicPostgreSQLScore({}:{})",
|
"PublicPostgreSQLScore({}:{})",
|
||||||
self.postgres_score.namespace, self.hostname
|
self.postgres_score.config.namespace, self.hostname
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -64,7 +64,7 @@ impl<T: Topology + K8sclient + TlsRouter + Send + Sync> Score<T> for PublicPostg
|
|||||||
/// Custom interpret: deploy Postgres then install public TLS route.
|
/// Custom interpret: deploy Postgres then install public TLS route.
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
struct PublicPostgreSQLInterpret {
|
struct PublicPostgreSQLInterpret {
|
||||||
postgres_score: PostgreSQLScore,
|
postgres_score: K8sPostgreSQLScore,
|
||||||
tls_route: TlsRoute,
|
tls_route: TlsRoute,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -93,7 +93,7 @@ impl<T: Topology + K8sclient + TlsRouter + Send + Sync> Interpret<T> for PublicP
|
|||||||
|
|
||||||
Ok(Outcome::success(format!(
|
Ok(Outcome::success(format!(
|
||||||
"Public CNPG cluster '{}' deployed with TLS passthrough route '{}'",
|
"Public CNPG cluster '{}' deployed with TLS passthrough route '{}'",
|
||||||
self.postgres_score.name.clone(),
|
self.postgres_score.config.cluster_name.clone(),
|
||||||
self.tls_route.hostname
|
self.tls_route.hostname
|
||||||
)))
|
)))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,8 @@ use std::fmt;
|
|||||||
pub struct StorageSize {
|
pub struct StorageSize {
|
||||||
size_bytes: u64,
|
size_bytes: u64,
|
||||||
#[serde(skip)]
|
#[serde(skip)]
|
||||||
|
display_value: Option<u64>,
|
||||||
|
#[serde(skip)]
|
||||||
display_suffix: Option<String>,
|
display_suffix: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -12,6 +14,7 @@ impl StorageSize {
|
|||||||
pub fn new(size_bytes: u64) -> Self {
|
pub fn new(size_bytes: u64) -> Self {
|
||||||
Self {
|
Self {
|
||||||
size_bytes,
|
size_bytes,
|
||||||
|
display_value: None,
|
||||||
display_suffix: None,
|
display_suffix: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -19,6 +22,7 @@ impl StorageSize {
|
|||||||
pub fn b(size: u64) -> Self {
|
pub fn b(size: u64) -> Self {
|
||||||
Self {
|
Self {
|
||||||
size_bytes: size,
|
size_bytes: size,
|
||||||
|
display_value: Some(size),
|
||||||
display_suffix: Some("B".to_string()),
|
display_suffix: Some("B".to_string()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -26,6 +30,7 @@ impl StorageSize {
|
|||||||
pub fn kb(size: u64) -> Self {
|
pub fn kb(size: u64) -> Self {
|
||||||
Self {
|
Self {
|
||||||
size_bytes: size * 1024,
|
size_bytes: size * 1024,
|
||||||
|
display_value: Some(size),
|
||||||
display_suffix: Some("KB".to_string()),
|
display_suffix: Some("KB".to_string()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -33,6 +38,7 @@ impl StorageSize {
|
|||||||
pub fn mb(size: u64) -> Self {
|
pub fn mb(size: u64) -> Self {
|
||||||
Self {
|
Self {
|
||||||
size_bytes: size * 1024 * 1024,
|
size_bytes: size * 1024 * 1024,
|
||||||
|
display_value: Some(size),
|
||||||
display_suffix: Some("MB".to_string()),
|
display_suffix: Some("MB".to_string()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -40,6 +46,7 @@ impl StorageSize {
|
|||||||
pub fn gb(size: u64) -> Self {
|
pub fn gb(size: u64) -> Self {
|
||||||
Self {
|
Self {
|
||||||
size_bytes: size * 1024 * 1024 * 1024,
|
size_bytes: size * 1024 * 1024 * 1024,
|
||||||
|
display_value: Some(size),
|
||||||
display_suffix: Some("GB".to_string()),
|
display_suffix: Some("GB".to_string()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -47,13 +54,15 @@ impl StorageSize {
|
|||||||
pub fn gi(size: u64) -> Self {
|
pub fn gi(size: u64) -> Self {
|
||||||
Self {
|
Self {
|
||||||
size_bytes: size * 1024 * 1024 * 1024,
|
size_bytes: size * 1024 * 1024 * 1024,
|
||||||
display_suffix: Some("GiB".to_string()),
|
display_value: Some(size),
|
||||||
|
display_suffix: Some("Gi".to_string()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn tb(size: u64) -> Self {
|
pub fn tb(size: u64) -> Self {
|
||||||
Self {
|
Self {
|
||||||
size_bytes: size * 1024 * 1024 * 1024 * 1024,
|
size_bytes: size * 1024 * 1024 * 1024 * 1024,
|
||||||
|
display_value: Some(size),
|
||||||
display_suffix: Some("TB".to_string()),
|
display_suffix: Some("TB".to_string()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -61,6 +70,7 @@ impl StorageSize {
|
|||||||
pub fn ti(size: u64) -> Self {
|
pub fn ti(size: u64) -> Self {
|
||||||
Self {
|
Self {
|
||||||
size_bytes: size * 1024 * 1024 * 1024 * 1024,
|
size_bytes: size * 1024 * 1024 * 1024 * 1024,
|
||||||
|
display_value: Some(size),
|
||||||
display_suffix: Some("TiB".to_string()),
|
display_suffix: Some("TiB".to_string()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -73,7 +83,8 @@ impl StorageSize {
|
|||||||
impl fmt::Display for StorageSize {
|
impl fmt::Display for StorageSize {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
if let Some(suffix) = &self.display_suffix {
|
if let Some(suffix) = &self.display_suffix {
|
||||||
write!(f, "{}{}", self.size_bytes, suffix)
|
let value = self.display_value.unwrap_or(self.size_bytes);
|
||||||
|
write!(f, "{}{}", value, suffix)
|
||||||
} else {
|
} else {
|
||||||
write!(f, "{}B", self.size_bytes)
|
write!(f, "{}B", self.size_bytes)
|
||||||
}
|
}
|
||||||
@@ -95,42 +106,42 @@ mod tests {
|
|||||||
fn test_kilobytes() {
|
fn test_kilobytes() {
|
||||||
let size = StorageSize::kb(2);
|
let size = StorageSize::kb(2);
|
||||||
assert_eq!(size.bytes(), 2048);
|
assert_eq!(size.bytes(), 2048);
|
||||||
assert_eq!(size.to_string(), "2048KB");
|
assert_eq!(size.to_string(), "2KB");
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_megabytes() {
|
fn test_megabytes() {
|
||||||
let size = StorageSize::mb(3);
|
let size = StorageSize::mb(3);
|
||||||
assert_eq!(size.bytes(), 3 * 1024 * 1024);
|
assert_eq!(size.bytes(), 3 * 1024 * 1024);
|
||||||
assert_eq!(size.to_string(), "3145728MB");
|
assert_eq!(size.to_string(), "3MB");
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_gigabytes() {
|
fn test_gigabytes() {
|
||||||
let size = StorageSize::gb(4);
|
let size = StorageSize::gb(4);
|
||||||
assert_eq!(size.bytes(), 4 * 1024 * 1024 * 1024);
|
assert_eq!(size.bytes(), 4 * 1024 * 1024 * 1024);
|
||||||
assert_eq!(size.to_string(), "4294967296GB");
|
assert_eq!(size.to_string(), "4GB");
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_gibibytes() {
|
fn test_gibibytes() {
|
||||||
let size = StorageSize::gi(1);
|
let size = StorageSize::gi(1);
|
||||||
assert_eq!(size.bytes(), 1024 * 1024 * 1024);
|
assert_eq!(size.bytes(), 1024 * 1024 * 1024);
|
||||||
assert_eq!(size.to_string(), "1073741824GiB");
|
assert_eq!(size.to_string(), "1Gi");
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_terabytes() {
|
fn test_terabytes() {
|
||||||
let size = StorageSize::tb(5);
|
let size = StorageSize::tb(5);
|
||||||
assert_eq!(size.bytes(), 5 * 1024 * 1024 * 1024 * 1024);
|
assert_eq!(size.bytes(), 5 * 1024 * 1024 * 1024 * 1024);
|
||||||
assert_eq!(size.to_string(), "5497558138880TB");
|
assert_eq!(size.to_string(), "5TB");
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_tebibytes() {
|
fn test_tebibytes() {
|
||||||
let size = StorageSize::ti(1);
|
let size = StorageSize::ti(1);
|
||||||
assert_eq!(size.bytes(), 1024 * 1024 * 1024 * 1024);
|
assert_eq!(size.bytes(), 1024 * 1024 * 1024 * 1024);
|
||||||
assert_eq!(size.to_string(), "1099511627776TiB");
|
assert_eq!(size.to_string(), "1Ti");
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -155,6 +166,6 @@ mod tests {
|
|||||||
fn test_ord() {
|
fn test_ord() {
|
||||||
let one_gb = StorageSize::gb(1);
|
let one_gb = StorageSize::gb(1);
|
||||||
let one_gi = StorageSize::gi(1);
|
let one_gi = StorageSize::gi(1);
|
||||||
assert!(one_gb < one_gi); // 1GB = 1000MB, 1GiB = 1024MB
|
assert!(one_gb < one_gi); // 1GB = 1000MB, 1Gi = 1024MB
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user