use std::path::Path;
use anyhow::{Context as _, Result};
use openmoat_hosts::{HookFile, HookFileState, yaml_hooks};
use serde_json::Value;
use super::{HOOK_BACKUP, HookState, HostConfig, Outcome, consent};
use crate::home::{Home, write_private};
use crate::sandbox::install::read_text;
pub fn install(
home: &Home,
config: &HostConfig,
layout: &'static dyn HookFile,
binary: &Path,
dry_run: bool,
) -> Result<Outcome> {
let path = &config.settings_path;
let current = read_text(path)?;
let (edited, text) = edit(config, layout, binary, ¤t)?;
let consent = consent::planned(config, binary)?;
let outcome = if consent.is_some() {
edited.max(Outcome::Updated)
} else {
edited
};
if outcome == Outcome::Unchanged || dry_run {
return Ok(outcome);
}
if let Some((file, text)) = consent {
super::back_up(home, &file, HOOK_BACKUP)?;
write_private(&file, text.as_bytes())?;
}
if edited != Outcome::Unchanged {
super::back_up(home, path, HOOK_BACKUP)?;
write_private(path, text.as_bytes())?;
}
Ok(outcome)
}
fn edit(
config: &HostConfig,
layout: &'static dyn HookFile,
binary: &Path,
current: &str,
) -> Result<(Outcome, String)> {
Ok(yaml_hooks::upsert(
layout,
current,
&config.settings_path.display().to_string(),
config.host.id(),
config.hooks,
&binary.to_string_lossy(),
)?)
}
pub fn render(
config: &HostConfig,
layout: &'static dyn HookFile,
binary: &Path,
current: &str,
) -> Result<Option<String>> {
let (outcome, text) = edit(config, layout, binary, current)?;
Ok((outcome != Outcome::Unchanged).then_some(text))
}
pub fn read(path: &Path) -> Result<Value> {
yaml_hooks::parse(&read_text(path)?)
.map_err(anyhow::Error::msg)
.with_context(|| format!("parsing {}", path.display()))
}
pub fn state(config: &HostConfig, layout: &'static dyn HookFile, binary: &Path) -> HookState {
let root = match read(&config.settings_path) {
Ok(root) => root,
Err(e) => return HookState::Unreadable(format!("{e:#}")),
};
let id = config.host.id();
let binary_text = binary.to_string_lossy();
let state = match layout.state(&root, id, config.hooks, &binary_text, || false) {
HookFileState::Missing => HookState::Missing,
HookFileState::Stale { command } => HookState::Stale { command },
HookFileState::Installed => match consent::missing(config, binary) {
Some(why) => HookState::Unapproved(why),
None => HookState::Installed,
},
};
super::hook_file::unless_fails_open(state, config, layout, &root)
}
pub fn remove(
config: &HostConfig,
layout: &'static dyn HookFile,
text: &str,
) -> Result<Option<String>> {
Ok(yaml_hooks::remove(
layout,
text,
&config.settings_path.display().to_string(),
config.host.id(),
)?)
}
#[cfg(test)]
mod tests;