feat(postgresql): request extensions on CNPG clusters #350
@@ -72,6 +72,7 @@ pub struct PostgreSQLConfig {
|
|||||||
/// exist before returning. This ensures that callers like `get_endpoint()`
|
/// exist before returning. This ensures that callers like `get_endpoint()`
|
||||||
/// won't fail with "service not found" immediately after deployment.
|
/// won't fail with "service not found" immediately after deployment.
|
||||||
pub wait_for_ready: bool,
|
pub wait_for_ready: bool,
|
||||||
|
pub extensions: Vec<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PostgreSQLConfig {
|
impl PostgreSQLConfig {
|
||||||
@@ -92,6 +93,7 @@ impl Default for PostgreSQLConfig {
|
|||||||
namespace: "harmony".to_string(),
|
namespace: "harmony".to_string(),
|
||||||
wait_for_ready: true,
|
wait_for_ready: true,
|
||||||
version: None,
|
version: None,
|
||||||
|
extensions: Vec::new(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -55,6 +55,35 @@ impl Default for ClusterSpec {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(CustomResource, Deserialize, Serialize, Clone, Debug, Default)]
|
||||||
|
#[kube(
|
||||||
|
group = "postgresql.cnpg.io",
|
||||||
|
version = "v1",
|
||||||
|
kind = "Database",
|
||||||
|
plural = "databases",
|
||||||
|
namespaced = true,
|
||||||
|
derive = "Default",
|
||||||
|
schema = "disabled"
|
||||||
|
)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
pub struct DatabaseSpec {
|
||||||
|
pub name: String,
|
||||||
|
pub owner: String,
|
||||||
|
pub cluster: ClusterReference,
|
||||||
|
pub extensions: Vec<DatabaseExtension>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize, Serialize, Clone, Debug, Default)]
|
||||||
|
pub struct ClusterReference {
|
||||||
|
pub name: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize, Serialize, Clone, Debug)]
|
||||||
|
pub struct DatabaseExtension {
|
||||||
|
pub name: String,
|
||||||
|
pub ensure: String,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Deserialize, Serialize, Clone, Debug, Default)]
|
#[derive(Deserialize, Serialize, Clone, Debug, Default)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct Storage {
|
pub struct Storage {
|
||||||
|
|||||||
@@ -31,6 +31,7 @@ impl<T: PostgreSQL + TlsRouter> PostgreSQL for FailoverTopology<T> {
|
|||||||
namespace: config.namespace.clone(),
|
namespace: config.namespace.clone(),
|
||||||
wait_for_ready: config.wait_for_ready,
|
wait_for_ready: config.wait_for_ready,
|
||||||
version: config.version.clone(),
|
version: config.version.clone(),
|
||||||
|
extensions: config.extensions.clone(),
|
||||||
};
|
};
|
||||||
|
|
||||||
info!(
|
info!(
|
||||||
@@ -148,6 +149,7 @@ impl<T: PostgreSQL + TlsRouter> PostgreSQL for FailoverTopology<T> {
|
|||||||
namespace: config.namespace.clone(),
|
namespace: config.namespace.clone(),
|
||||||
wait_for_ready: config.wait_for_ready,
|
wait_for_ready: config.wait_for_ready,
|
||||||
version: config.version.clone(),
|
version: config.version.clone(),
|
||||||
|
extensions: config.extensions.clone(),
|
||||||
};
|
};
|
||||||
|
|
||||||
info!(
|
info!(
|
||||||
|
|||||||
@@ -4,8 +4,8 @@ use crate::inventory::Inventory;
|
|||||||
use crate::modules::k8s::resource::K8sResourceScore;
|
use crate::modules::k8s::resource::K8sResourceScore;
|
||||||
use crate::modules::postgresql::capability::{PostgreSQLConfig, PostgreSQLRootAccountRef};
|
use crate::modules::postgresql::capability::{PostgreSQLConfig, PostgreSQLRootAccountRef};
|
||||||
use crate::modules::postgresql::cnpg::{
|
use crate::modules::postgresql::cnpg::{
|
||||||
Bootstrap, Cluster, ClusterSpec, ExternalCluster, Initdb, PgBaseBackup, ReplicaSpec,
|
Bootstrap, Cluster, ClusterReference, ClusterSpec, Database, DatabaseExtension, DatabaseSpec,
|
||||||
SecretKeySelector, Storage,
|
ExternalCluster, Initdb, PgBaseBackup, ReplicaSpec, SecretKeySelector, Storage,
|
||||||
};
|
};
|
||||||
use crate::modules::postgresql::operator::{
|
use crate::modules::postgresql::operator::{
|
||||||
CloudNativePgOperatorHelmScore, CloudNativePgOperatorScore,
|
CloudNativePgOperatorHelmScore, CloudNativePgOperatorScore,
|
||||||
@@ -45,7 +45,7 @@ fn is_forbidden<E: std::fmt::Display>(e: &E) -> bool {
|
|||||||
/// ```
|
/// ```
|
||||||
///
|
///
|
||||||
/// # Limitations (Happy Path)
|
/// # Limitations (Happy Path)
|
||||||
/// - No backups, monitoring, extensions configured.
|
/// - No backups or monitoring configured.
|
||||||
#[derive(Debug, Clone, Serialize, Default)]
|
#[derive(Debug, Clone, Serialize, Default)]
|
||||||
pub struct K8sPostgreSQLScore {
|
pub struct K8sPostgreSQLScore {
|
||||||
pub config: PostgreSQLConfig,
|
pub config: PostgreSQLConfig,
|
||||||
@@ -281,6 +281,32 @@ impl K8sPostgreSQLInterpret {
|
|||||||
tokio::time::sleep(std::time::Duration::from_secs(2)).await;
|
tokio::time::sleep(std::time::Duration::from_secs(2)).await;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn app_database(&self) -> Database {
|
||||||
|
Database {
|
||||||
|
metadata: ObjectMeta {
|
||||||
|
name: Some(format!("{}-app", self.config.cluster_name)),
|
||||||
|
namespace: Some(self.config.namespace.clone()),
|
||||||
|
..ObjectMeta::default()
|
||||||
|
},
|
||||||
|
spec: DatabaseSpec {
|
||||||
|
name: "app".to_string(),
|
||||||
|
owner: "app".to_string(),
|
||||||
|
cluster: ClusterReference {
|
||||||
|
name: self.config.cluster_name.clone(),
|
||||||
|
},
|
||||||
|
extensions: self
|
||||||
|
.config
|
||||||
|
.extensions
|
||||||
|
.iter()
|
||||||
|
.map(|name| DatabaseExtension {
|
||||||
|
name: name.clone(),
|
||||||
|
ensure: "present".to_string(),
|
||||||
|
})
|
||||||
|
.collect(),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
@@ -330,6 +356,16 @@ impl<T: Topology + K8sclient + HelmCommand + 'static> Interpret<T> for K8sPostgr
|
|||||||
self.wait_for_primary(topology).await?;
|
self.wait_for_primary(topology).await?;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if !self.config.extensions.is_empty() {
|
||||||
|
K8sResourceScore::single(
|
||||||
|
self.app_database(),
|
||||||
|
Some(self.config.namespace.clone()),
|
||||||
|
)
|
||||||
|
.create_interpret()
|
||||||
|
.execute(inventory, topology)
|
||||||
|
.await?;
|
||||||
|
}
|
||||||
|
|
||||||
Ok(outcome)
|
Ok(outcome)
|
||||||
}
|
}
|
||||||
super::capability::PostgreSQLClusterRole::Replica(replica_config) => {
|
super::capability::PostgreSQLClusterRole::Replica(replica_config) => {
|
||||||
@@ -498,6 +534,33 @@ mod tests {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn app_database_requests_extensions_on_the_app_database() {
|
||||||
|
let interpret = K8sPostgreSQLInterpret {
|
||||||
|
config: PostgreSQLConfig {
|
||||||
|
cluster_name: "iaai-pg".to_string(),
|
||||||
|
namespace: "iaai".to_string(),
|
||||||
|
extensions: vec!["vector".to_string()],
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
serde_json::to_value(interpret.app_database()).unwrap(),
|
||||||
|
serde_json::json!({
|
||||||
|
"apiVersion": "postgresql.cnpg.io/v1",
|
||||||
|
"kind": "Database",
|
||||||
|
"metadata": { "name": "iaai-pg-app", "namespace": "iaai" },
|
||||||
|
"spec": {
|
||||||
|
"name": "app",
|
||||||
|
"owner": "app",
|
||||||
|
"cluster": { "name": "iaai-pg" },
|
||||||
|
"extensions": [{ "name": "vector", "ensure": "present" }]
|
||||||
|
}
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn cnpg_image_name_maps_tags_and_full_refs() {
|
fn cnpg_image_name_maps_tags_and_full_refs() {
|
||||||
assert_eq!(cnpg_image_name(None), None);
|
assert_eq!(cnpg_image_name(None), None);
|
||||||
|
|||||||
Reference in New Issue
Block a user