WIP
This commit is contained in:
3
crete/.cargo/config.toml
Normal file
3
crete/.cargo/config.toml
Normal file
@@ -0,0 +1,3 @@
|
||||
[target.x86_64-unknown-linux-gnu]
|
||||
linker = "clang"
|
||||
rustflags = ["-C", "link-arg=-fuse-ld=mold"]
|
||||
1
crete/.gitignore
vendored
Normal file
1
crete/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
target/
|
||||
2659
crete/Cargo.lock
generated
Normal file
2659
crete/Cargo.lock
generated
Normal file
File diff suppressed because it is too large
Load Diff
20
crete/Cargo.toml
Normal file
20
crete/Cargo.toml
Normal file
@@ -0,0 +1,20 @@
|
||||
[package]
|
||||
name = "crete"
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
default-run = "crete"
|
||||
|
||||
[dependencies]
|
||||
clap = { version = "4.5.60", features = ["derive"] }
|
||||
gix-discover = "0.48.0"
|
||||
minijinja = { version = "2.17.1", features = ["json", "loader"] }
|
||||
optional_struct = "0.5.2"
|
||||
regress = "0.11.0"
|
||||
reqwest = { version = "0.13.2", features = ["blocking"] }
|
||||
schemars = { version = "1.2.1", features = ["semver1"] }
|
||||
semver = { version = "1.0.27", features = ["serde"] }
|
||||
serde = { version = "1.0.228", features = ["derive"] }
|
||||
serde_context = "0.1.0"
|
||||
serde_json = "1.0.149"
|
||||
serde_yaml = "0.9.34"
|
||||
walkdir = "2.5.0"
|
||||
4
crete/rust-toolchain.toml
Normal file
4
crete/rust-toolchain.toml
Normal file
@@ -0,0 +1,4 @@
|
||||
[toolchain]
|
||||
channel = "nightly-2026-03-12"
|
||||
components = ["rustfmt", "clippy", "rust-analyzer"]
|
||||
profile = "minimal"
|
||||
23
crete/src/bin/schemas.rs
Normal file
23
crete/src/bin/schemas.rs
Normal file
@@ -0,0 +1,23 @@
|
||||
use std::{fs::File, io::Write};
|
||||
|
||||
use crete::{cluster::Cluster, node::OptionalNode};
|
||||
use repo_path_lib::repo_dir;
|
||||
use schemars::{JsonSchema, schema_for};
|
||||
|
||||
fn write<T>(name: &str)
|
||||
where
|
||||
T: JsonSchema,
|
||||
{
|
||||
let mut path = repo_dir().join("schemas").join(name);
|
||||
path.add_extension("json");
|
||||
let mut file = File::create(path).unwrap();
|
||||
|
||||
let schema = serde_json::to_string_pretty(&schema_for!(T)).unwrap();
|
||||
file.write_all(schema.as_bytes()).unwrap();
|
||||
}
|
||||
|
||||
// TODO: Create directory if it does not exist
|
||||
fn main() {
|
||||
write::<Cluster>("cluster");
|
||||
write::<OptionalNode>("node");
|
||||
}
|
||||
71
crete/src/cluster.rs
Normal file
71
crete/src/cluster.rs
Normal file
@@ -0,0 +1,71 @@
|
||||
use std::net::Ipv4Addr;
|
||||
|
||||
use minijinja::Environment;
|
||||
use schemars::JsonSchema;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::{
|
||||
get_talos_config_path,
|
||||
node::{Node, OptionalNode},
|
||||
patch::Patches,
|
||||
};
|
||||
|
||||
#[derive(Debug, Deserialize, JsonSchema, Clone)]
|
||||
#[serde(rename_all = "camelCase", deny_unknown_fields)]
|
||||
pub struct Base {
|
||||
#[serde(default)]
|
||||
pub(crate) kernel_args: Vec<String>,
|
||||
#[serde(default)]
|
||||
pub(crate) patches: Patches,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, JsonSchema, Clone)]
|
||||
#[serde(rename_all = "camelCase", deny_unknown_fields)]
|
||||
pub struct Version {
|
||||
kubernetes: semver::Version,
|
||||
talos: semver::Version,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, JsonSchema, Clone)]
|
||||
#[serde(rename_all = "camelCase", deny_unknown_fields)]
|
||||
pub enum ClusterEnv {
|
||||
Production,
|
||||
Staging,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, JsonSchema, Clone)]
|
||||
#[serde(rename_all = "camelCase", deny_unknown_fields)]
|
||||
pub struct Cluster {
|
||||
#[serde(skip_deserializing)]
|
||||
name: String,
|
||||
version: Version,
|
||||
nodes: Vec<String>,
|
||||
cluster_env: ClusterEnv,
|
||||
control_plane_ip: Ipv4Addr,
|
||||
secrets_file: String,
|
||||
#[serde(default, skip_serializing)]
|
||||
pub(crate) default: OptionalNode,
|
||||
#[serde(skip_serializing)]
|
||||
pub(crate) base: Base,
|
||||
// pub secrets_file: PathBuf,
|
||||
}
|
||||
|
||||
impl Cluster {
|
||||
pub fn get(cluster_name: &str) -> Self {
|
||||
let mut path = get_talos_config_path().join("clusters").join(cluster_name);
|
||||
path.add_extension("yaml");
|
||||
let content = std::fs::read_to_string(path).unwrap();
|
||||
|
||||
let mut cluster: Self = serde_yaml::from_str(&content).unwrap();
|
||||
cluster.name = cluster_name.to_string();
|
||||
|
||||
cluster
|
||||
}
|
||||
|
||||
pub fn nodes(&self, env: &Environment) -> Vec<Node> {
|
||||
self.nodes
|
||||
.iter()
|
||||
.map(|node_name| Node::get(node_name, env, self))
|
||||
.collect()
|
||||
}
|
||||
}
|
||||
96
crete/src/environment.rs
Normal file
96
crete/src/environment.rs
Normal file
@@ -0,0 +1,96 @@
|
||||
use std::{
|
||||
net::Ipv4Addr,
|
||||
ops::Deref,
|
||||
path::{Path, PathBuf},
|
||||
str::FromStr,
|
||||
};
|
||||
|
||||
use minijinja::{AutoEscape, Environment, path_loader};
|
||||
use walkdir::WalkDir;
|
||||
|
||||
use crate::{get_repo_path, get_talos_config_path};
|
||||
|
||||
/// Transparent wrapper around minijinja::Environment that loads templates from a path and
|
||||
/// configures better defaults. It also implements IntoIter, making it possible to iterate over all
|
||||
/// the templates.
|
||||
pub struct PathEnvironment<'a> {
|
||||
env: Environment<'a>,
|
||||
path: PathBuf,
|
||||
}
|
||||
|
||||
impl<'a> PathEnvironment<'a> {
|
||||
pub fn new(path: &Path) -> Self {
|
||||
let mut env = Environment::new();
|
||||
|
||||
// Configure jinja
|
||||
env.set_trim_blocks(true);
|
||||
env.set_lstrip_blocks(true);
|
||||
env.set_undefined_behavior(minijinja::UndefinedBehavior::Strict);
|
||||
env.set_auto_escape_callback(|_| AutoEscape::None);
|
||||
|
||||
// Add filters
|
||||
env.add_filter("to_prefix", |netmask: String| {
|
||||
let netmask = Ipv4Addr::from_str(&netmask).map_err(|err| {
|
||||
minijinja::Error::new(minijinja::ErrorKind::InvalidOperation, err.to_string())
|
||||
})?;
|
||||
let mask = netmask.to_bits();
|
||||
let prefix = mask.leading_ones();
|
||||
|
||||
if mask.checked_shl(prefix).unwrap_or(0) == 0 {
|
||||
Ok(prefix as u8)
|
||||
} else {
|
||||
Err(minijinja::Error::new(
|
||||
minijinja::ErrorKind::InvalidOperation,
|
||||
"invalid IP prefix length",
|
||||
))
|
||||
}
|
||||
});
|
||||
|
||||
// Add path loader
|
||||
env.set_loader(path_loader(&path));
|
||||
|
||||
Self {
|
||||
path: path.into(),
|
||||
env,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new_patches() -> Self {
|
||||
Self::new(&get_talos_config_path().join("patches"))
|
||||
}
|
||||
|
||||
pub fn new_templates() -> Self {
|
||||
Self::new(&get_repo_path().join("templates"))
|
||||
}
|
||||
}
|
||||
|
||||
// Make PathEnvironment act like a normal Environment transparently
|
||||
impl<'a> Deref for PathEnvironment<'a> {
|
||||
type Target = Environment<'a>;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.env
|
||||
}
|
||||
}
|
||||
|
||||
// Iterate over all the files in the path
|
||||
impl<'a, 'b> IntoIterator for &'b PathEnvironment<'a> {
|
||||
type Item = String;
|
||||
|
||||
type IntoIter = impl Iterator<Item = Self::Item>;
|
||||
|
||||
fn into_iter(self) -> Self::IntoIter {
|
||||
// Find all templates in path
|
||||
WalkDir::new(&self.path)
|
||||
.into_iter()
|
||||
.filter_map(|e| e.ok())
|
||||
.filter(|e| e.metadata().unwrap().is_file())
|
||||
.map(|e| {
|
||||
e.path()
|
||||
.strip_prefix(&self.path)
|
||||
.expect("All paths should have prefix")
|
||||
.to_string_lossy()
|
||||
.to_string()
|
||||
})
|
||||
}
|
||||
}
|
||||
28
crete/src/lib.rs
Normal file
28
crete/src/lib.rs
Normal file
@@ -0,0 +1,28 @@
|
||||
#![feature(impl_trait_in_assoc_type)]
|
||||
use std::{
|
||||
path::{Path, PathBuf},
|
||||
sync::OnceLock,
|
||||
};
|
||||
|
||||
pub mod cluster;
|
||||
pub mod environment;
|
||||
pub mod node;
|
||||
pub mod patch;
|
||||
pub mod schematic;
|
||||
pub mod secret;
|
||||
|
||||
pub(crate) static REPO_PATH: OnceLock<PathBuf> = OnceLock::new();
|
||||
|
||||
pub fn set_repo_path(path: impl Into<PathBuf>) {
|
||||
REPO_PATH
|
||||
.set(path.into())
|
||||
.expect("Repo path already initialized");
|
||||
}
|
||||
|
||||
fn get_repo_path() -> &'static Path {
|
||||
REPO_PATH.get().expect("Repo path not initialized")
|
||||
}
|
||||
|
||||
fn get_talos_config_path() -> PathBuf {
|
||||
get_repo_path().join("talos")
|
||||
}
|
||||
86
crete/src/main.rs
Normal file
86
crete/src/main.rs
Normal file
@@ -0,0 +1,86 @@
|
||||
use std::path::PathBuf;
|
||||
|
||||
use clap::{Args, Parser, Subcommand};
|
||||
use crete::{cluster::Cluster, environment::PathEnvironment, set_repo_path};
|
||||
use gix_discover::repository::Path;
|
||||
use minijinja::context;
|
||||
|
||||
#[derive(Debug, Parser)]
|
||||
#[command(version, about)]
|
||||
#[command(propagate_version = true)]
|
||||
struct Cli {
|
||||
#[command(flatten)]
|
||||
global_opts: GlobalOpts,
|
||||
|
||||
#[command(subcommand)]
|
||||
command: Commands,
|
||||
}
|
||||
|
||||
#[derive(Debug, Args)]
|
||||
struct GlobalOpts {
|
||||
#[arg(global = true, short, long, action = clap::ArgAction::Count)]
|
||||
/// Use verbose output
|
||||
verbose: u8,
|
||||
|
||||
#[arg(global = true, short, long, value_parser = path_is_repo, default_value_os_t = PathBuf::from("."))]
|
||||
/// Path to the repo containing the config files
|
||||
repo: PathBuf,
|
||||
}
|
||||
|
||||
fn path_is_repo(path: &str) -> Result<PathBuf, String> {
|
||||
let path = PathBuf::from(path);
|
||||
let (repo, _trust) = gix_discover::upwards(&path).map_err(|err| err.to_string())?;
|
||||
|
||||
let work_dir = match repo {
|
||||
Path::LinkedWorkTree { work_dir, .. } => work_dir,
|
||||
Path::WorkTree(work_dir) => work_dir,
|
||||
Path::Repository(git_dir) => {
|
||||
return Err(format!(
|
||||
"Repo '{}' has no working directory",
|
||||
git_dir.to_string_lossy()
|
||||
));
|
||||
}
|
||||
};
|
||||
|
||||
Ok(work_dir)
|
||||
}
|
||||
|
||||
#[derive(Debug, Subcommand)]
|
||||
enum Commands {
|
||||
/// Generate talos config file
|
||||
Generate,
|
||||
}
|
||||
|
||||
fn generate(opts: &GlobalOpts) {
|
||||
set_repo_path(&opts.repo);
|
||||
|
||||
let cluster = Cluster::get("testing");
|
||||
|
||||
let patch_env = PathEnvironment::new_patches();
|
||||
let nodes = cluster.nodes(&patch_env);
|
||||
|
||||
let template_env = PathEnvironment::new_templates();
|
||||
|
||||
for template_name in &template_env {
|
||||
let template = template_env.get_template(&template_name).unwrap();
|
||||
|
||||
let result = template
|
||||
.render(context! {
|
||||
cluster,
|
||||
nodes,
|
||||
root => opts.repo
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
println!("###### {template_name} ######");
|
||||
println!("{result}");
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let cli = Cli::parse();
|
||||
|
||||
match cli.command {
|
||||
Commands::Generate => generate(&cli.global_opts),
|
||||
}
|
||||
}
|
||||
140
crete/src/node.rs
Normal file
140
crete/src/node.rs
Normal file
@@ -0,0 +1,140 @@
|
||||
use std::net::Ipv4Addr;
|
||||
|
||||
use minijinja::Environment;
|
||||
use optional_struct::{Applicable, optional_struct};
|
||||
use schemars::JsonSchema;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::{
|
||||
cluster::Cluster,
|
||||
get_talos_config_path,
|
||||
patch::{OptionalPatches, Patches},
|
||||
schematic::Schematic,
|
||||
secret::Secret,
|
||||
};
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, JsonSchema, Clone, Copy, PartialEq, Eq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
enum NodeType {
|
||||
Worker,
|
||||
#[serde(rename(serialize = "controlplane"))]
|
||||
ControlPlane,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, JsonSchema, Clone, Copy, PartialEq, Eq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
enum NodeArch {
|
||||
Amd64,
|
||||
}
|
||||
|
||||
#[optional_struct]
|
||||
#[derive(Debug, Serialize, Deserialize, JsonSchema, Clone, PartialEq, Eq)]
|
||||
#[serde(rename_all = "camelCase", deny_unknown_fields)]
|
||||
struct Tailscale {
|
||||
auth_key: Secret,
|
||||
advertise_routes: bool,
|
||||
#[serde(default)]
|
||||
server: Option<String>,
|
||||
}
|
||||
|
||||
#[optional_struct]
|
||||
#[derive(Debug, Serialize, Deserialize, JsonSchema)]
|
||||
#[serde(rename_all = "camelCase", deny_unknown_fields)]
|
||||
struct Network {
|
||||
interface: String,
|
||||
ip: Ipv4Addr,
|
||||
netmask: Ipv4Addr,
|
||||
gateway: Ipv4Addr,
|
||||
dns: [Ipv4Addr; 2],
|
||||
#[optional_rename(OptionalTailscale)]
|
||||
#[optional_wrap]
|
||||
tailscale: Tailscale,
|
||||
}
|
||||
|
||||
#[optional_struct]
|
||||
#[derive(Debug, Serialize, Deserialize, JsonSchema, Default)]
|
||||
#[serde(rename_all = "camelCase", deny_unknown_fields)]
|
||||
struct Install {
|
||||
auto: bool,
|
||||
disk: String,
|
||||
serial: Option<String>,
|
||||
}
|
||||
|
||||
#[optional_struct]
|
||||
#[derive(Debug, Serialize, Deserialize, JsonSchema)]
|
||||
#[serde(rename_all = "camelCase", deny_unknown_fields)]
|
||||
pub struct Node {
|
||||
#[serde(skip_deserializing)]
|
||||
hostname: String,
|
||||
arch: NodeArch,
|
||||
schematic: Schematic,
|
||||
r#type: NodeType,
|
||||
#[optional_rename(OptionalNetwork)]
|
||||
#[optional_wrap]
|
||||
network: Network,
|
||||
ntp: String,
|
||||
#[optional_rename(OptionalInstall)]
|
||||
#[optional_wrap]
|
||||
install: Install,
|
||||
kernel_args: Vec<String>,
|
||||
#[optional_rename(OptionalPatches)]
|
||||
#[optional_wrap]
|
||||
pub(crate) patches: Patches,
|
||||
sops: Secret,
|
||||
}
|
||||
|
||||
impl Node {
|
||||
pub fn get(node_name: &str, env: &Environment, cluster: &Cluster) -> Self {
|
||||
let mut path = get_talos_config_path().join("nodes").join(node_name);
|
||||
let named = OptionalNode {
|
||||
hostname: Some(
|
||||
path.file_name()
|
||||
.expect("Path should be valid")
|
||||
.to_string_lossy()
|
||||
.to_string(),
|
||||
),
|
||||
..OptionalNode::default()
|
||||
};
|
||||
|
||||
path.add_extension("yaml");
|
||||
let content = std::fs::read_to_string(path).unwrap();
|
||||
|
||||
let node: OptionalNode = serde_yaml::from_str(&content).unwrap();
|
||||
|
||||
// We want all vectors to be empty vectors by default
|
||||
// Sadly we have to this manually
|
||||
// TODO: Find a better way of doing this
|
||||
let default = OptionalNode {
|
||||
patches: Some(OptionalPatches {
|
||||
all: Some(vec![]),
|
||||
control_plane: Some(vec![]),
|
||||
}),
|
||||
kernel_args: vec![].into(),
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
// Combine all the optional node parts into complete struct
|
||||
let mut node: Node = default
|
||||
// Apply cluster default settings
|
||||
.apply(cluster.default.clone())
|
||||
// Apply hostname based on filename
|
||||
.apply(named)
|
||||
// Override node specific settings
|
||||
.apply(node)
|
||||
.try_into()
|
||||
.unwrap();
|
||||
|
||||
// Prepend the cluster base values
|
||||
let mut kernel_args = cluster.base.kernel_args.clone();
|
||||
kernel_args.extend(node.kernel_args);
|
||||
node.kernel_args = kernel_args;
|
||||
|
||||
let patches = cluster.base.patches.clone().extend(node.patches);
|
||||
node.patches = patches;
|
||||
|
||||
// Render patches
|
||||
node.patches = node.patches.clone().render(env, cluster, &node);
|
||||
|
||||
node
|
||||
}
|
||||
}
|
||||
64
crete/src/patch.rs
Normal file
64
crete/src/patch.rs
Normal file
@@ -0,0 +1,64 @@
|
||||
use minijinja::{Environment, context};
|
||||
use optional_struct::optional_struct;
|
||||
use schemars::JsonSchema;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::{cluster::Cluster, node::Node};
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, JsonSchema, Clone, PartialEq, Eq)]
|
||||
#[serde(untagged)]
|
||||
pub(crate) enum Patch {
|
||||
Name(String),
|
||||
#[serde(skip_deserializing)]
|
||||
#[schemars(with = "serde_json::Value")]
|
||||
Resolved(serde_yaml::Value),
|
||||
}
|
||||
|
||||
#[optional_struct]
|
||||
#[derive(Debug, Serialize, Deserialize, JsonSchema, Clone, PartialEq, Eq, Default)]
|
||||
#[serde(rename_all = "camelCase", deny_unknown_fields)]
|
||||
pub struct Patches {
|
||||
pub(crate) all: Vec<Patch>,
|
||||
pub(crate) control_plane: Vec<Patch>,
|
||||
}
|
||||
|
||||
fn render(patches: Vec<Patch>, env: &Environment, cluster: &Cluster, node: &Node) -> Vec<Patch> {
|
||||
patches
|
||||
.into_iter()
|
||||
.map(|patch| {
|
||||
if let Patch::Name(name) = patch {
|
||||
let content = env
|
||||
.get_template(&name)
|
||||
.unwrap()
|
||||
.render(context! {
|
||||
node,
|
||||
cluster
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
Patch::Resolved(serde_yaml::from_str(&content).unwrap())
|
||||
} else {
|
||||
patch
|
||||
}
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
impl Patches {
|
||||
pub(crate) fn extend(mut self, other: Self) -> Self {
|
||||
self.all.extend(other.all);
|
||||
self.control_plane.extend(other.control_plane);
|
||||
|
||||
Self {
|
||||
all: self.all,
|
||||
control_plane: self.control_plane,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn render(self, env: &Environment, cluster: &Cluster, node: &Node) -> Self {
|
||||
Self {
|
||||
all: render(self.all.clone(), env, cluster, node),
|
||||
control_plane: render(self.control_plane.clone(), env, cluster, node),
|
||||
}
|
||||
}
|
||||
}
|
||||
33
crete/src/schematic.rs
Normal file
33
crete/src/schematic.rs
Normal file
@@ -0,0 +1,33 @@
|
||||
use schemars::JsonSchema;
|
||||
use serde::{Deserialize, Deserializer, Serialize};
|
||||
|
||||
use crate::get_talos_config_path;
|
||||
|
||||
fn deserialize_schematic<'de, D>(deserializer: D) -> Result<String, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
let name: String = Deserialize::deserialize(deserializer)?;
|
||||
let path = get_talos_config_path().join("schematics").join(name);
|
||||
let content = std::fs::read_to_string(path).unwrap().trim().to_owned();
|
||||
|
||||
let client = reqwest::blocking::Client::new();
|
||||
let res = client
|
||||
.post("https://factory.talos.dev/schematics")
|
||||
.body(content)
|
||||
.send()
|
||||
.map_err(serde::de::Error::custom)?;
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
struct Response {
|
||||
id: String,
|
||||
}
|
||||
|
||||
let response: Response = serde_json::from_str(&res.text().map_err(serde::de::Error::custom)?)
|
||||
.map_err(serde::de::Error::custom)?;
|
||||
|
||||
Ok(response.id)
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, JsonSchema, Clone, PartialEq, Eq)]
|
||||
pub(crate) struct Schematic(#[serde(deserialize_with = "deserialize_schematic")] String);
|
||||
34
crete/src/secret.rs
Normal file
34
crete/src/secret.rs
Normal file
@@ -0,0 +1,34 @@
|
||||
use schemars::JsonSchema;
|
||||
use serde::{Deserialize, Deserializer, Serialize};
|
||||
|
||||
use crate::get_talos_config_path;
|
||||
|
||||
#[derive(Debug, Deserialize, JsonSchema)]
|
||||
#[serde(rename_all = "camelCase", untagged)]
|
||||
enum SecretHelper {
|
||||
String(String),
|
||||
File { file: String },
|
||||
}
|
||||
|
||||
pub(crate) fn deserialize_secret<'de, D>(deserializer: D) -> Result<String, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
let secret: SecretHelper = Deserialize::deserialize(deserializer)?;
|
||||
let value = match secret {
|
||||
SecretHelper::String(value) => value,
|
||||
SecretHelper::File { file } => {
|
||||
let path = get_talos_config_path().join("secrets").join(file);
|
||||
std::fs::read_to_string(path).unwrap().trim().to_owned()
|
||||
}
|
||||
};
|
||||
|
||||
Ok(value)
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, JsonSchema, Clone, PartialEq, Eq)]
|
||||
pub(crate) struct Secret(
|
||||
#[serde(deserialize_with = "deserialize_secret")]
|
||||
#[schemars(with = "SecretHelper")]
|
||||
String,
|
||||
);
|
||||
Reference in New Issue
Block a user