wip: Expose postgres publicly. Created tlsroute capability and postgres implementations
Some checks failed
Run Check Script / check (pull_request) Failing after 41s

This commit is contained in:
2025-12-13 09:47:59 -05:00
parent 2e367d88d4
commit b61e4f9a96
3 changed files with 172 additions and 0 deletions

View File

@@ -1,11 +1,19 @@
use async_trait::async_trait;
use cidr::Ipv4Cidr;
use derive_new::new;
use super::{IpAddress, LogicalHost};
/// Basic network router abstraction (L3 IP routing/gateway).
/// Distinguished from TlsRouter (L4 TLS passthrough).
pub trait Router: Send + Sync {
/// Gateway IP address for this subnet/router.
fn get_gateway(&self) -> IpAddress;
/// CIDR block managed by this router.
fn get_cidr(&self) -> Ipv4Cidr;
/// Logical host associated with this router.
fn get_host(&self) -> LogicalHost;
}
@@ -38,3 +46,76 @@ impl Router for UnmanagedRouter {
todo!()
}
}
#[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;
/// 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
/// target_port: 5432,
/// };
/// ```
pub struct TlsRoute {
/// Public hostname clients connect to (TLS SNI, port 443 implicit).
/// Router matches this for passthrough forwarding.
pub hostname: String,
/// Backend/host identifier (k8s Service, HAProxy upstream, IP/FQDN, etc.).
pub backend: String,
/// Backend TCP port (Postgres: 5432).
pub target_port: u16,
}
#[async_trait]
/// 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
/// // 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 };
/// ```
pub trait TlsRouter: Send + Sync {
/// 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>;
/// Installed route's public hostname.
fn hostname(&self) -> String;
/// Installed route's backend identifier.
fn backend(&self) -> String;
/// Installed route's backend port.
fn target_port(&self) -> u16;
}
impl std::fmt::Debug for dyn TlsRouter {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_fmt(format_args!(
"TlsRouter[hostname={}, backend={}:{}]",
self.hostname(),
self.backend(),
self.target_port()
))
}
}

View File

@@ -1,6 +1,8 @@
pub mod capability;
mod score;
pub use score::*;
mod score_public;
pub use score_public::*;
pub mod failover;
mod operator;

View File

@@ -0,0 +1,89 @@
use async_trait::async_trait;
use serde::Serialize;
use crate::domain::topology::router::{TlsRoute, TlsRouter};
use crate::interpret::Interpret;
use crate::modules::k8s::resource::K8sResourceScore;
use crate::modules::postgresql::cnpg::{Bootstrap, Cluster, ClusterSpec, Initdb, Storage};
use crate::modules::postgresql::PostgreSQLScore;
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;
/// let score = PublicPostgreSQLScore::new("harmony", "pg-rw.example.com");
/// ```
#[derive(Debug, Clone, Serialize)]
pub struct PublicPostgreSQLScore {
/// Inner non-public Postgres cluster config.
pub inner: PostgreSQLScore,
/// Public hostname for RW TLS passthrough (port 443 → cluster-rw:5432).
pub hostname: String,
}
impl Default for PublicPostgreSQLScore {
fn default() -> Self {
Self {
inner: PostgreSQLScore::default(),
hostname: "postgres.default.public".to_string(),
}
}
}
impl PublicPostgreSQLScore {
pub fn new(namespace: &str, hostname: &str) -> Self {
Self {
inner: PostgreSQLScore::new(namespace),
hostname: hostname.to_string(),
}
}
}
/// Custom interpret: deploy Postgres then install public TLS route.
#[derive(Debug, Clone)]
struct PublicPostgreSQLInterpret {
postgres_score: PostgreSQLScore,
tls_route: TlsRoute,
}
#[async_trait]
impl<T: Topology + K8sclient + TlsRouter + Send + Sync> Interpret<T> for PublicPostgreSQLInterpret {
async fn interpret(&self, topo: &T) -> Result<(), Box<dyn std::error::Error>> {
// Deploy CNPG cluster first (creates -rw service)
self.postgres_score.create_interpret().interpret(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<dyn std::error::Error>)?;
Ok(())
}
}
impl<T: Topology + K8sclient + TlsRouter + Send + Sync> Score<T> for PublicPostgreSQLScore {
fn create_interpret(&self) -> Box<dyn Interpret<T>> {
let rw_backend = format!("{}-rw", self.inner.name);
let tls_route = TlsRoute {
hostname: self.hostname.clone(),
backend: rw_backend,
target_port: 5432,
};
Box::new(PublicPostgreSQLInterpret {
postgres_score: self.inner.clone(),
tls_route,
})
}
fn name(&self) -> String {
format!("PublicPostgreSQLScore({}:{})", self.inner.namespace, self.hostname)
}
}
// TODO: Add RO route (separate hostname/backend="cluster-ro"), backups, failover logic.