Make some more changes to machine setup
Work being done as an attempt to be able to
create a small monitoring server
Former-commit-id: f7d4bef46c
This commit is contained in:
parent
84818b01be
commit
07360c41da
17 changed files with 449 additions and 289 deletions
108
rust/program/openbao-helper/src/enventry.rs
Normal file
108
rust/program/openbao-helper/src/enventry.rs
Normal file
|
@ -0,0 +1,108 @@
|
|||
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)
|
||||
}
|
|
@ -2,7 +2,10 @@ use std::{collections::BTreeSet, ffi::CString};
|
|||
|
||||
use anyhow::Context as _;
|
||||
use clap::{Parser, Subcommand};
|
||||
use serde::Deserialize;
|
||||
|
||||
mod enventry;
|
||||
|
||||
use enventry::*;
|
||||
|
||||
fn main() {
|
||||
common::entrypoint(program);
|
||||
|
@ -45,6 +48,10 @@ pub enum Endpoint {
|
|||
Hcloud,
|
||||
#[value(name = "unifi")]
|
||||
Unifi,
|
||||
#[value(name = "vault")]
|
||||
Vault,
|
||||
#[value(name = "authentik")]
|
||||
Authentik,
|
||||
}
|
||||
|
||||
impl Endpoint {
|
||||
|
@ -52,23 +59,31 @@ impl Endpoint {
|
|||
match self {
|
||||
Self::Openstack => {
|
||||
let data = OpenstackData::read_from_bao()?;
|
||||
Ok(data.into_env_data())
|
||||
Ok(data.into())
|
||||
}
|
||||
Self::Aws => {
|
||||
let data = AwsData::read_from_bao()?;
|
||||
Ok(data.into_env_data())
|
||||
Ok(data.into())
|
||||
}
|
||||
Self::Hcloud => {
|
||||
let data = HcloudData::read_from_bao()?;
|
||||
Ok(data.into_env_data())
|
||||
Ok(data.into())
|
||||
}
|
||||
Self::Cloudflare => {
|
||||
let data = CloudflareData::read_from_bao()?;
|
||||
Ok(data.into_env_data())
|
||||
Ok(data.into())
|
||||
}
|
||||
Self::Unifi => {
|
||||
let data = UnifiData::read_from_bao()?;
|
||||
Ok(data.into_env_data())
|
||||
Ok(data.into())
|
||||
}
|
||||
Self::Authentik => {
|
||||
let data = AuthentikData::read_from_bao()?;
|
||||
Ok(data.into())
|
||||
}
|
||||
Self::Vault => {
|
||||
let data = VaultData::read_from_bao()?;
|
||||
Ok(data.into())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -82,267 +97,88 @@ fn program() -> anyhow::Result<()> {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
struct OpenBaoKvEntry<T> {
|
||||
data: OpenBaoKvEntryData<T>,
|
||||
macro_rules! entry_definition {
|
||||
($config_id:ident, $id: ident, $bao_key: expr, $secrets: expr) => {
|
||||
struct $config_id;
|
||||
|
||||
impl EnvEntryConfig for $config_id {
|
||||
const SECRETS: &'static [&'static str] = $secrets;
|
||||
const BAO_KEY: &'static str = $bao_key;
|
||||
}
|
||||
|
||||
type $id = EnvEntry<$config_id>;
|
||||
};
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
struct OpenBaoKvEntryData<T> {
|
||||
data: T,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
struct OpenstackData {
|
||||
username: String,
|
||||
password: String,
|
||||
tenant_name: String,
|
||||
auth_url: String,
|
||||
endpoint_type: String,
|
||||
region: String,
|
||||
}
|
||||
|
||||
fn read_bao_data<T: for<'de> Deserialize<'de>>(key: &str) -> anyhow::Result<T> {
|
||||
let mut cmd = common::proc::Command::new("bao");
|
||||
cmd.args(["kv", "get", "-format=json", "-mount=opentofu", key]);
|
||||
let result: OpenBaoKvEntry<T> = cmd.try_spawn_to_json()?;
|
||||
Ok(result.data.data)
|
||||
}
|
||||
|
||||
impl OpenstackData {
|
||||
pub fn read_from_env() -> anyhow::Result<Self> {
|
||||
let username = common::env::read_env("TF_VAR_openstack_username")?;
|
||||
let password = common::env::read_env("TF_VAR_openstack_password")?;
|
||||
let tenant_name = common::env::read_env("TF_VAR_openstack_tenant_name")?;
|
||||
let auth_url = common::env::read_env("TF_VAR_openstack_auth_url")?;
|
||||
let endpoint_type = common::env::read_env("TF_VAR_openstack_endpoint_type")?;
|
||||
let region = common::env::read_env("TF_VAR_openstack_region")?;
|
||||
Ok(Self {
|
||||
username,
|
||||
password,
|
||||
tenant_name,
|
||||
auth_url,
|
||||
endpoint_type,
|
||||
region,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn read_from_bao() -> anyhow::Result<Self> {
|
||||
let data = read_bao_data("openstack")?;
|
||||
Ok(data)
|
||||
}
|
||||
|
||||
pub fn into_env_data(self) -> Vec<(&'static str, String)> {
|
||||
vec![
|
||||
("TF_VAR_openstack_username", self.username),
|
||||
("TF_VAR_openstack_password", self.password),
|
||||
("TF_VAR_openstack_tenant_name", self.tenant_name),
|
||||
("TF_VAR_openstack_auth_url", self.auth_url),
|
||||
("TF_VAR_openstack_endpoint_type", self.endpoint_type),
|
||||
("TF_VAR_openstack_region", self.region),
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
impl IntoIterator for OpenstackData {
|
||||
type Item = (&'static str, String);
|
||||
|
||||
type IntoIter = <Vec<(&'static str, String)> as IntoIterator>::IntoIter;
|
||||
|
||||
fn into_iter(self) -> Self::IntoIter {
|
||||
vec![
|
||||
("username", self.username),
|
||||
("password", self.password),
|
||||
("tenant_name", self.tenant_name),
|
||||
("auth_url", self.auth_url),
|
||||
("endpoint_type", self.endpoint_type),
|
||||
("region", self.region),
|
||||
]
|
||||
.into_iter()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
struct CloudflareData {
|
||||
token: String,
|
||||
email: String,
|
||||
}
|
||||
|
||||
impl CloudflareData {
|
||||
pub fn read_from_env() -> anyhow::Result<Self> {
|
||||
let token = common::env::read_env("TF_VAR_cloudflare_token")?;
|
||||
let email = common::env::read_env("TF_VAR_cloudflare_email")?;
|
||||
Ok(Self { token, email })
|
||||
}
|
||||
|
||||
pub fn read_from_bao() -> anyhow::Result<Self> {
|
||||
let data = read_bao_data("cloudflare")?;
|
||||
Ok(data)
|
||||
}
|
||||
|
||||
pub fn into_env_data(self) -> Vec<(&'static str, String)> {
|
||||
vec![
|
||||
("TF_VAR_cloudflare_token", self.token),
|
||||
("TF_VAR_cloudflare_email", self.email),
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
impl IntoIterator for CloudflareData {
|
||||
type Item = (&'static str, String);
|
||||
|
||||
type IntoIter = <Vec<(&'static str, String)> as IntoIterator>::IntoIter;
|
||||
|
||||
fn into_iter(self) -> Self::IntoIter {
|
||||
vec![("token", self.token), ("email", self.email)].into_iter()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
struct AwsData {
|
||||
key_id: String,
|
||||
secret_access_key: String,
|
||||
}
|
||||
|
||||
impl AwsData {
|
||||
pub fn read_from_env() -> anyhow::Result<Self> {
|
||||
let key_id = common::env::read_env("AWS_ACCESS_KEY_ID")?;
|
||||
let secret_access_key = common::env::read_env("AWS_SECRET_ACCESS_KEY")?;
|
||||
Ok(Self {
|
||||
key_id,
|
||||
secret_access_key,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn read_from_bao() -> anyhow::Result<Self> {
|
||||
let data = read_bao_data("aws")?;
|
||||
Ok(data)
|
||||
}
|
||||
|
||||
pub fn into_env_data(self) -> Vec<(&'static str, String)> {
|
||||
vec![
|
||||
("AWS_ACCESS_KEY_ID", self.key_id),
|
||||
("AWS_SECRET_ACCESS_KEY", self.secret_access_key),
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
impl IntoIterator for AwsData {
|
||||
type Item = (&'static str, String);
|
||||
|
||||
type IntoIter = <Vec<(&'static str, String)> as IntoIterator>::IntoIter;
|
||||
|
||||
fn into_iter(self) -> Self::IntoIter {
|
||||
vec![
|
||||
("key_id", self.key_id),
|
||||
("secret_access_key", self.secret_access_key),
|
||||
]
|
||||
.into_iter()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
struct HcloudData {
|
||||
api_token: String,
|
||||
}
|
||||
|
||||
impl HcloudData {
|
||||
pub fn read_from_env() -> anyhow::Result<Self> {
|
||||
let api_token = common::env::read_env("TF_VAR_hcloud_api_token")?;
|
||||
Ok(Self { api_token })
|
||||
}
|
||||
|
||||
pub fn read_from_bao() -> anyhow::Result<Self> {
|
||||
let data = read_bao_data("hcloud")?;
|
||||
Ok(data)
|
||||
}
|
||||
|
||||
pub fn into_env_data(self) -> Vec<(&'static str, String)> {
|
||||
vec![("TF_VAR_hcloud_api_token", self.api_token)]
|
||||
}
|
||||
}
|
||||
|
||||
impl IntoIterator for HcloudData {
|
||||
type Item = (&'static str, String);
|
||||
|
||||
type IntoIter = <Vec<(&'static str, String)> as IntoIterator>::IntoIter;
|
||||
|
||||
fn into_iter(self) -> Self::IntoIter {
|
||||
vec![("api_token", self.api_token)].into_iter()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
struct UnifiData {
|
||||
username: String,
|
||||
password: String,
|
||||
url: String,
|
||||
}
|
||||
|
||||
impl UnifiData {
|
||||
pub fn read_from_env() -> anyhow::Result<Self> {
|
||||
let username = common::env::read_env("UNIFI_USERNAME")?;
|
||||
let password = common::env::read_env("UNIFI_PASSWORD")?;
|
||||
let url = common::env::read_env("UNIFI_API")?;
|
||||
Ok(Self {
|
||||
username,
|
||||
password,
|
||||
url,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn read_from_bao() -> anyhow::Result<Self> {
|
||||
let data = read_bao_data("unifi")?;
|
||||
Ok(data)
|
||||
}
|
||||
|
||||
pub fn into_env_data(self) -> Vec<(&'static str, String)> {
|
||||
vec![
|
||||
("UNIFI_USERNAME", self.username),
|
||||
("UNIFI_PASSWORD", self.password),
|
||||
("UNIFI_API", self.url),
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
impl IntoIterator for UnifiData {
|
||||
type Item = (&'static str, String);
|
||||
|
||||
type IntoIter = <Vec<(&'static str, String)> as IntoIterator>::IntoIter;
|
||||
|
||||
fn into_iter(self) -> Self::IntoIter {
|
||||
vec![
|
||||
("username", self.username),
|
||||
("password", self.password),
|
||||
("url", self.url),
|
||||
]
|
||||
.into_iter()
|
||||
}
|
||||
}
|
||||
entry_definition!(
|
||||
OpenstackDataConfig,
|
||||
OpenstackData,
|
||||
"openstack",
|
||||
&[
|
||||
"TF_VAR_openstack_username",
|
||||
"TF_VAR_openstack_password",
|
||||
"TF_VAR_openstack_tenant_name",
|
||||
"TF_VAR_openstack_auth_url",
|
||||
"TF_VAR_openstack_endpoint_type",
|
||||
"TF_VAR_openstack_region"
|
||||
]
|
||||
);
|
||||
entry_definition!(
|
||||
CloudflareDataConfig,
|
||||
CloudflareData,
|
||||
"cloudflare",
|
||||
&["TF_VAR_cloudflare_token", "TF_VAR_cloudflare_email"]
|
||||
);
|
||||
entry_definition!(
|
||||
AwsDataConfig,
|
||||
AwsData,
|
||||
"aws",
|
||||
&["AWS_ACCESS_KEY_ID", "AWS_SECRET_ACCESS_KEY"]
|
||||
);
|
||||
entry_definition!(
|
||||
HcloudDataConfig,
|
||||
HcloudData,
|
||||
"hcloud",
|
||||
&["TF_VAR_hcloud_api_token"]
|
||||
);
|
||||
entry_definition!(
|
||||
UnifiDataConfig,
|
||||
UnifiData,
|
||||
"unifi",
|
||||
&["UNIFI_USERNAME", "UNIFI_PASSWORD", "UNIFI_API"]
|
||||
);
|
||||
entry_definition!(VaultDataConfig, VaultData, "vault", &["VAULT_TOKEN"]);
|
||||
entry_definition!(
|
||||
AuthentikDataConfig,
|
||||
AuthentikData,
|
||||
"authentik",
|
||||
&["AUTHENTIK_TOKEN", "TF_VAR_authentik_username"]
|
||||
);
|
||||
|
||||
fn transfer() -> anyhow::Result<()> {
|
||||
let openstack = OpenstackData::read_from_env()?;
|
||||
let cloudflare = CloudflareData::read_from_env()?;
|
||||
let aws = AwsData::read_from_env()?;
|
||||
let hcloud = HcloudData::read_from_env()?;
|
||||
let unifi = UnifiData::read_from_env()?;
|
||||
let openstack = OpenstackData::try_new_from_env()?;
|
||||
let cloudflare = CloudflareData::try_new_from_env()?;
|
||||
let aws = AwsData::try_new_from_env()?;
|
||||
let hcloud = HcloudData::try_new_from_env()?;
|
||||
let unifi = UnifiData::try_new_from_env()?;
|
||||
let authentik = AuthentikData::try_new_from_env()?;
|
||||
let vault = VaultData::try_new_from_env()?;
|
||||
|
||||
write_kv_data("openstack", openstack)?;
|
||||
write_kv_data("cloudflare", cloudflare)?;
|
||||
write_kv_data("aws", aws)?;
|
||||
write_kv_data("hcloud", hcloud)?;
|
||||
write_kv_data("unifi", unifi)?;
|
||||
write_kv_data(openstack)?;
|
||||
write_kv_data(cloudflare)?;
|
||||
write_kv_data(aws)?;
|
||||
write_kv_data(hcloud)?;
|
||||
write_kv_data(unifi)?;
|
||||
write_kv_data(authentik)?;
|
||||
write_kv_data(vault)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn write_kv_data(
|
||||
key: &str,
|
||||
data: impl IntoIterator<Item = (&'static str, String)>,
|
||||
) -> anyhow::Result<()> {
|
||||
fn write_kv_data<T: EnvEntryConfig>(entry: EnvEntry<T>) -> anyhow::Result<()> {
|
||||
let mut cmd = common::proc::Command::new("bao");
|
||||
cmd.args(["kv", "put", "-mount=opentofu"]);
|
||||
cmd.arg(key);
|
||||
for (key, value) in data {
|
||||
cmd.arg(T::BAO_KEY);
|
||||
for (key, value) in entry {
|
||||
cmd.arg(format!("{key}={value}"));
|
||||
}
|
||||
cmd.try_spawn_to_string()?;
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue