use std::io::Read;
use std::path::Path;
use super::io_helpers::{mtime_info, relativize};
use super::types::{EnvSource, EnvSourceKind, ValuePresence};
pub fn parse_sops_file(path: &Path, root: &Path, base_rank: u8) -> Option<(String, EnvSource)> {
let mut head = String::new();
let mut file = std::fs::File::open(path).ok()?;
let mut buf = [0u8; 16 * 1024];
let n = file.read(&mut buf).ok()?;
head.push_str(&String::from_utf8_lossy(&buf[..n]));
if !is_sops_encrypted(&head) {
return None;
}
let rel = relativize(path, root);
let (mtime, age) = mtime_info(path);
Some((
"__sops__".to_string(),
EnvSource {
kind: EnvSourceKind::SopsFile,
path: rel,
line: None,
mtime: mtime.unwrap_or_default(),
mtime_age_days: age,
git_age_days: None,
value_present: ValuePresence::Encrypted {
marker: "SOPS".into(),
},
precedence_rank: base_rank,
},
))
}
pub fn is_sops_encrypted(head: &str) -> bool {
head.contains("ENC[AES256_GCM")
|| head.contains("\nsops:\n")
|| head.contains("\nsops:")
|| head.starts_with("sops:")
|| head.contains("\"sops\":")
}
#[cfg(test)]
mod tests {
use super::*;
use std::fs;
use tempfile::TempDir;
#[test]
fn detects_sops_yaml_block() {
let tmp = TempDir::new().unwrap();
let path = tmp.path().join("secrets.sops.yaml");
fs::write(
&path,
"DATABASE_URL: ENC[AES256_GCM,data:abcd,iv:efgh,tag:zzz,type:str]
sops:
kms: []
age:
- recipient: age1xxx
",
)
.unwrap();
let result = parse_sops_file(&path, tmp.path(), 78).unwrap();
assert!(matches!(
result.1.value_present,
ValuePresence::Encrypted { .. }
));
assert_eq!(result.1.kind, EnvSourceKind::SopsFile);
}
#[test]
fn ignores_plain_yaml() {
let tmp = TempDir::new().unwrap();
let path = tmp.path().join("plain.yaml");
fs::write(&path, "DATABASE_URL: postgres://localhost/x\n").unwrap();
assert!(parse_sops_file(&path, tmp.path(), 78).is_none());
}
}