All ProjectsHome
jobrunner
jobrunner/src/main.rs
main.rs Raw
#[macro_use]
extern crate serde_derive;

extern crate docopt;
extern crate chrono;

mod config;
use config::Config;
mod job;
use job::Job;
mod run;
use run::Run;

use docopt::Docopt;

use std::env;
use std::io;
use std::fs;
use std::path::{Path, PathBuf};
use std::process;

const USAGE: &'static str = "
Job Runner

Usage:
    job <command> [<name>]
    job [options]

Options:
    -h --help   Show this screen.
    --version   Show version.

Commands:
    list            List available projects
    info <name>     Show information for <name>
    run <name>      Run job <name>
";

const VERSION: &'static str = env!("CARGO_PKG_VERSION");

#[derive(Debug, Deserialize)]
struct Args {
    arg_command: Option<Command>,
    arg_name: String,
}

#[derive(Debug, Deserialize)]
enum Command {
    List, Info, Run,
}

fn list_jobs(dir: &Path) -> io::Result<Vec<Job>> {
    let mut v: Vec<Job> = vec![];
    for entry in try!(fs::read_dir(dir)) {
        let entry = try!(entry);
        if !try!(fs::metadata(entry.path())).is_dir() { continue; }
        match Job::from_dir(entry.path().as_path()) {
            Ok(j) => v.push(j),
            Err(_) => continue,
        }
    }
    Ok(v)
}

fn print_job_list(conf: &Config) {
    let l = match list_jobs(conf.base_dir.as_path()) {
        Ok(v) => v,
        Err(e) => { println!("Unable to get job list: {}", e); return },
    };
    println!("Available jobs:");
    for job in l.iter() {
        println!("{}", job);
    }
}

fn print_job_info(job_name: &String, conf: &Config) {
    let job = match Job::from_dir(conf.base_dir.join(job_name).as_path()) {
        Ok(x) => x,
        Err(e) => { println!("Error retrieving configuration for {}: {}", job_name, e); return },
    };
    
    println!("{}", job);
}

fn print_job_run(job_name: &String, conf: &Config) {
    let mut job = match Job::from_dir(conf.base_dir.join(job_name).as_path()) {
        Ok(x) => x,
        Err(e) => { println!("Error retrieving configuration for {}: {}", job_name, e); return },
    };
    job.name = job_name.clone(); //This is to allow for directories in name
    
    let mut run = Run::new(&job, conf);
    
    //TODO: do we want to exit nonzero if the job exited nonzero? probably
    match run.output() {
        Ok(result) => println!("Run of {} ({}) at {} completed, output in {:?}",
                                job.display,
                                job.name,
                                result.run_start.to_rfc3339(), 
                                result.archive_dir),
        Err(e) => { 
            println!("Run failed: {}", e);
            process::exit(2);
        },
    }
}

fn main() {
    let args: Args = Docopt::new(USAGE)
                             .and_then(|d| d.version(Some(VERSION.to_string())).deserialize())
                             .unwrap_or_else(|e| e.exit());
    
    let conf_dir = match env::current_dir() {
        Ok(ref p) => p.to_path_buf(),
        Err(e) => { println!("Could not get current dir: {}", e); PathBuf::from("/tmp/jobs") },
    };
    let config = match Config::from_file(&conf_dir.join("config.toml")) {
        Ok(c) => c,
        Err(e) => { println!("Error reading configuration file in {:?}: {}", conf_dir, e); process::exit(1); },
    };
    
    match args.arg_command {
        Some(Command::List) => print_job_list(&config),
        Some(Command::Info) | Some(Command::Run) if args.arg_name.is_empty() => {
                println!("{:?} needs a job name", args.arg_command.unwrap());
                println!("{}", USAGE);
                process::exit(1);
            },
        Some(Command::Info) => print_job_info(&args.arg_name, &config),
        Some(Command::Run) => print_job_run(&args.arg_name, &config),
        None => {
                println!("No command specified");
                println!("{}", USAGE);
            },
    }
    
    //println!("{:?}", args);
}