Skip to content

Commit

Permalink
Add config
Browse files Browse the repository at this point in the history
  • Loading branch information
lukaspustina committed Dec 19, 2019
1 parent 545d7b8 commit db995b3
Show file tree
Hide file tree
Showing 6 changed files with 236 additions and 0 deletions.
31 changes: 31 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,10 @@ path = "src/lib.rs"

[dependencies]
log = "0.4"
serde = { version = "1.0", features = ["derive"] }
snafu = "0.6"
subprocess = "0.1"
toml = "0.5"

[dev-dependencies]
env_logger = "0.7"
Expand Down
50 changes: 50 additions & 0 deletions contrib/osx.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
[defaults]
timeout = 1

[[command]]
name = "hostname"
title = "Hostname"
command = "/bin/hostname -"
timeout = 1
default_run = true

[[command]]
name = "date"
title = "Now"
command = '/bin/date -u +"%Y-%m-%dT%H:%M:%SZ"'
timeout = 1
default_run = true

[[command]]
name = "uname"
title = "Host OS"
description = "Basic host OS information"
command = "/usr/bin/uname -a"
timeout = 1
default_run = true

[[command]]
name = "uptime"
title = "Current Load"
description = "Current load and uptime"
command = "/usr/bin/uptime"
timeout = 1
default_run = true

[[command]]
name = "vm_stat"
title = "Virtual Memory statistics"
description = "Current memory usage and statistics in pages"
command = "vm_stat -c 5 1"
timeout = 5
default_run = true

[[command]]
name = "iostat"
title = "Kernel I/O statistics"
description = "Current I/O statistics per dev in KB"
command = "/usr/sbin/iostat -c 5 -K"
timeout = 5
default_run = true

# vim: set ft=toml:
38 changes: 38 additions & 0 deletions src/command.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
use log::{debug,trace};
use serde::{Deserialize, Deserializer, de};
use std::time::Duration;
use std::io::Read;
use snafu::{ResultExt, Snafu};
use subprocess::{Popen, PopenConfig, PopenError, Redirection};
use serde::de::Visitor;
use core::fmt;

/// Error type
#[derive(Debug, Snafu)]
Expand Down Expand Up @@ -37,14 +40,40 @@ pub type Result<T, E = Error> = std::result::Result<T, E>;
/// _ => println!("Command execution failed"),
/// };
/// ```
#[derive(Debug, Deserialize, PartialEq)]
pub struct Command {
pub(crate) name: String,
pub(crate) title: Option<String>,
pub(crate) description: Option<String>,
#[serde(rename = "command", deserialize_with = "de_ser_args")]
pub(crate) args: Vec<String>,
#[serde(rename = "timeout")]
pub(crate) timeout_sec: u64,
pub(crate) default_run: bool,
}

fn de_ser_args<'de, D>(deserializer: D) -> ::std::result::Result<Vec<String>, D::Error>
where
D: Deserializer<'de>,
{
struct ArgsVisitor;

impl<'a> Visitor<'a> for ArgsVisitor {
type Value = Vec<String>;

fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
formatter.write_str("valid command string")
}

fn visit_str<E>(self, s: &str) -> ::std::result::Result<Self::Value, E> where E: de::Error {
let args: Vec<_> = s.split(' ').map(|x| x.into()).collect();
Ok(args)
}
}

deserializer.deserialize_string(ArgsVisitor)
}

impl Command {
/// Create new command with default values
pub fn new<T: Into<String>>(name: T, command: T, timeout_sec: u64) -> Command {
Expand All @@ -54,6 +83,7 @@ impl Command {
Command {
name: name.into(),
title: None,
description: None,
args,
timeout_sec,
default_run: true,
Expand All @@ -68,6 +98,14 @@ impl Command {
}
}

/// Set description of command
pub fn description<T: Into<String>>(self, description: T) -> Command {
Command {
description: Some(description.into()),
..self
}
}

/// Set whether to run this command by default
pub fn run_by_default(self, value: bool) -> Command {
Command {
Expand Down
112 changes: 112 additions & 0 deletions src/config.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
use crate::command::Command;

use serde::Deserialize;
use snafu::{ResultExt, Snafu};
use std::io::Read;
use std::path::{Path, PathBuf};
use std::fs::File;

/// Error type
#[derive(Debug, Snafu)]
#[allow(missing_docs)]
pub enum Error {
/// Failed to parse Config
#[snafu(display("Failed parse config: {}", source))]
ParsingFailed { source: toml::de::Error},
/// Failed to read file
#[snafu(display("Failed read file config '{:?}': {}", path, source))]
ReadFileFailed { path: PathBuf, source: std::io::Error },
}

/// Result type
pub type Result<T, E = Error> = std::result::Result<T, E>;

#[derive(Debug, Deserialize, PartialEq)]
pub struct Config {
pub defaults: Defaults,
#[serde(rename = "command")]
pub commands: Vec<Command>,
}

impl Config {
pub fn from_str(toml: &str) -> Result<Config> {
let config: Config = toml::from_str(toml).context(ParsingFailed {})?;
Ok(config)
}

pub fn from_file<P: AsRef<Path>>(path: P) -> Result<Config> {
let mut file = File::open(path.as_ref())
.context(ReadFileFailed{ path: path.as_ref().to_path_buf() })?;
let mut toml = String::new();
file.read_to_string(&mut toml)
.context(ReadFileFailed{ path: path.as_ref().to_path_buf() })?;
Config::from_str(&toml)
}
}

#[derive(Debug, Deserialize, PartialEq)]
pub struct Defaults {
#[serde(default = "default_timeout")]
pub timeout: u64,
}

fn default_timeout() -> u64 {
5
}

#[cfg(test)]
mod tests {
use super::*;

use spectral::prelude::*;

#[test]
fn config_read_ok() {
let config_txt = r#"
[defaults]
timeout = 5
[[command]]
name = "uname"
title = "Host OS"
description = "Basic host OS information"
command = "/usr/bin/uname -a"
timeout = 1
default_run = true
"#;
let defaults = Defaults {
timeout: 5
};
let mut commands = Vec::new();
commands.push( Command::new("uname", "/usr/bin/uname -a", 1)
.title("Host OS")
.description("Basic host OS information")
.run_by_default(true));
let expected = Config {
defaults,
commands,
};

let config = Config::from_str(config_txt);

asserting("Reading config from toml").that(&config)
.is_ok()
.is_equal_to(&expected);
}

#[test]
fn config_file_ok() {
#[cfg(target_os = "macos")]
let path = "contrib/osx.conf";
#[cfg(target_os = "linux")]
let path = "contrib/linux.conf";

let config = Config::from_file(path);

asserting("Reading config from file").that(&config)
.is_ok()
.map(|x| &x.commands)
.has_length(6)
}
}
3 changes: 3 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@
/// External CLI commands used to collect USE report information.
pub mod command;

/// Configuration
pub mod config;

/// Trait and default implementation to run commands and collect their output
pub mod runner;

Expand Down

0 comments on commit db995b3

Please sign in to comment.