use super::helpers::*;
use crate::core::types;
use std::path::Path;
pub(crate) fn event_resource(event: &types::ProvenanceEvent) -> Option<&str> {
match event {
types::ProvenanceEvent::ResourceStarted { resource, .. }
| types::ProvenanceEvent::ResourceConverged { resource, .. }
| types::ProvenanceEvent::ResourceFailed { resource, .. }
| types::ProvenanceEvent::DriftDetected { resource, .. }
| types::ProvenanceEvent::SecretAccessed { resource, .. } => Some(resource.as_str()),
_ => None,
}
}
pub(crate) fn collect_resource_events(
state_dir: &Path,
machine_filter: Option<&str>,
resource: &str,
) -> Result<Vec<types::TimestampedEvent>, String> {
let mut entries = super::history::load_machine_events(state_dir, machine_filter)?;
entries.retain(|te| event_resource(&te.event) == Some(resource));
Ok(entries)
}
pub(crate) fn cmd_history_resource(
state_dir: &Path,
machine_filter: Option<&str>,
resource: &str,
limit: usize,
json: bool,
) -> Result<(), String> {
if !state_dir.exists() {
return Err(format!(
"state directory {} does not exist — run `forjar apply` first",
state_dir.display()
));
}
let mut entries = collect_resource_events(state_dir, machine_filter, resource)?;
entries.sort_by(|a, b| a.ts.cmp(&b.ts));
if entries.len() > limit {
entries = entries.split_off(entries.len() - limit);
}
if json {
println!(
"{}",
serde_json::to_string_pretty(&entries).map_err(|e| format!("JSON error: {e}"))?
);
} else {
println!("History for resource '{}':\n", bold(resource));
if entries.is_empty() {
println!(" (no events found)");
} else {
for entry in &entries {
println!(" {} {}", entry.ts, describe_resource_event(&entry.event));
}
}
}
Ok(())
}
fn describe_resource_event(event: &types::ProvenanceEvent) -> String {
match event {
types::ProvenanceEvent::ResourceStarted {
machine, action, ..
} => format!("started {machine} ({action})"),
types::ProvenanceEvent::ResourceConverged {
machine,
duration_seconds,
hash,
..
} => format!("converged {machine} ({duration_seconds:.3}s, {hash})"),
types::ProvenanceEvent::ResourceFailed { machine, error, .. } => {
format!("FAILED {machine} — {error}")
}
types::ProvenanceEvent::DriftDetected {
machine,
expected_hash,
actual_hash,
..
} => format!("drift {machine} (expected {expected_hash}, actual {actual_hash})"),
other => format!("{other:?}"),
}
}