secrets-sync 0.1.1

secret-sync, a rusty program
Documentation
use std::collections::{HashMap, HashSet};
use duct::cmd;
use lazy_settings::settings;
use eyre::Result;
use gazebo::prelude::SliceExt;
use itertools::Itertools;
use crate::provider::SecretProvider;

#[settings(key = "fly")]
struct FlySettings {
    application: String,
}

pub struct FlyProvider {
    settings: FlySettings,
}

impl FlyProvider {
    pub fn new(instance_name: &str) -> Result<Self> {
        Ok(Self {
            settings: FlySettings::get(instance_name)?
        })
    }
}

impl SecretProvider for FlyProvider {
    fn get_keys(&self) -> Result<HashSet<String>> {
        let secrets_json = cmd!("flyctl", "secrets", "-a", self.settings.application(), "list", "--json").read()?;
        let secrets: Vec<HashMap<String, String>> = serde_json::from_str(secrets_json.as_str())?;
        let result: Result<HashSet<String>> = secrets.into_iter().map(|m| m.get("Name").cloned().ok_or(eyre::eyre!("Invalid value"))).collect();
        Ok(result?)
    }

    fn get_all(&self) -> Result<HashMap<String, String>> {
        todo!()
    }

    fn set_all(&self, secrets: &HashMap<String, String>) -> Result<()> {
        let new_keys: HashSet<String> = secrets.clone().into_keys().collect();
        let keys_to_be_removed: Vec<String> = self.get_keys()?.difference(&new_keys).map(|x| x.clone()).collect();

        if !keys_to_be_removed.is_empty() {
            let mut args = vec!["secrets".to_string(), "-a".to_string(), self.settings.application().to_string(), "unset".to_string()];
            args.extend(keys_to_be_removed);
            cmd("fly", &args).run()?;
        }

        let stdin_string = secrets.iter().map(|(k, v)| {
            if v.contains("\n") {
                format!("{}=\"\"\"{}\"\"\"", k, v)
            } else {
                format!("{}={}", k, v)
            }
        }).join("\n");
        cmd!("fly", "secrets", "-a", self.settings.application(), "import").stdin_bytes(stdin_string.as_bytes())
            .read()?;
        Ok(())
    }

    fn remove_keys(&self) -> Result<()> {
        todo!()
    }
}

#[cfg(test)]
mod test {
    use super::*;
    use maplit::hashmap;

    #[test]
    fn test_fly() {
        let settings = FlySettings::get("test").unwrap();
        let provider = FlyProvider {
            settings
        };
        dbg!(provider.set_all(&hashmap! {
            "a".to_string() => "haha".to_string(),
            "b".to_string() => r#"yeah
            yeah"#.to_string(),
        }).unwrap());
    }
}