All checks were successful
Run Check Script / check (pull_request) Successful in 2m21s
97 lines
3.1 KiB
Rust
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(())
|
|
}
|