diff --git a/harmony/src/domain/topology/mod.rs b/harmony/src/domain/topology/mod.rs index 64e12d0..0e49efd 100644 --- a/harmony/src/domain/topology/mod.rs +++ b/harmony/src/domain/topology/mod.rs @@ -15,7 +15,7 @@ pub use k8s_anywhere::*; pub use localhost::*; pub mod k8s; mod load_balancer; -mod router; +pub mod router; mod tftp; use async_trait::async_trait; pub use ha_cluster::*; diff --git a/harmony/src/domain/topology/router.rs b/harmony/src/domain/topology/router.rs index 3c8721d..c1a48fc 100644 --- a/harmony/src/domain/topology/router.rs +++ b/harmony/src/domain/topology/router.rs @@ -47,17 +47,16 @@ impl Router for UnmanagedRouter { } } - #[derive(Clone, Debug)] /// Desired state config for a TLS passthrough route. /// Forwards external TLS (port 443) → backend service:target_port (no termination at router). /// Inspired by CNPG multisite: exposes `-rw`/`-ro` services publicly via OKD Route/HAProxy/K8s /// Gateway etc. -/// +/// /// # Example /// ``` -/// use harmony::domain::topology::router::TlsRoute; +/// use harmony::topology::router::TlsRoute; /// let postgres_rw = TlsRoute { /// hostname: "postgres-cluster-example.public.domain.io".to_string(), /// backend: "postgres-cluster-example-rw".to_string(), // k8s Service or HAProxy upstream @@ -81,19 +80,19 @@ pub struct TlsRoute { /// Installs and queries TLS passthrough routes (L4 TCP/SNI forwarding, no TLS termination). /// Agnostic to impl: OKD Route, AWS NLB+HAProxy, k3s Envoy Gateway, Apache ProxyPass. /// Used by PostgreSQL capability to expose CNPG clusters multisite (site1 → site2 replication). -/// +/// /// # Usage -/// ```rust,no_run +/// ```ignore +/// use harmony::topology::router::TlsRoute; /// // After CNPG deploy, expose RW endpoint -/// let topology = okd_topology(); -/// let route = TlsRoute { /* ... */ }; -/// topology.install_route(route).await?; // OKD Route, HAProxy reload, etc. -/// -/// // Client: psql \\"host={route.hostname} port=443 sslmode=verify-ca sslnegotiation=direct\\" -/// let public_ep = Endpoint { host: topology.hostname(), port: 443 }; +/// async fn route() { +/// let topology = okd_topology(); +/// let route = TlsRoute { /* ... */ }; +/// topology.install_route(route).await; // OKD Route, HAProxy reload, etc. +/// } /// ``` pub trait TlsRouter: Send + Sync { -/// Provisions the route (idempotent where possible). + /// Provisions the route (idempotent where possible). /// Example: OKD Route{ host, to: backend:target_port, tls: {passthrough} }; /// HAProxy frontend→backend \"postgres-upstream\". async fn install_route(&self, config: TlsRoute) -> Result<(), String>; @@ -118,4 +117,3 @@ impl std::fmt::Debug for dyn TlsRouter { )) } } - diff --git a/harmony/src/modules/postgresql/cnpg/crd.rs b/harmony/src/modules/postgresql/cnpg/crd.rs index 5f06156..122923e 100644 --- a/harmony/src/modules/postgresql/cnpg/crd.rs +++ b/harmony/src/modules/postgresql/cnpg/crd.rs @@ -1,4 +1,4 @@ -use kube::{api::ObjectMeta, CustomResource}; +use kube::{CustomResource, api::ObjectMeta}; use serde::{Deserialize, Serialize}; #[derive(CustomResource, Deserialize, Serialize, Clone, Debug)] diff --git a/harmony/src/modules/postgresql/score_public.rs b/harmony/src/modules/postgresql/score_public.rs index 1bbe8fa..73c2193 100644 --- a/harmony/src/modules/postgresql/score_public.rs +++ b/harmony/src/modules/postgresql/score_public.rs @@ -1,20 +1,23 @@ use async_trait::async_trait; +use harmony_types::id::Id; use serde::Serialize; +use crate::data::Version; use crate::domain::topology::router::{TlsRoute, TlsRouter}; -use crate::interpret::Interpret; +use crate::interpret::{Interpret, InterpretError, InterpretName, InterpretStatus, Outcome}; +use crate::inventory::Inventory; use crate::modules::k8s::resource::K8sResourceScore; -use crate::modules::postgresql::cnpg::{Bootstrap, Cluster, ClusterSpec, Initdb, Storage}; use crate::modules::postgresql::PostgreSQLScore; +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 a public PostgreSQL cluster: CNPG + TLS passthrough route for RW endpoint. /// For failover/multisite: exposes single-instance or small HA Postgres publicly. -/// +/// /// Sequence: PostgreSQLScore → TlsRouter::install_route (RW backend). -/// +/// /// # Usage /// ``` /// use harmony::modules::postgresql::PublicPostgreSQLScore; @@ -55,14 +58,35 @@ struct PublicPostgreSQLInterpret { #[async_trait] impl Interpret for PublicPostgreSQLInterpret { - async fn interpret(&self, topo: &T) -> Result<(), Box> { + fn get_name(&self) -> InterpretName { + InterpretName::Custom("PublicPostgreSQLInterpret") + } + fn get_version(&self) -> Version { + todo!() + } + fn get_status(&self) -> InterpretStatus { + todo!() + } + fn get_children(&self) -> Vec { + todo!() + } + async fn execute(&self, inventory: &Inventory, topo: &T) -> Result { // Deploy CNPG cluster first (creates -rw service) - self.postgres_score.create_interpret().interpret(topo).await?; + self.postgres_score + .create_interpret() + .execute(inventory, topo) + .await?; // Expose RW publicly via TLS passthrough - topo.install_route(self.tls_route.clone()).await.map_err(|e| Box::new(std::io::Error::new(std::io::ErrorKind::Other, e)) as Box)?; + topo.install_route(self.tls_route.clone()) + .await + .map_err(|e| InterpretError::new(e))?; - Ok(()) + Ok(Outcome::success(format!( + "Public CNPG cluster '{}' deployed with TLS passthrough route '{}'", + self.postgres_score.name.clone(), + self.tls_route.hostname + ))) } } @@ -82,8 +106,11 @@ impl Score for PublicPostg } fn name(&self) -> String { - format!("PublicPostgreSQLScore({}:{})", self.inner.namespace, self.hostname) + format!( + "PublicPostgreSQLScore({}:{})", + self.inner.namespace, self.hostname + ) } -} +} // TODO: Add RO route (separate hostname/backend="cluster-ro"), backups, failover logic.