use serde_json::json;
use crate::logging::StageLogger;
use crate::logging::{AuditSink, FactsEmitter};
use crate::types::Action;
pub(crate) fn do_rollback<E: FactsEmitter, A: AuditSink>(
api: &super::super::Switchyard<E, A>,
plan_id: &uuid::Uuid,
executed: &[Action],
executed_indices: &[usize],
dry: bool,
slog: &StageLogger<'_>,
rollback_errors: &mut Vec<String>,
) -> Vec<String> {
let mut rolled_paths: Vec<String> = Vec::new();
for (pos, prev) in executed.iter().enumerate().rev() {
let idx = executed_indices.get(pos).copied().unwrap_or(pos);
let aid = crate::types::ids::action_id(plan_id, prev, idx).to_string();
match prev {
Action::EnsureSymlink {
source: _source,
target,
} => {
match crate::fs::restore::restore_file(
target,
dry,
api.policy.apply.best_effort_restore,
&api.policy.backup.tag,
) {
Ok(()) => {
slog.rollback()
.action_id(aid)
.path(target.as_path().display().to_string())
.emit_success();
rolled_paths.push(target.as_path().display().to_string());
}
Err(e) => {
rollback_errors.push(format!(
"rollback restore {} failed: {}",
target.as_path().display(),
e
));
slog.rollback()
.action_id(aid)
.path(target.as_path().display().to_string())
.field("error_detail", json!(e.to_string()))
.error_id(crate::api::errors::ErrorId::E_RESTORE_FAILED)
.exit_code_for(crate::api::errors::ErrorId::E_RESTORE_FAILED)
.emit_failure();
rolled_paths.push(target.as_path().display().to_string());
}
}
}
Action::RestoreFromBackup { .. } => {
rollback_errors.push(
"rollback of RestoreFromBackup not supported (no prior state)".to_string(),
);
slog.rollback()
.action_id(aid)
.field("error_detail", json!("restore inverse unavailable"))
.error_id(crate::api::errors::ErrorId::E_RESTORE_FAILED)
.exit_code_for(crate::api::errors::ErrorId::E_RESTORE_FAILED)
.emit_failure();
}
}
}
rolled_paths
}
pub(crate) fn emit_summary(slog: &StageLogger<'_>, rollback_errors: &[String]) {
let rb_decision = if rollback_errors.is_empty() {
"success"
} else {
"failure"
};
let mut rb_extra = json!({});
if rb_decision == "failure" {
if let Some(obj) = rb_extra.as_object_mut() {
obj.insert(
"error_id".to_string(),
json!(crate::api::errors::id_str(
crate::api::errors::ErrorId::E_RESTORE_FAILED
)),
);
obj.insert(
"exit_code".to_string(),
json!(crate::api::errors::exit_code_for(
crate::api::errors::ErrorId::E_RESTORE_FAILED
)),
);
obj.insert(
"summary_error_ids".to_string(),
json!([
crate::api::errors::id_str(crate::api::errors::ErrorId::E_RESTORE_FAILED),
crate::api::errors::id_str(crate::api::errors::ErrorId::E_POLICY)
]),
);
}
}
if rb_decision == "failure" {
slog.rollback_summary().merge(&rb_extra).emit_failure();
} else {
slog.rollback_summary().merge(&rb_extra).emit_success();
}
}