use std::collections::HashMap;
use serde_json::Value;
use super::{command::SatApplyOptions, plan::SatElement};
use crate::cli::http_client::MantaClient;
pub async fn dispatch_plan(
client: &MantaClient,
token: &str,
plan: Vec<SatElement>,
opts: &SatApplyOptions<'_>,
) -> anyhow::Result<Value> {
let mut ref_lookup: HashMap<String, String> = HashMap::new();
let mut configurations: Vec<Value> = Vec::new();
let mut images: Vec<Value> = Vec::new();
let mut session_templates: Vec<Value> = Vec::new();
let mut bos_sessions: Vec<Value> = Vec::new();
for element in plan {
match element {
SatElement::Configuration(body) => {
let cfg = client
.apply_sat_configuration(token, &body, opts.overwrite, opts.dry_run)
.await?;
configurations.push(cfg);
}
SatElement::Image(body) => {
let label = image_label(&body);
let img = client
.apply_sat_image(
token,
&body,
&ref_lookup,
opts.ansible_verbosity_opt,
opts.ansible_passthrough_opt,
opts.watch_logs,
opts.timestamps,
opts.dry_run,
)
.await?;
if let Some(lab) = label {
let id = resolve_image_id(&img, &lab);
ref_lookup.insert(lab, id);
}
images.push(img);
}
SatElement::SessionTemplate(body) => {
let resp = client
.apply_sat_session_template(
token,
&body,
&ref_lookup,
opts.reboot,
opts.dry_run,
)
.await?;
let mut obj = match resp {
Value::Object(o) => o,
other => anyhow::bail!(
"session_template response was not an object: {other}"
),
};
if let Some(tpl) = obj.remove("template") {
session_templates.push(tpl);
}
if let Some(Value::Object(_)) = obj.get("session")
&& let Some(s) = obj.remove("session")
{
bos_sessions.push(s);
}
}
}
}
Ok(serde_json::json!({
"configurations": configurations,
"images": images,
"session_templates": session_templates,
"bos_sessions": bos_sessions,
}))
}
fn image_label(body: &Value) -> Option<String> {
body
.get("ref_name")
.or_else(|| body.get("name"))
.and_then(Value::as_str)
.map(str::to_string)
}
fn resolve_image_id(img: &Value, label: &str) -> String {
img
.get("id")
.and_then(Value::as_str)
.map(str::to_string)
.unwrap_or_else(|| format!("dry-run-{label}"))
}
#[cfg(test)]
mod tests {
use super::{image_label, resolve_image_id};
use serde_json::json;
#[test]
fn image_label_prefers_ref_name() {
let body = json!({ "name": "my-image", "ref_name": "base" });
assert_eq!(image_label(&body), Some("base".to_string()));
}
#[test]
fn image_label_falls_back_to_name() {
let body = json!({ "name": "my-image" });
assert_eq!(image_label(&body), Some("my-image".to_string()));
}
#[test]
fn image_label_returns_none_when_neither_field_present() {
let body = json!({ "configuration": "cfg-1" });
assert_eq!(image_label(&body), None);
}
#[test]
fn resolve_image_id_uses_response_id_when_present() {
let img = json!({ "id": "abc-123", "name": "my-image" });
assert_eq!(resolve_image_id(&img, "base"), "abc-123");
}
#[test]
fn resolve_image_id_falls_back_to_synthetic_when_id_missing() {
let img = json!({ "name": "my-image" });
assert_eq!(resolve_image_id(&img, "base"), "dry-run-base");
}
#[test]
fn resolve_image_id_passes_through_dryrun_uuid_from_csm_rs() {
let img = json!({ "id": "DRYRUN_a1b2", "name": "my-image" });
assert_eq!(resolve_image_id(&img, "base"), "DRYRUN_a1b2");
}
}