Files
harmony/harmony_config/examples/openbao_chain.rs
Jean-Gabriel Gill-Couture f37ab6aba1
All checks were successful
Run Check Script / check (pull_request) Successful in 2m21s
feat: harmony_config densification of code and comments, remove much bloat
2026-05-29 12:12:41 -04:00

97 lines
3.1 KiB
Rust

//! Dev-binary template: `ConfigClient` against an OpenBao-backed chain.
//!
//! Bring up the OpenBao + Zitadel stack first (`cargo run -p
//! example-harmony-sso`), then export the connection env vars and run this:
//!
//! ```bash
//! export OPENBAO_URL=http://bao.harmony.local:8080
//! export HARMONY_SSO_URL=http://sso.harmony.local:8080
//! export HARMONY_SSO_CLIENT_ID=<the harmony-cli client id> # or OPENBAO_TOKEN=<token>
//! cargo run -p harmony_config --example openbao_chain
//! ```
//!
//! If OpenBao is unreachable the chain degrades to env → prompt, so the
//! round-trip steps below need a reachable OpenBao to persist.
use harmony_config::{Config, ConfigClient, ConfigExt};
use log::info;
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Config)]
struct AppConfig {
host: String,
port: u16,
}
impl Default for AppConfig {
fn default() -> Self {
Self {
host: "production.example.com".to_string(),
port: 443,
}
}
}
// `password` is `#[config(secret)]`, so the whole struct is Secret-class:
// masked in logs and prompted via `inquire::Password`.
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Config)]
struct DatabaseCredentials {
host: String,
username: String,
#[config(secret)]
password: String,
}
impl Default for DatabaseCredentials {
fn default() -> Self {
Self {
host: "db.example.com".to_string(),
username: "app_rw".to_string(),
password: "rotate-me-please".to_string(),
}
}
}
// No `Default`, so `get_or_prompt` falls through to an interactive prompt.
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Config)]
struct ApiCredentials {
client_id: String,
#[config(secret)]
client_secret: String,
}
// Read `T`, or store `default` and read it back to prove the round-trip.
async fn round_trip<T>(client: &ConfigClient, default: T) -> anyhow::Result<()>
where
T: Config + std::fmt::Debug + Clone + PartialEq,
{
match client.get::<T>().await {
Ok(found) => info!("[{}] read existing: {:?}", T::KEY, found.masked()),
Err(harmony_config::ConfigError::NotFound { .. }) => {
client.set(&default).await?;
let back: T = client.get().await?;
anyhow::ensure!(back == default, "round-trip mismatch for {}", T::KEY);
info!("[{}] stored + verified: {:?}", T::KEY, back.masked());
}
Err(e) => return Err(e.into()),
}
Ok(())
}
#[tokio::main]
async fn main() -> anyhow::Result<()> {
env_logger::Builder::from_env(env_logger::Env::default().default_filter_or("info")).init();
let client = ConfigClient::for_namespace("harmony").await;
round_trip(&client, AppConfig::default()).await?;
round_trip(&client, DatabaseCredentials::default()).await?;
// No default + Secret class: prompts, with `client_secret` read via Password.
let api: ApiCredentials = client.get_or_prompt().await?;
info!("[{}] resolved: {:?}", ApiCredentials::KEY, api.masked());
Ok(())
}