-
Notifications
You must be signed in to change notification settings - Fork 12
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #40 from jjcomer/refactor
Refactor cli and server logic
- Loading branch information
Showing
9 changed files
with
1,690 additions
and
1,633 deletions.
There are no files selected for viewing
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -5,7 +5,7 @@ doc = false | |
|
||
[package] | ||
name = 'hogan' | ||
version = '0.7.1' | ||
version = '0.7.2' | ||
authors = ['Jonathan Morley <[email protected]>'] | ||
edition = '2018' | ||
|
||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,215 @@ | ||
use crate::app::config::App; | ||
use crate::app::config::AppCommon; | ||
use failure::Error; | ||
use hogan::config::ConfigDir; | ||
use hogan::template::TemplateDir; | ||
use regex::Regex; | ||
use std::fs::File; | ||
use std::fs::OpenOptions; | ||
use std::io::ErrorKind::AlreadyExists; | ||
use std::io::Write; | ||
use std::path::PathBuf; | ||
|
||
pub fn cli( | ||
templates_path: PathBuf, | ||
environments_regex: Regex, | ||
templates_regex: Regex, | ||
common: AppCommon, | ||
ignore_existing: bool, | ||
) -> Result<(), Error> { | ||
let handlebars = hogan::transform::handlebars(common.strict); | ||
|
||
let template_dir = TemplateDir::new(templates_path)?; | ||
let mut templates = template_dir.find(templates_regex); | ||
println!("Loaded {} template file(s)", templates.len()); | ||
|
||
let config_dir = ConfigDir::new(common.configs_url, &common.ssh_key)?; | ||
let environments = config_dir.find(App::config_regex(&environments_regex)?); | ||
println!("Loaded {} config file(s)", environments.len()); | ||
|
||
for environment in environments { | ||
println!("Updating templates for {}", environment.environment); | ||
|
||
for template in &mut templates { | ||
debug!("Transforming {:?}", template.path); | ||
|
||
let rendered = template.render(&handlebars, &environment)?; | ||
trace!("Rendered: {:?}", rendered.contents); | ||
|
||
if ignore_existing { | ||
if let Err(e) = match OpenOptions::new() | ||
.write(true) | ||
.create_new(true) | ||
.open(&rendered.path) | ||
{ | ||
Ok(ref mut f) => f.write_all(&rendered.contents), | ||
Err(ref e) if e.kind() == AlreadyExists => { | ||
println!("Skipping {:?} - config already exists.", rendered.path); | ||
trace!("Skipping {:?} - config already exists.", rendered.path); | ||
Ok(()) | ||
} | ||
Err(e) => Err(e), | ||
} { | ||
bail!("Error transforming {:?} due to {}", rendered.path, e) | ||
} | ||
} else if let Err(e) = File::create(&rendered.path)?.write_all(&rendered.contents) { | ||
bail!("Error transforming {:?} due to {}", rendered.path, e) | ||
} | ||
} | ||
} | ||
|
||
Ok(()) | ||
} | ||
|
||
#[cfg(test)] | ||
mod tests { | ||
use assert_cmd; | ||
use dir_diff; | ||
use fs_extra; | ||
use predicates; | ||
use tempfile; | ||
|
||
use self::assert_cmd::prelude::*; | ||
use self::fs_extra::dir; | ||
use self::predicates::prelude::*; | ||
use std::io::Write; | ||
use std::path::Path; | ||
use std::process::Command; | ||
|
||
#[cfg(not(all(target_env = "msvc", target_arch = "x86_64")))] | ||
#[test] | ||
fn test_transform() { | ||
let temp_dir = tempfile::tempdir().unwrap(); | ||
|
||
fs_extra::copy_items( | ||
&vec!["tests/fixtures/projects/templates"], | ||
temp_dir.path(), | ||
&dir::CopyOptions::new(), | ||
) | ||
.unwrap(); | ||
|
||
let templates_path = temp_dir.path().join("templates"); | ||
|
||
let mut cmd = Command::cargo_bin(env!("CARGO_PKG_NAME")).unwrap(); | ||
|
||
let cmd = cmd.args(&[ | ||
"transform", | ||
"--configs", | ||
"tests/fixtures/configs", | ||
"--templates", | ||
templates_path.to_str().unwrap(), | ||
]); | ||
|
||
cmd.assert().success(); | ||
|
||
cmd.assert().stdout( | ||
predicate::str::contains(format!(r#"Finding Files: {:?}"#, templates_path)).from_utf8(), | ||
); | ||
|
||
cmd.assert().stdout( | ||
predicate::str::contains( | ||
r"regex: /^[^.]*(\w+\.)*template([-.].+)?\.(config|ya?ml|properties)/", | ||
) | ||
.from_utf8(), | ||
); | ||
|
||
cmd.assert() | ||
.stdout(predicate::str::contains("Loaded 6 template file(s)").from_utf8()); | ||
|
||
cmd.assert().stdout( | ||
predicate::str::contains(r#"Finding Files: "tests/fixtures/configs""#).from_utf8(), | ||
); | ||
|
||
cmd.assert() | ||
.stdout(predicate::str::contains(r#"regex: /config\..+\.json$/"#).from_utf8()); | ||
|
||
cmd.assert() | ||
.stdout(predicate::str::contains("Loaded 4 config file(s)").from_utf8()); | ||
|
||
for environment in ["EMPTY", "ENVTYPE", "TEST", "TEST2"].iter() { | ||
cmd.assert().stdout( | ||
predicate::str::contains(format!("Updating templates for {}", environment)) | ||
.from_utf8(), | ||
); | ||
} | ||
|
||
assert!(!dir_diff::is_different( | ||
&templates_path.join("project-1"), | ||
&Path::new("tests/fixtures/projects/rendered/project-1") | ||
) | ||
.unwrap()); | ||
|
||
assert!(!dir_diff::is_different( | ||
&templates_path.join("project-2"), | ||
&Path::new("tests/fixtures/projects/rendered/project-2") | ||
) | ||
.unwrap()); | ||
} | ||
|
||
#[cfg(not(all(target_env = "msvc", target_arch = "x86_64")))] | ||
#[test] | ||
fn test_ignore_existing() { | ||
let temp_dir = tempfile::tempdir().unwrap(); | ||
|
||
fs_extra::copy_items( | ||
&vec!["tests/fixtures/projects/templates"], | ||
temp_dir.path(), | ||
&dir::CopyOptions::new(), | ||
) | ||
.unwrap(); | ||
|
||
let templates_path = temp_dir.path().join("templates"); | ||
|
||
let ignore_path = templates_path.join("project-1/Web.EMPTY.config"); | ||
if let Ok(ref mut f) = std::fs::OpenOptions::new() | ||
.write(true) | ||
.create(true) | ||
.open(&ignore_path) | ||
{ | ||
f.write_all(b"Hamburger.") | ||
.expect("Failed to create test file for ignore.") | ||
} | ||
|
||
let mut cmd = Command::cargo_bin(env!("CARGO_PKG_NAME")).unwrap(); | ||
let cmd = cmd.args(&[ | ||
"transform", | ||
"--configs", | ||
"tests/fixtures/configs", | ||
"--templates", | ||
templates_path.to_str().unwrap(), | ||
"-i", | ||
]); | ||
|
||
cmd.assert().success(); | ||
|
||
// assert that running the command with the ignore flag | ||
// did not overwrite the manually created project-1/Web.EMPTY.config | ||
let data2 = | ||
std::fs::read_to_string(&ignore_path).expect("Failed to read test file for ignore."); | ||
assert!(data2 == "Hamburger."); | ||
|
||
// after running the command again without the ignore flag | ||
// assert that the configs now match those in the rendered directory | ||
let mut cmd = Command::cargo_bin(env!("CARGO_PKG_NAME")).unwrap(); | ||
let cmd = cmd.args(&[ | ||
"transform", | ||
"--configs", | ||
"tests/fixtures/configs", | ||
"--templates", | ||
templates_path.to_str().unwrap(), | ||
]); | ||
cmd.assert().success(); | ||
|
||
assert!(!dir_diff::is_different( | ||
&templates_path.join("project-1"), | ||
&Path::new("tests/fixtures/projects/rendered/project-1") | ||
) | ||
.unwrap()); | ||
|
||
assert!(!dir_diff::is_different( | ||
&templates_path.join("project-2"), | ||
&Path::new("tests/fixtures/projects/rendered/project-2") | ||
) | ||
.unwrap()); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,139 @@ | ||
use failure::Error; | ||
use hogan::config::ConfigUrl; | ||
use regex::{Regex, RegexBuilder}; | ||
use std::path::PathBuf; | ||
use structopt; | ||
use structopt::clap::AppSettings; | ||
use structopt::StructOpt; | ||
|
||
/// Transform templates with handlebars | ||
#[derive(StructOpt, Debug)] | ||
#[structopt(setting = AppSettings::InferSubcommands)] | ||
pub struct App { | ||
/// Sets the level of verbosity | ||
#[structopt(short = "v", long = "verbose", parse(from_occurrences), global = true)] | ||
pub verbosity: usize, | ||
|
||
#[structopt(subcommand)] | ||
pub cmd: AppCommand, | ||
} | ||
|
||
#[derive(StructOpt, Debug)] | ||
pub enum AppCommand { | ||
/// Transform handlebars template files against config files | ||
#[structopt(name = "transform")] | ||
Transform { | ||
#[structopt(flatten)] | ||
common: AppCommon, | ||
|
||
/// Filter environments to render templates for | ||
#[structopt( | ||
short = "e", | ||
long = "environments-filter", | ||
parse(try_from_str = App::parse_regex), | ||
default_value = ".+", | ||
value_name = "REGEX" | ||
)] | ||
environments_regex: Regex, | ||
|
||
/// Template source (recursive) | ||
#[structopt( | ||
short = "t", | ||
long = "templates", | ||
parse(from_os_str), | ||
default_value = ".", | ||
value_name = "DIR" | ||
)] | ||
templates_path: PathBuf, | ||
|
||
/// Filter templates to transform | ||
#[structopt( | ||
short = "f", | ||
long = "templates-filter", | ||
parse(try_from_str = App::parse_regex), | ||
default_value = "^[^.]*(\\w+\\.)*template([-.].+)?\\.(config|ya?ml|properties)", | ||
value_name = "REGEX" | ||
)] | ||
templates_regex: Regex, | ||
|
||
/// Ignore existing config files intead of overwriting | ||
#[structopt(short = "i", long = "ignore-existing")] | ||
ignore_existing: bool, | ||
}, | ||
/// Respond to HTTP requests to transform a template | ||
#[structopt(name = "server")] | ||
Server { | ||
#[structopt(flatten)] | ||
common: AppCommon, | ||
|
||
/// Port to serve requests on | ||
#[structopt(short = "p", long = "port", default_value = "80", value_name = "PORT")] | ||
port: u16, | ||
|
||
/// The address for the server to bind on | ||
#[structopt( | ||
short = "b", | ||
long = "address", | ||
default_value = "0.0.0.0", | ||
value_name = "ADDRESS" | ||
)] | ||
address: String, | ||
|
||
/// Set the size of the SHA LRU cache | ||
#[structopt(long = "cache", default_value = "100", value_name = "CACHE_SIZE")] | ||
cache_size: usize, | ||
|
||
/// Filter environments to render templates for | ||
#[structopt( | ||
short = "e", | ||
long = "environments-filter", | ||
parse(try_from_str = App::parse_regex), | ||
default_value = ".+", | ||
value_name = "REGEX" | ||
)] | ||
environments_regex: Regex, | ||
|
||
/// If datadog monitoring is enabled | ||
#[structopt(short = "d", long = "datadog")] | ||
datadog: bool, | ||
}, | ||
} | ||
|
||
#[derive(StructOpt, Debug)] | ||
pub struct AppCommon { | ||
/// Config source. Accepts file and git URLs. Paths within a git repository may be appended | ||
/// to a git URL, and branches may be specified as a URL fragment (recursive if applicable) | ||
#[structopt(short = "c", long = "configs", value_name = "URL")] | ||
pub configs_url: ConfigUrl, | ||
|
||
/// SSH key to use if configs URL requires authentication | ||
#[structopt( | ||
short = "k", | ||
long = "ssh-key", | ||
parse(from_str = App::parse_path_buf), | ||
default_value = "~/.ssh/id_rsa", | ||
value_name = "FILE" | ||
)] | ||
pub ssh_key: PathBuf, | ||
|
||
/// Throw errors if values do not exist in configs | ||
#[structopt(short = "s", long = "strict")] | ||
pub strict: bool, | ||
} | ||
|
||
impl App { | ||
pub fn config_regex(environment: &Regex) -> Result<Regex, Error> { | ||
App::parse_regex(&format!("config\\.{}\\.json$", environment)) | ||
} | ||
|
||
pub fn parse_regex(src: &str) -> Result<Regex, Error> { | ||
RegexBuilder::new(src) | ||
.case_insensitive(true) | ||
.build() | ||
.map_err(|e| e.into()) | ||
} | ||
|
||
pub fn parse_path_buf(src: &str) -> PathBuf { | ||
PathBuf::from(shellexpand::tilde(src).into_owned()) | ||
} | ||
} |
File renamed without changes.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
pub mod cli; | ||
pub mod config; | ||
mod datadogstatsd; | ||
pub mod server; |
Oops, something went wrong.