use isoloom_spec::syntax::{self, FieldPath, Problem, Step};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(super) enum Host {
New,
Existing,
}
pub(super) fn in_container(s: &Step) -> Option<(String, String)> {
s.container_refusal().map(|why| (why.to_owned(), "a VM target".to_owned()))
}
pub(super) fn on_vm(s: &Step, host: Host, windows: bool) -> Option<(String, String)> {
if host == Host::New || !windows || matches!(s, Step::Run { .. } | Step::Reboot | Step::Playbook(_)) {
return None;
}
Some((
format!(
"a `{}` step on a Windows host that already exists waits for Ansible's Windows modules",
s.kind()
),
"a `run:` PowerShell script for now, or a VM target".to_owned(),
))
}
pub(super) fn vm_refusals(name: &str, m: &syntax::Machine, host: Host, windows: bool) -> Vec<Problem> {
let Some(app) = m.app() else { return Vec::new() };
app.steps
.iter()
.enumerate()
.filter_map(|(i, s)| {
let (reason, fix) = on_vm(s, host, windows)?;
let at = FieldPath::new("machines").key(name).key("app").key("steps").index(i);
Some(Problem::new(syntax::Code::Refused, at, reason).fix(fix))
})
.collect()
}
#[cfg(test)]
mod tests {
use super::*;
fn machine(yaml: &str) -> syntax::Machine {
serde_yaml_ng::from_str(yaml).unwrap_or_else(|e| panic!("{yaml}: {e}"))
}
#[test]
fn a_new_vm_carries_every_step() {
let m = machine("{ os: debian@12, app: { steps: [{ run: a.sh }, { packages: [nginx] }, { copy: { from: site/, to: /s/ } }, { service: nginx }] } }");
assert_eq!(vm_refusals("m", &m, Host::New, false), []);
}
#[test]
fn a_linux_host_that_exists_runs_every_step_through_ansible() {
let m = machine("{ os: debian@12, app: { steps: [{ run: a.sh }, reboot, { playbook: p.yml }, { packages: [nginx] }, { service: nginx }] } }");
assert_eq!(
vm_refusals("m", &m, Host::Existing, false),
[],
"the Ansible CM tool carries every step on a Linux existing host"
);
}
#[test]
fn a_windows_host_that_exists_runs_its_scripts_and_waits_for_every_other_kind() {
let m = machine("{ os: windows-server@2022, app: { steps: [{ run: a.ps1 }, reboot, { packages: [Web-Server] }, { user: { name: a } }] } }");
let found = vm_refusals("m", &m, Host::Existing, true);
let paths: Vec<&str> = found.iter().map(|p| p.path.as_str()).collect();
assert_eq!(paths, ["machines.m.app.steps[2]", "machines.m.app.steps[3]"]);
assert_eq!(
found[0].reason,
"a `packages` step on a Windows host that already exists waits for Ansible's Windows modules"
);
}
#[test]
fn a_container_carries_every_step_with_a_container_form() {
let steps: Vec<Step> = serde_yaml_ng::from_str(
"[{ run: a.sh }, { packages: [{ name: nginx, version: \"1.2\" }] }, { user: { name: a } }, { service: nginx }, { firewall: { allow: [80] } }]",
)
.unwrap();
assert!(steps.iter().all(|s| in_container(s).is_none()));
assert!(in_container(&Step::Reboot).is_some());
}
}