Skip to main content

open_envault/
core.rs

1use anyhow::{Context, bail};
2use serde::{Deserialize, Serialize};
3use std::{
4    collections::BTreeMap,
5    fs,
6    path::{Path, PathBuf},
7};
8
9#[derive(Debug, Clone, Serialize, Deserialize)]
10pub struct Project {
11    pub project: String,
12    pub environments: BTreeMap<String, Environment>,
13}
14#[derive(Debug, Clone, Serialize, Deserialize)]
15pub struct Environment {
16    pub file: String,
17    #[serde(default = "default_schema")]
18    pub schema: String,
19    #[serde(default)]
20    pub recipients: Vec<String>,
21    #[serde(default)]
22    pub key_file: Option<String>,
23    #[serde(default)]
24    pub editor: Option<String>,
25}
26fn default_schema() -> String {
27    "config/env.schema.yaml".into()
28}
29pub fn discover(start: &Path) -> anyhow::Result<PathBuf> {
30    let mut current = start.canonicalize()?;
31    loop {
32        let candidate = current.join("open-envault.yaml");
33        if candidate.is_file() {
34            return Ok(candidate);
35        }
36        if !current.pop() {
37            bail!("open-envault.yaml not found")
38        }
39    }
40}
41pub fn load(path: &Path) -> anyhow::Result<Project> {
42    let text = fs::read_to_string(path).with_context(|| format!("read {}", path.display()))?;
43    serde_yaml::from_str(&text).context("parse open-envault.yaml")
44}
45pub fn environment<'a>(project: &'a Project, name: &str) -> anyhow::Result<&'a Environment> {
46    project
47        .environments
48        .get(name)
49        .with_context(|| format!("unknown environment: {name}"))
50}
51pub fn atomic_write(path: &Path, content: &[u8]) -> anyhow::Result<()> {
52    let tmp = path.with_extension("tmp");
53    fs::write(&tmp, content)?;
54    fs::rename(tmp, path)?;
55    Ok(())
56}