109 lines
3.1 KiB
Rust
109 lines
3.1 KiB
Rust
|
use std::{collections::BTreeMap, marker::PhantomData, vec::IntoIter};
|
||
|
|
||
|
use serde::Deserialize;
|
||
|
|
||
|
pub trait EnvEntryConfig {
|
||
|
const SECRETS: &'static [&'static str];
|
||
|
const BAO_KEY: &'static str;
|
||
|
}
|
||
|
|
||
|
pub struct EnvEntry<T>(Vec<(&'static str, String)>, PhantomData<T>);
|
||
|
|
||
|
impl<T: EnvEntryConfig> EnvEntry<T> {
|
||
|
pub fn try_new_from_env() -> anyhow::Result<Self> {
|
||
|
let mut result = Vec::with_capacity(T::SECRETS.len());
|
||
|
for key in T::SECRETS {
|
||
|
let value = common::env::read_env(key)?;
|
||
|
result.push((*key, value));
|
||
|
}
|
||
|
Ok(Self(result, PhantomData))
|
||
|
}
|
||
|
|
||
|
fn new_from_values(values: Vec<(&'static str, String)>) -> Self {
|
||
|
Self(values, PhantomData)
|
||
|
}
|
||
|
|
||
|
pub fn read_from_bao() -> anyhow::Result<Self> {
|
||
|
read_bao_data()
|
||
|
}
|
||
|
}
|
||
|
|
||
|
impl<T> From<EnvEntry<T>> for Vec<(&'static str, String)> {
|
||
|
fn from(value: EnvEntry<T>) -> Self {
|
||
|
value.0
|
||
|
}
|
||
|
}
|
||
|
|
||
|
impl<T> IntoIterator for EnvEntry<T> {
|
||
|
type Item = (&'static str, String);
|
||
|
|
||
|
type IntoIter = IntoIter<Self::Item>;
|
||
|
|
||
|
fn into_iter(self) -> Self::IntoIter {
|
||
|
self.0.into_iter()
|
||
|
}
|
||
|
}
|
||
|
|
||
|
impl<'de, T: EnvEntryConfig> serde::Deserialize<'de> for EnvEntry<T> {
|
||
|
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||
|
where
|
||
|
D: serde::Deserializer<'de>,
|
||
|
{
|
||
|
deserializer.deserialize_map(EnvEntryVisitor(PhantomData))
|
||
|
}
|
||
|
}
|
||
|
struct EnvEntryVisitor<T>(PhantomData<T>);
|
||
|
|
||
|
impl<'de, T: EnvEntryConfig> serde::de::Visitor<'de> for EnvEntryVisitor<T> {
|
||
|
type Value = EnvEntry<T>;
|
||
|
|
||
|
fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||
|
formatter.write_fmt(format_args!(
|
||
|
"a map with unique keys {} with string values",
|
||
|
T::SECRETS.join(", "),
|
||
|
))
|
||
|
}
|
||
|
|
||
|
fn visit_map<A>(self, mut map: A) -> Result<Self::Value, A::Error>
|
||
|
where
|
||
|
A: serde::de::MapAccess<'de>,
|
||
|
{
|
||
|
let mut values = BTreeMap::<&'static str, String>::new();
|
||
|
while let Some((key, value)) = map.next_entry::<&'de str, String>()? {
|
||
|
let mapped_key = T::SECRETS.iter().find(|n| **n == key).copied();
|
||
|
let Some(key) = mapped_key else {
|
||
|
return Err(serde::de::Error::unknown_field(key, T::SECRETS));
|
||
|
};
|
||
|
if values.contains_key(key) {
|
||
|
return Err(serde::de::Error::duplicate_field(key));
|
||
|
}
|
||
|
values.insert(key, value);
|
||
|
}
|
||
|
for key in T::SECRETS {
|
||
|
if !values.contains_key(key) {
|
||
|
return Err(serde::de::Error::missing_field(key));
|
||
|
}
|
||
|
}
|
||
|
let values = values.into_iter().collect();
|
||
|
let entry = EnvEntry::<T>::new_from_values(values);
|
||
|
Ok(entry)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
#[derive(Debug, Deserialize)]
|
||
|
struct OpenBaoKvEntry<T> {
|
||
|
data: OpenBaoKvEntryData<T>,
|
||
|
}
|
||
|
|
||
|
#[derive(Debug, Deserialize)]
|
||
|
struct OpenBaoKvEntryData<T> {
|
||
|
data: T,
|
||
|
}
|
||
|
|
||
|
fn read_bao_data<T: EnvEntryConfig>() -> anyhow::Result<EnvEntry<T>> {
|
||
|
let mut cmd = common::proc::Command::new("bao");
|
||
|
cmd.args(["kv", "get", "-format=json", "-mount=opentofu", T::BAO_KEY]);
|
||
|
let result: OpenBaoKvEntry<EnvEntry<T>> = cmd.try_spawn_to_json()?;
|
||
|
Ok(result.data.data)
|
||
|
}
|