use std::convert::TryFrom;
use structopt::clap::{App, Arg, ArgGroup, ArgMatches, SubCommand};
#[derive(Debug, PartialEq)]
pub enum Method {
Get,
Post,
}
impl TryFrom<&str> for Method {
type Error = String;
fn try_from(value: &str) -> Result<Self, Self::Error> {
match value {
"GET" => Ok(Method::Get),
"POST" => Ok(Method::Post),
_ => Err(format!("Method not supported: {}", value)),
}
}
}
#[derive(Debug, PartialEq)]
pub enum AckTarget {
Host(String),
Service(String),
}
impl AckTarget {
pub fn query(&self) -> Vec<(&'static str, String)> {
match self {
AckTarget::Host(host_name) => {
vec![("host", host_name.to_string())]
}
AckTarget::Service(service_name) => {
vec![("service", service_name.to_string())]
}
}
}
pub fn object_type(&self) -> String {
match self {
AckTarget::Host(_) => "Host".to_string(),
AckTarget::Service(_) => "Service".to_string(),
}
}
}
#[derive(Debug, PartialEq)]
pub struct Options {
pub url_string: Option<String>,
pub command: Command,
}
#[derive(Debug, PartialEq)]
pub enum ClientCommand {
RestApi {
method: Method,
read_body: bool,
path: String,
},
Status {
verbose: bool,
},
Ack {
target: AckTarget,
until: Option<u64>,
comment: Option<String>,
},
HostNotification {
host: String,
comment: String,
},
}
#[derive(Debug, PartialEq)]
pub enum KWalletCommand {
WritePassword,
ReadPassword,
RemovePassword,
}
#[derive(Debug, PartialEq)]
pub enum Command {
Client(ClientCommand),
KWallet(KWalletCommand),
}
impl From<KWalletCommand> for Command {
fn from(c: KWalletCommand) -> Self {
Command::KWallet(c)
}
}
impl From<ClientCommand> for Command {
fn from(c: ClientCommand) -> Self {
Command::Client(c)
}
}
pub fn parse_args(app: App) -> Result<Options, String> {
let args = add_options(app).get_matches();
parse_args_from(args)
}
fn parse_args_from(args: ArgMatches) -> Result<Options, String> {
let url_string_value = args.value_of("url");
Ok(Options {
url_string: url_string_value.map(ToString::to_string),
command: match args.subcommand() {
("rest-api", Some(args)) => parse_rest_api(args)?,
("status", Some(args)) => parse_status(args),
("ack", Some(args)) => parse_ack(args),
("host-notification", Some(args)) => parse_host_notification(args),
("kwallet", Some(args)) => match args.subcommand() {
("write-password", _) => KWalletCommand::WritePassword.into(),
("read-password", _) => KWalletCommand::ReadPassword.into(),
("remove-password", _) => KWalletCommand::RemovePassword.into(),
_ => {
Err("kwallet subcommand required. Try --help for more information".to_string())?
}
},
_ => Err("Subcommand required. Try --help for more information".to_string())?,
},
})
}
fn parse_rest_api(args: &ArgMatches) -> Result<Command, String> {
let method = Method::try_from(args.value_of("method").unwrap())?;
let read_body = args.is_present("body");
let path = args.value_of("path").unwrap().to_owned();
Ok(ClientCommand::RestApi {
method,
read_body,
path,
}
.into())
}
fn parse_status(args: &ArgMatches) -> Command {
let verbose = args.is_present("verbose");
ClientCommand::Status { verbose }.into()
}
fn parse_ack(args: &ArgMatches) -> Command {
let target = if args.is_present("host") {
AckTarget::Host(args.value_of("host").unwrap().to_owned())
} else {
AckTarget::Service(args.value_of("service").unwrap().to_owned())
};
let until = args.value_of("until").map(|v| v.parse().unwrap());
let comment = args.value_of("comment").map(str::to_owned);
ClientCommand::Ack {
target,
until,
comment,
}
.into()
}
fn parse_host_notification(args: &ArgMatches) -> Command {
let host = args.value_of("host").unwrap().to_owned();
let comment = args.value_of("comment").unwrap().to_owned();
ClientCommand::HostNotification { host, comment }.into()
}
fn add_options<'a, 'b>(app: App<'a, 'b>) -> App<'a, 'b> {
app.arg(Arg::with_name("url").required(false).index(1))
.subcommand(
SubCommand::with_name("rest-api")
.about("Make a custom request to the REST API and print the result")
.arg(
Arg::with_name("body")
.long("--read-body")
.required(false)
.help("read the request body from stdin"),
)
.arg(
Arg::with_name("method")
.short("X")
.required(false)
.default_value("GET")
.number_of_values(1)
.validator(|arg| {
if arg == "GET" || arg == "POST" {
Ok(())
} else {
Err(String::from("method should be either GET or POST"))
}
}),
)
.arg(Arg::with_name("path").required(true).index(1)),
)
.subcommand(
SubCommand::with_name("status")
.about("Summary of the overall monitoring status")
.arg(Arg::with_name("verbose").short("v").long("--verbose")),
)
.subcommand(
SubCommand::with_name("ack")
.about("Acknowledge a host or service")
.arg(Arg::with_name("host").long("--host").number_of_values(1))
.arg(
Arg::with_name("service")
.long("--service")
.number_of_values(1),
)
.group(
ArgGroup::with_name("target")
.args(&["host", "service"])
.required(true),
)
.arg(
Arg::with_name("until")
.long("--until")
.number_of_values(1)
.validator(|v| {
v.parse::<u64>()
.map(|_| ())
.map_err(|_| "could not parse unix-second timestamp".to_owned())
}),
)
.arg(
Arg::with_name("comment")
.long("--comment")
.number_of_values(1),
),
)
.subcommand(
SubCommand::with_name("host-notification")
.about("Send a custom notification for a host")
.arg(
Arg::with_name("host")
.long("--host")
.required(true)
.number_of_values(1),
)
.arg(
Arg::with_name("comment")
.long("--comment")
.required(true)
.number_of_values(1),
),
)
.subcommand(
SubCommand::with_name("kwallet")
.about("edit kwallet passwords for host")
.subcommand(SubCommand::with_name("read-password"))
.subcommand(SubCommand::with_name("write-password"))
.subcommand(SubCommand::with_name("remove-password")),
)
}
#[cfg(test)]
mod test {
use super::*;
const STATUS_COMMAND: Command = Command::Client(ClientCommand::Status { verbose: false });
#[test]
fn test_url_present() {
let options = parse_args_from(
add_options(App::new("test"))
.get_matches_from_safe(vec!["icinga-client", "my-server", "status"])
.unwrap(),
)
.unwrap();
assert_eq!(
options,
Options {
url_string: Some("my-server".to_string()),
command: STATUS_COMMAND
}
)
}
#[test]
fn test_url_absent() {
let options = parse_args_from(
add_options(App::new("test"))
.get_matches_from_safe(vec!["icinga-client", "status"])
.unwrap(),
)
.unwrap();
assert_eq!(
options,
Options {
url_string: None,
command: STATUS_COMMAND
}
)
}
}