envault_processor/
config.rs

1use crate::env::key_for;
2use crate::error::ConfigError;
3use crate::file::File;
4use envault_cipher::aes::{decrypt, encrypt};
5#[allow(unused_imports)]
6use rand::{Rng, SeedableRng};
7use std::collections::HashMap;
8
9pub struct Config {
10    pub encoded: Option<File>,
11    pub decoded: Option<File>,
12}
13#[allow(dead_code)]
14impl Config {
15    fn new(encoded: Option<File>, decoded: Option<File>) -> Self {
16        Config { encoded, decoded }
17    }
18
19    pub fn load(
20        encoded_path: Option<&str>,
21        decoded_path: Option<&str>,
22    ) -> Result<Self, ConfigError> {
23        Ok(Config {
24            encoded: match encoded_path {
25                Some(path) => Some(File::load(path)?),
26                None => None,
27            },
28            decoded: match decoded_path {
29                Some(path) => Some(File::load(path)?),
30                None => None,
31            },
32        })
33    }
34
35    pub fn save(&self, encoded_path: &str) -> Result<(), ConfigError> {
36        if let Some(encoded) = &self.encoded {
37            encoded.save(encoded_path)?;
38            Ok(())
39        } else {
40            Err(ConfigError::IllegalState)
41        }
42    }
43
44    pub fn export(&self, env: &str) -> Result<String, ConfigError> {
45        let mut result = String::new();
46        if let Some(encoded) = &self.encoded {
47            for (_env, key, value) in encoded.iter() {
48                if env == _env {
49                    let password = key_for(_env, key)?;
50                    result.push_str(&format!("export {}={};", key, decrypt(value, &password)?));
51                }
52            }
53        }
54        Ok(result)
55    }
56
57    pub fn apply(&mut self, env: Option<String>) -> Result<(), ConfigError> {
58        #[cfg(test)]
59        let mut rng = rand_chacha::ChaCha8Rng::seed_from_u64(10);
60        #[cfg(not(test))]
61        let mut rng = rand::thread_rng();
62
63        if let Some(decoded) = &self.decoded {
64            for (_env, key, value) in decoded.iter() {
65                if let Some(env) = env.clone() {
66                    if env != _env {
67                        continue;
68                    }
69                }
70                let password = key_for(_env, key)?;
71                let encrypted = encrypt(value, &password, &mut rng)?;
72                match &mut self.encoded {
73                    Some(ref mut encoded) => {
74                        encoded.set(_env, key, &encrypted);
75                    }
76                    None => {
77                        let mut root = HashMap::new();
78                        let mut child = HashMap::new();
79                        child.insert(key.to_string(), encrypted);
80                        root.insert(_env.to_string(), child);
81                        self.encoded = Some(File::new(root));
82                    }
83                }
84            }
85        }
86        Ok(())
87    }
88}
89
90#[cfg(test)]
91mod tests {
92    use super::*;
93
94    #[test]
95    fn test_config() {
96        std::env::set_var("ENV_KEY__prd__a", "password");
97        let old = vec![("prd", "a", "old_value")];
98        let new = vec![("prd", "a", "new_value")];
99        let mut config = Config::new(
100            Some(File::new(to_hash_map(old))),
101            Some(File::new(to_hash_map(new))),
102        );
103        config
104            .encoded
105            .clone()
106            .unwrap()
107            .iter()
108            .for_each(|(env, key, value)| {
109                assert_eq!(env, "prd");
110                assert_eq!(key, "a");
111                assert_eq!(value, "old_value");
112            });
113        config.apply(None).unwrap();
114        config
115            .encoded
116            .unwrap()
117            .iter()
118            .for_each(|(env, key, value)| {
119                assert_eq!(env, "prd");
120                assert_eq!(key, "a");
121                assert_eq!(value, "U2FsdGVkX19Wak5BUmlqM6K9BRN7rxlC2+NUsA+Qo4k=");
122            });
123    }
124
125    #[test]
126    fn test_export() {
127        std::env::set_var("ENV_KEY__prd__a", "password");
128        let new = vec![("prd", "a", "U2FsdGVkX19Wak5BUmlqM6K9BRN7rxlC2+NUsA+Qo4k=")];
129        let config = Config::new(Some(File::new(to_hash_map(new))), None);
130        let res = config.export("prd").unwrap();
131        assert_eq!(res, "export a=new_value;");
132    }
133
134    fn to_hash_map(v: Vec<(&str, &str, &str)>) -> HashMap<String, HashMap<String, String>> {
135        let mut hm = HashMap::new();
136        for (env, key, value) in v {
137            hm.entry(env.to_string())
138                .or_insert(HashMap::new())
139                .insert(key.to_string(), value.to_string());
140        }
141        hm
142    }
143}