egc 0.1.1

This library implements an Akamai OPEN EdgeGrid Client in Rust
extern crate docopt;
extern crate glob;
extern crate libedgegrid;
extern crate libmultilog;
#[macro_use]
extern crate log;
extern crate regex;
extern crate rustc_serialize;
extern crate time;

use docopt::Docopt;
use libedgegrid::auth;
use libmultilog::multi::{MultiLogger, init_multi_logger};
use log::{LogLevel, LogRecord};
use log::LogLevelFilter::*;
use regex::Regex;
use self::InitError::*;
use std::collections::HashMap;
use std::env;
use std::fs::{self, File};
use std::io::{self, BufReader, BufWriter, ErrorKind, Read, Write};
use std::path::Path;
use std::process;

mod api;
mod version;

#[cfg_attr(rustfmt, rustfmt_skip)]
static USAGE: &'static str = "egc - Run the Akamai EdgeGrid Client

Usage:
    egc [options] help [<helpcmd>]
    egc [options] (alert | ccu | dt | events | lds) <akargs>...
    egc -h
    egc -V [-v]

Options:
    -h --help              Show this message.
    -V --version           Show version information.
    -v --verbose           Turn on verbose output.
    -f CPATH --file=CPATH  Configuration file path.
    -q --quiet             Disable stdout logging.
    --logfile=FPATH        Enable file logging at path.
    -c --cpcode            Enable cpcode purge request.
    -i --byid              Lookup the alert by id.
    -t MS --timeout=MS     Set the curl timeout for the request in milliseconds.

Examples:
    N/A";

#[derive(Debug, RustcDecodable)]
pub struct Args {
    arg_akargs: Vec<String>,
    arg_helpcmd: String,
    cmd_alert: bool,
    cmd_ccu: bool,
    cmd_dt: bool,
    cmd_events: bool,
    cmd_help: bool,
    cmd_lds: bool,
    flag_byid: bool,
    flag_cpcode: bool,
    flag_file: String,
    flag_help: bool,
    flag_logfile: String,
    flag_quiet: bool,
    flag_timeout: usize,
    flag_verbose: bool,
    flag_version: bool,
}

pub type AuthsHashMap = HashMap<String, auth::EdgeGridAuth>;

#[derive(Debug)]
enum InitError {
    IO(io::Error),
    GLOB(glob::PatternError),
    LOG(log::SetLoggerError),
    RE(regex::Error),
}

impl From<io::Error> for InitError {
    fn from(err: io::Error) -> InitError {
        IO(err)
    }
}

impl From<glob::PatternError> for InitError {
    fn from(err: glob::PatternError) -> InitError {
        GLOB(err)
    }
}

impl From<log::SetLoggerError> for InitError {
    fn from(err: log::SetLoggerError) -> InitError {
        LOG(err)
    }
}

impl From<regex::Error> for InitError {
    fn from(err: regex::Error) -> InitError {
        RE(err)
    }
}

fn stdoutfn(record: &LogRecord) {
    println!("{}", record.args());
}

fn fileoutfn(record: &LogRecord, w: &mut BufWriter<File>) {
    let now = time::now();
    w.write_fmt(format_args!("{} {:5} {:4} -- {}: {}\n",
                             now.rfc3339(),
                             record.level(),
                             record.location().line(),
                             record.location().module_path(),
                             record.args()))
     .and(w.flush())
     .unwrap();
}

fn init(stdout: bool, file_path: Option<&Path>, verbose: bool) -> Result<(), InitError> {
    let mut ml: MultiLogger = Default::default();

    if stdout {
        ml.enable_stdout(stdoutfn);
    }

    if file_path.is_some() {
        let ref mut logpath = file_path.unwrap();
        let parent = try!(logpath.parent().ok_or_else(|| {
            io::Error::new(ErrorKind::Other, "Unable to determine parent!")
        }));

        match fs::metadata(parent) {
            Ok(_) => {}
            Err(_) => {
                try!(fs::create_dir_all(parent));
            }
        }

        ml.enable_file(fileoutfn, logpath.to_path_buf());
        let r = try!(Regex::new("^mio"));
        ml.add_file_filter(LogLevel::Debug, r);
    }

    try!(init_multi_logger(if verbose {
                               Debug
                           } else {
                               Info
                           },
                           ml));
    Ok(())
}

fn parse_client_file(path: &Path) -> Result<auth::EdgeGridAuth, io::Error> {
    let client_file = try!(File::open(path));
    let mut reader = BufReader::new(client_file);
    let mut out = String::new();
    try!(reader.read_to_string(&mut out));
    let non_empty_lines: Vec<&str> = out.lines()
                                        .filter_map(|s| {
                                            let t = s.trim();
                                            if t.is_empty() {
                                                None
                                            } else {
                                                Some(t)
                                            }
                                        })
                                        .collect();
    let name_split: Vec<&str> = non_empty_lines[1].split(": ").collect();
    let name = name_split[1];
    let url_split: Vec<&str> = non_empty_lines[2].split(": ").collect();
    let baseurl = url_split[1].trim_right_matches('/');
    let access_token = non_empty_lines[4];
    let client_split: Vec<&str> = non_empty_lines[6].split(" ").collect();
    let client_filtered: Vec<&str> = client_split.into_iter()
                                                 .filter(|s| !s.is_empty())
                                                 .collect();
    let client_token = client_filtered[2];
    let client_secret = client_filtered[4];

    let mut egr = auth::EdgeGridAuth::new(name, baseurl);
    egr.set_access_token(access_token);
    egr.set_client_token(client_token);
    egr.set_client_secret(client_secret);
    Ok(egr)
}

