extern crate charmhelpers;
extern crate log;
extern crate memchr;
extern crate rusqlite;
#[macro_use]
extern crate serde_derive;
extern crate serde_json;
use std::collections::HashMap;
use std::env;
use std::error::Error;
use std::fs::OpenOptions;
use std::fmt;
use std::io;
use std::io::prelude::*;
use std::net::IpAddr;
use std::path::Path;
use std::str::FromStr;
pub use log::LogLevel;
use memchr::memchr;
pub use charmhelpers::core::hookenv::log;
pub mod macros;
pub mod unitdata;
#[derive(Debug)]
pub enum JujuError {
AddrParseError(std::net::AddrParseError),
FromUtf8Error(std::string::FromUtf8Error),
IoError(io::Error),
ParseIntError(std::num::ParseIntError),
RusqliteError(rusqlite::Error),
SerdeError(serde_json::Error),
VarError(std::env::VarError),
}
impl JujuError {
fn new(err: String) -> JujuError {
JujuError::IoError(io::Error::new(std::io::ErrorKind::Other, err))
}
pub fn to_string(&self) -> String {
match *self {
JujuError::AddrParseError(ref err) => err.description().to_string(),
JujuError::FromUtf8Error(ref err) => err.description().to_string(),
JujuError::IoError(ref err) => err.description().to_string(),
JujuError::ParseIntError(ref err) => err.description().to_string(),
JujuError::RusqliteError(ref err) => err.description().to_string(),
JujuError::SerdeError(ref err) => err.description().to_string(),
JujuError::VarError(ref err) => err.description().to_string(),
}
}
}
impl fmt::Display for JujuError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.write_str(self.description())
}
}
impl Error for JujuError {
fn description(&self) -> &str {
match *self {
JujuError::AddrParseError(ref err) => err.description(),
JujuError::FromUtf8Error(ref err) => err.description(),
JujuError::IoError(ref err) => err.description(),
JujuError::ParseIntError(ref err) => err.description(),
JujuError::RusqliteError(ref err) => err.description(),
JujuError::SerdeError(ref err) => err.description(),
JujuError::VarError(ref err) => err.description(),
}
}
fn cause(&self) -> Option<&Error> {
match *self {
JujuError::AddrParseError(ref err) => err.cause(),
JujuError::FromUtf8Error(ref err) => err.cause(),
JujuError::IoError(ref err) => err.cause(),
JujuError::ParseIntError(ref err) => err.cause(),
JujuError::SerdeError(ref err) => err.cause(),
JujuError::RusqliteError(ref err) => err.cause(),
JujuError::VarError(ref err) => err.cause(),
}
}
}
impl From<io::Error> for JujuError {
fn from(err: io::Error) -> JujuError {
JujuError::IoError(err)
}
}
impl From<std::string::FromUtf8Error> for JujuError {
fn from(err: std::string::FromUtf8Error) -> JujuError {
JujuError::FromUtf8Error(err)
}
}
impl From<std::num::ParseIntError> for JujuError {
fn from(err: std::num::ParseIntError) -> JujuError {
JujuError::ParseIntError(err)
}
}
impl From<std::env::VarError> for JujuError {
fn from(err: std::env::VarError) -> JujuError {
JujuError::VarError(err)
}
}
impl From<rusqlite::Error> for JujuError {
fn from(err: rusqlite::Error) -> JujuError {
JujuError::RusqliteError(err)
}
}
impl From<serde_json::Error> for JujuError {
fn from(err: serde_json::Error) -> JujuError {
JujuError::SerdeError(err)
}
}
impl From<std::net::AddrParseError> for JujuError {
fn from(err: std::net::AddrParseError) -> JujuError {
JujuError::AddrParseError(err)
}
}
#[derive(Debug)]
pub enum Transport {
Tcp,
Udp,
}
impl Transport {
fn to_string(self) -> String {
match self {
Transport::Tcp => "tcp".to_string(),
Transport::Udp => "udp".to_string(),
}
}
}
#[derive(Debug)]
pub enum StatusType {
Maintenance,
Waiting,
Active,
Blocked,
}
impl StatusType {
pub fn to_string(self) -> String {
match self {
StatusType::Maintenance => "maintenance".to_string(),
StatusType::Waiting => "waiting".to_string(),
StatusType::Active => "active".to_string(),
StatusType::Blocked => "blocked".to_string(),
}
}
}
#[derive(Debug)]
pub struct Status {
pub status_type: StatusType,
pub message: String,
}
#[derive(Debug)]
pub struct Context {
pub relation_type: String,
pub relation_id: usize,
pub unit: String,
pub relations: HashMap<String, String>,
}
impl Context {
pub fn new_from_env() -> Context {
let relations: HashMap<String, String> = HashMap::new();
let relation_type = env::var("JUJU_RELATION").unwrap_or("".to_string());
let relation_id_str = env::var("JUJU_RELATION_ID").unwrap_or("".to_string());
let parts: Vec<&str> = relation_id_str.split(":").collect();
let relation_id: usize;
if parts.len() > 1 {
relation_id = parts[1].parse::<usize>().unwrap_or(0);
} else {
relation_id = 0;
}
let unit = env::var("JUJU_UNIT_NAME").unwrap_or("".to_string());
Context {
relation_type: relation_type,
relation_id: relation_id,
unit: unit,
relations: relations,
}
}
}
#[derive(Debug)]
pub struct Config {
values: HashMap<String, String>,
}
impl Config {
pub fn new() -> Result<Self, JujuError> {
if Path::new(".juju-persistent-config").exists() {
let mut file = OpenOptions::new().read(true).open(".juju-persistent-config")?;
let mut s = String::new();
file.read_to_string(&mut s)?;
let previous_values: HashMap<String, String> = serde_json::from_str(&s)?;
Ok(Config { values: previous_values })
} else {
let current_values = config_get_all()?;
Ok(Config { values: current_values })
}
}
pub fn get(self, key: &str) -> Result<Option<String>, JujuError> {
let current_value = config_get(key)?;
Ok(current_value)
}
pub fn changed(self, key: &str) -> Result<bool, JujuError> {
match self.values.get(key) {
Some(previous_value) => {
let current_value = config_get(key)?;
match current_value {
Some(value) => Ok(&value != previous_value),
None => Ok(true),
}
}
None => Ok(true),
}
}
pub fn previous(self, key: &str) -> Option<String> {
match self.values.get(key) {
Some(previous_value) => Some(previous_value.clone()),
None => None,
}
}
}
impl Drop for Config {
fn drop(&mut self) {
let mut file = match OpenOptions::new()
.write(true)
.truncate(true)
.create(true)
.open(".juju-persistent-config") {
Ok(f) => f,
Err(e) => {
log(&format!("Unable to open .juju-persistent-config file for writing. Err: {}",
e),
Some(LogLevel::Error));
return;
}
};
let serialized = match serde_json::to_string(&self.values) {
Ok(f) => f,
Err(e) => {
log(&format!("Unable to serialize Config values: {:?}. Err: {}",
&self.values,
e),
Some(LogLevel::Error));
return;
}
};
match file.write(&serialized.as_bytes()) {
Ok(bytes_written) => {
log(&format!(".juju-persistent-config saved. Wrote {} bytes",
bytes_written),
Some(LogLevel::Debug));
}
Err(e) => {
log(&format!("Unable to write to .juju-persistent-config Err: {}", e),
Some(LogLevel::Error));
return;
}
}
}
}
#[derive(Debug)]
pub struct Relation {
pub name: String,
pub id: usize,
}
#[derive(Debug,PartialEq)]
pub struct Hook {
pub name: String,
pub callback: fn() -> Result<(), String>,
}
fn process_output(output: std::process::Output) -> Result<i32, JujuError> {
let status = output.status;
if status.success() {
return Ok(0);
} else {
return Err(JujuError::new(String::from_utf8(output.stderr)?));
}
}
pub fn add_metric(key: &str, value: &str) -> Result<i32, JujuError> {
let mut arg_list: Vec<String> = Vec::new();
arg_list.push(format!("{}={}", key, value));
let output = run_command("add-metric", &arg_list, false)?;
return process_output(output);
}
pub fn az_info() -> Result<String, JujuError> {
let az = env::var("JUJU_AVAILABILITY_ZONE")?;
return Ok(az);
}
pub fn meter_status() -> Result<String, JujuError> {
let status = env::var("JUJU_METER_STATUS")?;
return Ok(status);
}
pub fn meter_info() -> Result<String, JujuError> {
let info = env::var("JUJU_METER_INFO")?;
return Ok(info);
}
pub fn reboot() -> Result<i32, JujuError> {
let output = run_command_no_args("juju-reboot", true)?;
return process_output(output);
}
pub fn application_version_set(version: &str) -> Result<i32, JujuError> {
let mut arg_list: Vec<String> = Vec::new();
arg_list.push(version.to_string());
let output = run_command("application-version-set", &arg_list, false)?;
return process_output(output);
}
pub fn action_get_all() -> Result<HashMap<String, String>, JujuError> {
let output = run_command_no_args("action-get", false)?;
let values = String::from_utf8(output.stdout)?;
let mut map: HashMap<String, String> = HashMap::new();
for line in values.lines() {
let parts: Vec<&str> = line.split(":").collect();
if parts.len() == 2 {
map.insert(parts[0].to_string(), parts[1].trim().to_string());
}
}
return Ok(map);
}
pub fn action_get(key: &str) -> Result<Option<String>, JujuError> {
let mut arg_list: Vec<String> = Vec::new();
arg_list.push(key.to_string());
let output = run_command("action-get", &arg_list, false)?;
let value = String::from_utf8(output.stdout)?.trim().to_string();
if value.is_empty() {
Ok(None)
} else {
Ok(Some(value))
}
}
pub fn action_name() -> Result<String, JujuError> {
let name = env::var("JUJU_ACTION_NAME")?;
return Ok(name);
}
pub fn action_uuid() -> Result<String, JujuError> {
let uuid = env::var("JUJU_ACTION_UUID")?;
return Ok(uuid);
}
pub fn action_tag() -> Result<String, JujuError> {
let tag = env::var("JUJU_ACTION_TAG")?;
return Ok(tag);
}
pub fn action_set(key: &str, value: &str) -> Result<i32, JujuError> {
let mut arg_list: Vec<String> = Vec::new();
arg_list.push(format!("{}={}", key, value));
let output = run_command("action-set", &arg_list, false)?;
return process_output(output);
}
pub fn action_fail(msg: &str) -> Result<i32, JujuError> {
let mut arg_list: Vec<String> = Vec::new();
arg_list.push(msg.to_string());
let output = run_command("action-fail", &arg_list, false)?;
return process_output(output);
}
pub fn unit_get_private_addr() -> Result<IpAddr, JujuError> {
let mut arg_list: Vec<String> = Vec::new();
arg_list.push("private-address".to_string());
let output = run_command("unit-get", &arg_list, false)?;
let private_addr: String = String::from_utf8(output.stdout)?;
let ip = IpAddr::from_str(private_addr.trim())?;
return Ok(ip);
}
pub fn unit_get_public_addr() -> Result<IpAddr, JujuError> {
let mut arg_list: Vec<String> = Vec::new();
arg_list.push("public-address".to_string());
let output = run_command("unit-get", &arg_list, false)?;
let public_addr = String::from_utf8(output.stdout)?;
let ip = IpAddr::from_str(public_addr.trim())?;
return Ok(ip);
}
pub fn config_get(key: &str) -> Result<Option<String>, JujuError> {
let mut arg_list: Vec<String> = Vec::new();
arg_list.push(key.to_string());
let output = run_command("config-get", &arg_list, false)?;
let value = String::from_utf8(output.stdout)?.trim().to_string();
if value.is_empty() {
Ok(None)
} else {
Ok(Some(value))
}
}
pub fn config_get_all() -> Result<HashMap<String, String>, JujuError> {
let mut values: HashMap<String, String> = HashMap::new();
let arg_list: Vec<String> = vec!["--all".to_string()];
let output = run_command("config-get", &arg_list, false)?;
let output_str = String::from_utf8(output.stdout)?;
for line in output_str.lines() {
if let Some(position) = memchr(b':', &line.as_bytes()) {
values.insert(line[0..position].trim().to_string(),
line[position + 1..].trim().to_string());
}
}
return Ok(values);
}
pub fn open_port(port: usize, transport: Transport) -> Result<i32, JujuError> {
let mut arg_list: Vec<String> = Vec::new();
let port_string = format!("{}/{}", port.to_string(), transport.to_string());
arg_list.push(port_string);
let output = run_command("open-port", &arg_list, false)?;
return process_output(output);
}
pub fn close_port(port: usize, transport: Transport) -> Result<i32, JujuError> {
let mut arg_list: Vec<String> = Vec::new();
let port_string = format!("{}/{}", port.to_string(), transport.to_string());
arg_list.push(port_string);
let output = run_command("close-port", &arg_list, false)?;
return process_output(output);
}
pub fn relation_set(key: &str, value: &str) -> Result<i32, JujuError> {
let mut arg_list: Vec<String> = Vec::new();
let arg = format!("{}={}", key.clone(), value);
arg_list.push(arg);
let output = run_command("relation-set", &arg_list, false)?;
return process_output(output);
}
pub fn relation_set_by_id(key: &str, value: &str, id: &Relation) -> Result<String, JujuError> {
let mut arg_list: Vec<String> = Vec::new();
arg_list.push(format!("-r {}:{}", id.name, id.id.to_string()));
arg_list.push(format!("{}={}", key, value).to_string());
let output = run_command("relation-set", &arg_list, false)?;
let relation = String::from_utf8(output.stdout)?;
return Ok(relation);
}
pub fn relation_get(key: &str) -> Result<Option<String>, JujuError> {
let mut arg_list: Vec<String> = Vec::new();
arg_list.push(key.to_string());
let output = run_command("relation-get", &arg_list, false)?;
let value = String::from_utf8(output.stdout)?.trim().to_string();
if value.is_empty() {
Ok(None)
} else {
Ok(Some(value))
}
}
pub fn relation_get_by_unit(key: &str, unit: &Relation) -> Result<Option<String>, JujuError> {
let mut arg_list: Vec<String> = Vec::new();
arg_list.push(key.to_string());
arg_list.push(format!("{}/{}", unit.name, unit.id.to_string()));
let output = run_command("relation-get", &arg_list, false)?;
let relation = String::from_utf8(output.stdout)?.trim().to_string();
if relation.is_empty() {
Ok(None)
} else {
Ok(Some(relation))
}
}
pub fn relation_get_by_id(key: &str,
id: &Relation,
unit: &Relation)
-> Result<Option<String>, JujuError> {
let mut arg_list: Vec<String> = Vec::new();
arg_list.push(format!("-r {}:{}", id.name, id.id.to_string()));
arg_list.push(format!("{}", key.to_string()));
arg_list.push(format!("{}/{}", unit.name, unit.id.to_string()));
let output = run_command("relation-get", &arg_list, false)?;
let relation = String::from_utf8(output.stdout)?.trim().to_string();
if relation.is_empty() {
Ok(None)
} else {
Ok(Some(relation))
}
}
pub fn relation_list() -> Result<Vec<Relation>, JujuError> {
let mut related_units: Vec<Relation> = Vec::new();
let output = run_command_no_args("relation-list", false)?;
let output_str = String::from_utf8(output.stdout)?;
log(&format!("relation-list output: {}", output_str),
Some(LogLevel::Debug));
for line in output_str.lines() {
let v: Vec<&str> = line.split('/').collect();
let id: usize = v[1].parse::<usize>()?;
let r: Relation = Relation {
name: v[0].to_string(),
id: id,
};
related_units.push(r);
}
return Ok(related_units);
}
pub fn relation_list_by_id(id: &Relation) -> Result<Vec<Relation>, JujuError> {
let mut related_units: Vec<Relation> = Vec::new();
let mut arg_list: Vec<String> = Vec::new();
arg_list.push(format!("-r {}:{}", id.name, id.id.to_string()));
let output = run_command("relation-list", &arg_list, false)?;
let output_str = String::from_utf8(output.stdout)?;
log(&format!("relation-list output: {}", output_str),
Some(LogLevel::Debug));
for line in output_str.lines() {
let v: Vec<&str> = line.split('/').collect();
let id: usize = v[1].parse::<usize>()?;
let r: Relation = Relation {
name: v[0].to_string(),
id: id,
};
related_units.push(r);
}
return Ok(related_units);
}
pub fn relation_ids() -> Result<Vec<Relation>, JujuError> {
let mut related_units: Vec<Relation> = Vec::new();
let output = run_command_no_args("relation-ids", false)?;
let output_str: String = String::from_utf8(output.stdout)?;
log(&format!("relation-ids output: {}", output_str),
Some(LogLevel::Debug));
for line in output_str.lines() {
let v: Vec<&str> = line.split(':').collect();
let id: usize = v[1].parse::<usize>()?;
let r: Relation = Relation {
name: v[0].to_string(),
id: id,
};
related_units.push(r);
}
return Ok(related_units);
}
pub fn relation_ids_by_identifier(id: &str) -> Result<Vec<Relation>, JujuError> {
let mut related_units: Vec<Relation> = Vec::new();
let mut arg_list: Vec<String> = Vec::new();
arg_list.push(id.to_string());
let output = run_command("relation-ids", &arg_list, false)?;
let output_str: String = String::from_utf8(output.stdout)?;
log(&format!("relation-ids output: {}", output_str),
Some(LogLevel::Debug));
for line in output_str.lines() {
let v: Vec<&str> = line.split(':').collect();
let id: usize = v[1].parse::<usize>()?;
let r: Relation = Relation {
name: v[0].to_string(),
id: id,
};
related_units.push(r);
}
return Ok(related_units);
}
pub fn status_set(status: Status) -> Result<i32, JujuError> {
let mut arg_list: Vec<String> = Vec::new();
arg_list.push(status.status_type.to_string());
arg_list.push(status.message);
let output = run_command("status-set", &arg_list, false)?;
return process_output(output);
}
pub fn status_get() -> Result<String, JujuError> {
let output = run_command_no_args("status-get", false)?;
return Ok(String::from_utf8(output.stdout)?);
}
pub fn storage_get_location() -> Result<Option<String>, JujuError> {
let mut arg_list: Vec<String> = Vec::new();
arg_list.push("location".to_string());
let output = run_command("storage-get", &arg_list, false)?;
let stdout = String::from_utf8(output.stdout)?.trim().to_string();
if stdout.is_empty() {
Ok(None)
} else {
Ok(Some(stdout))
}
}
pub fn storage_get(name: &str) -> Result<Option<String>, JujuError> {
let mut arg_list: Vec<String> = Vec::new();
arg_list.push("-s".to_string());
arg_list.push(name.to_string());
arg_list.push("location".to_string());
let output = run_command("storage-get", &arg_list, false)?;
let stdout = String::from_utf8(output.stdout)?.trim().to_string();
if stdout.is_empty() {
Ok(None)
} else {
Ok(Some(stdout))
}
}
pub fn storage_list() -> Result<String, JujuError> {
let output = run_command_no_args("storage-list", false)?;
return Ok(String::from_utf8(output.stdout)?);
}
pub fn process_hooks(registry: Vec<Hook>) -> Result<(), String> {
let hook_name = match charmhelpers::core::hookenv::hook_name() {
Some(s) => s,
_ => "".to_string(),
};
for hook in registry {
if hook_name.contains(&hook.name) {
return (hook.callback)();
}
}
return Err(format!("Warning: Unknown callback for hook {}", hook_name));
}
pub fn leader_get(attribute: Option<String>) -> Result<Option<String>, JujuError> {
let arg_list: Vec<String>;
match attribute {
Some(a) => arg_list = vec![a],
None => arg_list = vec!['-'.to_string()],
};
let output = run_command("leader-get", &arg_list, false)?;
let value = String::from_utf8(output.stdout)?.trim().to_string();
if value.is_empty() {
Ok(None)
} else {
Ok(Some(value))
}
}
pub fn leader_set(settings: HashMap<String, String>) -> Result<i32, JujuError> {
let mut arg_list: Vec<String> = Vec::new();
for (key, value) in settings {
arg_list.push(format!("{}={}", key, value));
}
let output = run_command("leader-set", &arg_list, false)?;
return process_output(output);
}
pub fn is_leader() -> Result<bool, JujuError> {
let output = run_command_no_args("is-leader", false)?;
let output_str: String = String::from_utf8(output.stdout)?;
match output_str.trim().as_ref() {
"True" => Ok(true),
"False" => Ok(false),
_ => Ok(false),
}
}
fn run_command_no_args(command: &str, as_root: bool) -> Result<std::process::Output, JujuError> {
if as_root {
let mut cmd = std::process::Command::new("sudo");
let output = cmd.output()?;
return Ok(output);
} else {
let mut cmd = std::process::Command::new(command);
let output = cmd.output()?;
return Ok(output);
}
}
fn run_command(command: &str,
arg_list: &Vec<String>,
as_root: bool)
-> Result<std::process::Output, JujuError> {
if as_root {
let mut cmd = std::process::Command::new("sudo");
cmd.arg(command);
for arg in arg_list {
cmd.arg(&arg);
}
let output = cmd.output()?;
return Ok(output);
} else {
let mut cmd = std::process::Command::new(command);
for arg in arg_list {
cmd.arg(&arg);
}
let output = cmd.output()?;
return Ok(output);
}
}