pub mod ffi;
use command::{CommandResult, CommandTarget};
use error::{Error, Result};
use host::Host;
use std::collections::HashMap;
use std::convert::Into;
use target::Target;
pub enum ServiceRunnable<'a> {
Command(&'a str),
Service(&'a str),
}
enum ServiceRunnableOwned {
Command(String),
Service(String),
}
impl<'a> From<ServiceRunnable<'a>> for ServiceRunnableOwned {
fn from(runnable: ServiceRunnable<'a>) -> ServiceRunnableOwned {
match runnable {
ServiceRunnable::Command(c) => ServiceRunnableOwned::Command(c.into()),
ServiceRunnable::Service(s) => ServiceRunnableOwned::Service(s.into()),
}
}
}
#[cfg_attr(feature = "local-run", doc = "let path: Option<String> = None;")]
#[cfg_attr(feature = "local-run", doc = "let mut host = Host::local(path).unwrap();")]
#[cfg_attr(feature = "remote-run", doc = "let mut host = Host::connect(\"hosts/myhost.json\").unwrap();")]
#[cfg_attr(feature = "local-run", doc = "# let path: Option<String> = None;")]
#[cfg_attr(feature = "local-run", doc = "# let mut host = Host::local(path).unwrap();")]
#[cfg_attr(feature = "remote-run", doc = "# let mut host = Host::connect(\"hosts/myhost.json\").unwrap();")]
#[cfg_attr(feature = "local-run", doc = "# let path: Option<String> = None;")]
#[cfg_attr(feature = "local-run", doc = "# let mut host = Host::local(path).unwrap();")]
#[cfg_attr(feature = "remote-run", doc = "# let mut host = Host::connect(\"hosts/myhost.json\").unwrap();")]
#[cfg_attr(feature = "local-run", doc = "# let path: Option<String> = None;")]
#[cfg_attr(feature = "local-run", doc = "# let mut host = Host::local(path).unwrap();")]
#[cfg_attr(feature = "remote-run", doc = "# let mut host = Host::connect(\"hosts/myhost.json\").unwrap();")]
#[cfg_attr(feature = "local-run", doc = "# let path: Option<String> = None;")]
#[cfg_attr(feature = "local-run", doc = "# let mut host = Host::local(path).unwrap();")]
#[cfg_attr(feature = "remote-run", doc = "# let mut host = Host::connect(\"hosts/myhost.json\").unwrap();")]
#[cfg_attr(feature = "local-run", doc = "# let path: Option<String> = None;")]
#[cfg_attr(feature = "local-run", doc = "# let mut host = Host::local(path).unwrap();")]
#[cfg_attr(feature = "remote-run", doc = "# let mut host = Host::connect(\"hosts/myhost.json\").unwrap();")]
#[cfg_attr(feature = "local-run", doc = "# let path: Option<String> = None;")]
#[cfg_attr(feature = "local-run", doc = "# let mut host = Host::local(path).unwrap();")]
#[cfg_attr(feature = "remote-run", doc = "# let mut host = Host::connect(\"hosts/myhost.json\").unwrap();")]
#[cfg_attr(feature = "local-run", doc = "# let path: Option<String> = None;")]
#[cfg_attr(feature = "local-run", doc = "# let mut host = Host::local(path).unwrap();")]
#[cfg_attr(feature = "remote-run", doc = "# let mut host = Host::connect(\"hosts/myhost.json\").unwrap();")]
#[cfg_attr(feature = "local-run", doc = "# let path: Option<String> = None;")]
#[cfg_attr(feature = "local-run", doc = "# let mut host = Host::local(path).unwrap();")]
#[cfg_attr(feature = "remote-run", doc = "# let mut host = Host::connect(\"hosts/myhost.json\").unwrap();")]
pub struct Service {
actions: HashMap<String, ServiceRunnableOwned>,
mapped_actions: Option<HashMap<String, String>>,
}
impl Service {
pub fn new_service<'a>(runnable: ServiceRunnable<'a>, mapped_actions: Option<HashMap<&'a str, &'a str>>) -> Service {
let mut actions = HashMap::new();
actions.insert("_", runnable);
Self::new_map(actions, mapped_actions)
}
pub fn new_map<'a>(actions: HashMap<&'a str, ServiceRunnable<'a>>, mapped_actions: Option<HashMap<&'a str, &'a str>>) -> Service {
let mut actions_owned = HashMap::new();
for (k, v) in actions {
actions_owned.insert(k.to_owned(), v.into());
}
let mapped_actions_owned = match mapped_actions {
Some(mapped) => {
let mut owned = HashMap::new();
for (k, v) in mapped {
owned.insert(k.to_owned(), v.to_owned());
}
Some(owned)
},
None => None,
};
Service {
actions: actions_owned,
mapped_actions: mapped_actions_owned,
}
}
#[cfg_attr(feature = "local-run", doc = "# let path: Option<String> = None;")]
#[cfg_attr(feature = "local-run", doc = "# let mut host = Host::local(path).unwrap();")]
#[cfg_attr(feature = "remote-run", doc = "# let mut host = Host::connect(\"hosts/myhost.json\").unwrap();")]
pub fn action(&self, host: &mut Host, action: &str) -> Result<Option<CommandResult>> {
let mut action = action;
if let Some(ref mapped) = self.mapped_actions {
if mapped.contains_key(action) {
action = mapped.get(action).unwrap();
}
}
if self.actions.contains_key(action) {
self.run(host, action, self.actions.get(action).unwrap(), false)
} else if self.actions.contains_key("_") {
self.run(host, action, self.actions.get("_").unwrap(), true)
} else {
Err(Error::Generic(format!("Unrecognised action {}", action)))
}
}
fn run(&self, host: &mut Host, action: &str, runnable: &ServiceRunnableOwned, default: bool) -> Result<Option<CommandResult>> {
match *runnable {
ServiceRunnableOwned::Service(ref name) => Target::service_action(host, name, action),
ServiceRunnableOwned::Command(ref cmd) => if default {
Ok(Some(try!(Target::exec(host, &format!("{} {}", cmd, action)))))
} else {
Ok(Some(try!(Target::exec(host, cmd))))
},
}
}
}
pub trait ServiceTarget {
fn service_action(host: &mut Host, name: &str, action: &str) -> Result<Option<CommandResult>>;
}
#[cfg(test)]
mod tests {
#[cfg(feature = "remote-run")]
use Host;
#[cfg(feature = "remote-run")]
use czmq::{ZMsg, ZSys};
#[cfg(feature = "remote-run")]
use super::*;
#[cfg(feature = "remote-run")]
use std::collections::HashMap;
#[cfg(feature = "remote-run")]
use std::thread;
#[cfg(feature = "remote-run")]
#[test]
fn test_action_default() {
ZSys::init();
let (client, mut server) = ZSys::create_pipe().unwrap();
let agent_mock = thread::spawn(move || {
let req = ZMsg::recv(&mut server).unwrap();
assert_eq!("service::action", req.popstr().unwrap().unwrap());
assert_eq!("nginx", req.popstr().unwrap().unwrap());
assert_eq!("start", req.popstr().unwrap().unwrap());
let rep = ZMsg::new();
rep.addstr("Ok").unwrap();
rep.addstr("0").unwrap();
rep.addstr("Service started...").unwrap();
rep.addstr("").unwrap();
rep.send(&mut server).unwrap();
let req = ZMsg::recv(&mut server).unwrap();
assert_eq!("service::action", req.popstr().unwrap().unwrap());
assert_eq!("nginx", req.popstr().unwrap().unwrap());
assert_eq!("start", req.popstr().unwrap().unwrap());
let rep = ZMsg::new();
rep.addstr("Ok").unwrap();
rep.send(&mut server).unwrap();
});
let mut host = Host::test_new(None, Some(client), None, None);
let service = Service::new_service(ServiceRunnable::Service("nginx"), None);
let result = service.action(&mut host, "start").unwrap().unwrap();
assert_eq!(result.exit_code, 0);
assert_eq!(result.stdout, "Service started...");
assert_eq!(result.stderr, "");
let result = service.action(&mut host, "start").unwrap();
assert!(result.is_none());
agent_mock.join().unwrap();
}
#[cfg(feature = "remote-run")]
#[test]
fn test_action_map() {
ZSys::init();
let (client, mut server) = ZSys::create_pipe().unwrap();
let agent_mock = thread::spawn(move || {
let msg = ZMsg::recv(&mut server).unwrap();
assert_eq!("service::action", msg.popstr().unwrap().unwrap());
assert_eq!("nginx", msg.popstr().unwrap().unwrap());
assert_eq!("start", msg.popstr().unwrap().unwrap());
let rep = ZMsg::new();
rep.addstr("Ok").unwrap();
rep.addstr("0").unwrap();
rep.addstr("Service started...").unwrap();
rep.addstr("").unwrap();
rep.send(&mut server).unwrap();
});
let mut host = Host::test_new(None, Some(client), None, None);
let mut map = HashMap::new();
map.insert("start", ServiceRunnable::Service("nginx"));
let service = Service::new_map(map, None);
let result = service.action(&mut host, "start").unwrap().unwrap();
assert_eq!(result.exit_code, 0);
assert_eq!(result.stdout, "Service started...");
assert_eq!(result.stderr, "");
agent_mock.join().unwrap();
}
#[cfg(feature = "remote-run")]
#[test]
fn test_action_mapped() {
ZSys::init();
let (client, mut server) = ZSys::create_pipe().unwrap();
let agent_mock = thread::spawn(move || {
let msg = ZMsg::recv(&mut server).unwrap();
assert_eq!("service::action", msg.popstr().unwrap().unwrap());
assert_eq!("nginx", msg.popstr().unwrap().unwrap());
assert_eq!("load", msg.popstr().unwrap().unwrap());
let rep = ZMsg::new();
rep.addstr("Ok").unwrap();
rep.addstr("0").unwrap();
rep.addstr("Service started...").unwrap();
rep.addstr("").unwrap();
rep.send(&mut server).unwrap();
});
let mut host = Host::test_new(None, Some(client), None, None);
let mut map = HashMap::new();
map.insert("start", "load");
let service = Service::new_service(ServiceRunnable::Service("nginx"), Some(map));
let result = service.action(&mut host, "start").unwrap().unwrap();
assert_eq!(result.exit_code, 0);
assert_eq!(result.stdout, "Service started...");
assert_eq!(result.stderr, "");
agent_mock.join().unwrap();
}
#[cfg(feature = "remote-run")]
#[test]
fn test_action_error() {
let mut host = Host::test_new(None, None, None, None);
let mut map = HashMap::new();
map.insert("start", ServiceRunnable::Service("nginx"));
let service = Service::new_map(map, None);
assert!(service.action(&mut host, "nonexistent").is_err());
}
#[cfg(feature = "remote-run")]
#[test]
fn test_action_command() {
ZSys::init();
let (client, mut server) = ZSys::create_pipe().unwrap();
let agent_mock = thread::spawn(move || {
let msg = ZMsg::recv(&mut server).unwrap();
assert_eq!("command::exec", msg.popstr().unwrap().unwrap());
assert_eq!("/usr/local/bin/nginx", msg.popstr().unwrap().unwrap());
let rep = ZMsg::new();
rep.addstr("Ok").unwrap();
rep.addstr("0").unwrap();
rep.addstr("Service started...").unwrap();
rep.addstr("").unwrap();
rep.send(&mut server).unwrap();
});
let mut host = Host::test_new(None, Some(client), None, None);
let mut map = HashMap::new();
map.insert("start", ServiceRunnable::Command("/usr/local/bin/nginx"));
let service = Service::new_map(map, None);
let result = service.action(&mut host, "start").unwrap().unwrap();
assert_eq!(result.exit_code, 0);
assert_eq!(result.stdout, "Service started...");
assert_eq!(result.stderr, "");
agent_mock.join().unwrap();
}
#[cfg(feature = "remote-run")]
#[test]
fn test_action_command_mapped() {
ZSys::init();
let (client, mut server) = ZSys::create_pipe().unwrap();
let agent_mock = thread::spawn(move || {
let msg = ZMsg::recv(&mut server).unwrap();
assert_eq!("command::exec", msg.popstr().unwrap().unwrap());
assert_eq!("/usr/local/bin/nginx -s", msg.popstr().unwrap().unwrap());
let rep = ZMsg::new();
rep.addstr("Ok").unwrap();
rep.addstr("0").unwrap();
rep.addstr("Service started...").unwrap();
rep.addstr("").unwrap();
rep.send(&mut server).unwrap();
});
let mut host = Host::test_new(None, Some(client), None, None);
let mut map = HashMap::new();
map.insert("start", "-s");
let service = Service::new_service(ServiceRunnable::Command("/usr/local/bin/nginx"), Some(map));
let result = service.action(&mut host, "start").unwrap().unwrap();
assert_eq!(result.exit_code, 0);
assert_eq!(result.stdout, "Service started...");
assert_eq!(result.stderr, "");
agent_mock.join().unwrap();
}
}