use std::net::IpAddr;
use super::{command_mechanism, ActionSpec};
use sysknife_types::RiskLevel;
pub fn specs() -> Vec<ActionSpec> {
use std::str::FromStr;
vec![
resolvectl_status(),
resolvectl_set_dns(
"eth0",
&[
IpAddr::from_str("1.1.1.1").unwrap(),
IpAddr::from_str("8.8.8.8").unwrap(),
],
),
]
}
pub fn resolvectl_status() -> ActionSpec {
ActionSpec {
action_name: "ResolvectlStatus",
mechanism: command_mechanism("resolvectl", ["status"]),
risk_level: RiskLevel::Low,
reboot_required: false,
rollback_available: false,
}
}
pub fn resolvectl_set_dns(interface: &str, servers: &[IpAddr]) -> ActionSpec {
let mut args = vec!["dns".to_string(), interface.to_string()];
args.extend(servers.iter().map(|ip| ip.to_string()));
ActionSpec {
action_name: "ResolvectlSetDns",
mechanism: super::ActionMechanism::Command {
program: "sudo",
args: {
let mut full = vec!["resolvectl".to_string()];
full.extend(args);
full
},
},
risk_level: RiskLevel::High,
reboot_required: false,
rollback_available: false,
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::actions::ActionMechanism;
use std::str::FromStr;
fn extract_args(spec: &ActionSpec) -> (&'static str, Vec<String>) {
match &spec.mechanism {
ActionMechanism::Command { program, args } => (*program, args.clone()),
_ => panic!("expected Command mechanism"),
}
}
#[test]
fn resolvectl_status_action_name() {
assert_eq!(resolvectl_status().action_name, "ResolvectlStatus");
}
#[test]
fn resolvectl_status_risk_is_low() {
assert_eq!(resolvectl_status().risk_level, RiskLevel::Low);
}
#[test]
fn resolvectl_status_argv() {
let spec = resolvectl_status();
let (prog, args) = extract_args(&spec);
assert_eq!(prog, "resolvectl");
assert_eq!(args, vec!["status"]);
}
#[test]
fn resolvectl_status_no_sudo() {
let (prog, _) = extract_args(&resolvectl_status());
assert_ne!(prog, "sudo");
}
fn ip(s: &str) -> IpAddr {
IpAddr::from_str(s).expect("test fixture must be a valid IP")
}
#[test]
fn resolvectl_set_dns_action_name() {
assert_eq!(
resolvectl_set_dns("eth0", &[ip("1.1.1.1")]).action_name,
"ResolvectlSetDns"
);
}
#[test]
fn resolvectl_set_dns_risk_is_high() {
assert_eq!(
resolvectl_set_dns("eth0", &[ip("1.1.1.1")]).risk_level,
RiskLevel::High
);
}
#[test]
fn resolvectl_set_dns_argv_ordering() {
let spec = resolvectl_set_dns("eth0", &[ip("1.1.1.1"), ip("8.8.8.8")]);
let (prog, args) = extract_args(&spec);
assert_eq!(prog, "sudo");
let a: Vec<&str> = args.iter().map(String::as_str).collect();
assert_eq!(a[0], "resolvectl");
assert_eq!(a[1], "dns");
assert_eq!(a[2], "eth0");
assert_eq!(a[3], "1.1.1.1");
assert_eq!(a[4], "8.8.8.8");
}
#[test]
fn resolvectl_set_dns_single_server() {
let spec = resolvectl_set_dns("wlp1s0", &[ip("9.9.9.9")]);
let (_, args) = extract_args(&spec);
let a: Vec<&str> = args.iter().map(String::as_str).collect();
assert!(a.contains(&"wlp1s0"));
assert!(a.contains(&"9.9.9.9"));
}
#[test]
fn resolvectl_set_dns_accepts_ipv6() {
let spec = resolvectl_set_dns("eth0", &[ip("2606:4700:4700::1111")]);
let (_, args) = extract_args(&spec);
let a: Vec<&str> = args.iter().map(String::as_str).collect();
assert!(a.contains(&"2606:4700:4700::1111"));
}
#[test]
fn specs_covers_all_action_names() {
let expected = ["ResolvectlStatus", "ResolvectlSetDns"];
let spec_names: Vec<&str> = specs().iter().map(|s| s.action_name).collect();
for name in &expected {
assert!(spec_names.contains(name), "specs() missing {name}");
}
}
}