use std::path::Path;
use isoloom_spec::syntax::placeholder::Place;
use isoloom_spec::syntax::{self, Code, FieldPath, Problem, Step, Warning};
use super::Lab;
use crate::configure::{DaemonTable, unstarted};
impl Lab {
#[must_use]
pub fn warnings(&self) -> Vec<Warning> {
let daemons = DaemonTable::common();
let mut out: Vec<Warning> = Vec::new();
for (name, m) in &self.source.machines {
let Some(os) = m.os else { continue };
let base = self
.source
.clones
.iter()
.find(|(_, c)| c.contains(name))
.map_or(name.as_str(), |(b, _)| b.as_str());
for u in unstarted(os, m, &daemons) {
let path = FieldPath::new("machines").key(base).key("app").key("steps").index(u.step);
if out.iter().any(|w| w.path == path && w.reason.starts_with(&format!("`{}`", u.package))) {
continue;
}
let mut w = Warning::new(
path,
format!(
"`{}` installs a daemon that starts on Debian VMs only (their packages start it): nothing starts it in a container or on another family's VM",
u.package
),
)
.fix(format!("`service: {unit}`, or `{{ service: {{ name: {unit}, enabled: false }} }}`", unit = u.unit));
w.span = self.source.spans.find(&w.path);
out.push(w);
}
}
out.sort_by_key(|w| w.span.map(|s| (s.line, s.column)));
out
}
}
pub(super) fn step_files(source: &syntax::Spec, dir: &Path) -> Vec<Problem> {
let mut out = Vec::new();
for (name, m) in &source.machines {
let Some(app) = m.app() else { continue };
for (i, step) in app.steps.iter().enumerate() {
let at = FieldPath::new("machines").key(name).key("app").key("steps").index(i);
match step {
Step::Copy(c) if !dir.join(c.from.as_str()).exists() => out.push(missing(at.key("copy").key("from"), c.from.as_str())),
Step::Template(t) => out.extend(template(
source,
name,
&at.key("template").key("from"),
&dir.join(t.from.as_str()),
t.from.as_str(),
)),
_ => {}
}
}
}
out
}
pub(super) fn setting_files(source: &syntax::Spec, catalogue: &isoloom_catalogue::Catalogue, dir: &Path) -> Vec<Problem> {
let mut out = Vec::new();
for name in source.machines.keys() {
let Ok(resolved) = catalogue.resolve(source, name) else { continue };
for (setting, value) in resolved.iter().flat_map(|r| r.settings.iter()) {
let (isoloom_catalogue::SettingValue::Folder(path) | isoloom_catalogue::SettingValue::File(path)) = value else {
continue;
};
if !dir.join(path.as_str()).exists() {
out.push(missing(FieldPath::new("machines").key(name).key("with").key(setting.as_str()), path.as_str()));
}
}
}
out
}
fn missing(at: FieldPath, path: &str) -> Problem {
Problem::new(Code::InvalidValue, at, format!("`{path}` doesn't exist in the project folder"))
}
fn template(source: &syntax::Spec, machine: &str, at: &FieldPath, file: &Path, from: &str) -> Vec<Problem> {
if !file.exists() {
return vec![missing(at.clone(), from)];
}
let Ok(text) = std::fs::read_to_string(file) else {
return vec![Problem::new(
Code::InvalidValue,
at.clone(),
format!("`{from}` isn't a text file: a template is filled in as text"),
)];
};
let found = syntax::placeholder_problems(source, Some(machine), &text, Place::Template);
let Some(first) = found.first() else { return Vec::new() };
let code = if first.unclosed { Code::InvalidValue } else { Code::UnknownReference };
let reasons: Vec<&str> = found.iter().map(|u| u.reason.as_str()).collect();
vec![
Problem::new(code, at.clone(), format!("`{from}`: {}", reasons.join("; ")))
.fix("a template names `machine.name`, `machine.addresses.<network>`, `machines.<m>.addresses.<network>` and `machines.<m>.ports.<name>.port`"),
]
}
#[cfg(test)]
mod tests;