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()`
|
||||
/// won't fail with "service not found" immediately after deployment.
|
||||
pub wait_for_ready: bool,
|
||||
pub extensions: Vec<String>,
|
||||
}
|
||||
|
||||
impl PostgreSQLConfig {
|
||||
@@ -92,6 +93,7 @@ impl Default for PostgreSQLConfig {
|
||||
namespace: "harmony".to_string(),
|
||||
wait_for_ready: true,
|
||||
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)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct Storage {
|
||||
|
||||
@@ -31,6 +31,7 @@ impl<T: PostgreSQL + TlsRouter> PostgreSQL for FailoverTopology<T> {
|
||||
namespace: config.namespace.clone(),
|
||||
wait_for_ready: config.wait_for_ready,
|
||||
version: config.version.clone(),
|
||||
extensions: config.extensions.clone(),
|
||||
};
|
||||
|
||||
info!(
|
||||
@@ -148,6 +149,7 @@ impl<T: PostgreSQL + TlsRouter> PostgreSQL for FailoverTopology<T> {
|
||||
namespace: config.namespace.clone(),
|
||||
wait_for_ready: config.wait_for_ready,
|
||||
version: config.version.clone(),
|
||||
extensions: config.extensions.clone(),
|
||||
};
|
||||
|
||||
info!(
|
||||
|
||||
@@ -4,8 +4,8 @@ use crate::inventory::Inventory;
|
||||
use crate::modules::k8s::resource::K8sResourceScore;
|
||||
use crate::modules::postgresql::capability::{PostgreSQLConfig, PostgreSQLRootAccountRef};
|
||||
use crate::modules::postgresql::cnpg::{
|
||||
Bootstrap, Cluster, ClusterSpec, ExternalCluster, Initdb, PgBaseBackup, ReplicaSpec,
|
||||
SecretKeySelector, Storage,
|
||||
Bootstrap, Cluster, ClusterReference, ClusterSpec, Database, DatabaseExtension, DatabaseSpec,
|
||||
ExternalCluster, Initdb, PgBaseBackup, ReplicaSpec, SecretKeySelector, Storage,
|
||||
};
|
||||
use crate::modules::postgresql::operator::{
|
||||
CloudNativePgOperatorHelmScore, CloudNativePgOperatorScore,
|
||||
@@ -45,7 +45,7 @@ fn is_forbidden<E: std::fmt::Display>(e: &E) -> bool {
|
||||
/// ```
|
||||
///
|
||||
/// # Limitations (Happy Path)
|
||||
/// - No backups, monitoring, extensions configured.
|
||||
/// - No backups or monitoring configured.
|
||||
#[derive(Debug, Clone, Serialize, Default)]
|
||||
pub struct K8sPostgreSQLScore {
|
||||
pub config: PostgreSQLConfig,
|
||||
@@ -281,6 +281,32 @@ impl K8sPostgreSQLInterpret {
|
||||
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]
|
||||
@@ -330,6 +356,16 @@ impl<T: Topology + K8sclient + HelmCommand + 'static> Interpret<T> for K8sPostgr
|
||||
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)
|
||||
}
|
||||
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]
|
||||
fn cnpg_image_name_maps_tags_and_full_refs() {
|
||||
assert_eq!(cnpg_image_name(None), None);
|
||||
|
||||
Reference in New Issue
Block a user