use std::path::{Path, PathBuf};
use anyhow::{Result, bail};
use openmoat_hosts::ConsentFile;
use serde_json::Value;
use super::HostConfig;
use crate::sandbox::install::{parse_json, read_text};
pub fn of(config: &HostConfig) -> Option<(PathBuf, &'static dyn ConsentFile)> {
let consent = config.host.spec().install.as_ref()?.consent?;
Some((config.dir.join(consent.name()), consent))
}
fn read(path: &Path) -> Result<Value> {
parse_json(&read_text(path)?).map_err(|e| e.context(format!("reading {}", path.display())))
}
pub fn planned(config: &HostConfig, binary: &Path) -> Result<Option<(PathBuf, String)>> {
let Some((path, consent)) = of(config) else {
return Ok(None);
};
let current = read_text(&path)?;
match render(config, consent, &path, binary, ¤t)? {
Some(text) => Ok(Some((path, text))),
None => Ok(None),
}
}
pub fn render(
config: &HostConfig,
consent: &dyn ConsentFile,
path: &Path,
binary: &Path,
current: &str,
) -> Result<Option<String>> {
let mut doc =
parse_json(current).map_err(|e| e.context(format!("reading {}", path.display())))?;
let now = crate::time::rfc3339(crate::time::now_ms());
let changed = match consent.upsert(
&mut doc,
config.host.id(),
config.hooks,
&binary.to_string_lossy(),
&now,
) {
Ok(changed) => changed,
Err(why) => bail!(
"{}: {why}; fix or remove it, then run `openmoat init` again",
path.display()
),
};
Ok(changed.then(|| serde_json::to_string_pretty(&doc).unwrap_or_default() + "\n"))
}
pub fn missing(config: &HostConfig, binary: &Path) -> Option<String> {
let (path, consent) = of(config)?;
let approved = read(&path).is_ok_and(|doc| {
consent.holds(
&doc,
config.host.id(),
config.hooks,
&binary.to_string_lossy(),
)
});
(!approved).then(|| {
format!(
"hook not approved in {}: {}; run `openmoat init`",
path.display(),
consent.without()
)
})
}