feat: postgres

This commit is contained in:
Ricky Ng-Adam 2025-07-03 16:46:47 -04:00
parent 6bf10b093c
commit 0b8525fe05
10 changed files with 260 additions and 0 deletions

11
Cargo.lock generated
View File

@ -1351,6 +1351,16 @@ dependencies = [
"url",
]
[[package]]
name = "example-postgres"
version = "0.1.0"
dependencies = [
"async-trait",
"harmony",
"serde",
"tokio",
]
[[package]]
name = "example-rust"
version = "0.1.0"
@ -4807,6 +4817,7 @@ dependencies = [
"bytes",
"libc",
"mio 1.0.4",
"parking_lot",
"pin-project-lite",
"signal-hook-registry",
"socket2",

View File

@ -0,0 +1,10 @@
[package]
name = "example-postgres"
version = "0.1.0"
edition = "2021"
[dependencies]
harmony = { path = "../../harmony" }
tokio = { version = "1", features = ["full"] }
serde = { version = "1.0", features = ["derive"] }
async-trait = "0.1.80"

View File

@ -0,0 +1,84 @@
use async_trait::async_trait;
use harmony::{
data::{PostgresDatabase, PostgresUser},
interpret::InterpretError,
inventory::Inventory,
maestro::Maestro,
modules::postgres::PostgresScore,
topology::{PostgresServer, Topology},
};
use std::error::Error;
#[derive(Debug, Clone)]
struct MockTopology;
#[async_trait]
impl Topology for MockTopology {
fn name(&self) -> &str {
"MockTopology"
}
async fn ensure_ready(&self) -> Result<harmony::interpret::Outcome, InterpretError> {
Ok(harmony::interpret::Outcome::new(
harmony::interpret::InterpretStatus::SUCCESS,
"Mock topology is always ready".to_string(),
))
}
}
#[async_trait]
impl PostgresServer for MockTopology {
async fn ensure_users_exist(&self, users: Vec<PostgresUser>) -> Result<(), InterpretError> {
println!("Ensuring users exist:");
for user in users {
println!(" - {}: {}", user.name, user.password);
}
Ok(())
}
async fn ensure_databases_exist(
&self,
databases: Vec<PostgresDatabase>,
) -> Result<(), InterpretError> {
println!("Ensuring databases exist:");
for db in databases {
println!(" - {}: owner={}", db.name, db.owner);
}
Ok(())
}
}
#[tokio::main]
async fn main() -> Result<(), Box<dyn Error>> {
let users = vec![
PostgresUser {
name: "admin".to_string(),
password: "password".to_string(),
},
PostgresUser {
name: "user".to_string(),
password: "password".to_string(),
},
];
let databases = vec![
PostgresDatabase {
name: "app_db".to_string(),
owner: "admin".to_string(),
},
PostgresDatabase {
name: "user_db".to_string(),
owner: "user".to_string(),
},
];
let postgres_score = PostgresScore::new(users, databases);
let inventory = Inventory::empty();
let topology = MockTopology;
let maestro = Maestro::new(inventory, topology);
maestro.interpret(Box::new(postgres_score)).await?;
Ok(())
}

View File

@ -2,3 +2,6 @@ mod id;
mod version;
pub use id::*;
pub use version::*;
mod postgres;
pub use postgres::*;

View File

@ -0,0 +1,13 @@
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PostgresUser {
pub name: String,
pub password: String, // In a real scenario, this should be a secret type
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PostgresDatabase {
pub name: String,
pub owner: String,
}

View File

@ -22,6 +22,7 @@ pub enum InterpretName {
K3dInstallation,
TenantInterpret,
Application,
Postgres,
}
impl std::fmt::Display for InterpretName {
@ -39,6 +40,7 @@ impl std::fmt::Display for InterpretName {
InterpretName::K3dInstallation => f.write_str("K3dInstallation"),
InterpretName::TenantInterpret => f.write_str("Tenant"),
InterpretName::Application => f.write_str("Application"),
InterpretName::Postgres => f.write_str("Postgres"),
}
}
}

View File

@ -23,6 +23,9 @@ pub use network::*;
use serde::Serialize;
pub use tftp::*;
mod postgres;
pub use postgres::*;
mod helm_command;
pub use helm_command::*;

View File

@ -0,0 +1,14 @@
use crate::{
data::{PostgresDatabase, PostgresUser},
interpret::InterpretError,
};
use async_trait::async_trait;
#[async_trait]
pub trait PostgresServer {
async fn ensure_users_exist(&self, users: Vec<PostgresUser>) -> Result<(), InterpretError>;
async fn ensure_databases_exist(
&self,
databases: Vec<PostgresDatabase>,
) -> Result<(), InterpretError>;
}

View File

@ -16,3 +16,4 @@ pub mod opnsense;
pub mod prometheus;
pub mod tenant;
pub mod tftp;
pub mod postgres;

View File

@ -0,0 +1,119 @@
use async_trait::async_trait;
use derive_new::new;
use log::info;
use serde::{Deserialize, Serialize};
use crate::{
data::{PostgresDatabase, PostgresUser, Version},
interpret::{Interpret, InterpretError, InterpretName, InterpretStatus, Outcome},
inventory::Inventory,
score::Score,
topology::{PostgresServer, Topology},
};
#[derive(Debug, new, Clone, Serialize, Deserialize)]
pub struct PostgresScore {
users: Vec<PostgresUser>,
databases: Vec<PostgresDatabase>,
}
impl<T: Topology + PostgresServer> Score<T> for PostgresScore {
fn create_interpret(&self) -> Box<dyn Interpret<T>> {
Box::new(PostgresInterpret::new(self.clone()))
}
fn name(&self) -> String {
"PostgresScore".to_string()
}
}
#[derive(Debug, Clone)]
pub struct PostgresInterpret {
score: PostgresScore,
version: Version,
status: InterpretStatus,
}
impl PostgresInterpret {
pub fn new(score: PostgresScore) -> Self {
let version = Version::from("1.0.0").expect("Version should be valid");
Self {
version,
score,
status: InterpretStatus::QUEUED,
}
}
async fn ensure_users_exist<P: PostgresServer>(
&self,
postgres_server: &P,
) -> Result<Outcome, InterpretError> {
let users = &self.score.users;
postgres_server.ensure_users_exist(users.clone()).await?;
Ok(Outcome::new(
InterpretStatus::SUCCESS,
format!(
"PostgresInterpret ensured {} users exist successfully",
users.len()
),
))
}
async fn ensure_databases_exist<P: PostgresServer>(
&self,
postgres_server: &P,
) -> Result<Outcome, InterpretError> {
let databases = &self.score.databases;
postgres_server
.ensure_databases_exist(databases.clone())
.await?;
Ok(Outcome::new(
InterpretStatus::SUCCESS,
format!(
"PostgresInterpret ensured {} databases exist successfully",
databases.len()
),
))
}
}
#[async_trait]
impl<T: Topology + PostgresServer> Interpret<T> for PostgresInterpret {
fn get_name(&self) -> InterpretName {
InterpretName::Postgres
}
fn get_version(&self) -> crate::domain::data::Version {
self.version.clone()
}
fn get_status(&self) -> InterpretStatus {
self.status.clone()
}
fn get_children(&self) -> Vec<crate::domain::data::Id> {
todo!()
}
async fn execute(
&self,
inventory: &Inventory,
topology: &T,
) -> Result<Outcome, InterpretError> {
info!(
"Executing {} on inventory {inventory:?})",
<PostgresInterpret as Interpret<T>>::get_name(self)
);
self.ensure_users_exist(topology).await?;
self.ensure_databases_exist(topology).await?;
Ok(Outcome::new(
InterpretStatus::SUCCESS,
"Postgres Interpret execution successful".to_string(),
))
}
}