use crate::core::types::Resource;
mod classify_e01;
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Observability {
Observed { alt: &'static str },
Unobservable(&'static str),
Unmigrated(&'static str),
}
pub fn classify(field: &str) -> Option<Observability> {
FIELDS
.iter()
.find(|(name, _)| *name == field)
.map(|(_, obs)| obs.clone())
.or_else(|| classify_e01::classify(field))
}
static FIELDS: &[(&str, Observability)] = &[
("path", Observability::Observed { alt: "/tmp/forjar-alt-path" }),
("content", Observability::Observed { alt: "forjar-alt-content" }),
("source", Observability::Observed { alt: "//forjar-alt/share" }),
("fs_type", Observability::Observed { alt: "nfs" }),
("options", Observability::Observed { alt: "ro" }),
("owner", Observability::Observed { alt: "nobody" }),
("group", Observability::Observed { alt: "nogroup" }),
("mode", Observability::Observed { alt: "0600" }),
("version", Observability::Observed { alt: "0.0.1-forjar-alt" }),
("packages", Observability::Observed { alt: "forjar-alt-package" }),
("state", Observability::Observed { alt: "absent" }),
("target", Observability::Observed { alt: "/tmp/forjar-alt-target" }),
("provider", Observability::Unobservable("selects the mechanism, not the state; the host cannot report which provider installed a thing")),
("name", Observability::Unobservable("an identifier, not a property of the host")),
("fstype", Observability::Observed { alt: "nfs" }),
("shell", Observability::Observed { alt: "/usr/sbin/nologin" }),
("home", Observability::Observed { alt: "/tmp/forjar-alt-home" }),
("image", Observability::Observed { alt: "forjar/alt-image:0" }),
("ports", Observability::Observed { alt: "9999:9999" }),
("port", Observability::Observed { alt: "9999" }),
("volumes", Observability::Observed { alt: "/tmp/forjar-alt-vol" }),
("environment", Observability::Observed { alt: "FORJAR_ALT=1" }),
("restart", Observability::Observed { alt: "no" }),
("schedule", Observability::Observed { alt: "0 0 31 2 *" }),
("command", Observability::Observed { alt: "true # forjar-alt" }),
("overlay_ip", Observability::Observed { alt: "10.42.0.254" }),
("overlay_iface", Observability::Observed { alt: "forjar-alt0" }),
("protocol", Observability::Observed { alt: "udp" }),
("from", Observability::Observed { alt: "10.42.0.0/24" }),
("completion_check", Observability::Observed { alt: "false # forjar-alt-completion-check" }),
("action", Observability::Unobservable("names an operation to perform, not a state the host can report")),
("restart_on", Observability::Unobservable("a trigger condition, not observable state; the host cannot say what would have restarted it")),
("exec_start", Observability::Observed { alt: "/tmp/forjar-alt-exec.sh" }),
("exec_sha256", Observability::Observed { alt: "0000000000000000000000000000000000000000000000000000000000000000" }),
];
pub fn hashed_fields() -> Vec<String> {
use crate::core::planner::hashing::hash_desired_state;
let base = Resource::default();
let base_hash = hash_desired_state(&base);
let Ok(serde_json::Value::Object(map)) = serde_json::to_value(&base) else {
return Vec::new();
};
let mut out = Vec::new();
for key in map.keys() {
let mut probe = map.clone();
probe.insert(key.clone(), serde_json::json!("forjar-probe-sentinel"));
let as_list = {
let mut p = map.clone();
p.insert(key.clone(), serde_json::json!(["forjar-probe-sentinel"]));
p
};
for candidate in [probe, as_list] {
if let Ok(r) = serde_json::from_value::<Resource>(serde_json::Value::Object(candidate))
{
if hash_desired_state(&r) != base_hash {
out.push(key.clone());
break;
}
}
}
}
out.sort();
out.dedup();
out
}
#[cfg(test)]
mod tests;