Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 21 additions & 12 deletions src/cmd.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,23 +3,24 @@
//! These functions are mostly used by deployment steps to perform
//! the different actions they need to do while deployment or rolling back.

use std::process::{Command, Stdio, Output, ExitStatus};
use std::io;
use std::process::{Command, ExitStatus, Output, Stdio};

#[derive(Copy, Clone)]
pub enum FSResourceType {
File,
Directory,
Symlink
Symlink,
}

#[derive(Copy, Clone)]
pub enum SortOrder {
Asc,
Des,
}

/// Executes a command on the server and returns its output or an IO error.
pub fn exec_remote_cmd(host: &str, cmd: &str) -> Result<Output, io::Error> {

let output = Command::new("ssh").args(&[host, cmd]).output()?;

Ok(output)
Expand All @@ -28,7 +29,6 @@ pub fn exec_remote_cmd(host: &str, cmd: &str) -> Result<Output, io::Error> {
/// Executes a command on the server and pipes its output directly to stdout.
/// It returns the command `ExitStatus` or an IO error.
pub fn exec_remote_cmd_inherit_output(host: &str, cmd: &str) -> Result<ExitStatus, io::Error> {

let mut child = Command::new("ssh")
.args(&[host, cmd])
.stdout(Stdio::inherit())
Expand All @@ -41,12 +41,15 @@ pub fn exec_remote_cmd_inherit_output(host: &str, cmd: &str) -> Result<ExitStatu

/// Checks whether or not a given file exist on the server.
/// Allows checking for different types including File, Directory and Symlink.
pub fn exec_remote_file_exists(host: &str, file_path: &str, resource_type: FSResourceType) -> Result<bool, io::Error> {

pub fn exec_remote_file_exists(
host: &str,
file_path: &str,
resource_type: FSResourceType,
) -> Result<bool, io::Error> {
let type_key = match resource_type {
FSResourceType::File => "f",
FSResourceType::Directory => "d",
FSResourceType::Symlink => "L"
FSResourceType::Symlink => "L",
};

let cmd = format!("test -{} {}", type_key, file_path);
Expand All @@ -57,18 +60,24 @@ pub fn exec_remote_file_exists(host: &str, file_path: &str, resource_type: FSRes

/// Given a path, it returns a vector containing the filenames of all the files inside that path
/// sorted in descending order.
pub fn exec_remote_fetch_sorted_filenames_in_dir(host: &str, dir_path: &str, sort_order: SortOrder) -> Result<Vec<String>, io::Error> {

pub fn exec_remote_fetch_sorted_filenames_in_dir(
host: &str,
dir_path: &str,
sort_order: SortOrder,
) -> Result<Vec<String>, io::Error> {
let sort_cmd = match sort_order {
SortOrder::Asc => "sort",
SortOrder::Des => "sort -r"
SortOrder::Des => "sort -r",
};

let cmd = format!("find {} -maxdepth 1 -mindepth 1 -printf '%f\n' | {}", dir_path, sort_cmd);
let cmd = format!(
"find {} -maxdepth 1 -mindepth 1 -printf '%f\n' | {}",
dir_path, sort_cmd
);
let output = exec_remote_cmd(host, &cmd)?;
let stdout = String::from_utf8_lossy(&output.stdout);

let vector: Vec<String> = stdout.trim_right().split("\n").map(|x| String::from(x)).collect();
let vector: Vec<String> = stdout.trim_right().split('\n').map(String::from).collect();

Ok(vector)
}
10 changes: 7 additions & 3 deletions src/config/stages/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ pub enum ConfigError {
BadType(String, String),
///Parameters: (field_name)
MissingField(String),
IoError
IoError,
}

impl fmt::Display for ConfigError {
Expand All @@ -22,8 +22,12 @@ impl fmt::Display for ConfigError {
BadStage(ref env) => write!(f, "Couldn't find stage '{}' in config file", env),
BadType(ref name, ref expected) => {
write!(f, "type mismatch for '{}'. expected {}", name, expected)
},
MissingField(ref name) => write!(f, "Required field '{}' is missing from the config file", name),
}
MissingField(ref name) => write!(
f,
"Required field '{}' is missing from the config file",
name
),
IoError => write!(f, "I/O error while reading the config file"),
}
}
Expand Down
60 changes: 31 additions & 29 deletions src/config/stages/mod.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use std::collections::HashMap;
use std::fs::File;
use std::io::prelude::*;
use toml_edit::*;
use std::collections::HashMap;

pub mod error;

Expand All @@ -14,15 +14,15 @@ pub struct HostConfig {
pub keep_releases: i8,
pub repo_url: String,
pub link_files: Vec<String>,
pub link_dirs: Vec<String>
pub link_dirs: Vec<String>,
}

pub fn parse_config_file(file_name: &str, stage: &str) -> Result<HostConfig, ConfigError> {
let mut file = File::open(file_name)
.map_err(|_| ConfigError::NotFound(stage.into()))?;
let mut file = File::open(file_name).map_err(|_| ConfigError::NotFound(stage.into()))?;

let mut contents = String::new();
file.read_to_string(&mut contents).map_err(|_| ConfigError::IoError)?;
file.read_to_string(&mut contents)
.map_err(|_| ConfigError::IoError)?;

let doc = contents.parse::<Document>().unwrap();

Expand All @@ -31,45 +31,41 @@ pub fn parse_config_file(file_name: &str, stage: &str) -> Result<HostConfig, Con

let host_table = match doc[stage].as_table() {
Some(table) => table,
None => {
return Err(ConfigError::BadStage(stage.into()))
}
None => return Err(ConfigError::BadStage(stage.into())),
};

let mut config_map = HashMap::new();

//populate hash map with the stage config items
for (key, value) in host_table.iter() {
config_map.insert(key, value);
};
}

//if global table is present, override stage items with global items
if let Some(globals) = global_table {
for (key, value) in globals.iter() {
config_map.insert(key, value);
};
}
}

Ok(
HostConfig {
host: parse_string("host", &config_map)?,
deploy_path: parse_string("deploy-path", &config_map)?,
//keep_releases defaults to 3 if not present
keep_releases: parse_integer("keep-releases", &config_map).unwrap_or(3 as i64) as i8,
repo_url: parse_string("repo-url", &config_map)?,
link_files: parse_string_vector("linked-files", &config_map).unwrap_or(Vec::new()),
link_dirs: parse_string_vector("linked-dirs", &config_map).unwrap_or(Vec::new())
}
)
Ok(HostConfig {
host: parse_string("host", &config_map)?,
deploy_path: parse_string("deploy-path", &config_map)?,
//keep_releases defaults to 3 if not present
keep_releases: parse_integer("keep-releases", &config_map).unwrap_or(3 as i64) as i8,
repo_url: parse_string("repo-url", &config_map)?,
link_files: parse_string_vector("linked-files", &config_map).unwrap_or_default(),
link_dirs: parse_string_vector("linked-dirs", &config_map).unwrap_or_default(),
})
}

fn parse_integer(key: &str, hash_map: &HashMap<&str, &Item>) -> Result<i64, ConfigError> {
let value = match hash_map.get(key) {
Some(value) => match value.as_integer() {
Some(value) => value,
None => return Err(ConfigError::BadType(key.into(), "integer".into()))
None => return Err(ConfigError::BadType(key.into(), "integer".into())),
},
None => return Err(ConfigError::MissingField(key.into()))
None => return Err(ConfigError::MissingField(key.into())),
};

Ok(value)
Expand All @@ -79,21 +75,27 @@ fn parse_string(key: &str, hash_map: &HashMap<&str, &Item>) -> Result<String, Co
let value = match hash_map.get(key) {
Some(value) => match value.as_str() {
Some(value) => value,
None => return Err(ConfigError::BadType(key.into(), "string".into()))
None => return Err(ConfigError::BadType(key.into(), "string".into())),
},
None => return Err(ConfigError::MissingField(key.into()))
None => return Err(ConfigError::MissingField(key.into())),
};

Ok(value.into())
}

fn parse_string_vector(key: &str, hash_map: &HashMap<&str, &Item>) -> Result<Vec<String>, ConfigError> {
fn parse_string_vector(
key: &str,
hash_map: &HashMap<&str, &Item>,
) -> Result<Vec<String>, ConfigError> {
let value = match hash_map.get(key) {
Some(value) => match value.as_array() {
Some(value) => value.iter().map(|value| value.as_str().unwrap().to_string()).collect(),
None => return Err(ConfigError::BadType(key.into(), "array".into()))
Some(value) => value
.iter()
.map(|value| value.as_str().unwrap().to_string())
.collect(),
None => return Err(ConfigError::BadType(key.into(), "array".into())),
},
None => return Err(ConfigError::MissingField(key.into()))
None => return Err(ConfigError::MissingField(key.into())),
};

Ok(value)
Expand Down
2 changes: 1 addition & 1 deletion src/config/steps/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ pub enum StepConfigError {
AmbiguousPosition,
//The position field is missing from the configuration.
MissingPosition,
IoError
IoError,
}

impl fmt::Display for StepConfigError {
Expand Down
80 changes: 51 additions & 29 deletions src/config/steps/mod.rs
Original file line number Diff line number Diff line change
@@ -1,92 +1,114 @@
pub mod error;

use std::io::{ErrorKind, Read};
use std::fs::File;
use self::error::{StepConfigError, StepConfigError::*};
use toml_edit::*;
use std::collections::HashMap;
use std::fs::File;
use std::io::{ErrorKind, Read};
use toml_edit::*;

#[derive(Debug, Clone)]
pub enum StepPosition {
Before,
After
After,
}

#[derive(Debug, Clone)]
pub struct StepConfig {
pub name: String,
pub comand: String,
pub position: StepPosition,
pub ref_step: String
pub ref_step: String,
}

type Steps = HashMap<String, StepConfig>;

pub fn parse_steps_config_file(file_name: &str, stage: &str) -> Result<Vec<StepConfig>, StepConfigError> {
pub fn parse_steps_config_file(
file_name: &str,
stage: &str,
) -> Result<Vec<StepConfig>, StepConfigError> {
let file = File::open(file_name);

//return empty vector if steps file is not present.
if file.is_err() {
match file.unwrap_err().kind() {
ErrorKind::NotFound => return Ok(Vec::new()),
_ => return Err(StepConfigError::IoError)
_ => return Err(StepConfigError::IoError),
}
}

let mut file = file.unwrap();
let mut contents = String::new();
file.read_to_string(&mut contents).map_err(|_| StepConfigError::IoError)?;
file.read_to_string(&mut contents)
.map_err(|_| StepConfigError::IoError)?;

let doc = contents.parse::<Document>().unwrap();

let mut steps_map: HashMap<String, StepConfig> = HashMap::new();
parse_steps_and_add_to_hashmap(&doc, stage, &mut steps_map)?;
parse_steps_and_add_to_hashmap(&doc, "global", &mut steps_map)?;

let steps_collection: Vec<StepConfig> = steps_map.drain().map(|(_, k)| { k }).collect();
let steps_collection: Vec<StepConfig> = steps_map.drain().map(|(_, k)| k).collect();
Ok(steps_collection)
}

fn parse_steps_and_add_to_hashmap(doc: &Document, stage: &str, collection: &mut Steps)
-> Result<(), StepConfigError> {

fn parse_steps_and_add_to_hashmap(
doc: &Document,
stage: &str,
collection: &mut Steps,
) -> Result<(), StepConfigError> {
if let Some(stage_steps) = doc[stage]["steps"].as_array_of_tables() {
for table in stage_steps.iter() {
let step = parse_step_table(table)?;
collection.insert(step.name.to_owned(), step);
};
}
}

Ok(())
}

fn parse_step_table(table: &Table) -> Result<StepConfig, StepConfigError> {
let name = table.get("name").ok_or(MissingField("name".into()))?.as_str()
.ok_or(BadType("name".into(), "string".into()))?.to_string();

let comand = table.get("command").ok_or(MissingField("command".into()))?.as_str()
.ok_or(BadType("command".into(), "string".into()))?.to_string();
let name = table
.get("name")
.ok_or_else(|| MissingField("name".into()))?
.as_str()
.ok_or_else(|| BadType("name".into(), "string".into()))?
.to_string();

let comand = table
.get("command")
.ok_or_else(|| MissingField("command".into()))?
.as_str()
.ok_or_else(|| BadType("command".into(), "string".into()))?
.to_string();

if !table.contains_key("before") && !table.contains_key("after") {
return Err(AmbiguousPosition)
return Err(AmbiguousPosition);
};

let (position, ref_step) = if table.contains_key("before") {
let ref_step = table.get("before").unwrap().as_str().ok_or(BadType("before".into(), "string".into()))?.to_string();
let ref_step = table
.get("before")
.unwrap()
.as_str()
.ok_or_else(|| BadType("before".into(), "string".into()))?
.to_string();
(StepPosition::Before, ref_step)
} else if table.contains_key("after") {
let ref_step = table.get("after").unwrap().as_str().ok_or(BadType("after".into(), "string".into()))?.to_string();
let ref_step = table
.get("after")
.unwrap()
.as_str()
.ok_or_else(|| BadType("after".into(), "string".into()))?
.to_string();
(StepPosition::After, ref_step)
} else {
return Err(MissingPosition);
};

Ok(
StepConfig {
name,
comand,
position,
ref_step
}
)
Ok(StepConfig {
name,
comand,
position,
ref_step,
})
}
Loading