use std::collections::BTreeMap;
use std::path::{Path, PathBuf};
use serde::{Deserialize, Serialize};
use crate::record::SecretInfo;
use crate::store::StoreError;
#[derive(Debug, Default, Serialize, Deserialize)]
pub struct Index {
pub version: u32,
pub entries: BTreeMap<String, BTreeMap<String, SecretInfo>>,
}
impl Index {
pub fn path(root: &Path) -> PathBuf {
root.join("index.json")
}
pub fn load(root: &Path) -> Result<Self, StoreError> {
let p = Self::path(root);
if !p.exists() {
return Ok(Self {
version: 1,
entries: BTreeMap::new(),
});
}
let text = std::fs::read_to_string(p)?;
serde_json::from_str(&text).map_err(|e| StoreError::Encoding(e.to_string()))
}
pub fn save(&self, root: &Path) -> Result<(), StoreError> {
create_dir_private(root)?;
let text =
serde_json::to_string_pretty(self).map_err(|e| StoreError::Encoding(e.to_string()))?;
write_private(&Self::path(root), text.as_bytes())
}
pub fn upsert(&mut self, component: &str, info: SecretInfo) {
self.entries
.entry(component.to_string())
.or_default()
.insert(info.key.clone(), info);
}
pub fn remove(&mut self, component: &str, key: &str) {
if let Some(m) = self.entries.get_mut(component) {
m.remove(key);
if m.is_empty() {
self.entries.remove(component);
}
}
}
pub fn list(&self, component: Option<&str>) -> Vec<SecretInfo> {
match component {
Some(c) => self
.entries
.get(c)
.map(|m| m.values().cloned().collect())
.unwrap_or_default(),
None => self
.entries
.values()
.flat_map(|m| m.values().cloned())
.collect(),
}
}
}
pub fn create_dir_private(dir: &Path) -> Result<(), StoreError> {
std::fs::create_dir_all(dir)?;
#[cfg(unix)]
{
use std::os::unix::fs::PermissionsExt;
std::fs::set_permissions(dir, std::fs::Permissions::from_mode(0o700))?;
}
Ok(())
}
pub fn write_private(path: &Path, bytes: &[u8]) -> Result<(), StoreError> {
use std::io::Write;
let dir = path.parent().unwrap_or_else(|| Path::new("."));
create_dir_private(dir)?;
let tmp = path.with_extension("tmp");
match std::fs::remove_file(&tmp) {
Ok(()) => {}
Err(e) if e.kind() == std::io::ErrorKind::NotFound => {}
Err(e) => return Err(e.into()),
}
let mut opts = std::fs::OpenOptions::new();
opts.write(true).create_new(true);
#[cfg(unix)]
{
use std::os::unix::fs::OpenOptionsExt;
opts.mode(0o600);
}
let mut f = opts.open(&tmp)?;
f.write_all(bytes)?;
drop(f);
std::fs::rename(&tmp, path)?;
Ok(())
}