#![allow(dead_code)]
use forjar::core::plan_seal::digest;
use forjar::core::plan_selectors::PlanSelectors;
use forjar::core::types::{ExecutionPlan, PlanAction, PlannedChange, ResourceType};
use std::path::Path;
pub fn read_plan(path: &Path) -> serde_json::Value {
serde_json::from_str(&std::fs::read_to_string(path).expect("read plan")).expect("parse plan")
}
fn str_at(doc: &serde_json::Value, path: [&str; 2]) -> String {
doc[path[0]][path[1]]
.as_str()
.unwrap_or_else(|| panic!("plan file has no {}.{}", path[0], path[1]))
.to_string()
}
pub fn change(id: &str, machine: &str, action: PlanAction, description: &str) -> PlannedChange {
PlannedChange {
resource_id: id.to_string(),
machine: machine.to_string(),
resource_type: ResourceType::File,
action,
description: description.to_string(),
}
}
pub fn body(name: &str, changes: Vec<PlannedChange>, execution_order: &[&str]) -> ExecutionPlan {
let mut plan = ExecutionPlan {
name: name.to_string(),
changes,
execution_order: execution_order.iter().map(|s| s.to_string()).collect(),
to_create: 0,
to_update: 0,
to_destroy: 0,
unchanged: 0,
unprobed: Vec::new(),
};
for c in &plan.changes {
match c.action {
PlanAction::Create => plan.to_create += 1,
PlanAction::Update => plan.to_update += 1,
PlanAction::Destroy => plan.to_destroy += 1,
PlanAction::NoOp => plan.unchanged += 1,
}
}
plan
}
pub fn reseal_as(plan_path: &Path, body: &ExecutionPlan, selectors: &PlanSelectors) {
let honest = read_plan(plan_path);
let config_hash = str_at(&honest, ["seal", "config_hash"]);
let state_hash = str_at(&honest, ["seal", "state_hash"]);
let sealed_at = honest["seal"]["sealed_at_unix"]
.as_u64()
.expect("sealed_at");
let ttl = honest["seal"]["ttl_secs"].as_u64().expect("ttl");
let diff_hash = digest::diff_leg(body, selectors).expect("diff leg");
let seal = digest::compose(&config_hash, &state_hash, &diff_hash, sealed_at, ttl);
let changes: Vec<serde_json::Value> = body
.changes
.iter()
.map(|c| {
serde_json::json!({
"resource_id": c.resource_id,
"machine": c.machine,
"resource_type": c.resource_type,
"action": c.action,
"description": c.description,
})
})
.collect();
let forged = serde_json::json!({
"format": "forjar-plan-v2",
"config_file": honest["config_file"],
"config_hash": config_hash,
"name": body.name,
"to_create": body.to_create,
"to_update": body.to_update,
"to_destroy": body.to_destroy,
"unchanged": body.unchanged,
"execution_order": body.execution_order,
"changes": changes,
"selectors": selectors,
"seal": {
"version": honest["seal"]["version"],
"plan_id": digest::plan_id(&seal),
"config_hash": config_hash,
"state_hash": state_hash,
"diff_hash": diff_hash,
"sealed_at_unix": sealed_at,
"ttl_secs": ttl,
"seal": seal,
},
});
std::fs::write(
plan_path,
serde_json::to_string_pretty(&forged).expect("render"),
)
.expect("write forged plan");
}
pub fn reseal(plan_path: &Path, body: &ExecutionPlan) {
reseal_as(plan_path, body, &PlanSelectors::default());
}
pub fn combined(out: &std::process::Output) -> String {
format!(
"{}{}",
String::from_utf8_lossy(&out.stdout),
String::from_utf8_lossy(&out.stderr)
)
}