type ParseResult = Result<HashMap<String, auth::EdgeGridAuth>, InitError>;

fn parse_client_files() -> ParseResult {
    let mut clients = HashMap::new();
    let mut egcdir = try!(env::home_dir().ok_or_else(|| {
        io::Error::new(ErrorKind::Other, "Unable to determine home directory!")
    }));
    egcdir.push(".egc");

    let mut pattern = String::from(egcdir.to_str().unwrap());
    pattern.push_str("/client-*.txt");
    let glob = try!(glob::glob(&pattern[..]));

    for path in glob.filter_map(Result::ok) {
        debug!("Parsing {}", path.display());
        let egr = try!(parse_client_file(&path));
        clients.insert(String::from(egr.name()), egr);
    }
    Ok(clients)
}

fn exit(msg: String, code: u8) -> ! {
    if !msg.is_empty() {
        if code == 0 {
            info!("{}", msg);
        } else {
            error!("{}", msg);
        }
    }
    process::exit(code as i32)
}

fn get_auth<'a>(name: &str,
                timeout: usize,
                auths: &'a mut HashMap<String, auth::EdgeGridAuth>)
                -> &'a mut auth::EdgeGridAuth {
    let mut egr = match auths.get_mut(name) {
        Some(e) => e,
        None => {
            ::exit(String::from("Unable to get authorization"), 1);
        }
    };

    if timeout > 0 {
        egr.set_timeout(timeout);
    }
    egr
}

fn subcommand(base: &str, args: &Args) -> Vec<String> {
    let mut argv = Vec::new();
    argv.push(String::from(base));

    if args.flag_timeout > 0 {
        argv.push(String::from("-t"));
        argv.push(format!("{}", args.flag_timeout));
    }

    if args.flag_cpcode {
        argv.push(String::from("-c"));
    }

    if args.flag_byid {
        argv.push(String::from("-i"));
    }

    argv.append(&mut args.arg_akargs.clone());
    argv
}

fn main() {
    let args: Args = Docopt::new(USAGE)
                         .and_then(|d| d.decode())
                         .unwrap_or_else(|e| e.exit());

    let fp = if args.flag_file.is_empty() {
        None
    } else {
        Some(Path::new(&args.flag_file[..]))
    };
    match init(!args.flag_quiet, fp, args.flag_verbose) {
        Ok(_) => {}
        Err(e) => {
            exit(format!("{:?}", e), 1);
        }
    }

    if args.flag_version {
        println!("{}", version::version(args.flag_verbose));
        exit(String::new(), 0);
    } else if args.cmd_help {
        if args.arg_helpcmd.is_empty() {
            println!("{}", USAGE);
        } else {
            match &args.arg_helpcmd[..] {
                #[cfg(feature="ccu")]
                "ccu" => {
                    println!("{}", api::ccu::usage());
                }
                #[cfg(feature = "luna")]
                "alert" => {
                    println!("{}", api::luna::alert::usage());
                }
                #[cfg(feature = "luna")]
                "dt" => {
                    println!("{}", api::luna::dt::usage());
                }
                _ => {
                    println!("Unknown command!");
                }
            }
        }
        exit(String::new(), 0);
    } else {
        let mut auths = match parse_client_files() {
            Ok(a) => a,
            Err(e) => {
                exit(format!("{:?}", e), 1);
            }
        };

        match args.cmd_ccu {
            #[cfg(feature = "ccu")]
            true => api::ccu::parse_args(subcommand("ccu", &args), &mut auths),
            #[cfg(not(feature = "ccu"))]
            true => {}
            false => {}
        }

        match args.cmd_dt {
            #[cfg(feature = "luna")]
            true => api::luna::dt::parse_args(subcommand("dt", &args), &mut auths),
            #[cfg(not(feature = "luna"))]
            true => {}
            false => {}
        }

        match args.cmd_alert {
            #[cfg(feature = "luna")]
            true => api::luna::alert::parse_args(subcommand("alert", &args), &mut auths),
            #[cfg(not(feature = "luna"))]
            true => {}
            false => {}
        }
    }
}

// "https://staticcontent.vcorders.com/swc/xpedx/images/catalog/alert.png"
// "https://staticcontent.vcorders.com/swc/xpedx/images/catalog/pricing-icon.png"
// "https://xcontent.vcorders.com/productimages/I2226525_small.jpg"
// "https://staticcontent.vcorders.com/swc/Saalfeld/images/catalog/pricing-icon.png"