use super::{command_mechanism, ActionSpec};
use sysknife_types::RiskLevel;
pub fn specs() -> Vec<ActionSpec> {
vec![
pro_status(),
pro_attach("<REDACTED>"),
pro_detach(),
enable_pro_service("esm-apps"),
disable_pro_service("esm-apps"),
]
}
pub fn pro_status() -> ActionSpec {
ActionSpec {
action_name: "ProStatus",
mechanism: command_mechanism("pro", ["status", "--all"]),
risk_level: RiskLevel::Low,
reboot_required: false,
rollback_available: false,
}
}
pub fn pro_attach(token: &str) -> ActionSpec {
ActionSpec {
action_name: "ProAttach",
mechanism: command_mechanism("sudo", ["pro", "attach", token]),
risk_level: RiskLevel::High,
reboot_required: false,
rollback_available: false,
}
}
pub fn pro_detach() -> ActionSpec {
ActionSpec {
action_name: "ProDetach",
mechanism: command_mechanism("sudo", ["pro", "detach", "--assume-yes"]),
risk_level: RiskLevel::High,
reboot_required: false,
rollback_available: false,
}
}
pub fn enable_pro_service(service: &str) -> ActionSpec {
ActionSpec {
action_name: "EnableProService",
mechanism: command_mechanism("sudo", ["pro", "enable", service, "--assume-yes"]),
risk_level: RiskLevel::High,
reboot_required: false,
rollback_available: false,
}
}
pub fn disable_pro_service(service: &str) -> ActionSpec {
ActionSpec {
action_name: "DisableProService",
mechanism: command_mechanism("sudo", ["pro", "disable", service, "--assume-yes"]),
risk_level: RiskLevel::High,
reboot_required: false,
rollback_available: false,
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::actions::ActionMechanism;
fn extract_cmd(spec: &ActionSpec) -> (&'static str, Vec<&str>) {
match &spec.mechanism {
ActionMechanism::Command { program, args } => {
(*program, args.iter().map(String::as_str).collect())
}
_ => panic!("expected Command mechanism"),
}
}
#[test]
fn pro_status_action_name() {
assert_eq!(pro_status().action_name, "ProStatus");
}
#[test]
fn pro_status_risk_low() {
assert_eq!(pro_status().risk_level, RiskLevel::Low);
}
#[test]
fn pro_status_no_reboot() {
assert!(!pro_status().reboot_required);
}
#[test]
fn pro_status_argv() {
let spec = pro_status();
let (prog, args) = extract_cmd(&spec);
assert_eq!(prog, "pro");
assert!(args.contains(&"status"));
assert!(args.contains(&"--all"));
}
#[test]
fn pro_attach_action_name() {
assert_eq!(pro_attach("tok123").action_name, "ProAttach");
}
#[test]
fn pro_attach_risk_high() {
assert_eq!(pro_attach("tok123").risk_level, RiskLevel::High);
}
#[test]
fn pro_attach_no_automatic_rollback() {
assert!(!pro_attach("tok123").rollback_available);
}
#[test]
fn pro_attach_token_in_args() {
let spec = pro_attach("my-secret-token");
let (prog, args) = extract_cmd(&spec);
assert_eq!(prog, "sudo");
assert!(args.contains(&"pro"));
assert!(args.contains(&"attach"));
assert!(args.contains(&"my-secret-token"));
}
#[test]
fn pro_attach_action_name_does_not_contain_token() {
let token = "super-secret-ubuntu-pro-token";
let spec = pro_attach(token);
assert!(
!spec.action_name.contains(token),
"action_name must not contain the token value"
);
}
#[test]
fn pro_attach_specs_uses_redacted_sentinel() {
let spec = specs()
.into_iter()
.find(|s| s.action_name == "ProAttach")
.expect("ProAttach must be in specs()");
match &spec.mechanism {
ActionMechanism::Command { args, .. } => {
let token_arg = args.last().expect("token is the last arg");
assert_eq!(
token_arg, "<REDACTED>",
"specs() sentinel must be the literal string <REDACTED>"
);
}
_ => panic!("expected Command mechanism"),
}
}
#[test]
fn pro_detach_action_name() {
assert_eq!(pro_detach().action_name, "ProDetach");
}
#[test]
fn pro_detach_risk_high() {
assert_eq!(pro_detach().risk_level, RiskLevel::High);
}
#[test]
fn pro_detach_no_automatic_rollback() {
assert!(!pro_detach().rollback_available);
}
#[test]
fn pro_detach_argv() {
let spec = pro_detach();
let (prog, args) = extract_cmd(&spec);
assert_eq!(prog, "sudo");
assert!(args.contains(&"pro"));
assert!(args.contains(&"detach"));
assert!(args.contains(&"--assume-yes"));
}
#[test]
fn specs_covers_all_action_names() {
let expected = [
"ProStatus",
"ProAttach",
"ProDetach",
"EnableProService",
"DisableProService",
];
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}");
}
}
#[test]
fn enable_service_argv() {
let spec = enable_pro_service("esm-infra");
let (prog, args) = extract_cmd(&spec);
assert_eq!(prog, "sudo");
assert_eq!(args, vec!["pro", "enable", "esm-infra", "--assume-yes"]);
assert_eq!(spec.risk_level, RiskLevel::High);
}
#[test]
fn disable_service_argv() {
let spec = disable_pro_service("livepatch");
let (prog, args) = extract_cmd(&spec);
assert_eq!(prog, "sudo");
assert_eq!(args, vec!["pro", "disable", "livepatch", "--assume-yes"]);
assert_eq!(spec.risk_level, RiskLevel::High);
}
}