diff --git a/Cargo.lock b/Cargo.lock index b9cd904..22caa97 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -81,6 +81,11 @@ dependencies = [ "syn 0.12.15 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "either" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "failure" version = "0.1.1" @@ -105,6 +110,14 @@ name = "is_executable" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "itertools" +version = "0.7.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "either 1.5.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "kernel32-sys" version = "0.2.2" @@ -306,15 +319,16 @@ dependencies = [ [[package]] name = "terraform-zap" -version = "0.3.0" +version = "0.3.1" dependencies = [ "derive_more 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)", "failure 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", "is_executable 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "itertools 0.7.8 (registry+https://github.com/rust-lang/crates.io-index)", "structopt 0.2.7 (registry+https://github.com/rust-lang/crates.io-index)", "structopt-derive 0.2.7 (registry+https://github.com/rust-lang/crates.io-index)", "subprocess 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)", - "terraform-zap-ignore-lib 0.3.0", + "terraform-zap-ignore-lib 0.3.1", "toml 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)", "vlog 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", "which 2.0.0 (registry+https://github.com/rust-lang/crates.io-index)", @@ -324,7 +338,7 @@ dependencies = [ [[package]] name = "terraform-zap-ignore-lib" -version = "0.3.0" +version = "0.3.1" dependencies = [ "serde 1.0.39 (registry+https://github.com/rust-lang/crates.io-index)", "serde_derive 1.0.39 (registry+https://github.com/rust-lang/crates.io-index)", @@ -431,9 +445,11 @@ source = "registry+https://github.com/rust-lang/crates.io-index" "checksum clap 2.31.2 (registry+https://github.com/rust-lang/crates.io-index)" = "f0f16b89cbb9ee36d87483dc939fe9f1e13c05898d56d7b230a0d4dff033a536" "checksum crossbeam 0.2.12 (registry+https://github.com/rust-lang/crates.io-index)" = "bd66663db5a988098a89599d4857919b3acf7f61402e61365acfd3919857b9be" "checksum derive_more 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)" = "15aae1b9eaec2588e5d39a325b548d21144a9e1d0242d290d54cb7b97f3edda1" +"checksum either 1.5.0 (registry+https://github.com/rust-lang/crates.io-index)" = "3be565ca5c557d7f59e7cfcf1844f9e3033650c929c6566f511e8005f205c1d0" "checksum failure 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "934799b6c1de475a012a02dab0ace1ace43789ee4b99bcfbf1a2e3e8ced5de82" "checksum failure_derive 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "c7cdda555bb90c9bb67a3b670a0f42de8e73f5981524123ad8578aafec8ddb8b" "checksum is_executable 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "c16560f5b427baa1c46e9c112b76c0ededfa051ae23ab2e741a930ef3223e5a8" +"checksum itertools 0.7.8 (registry+https://github.com/rust-lang/crates.io-index)" = "f58856976b776fedd95533137617a02fb25719f40e7d9b01c7043cd65474f450" "checksum kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7507624b29483431c0ba2d82aece8ca6cdba9382bff4ddd0f7490560c056098d" "checksum libc 0.2.40 (registry+https://github.com/rust-lang/crates.io-index)" = "6fd41f331ac7c5b8ac259b8bf82c75c0fb2e469bbf37d2becbba9a6a2221965b" "checksum proc-macro2 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "cd07deb3c6d1d9ff827999c7f9b04cdfd66b1b17ae508e14fe47b620f2282ae0" diff --git a/Cargo.toml b/Cargo.toml index 3075f7f..34cc692 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "terraform-zap" -version = "0.3.0" +version = "0.3.1" authors = ["Chen Weiguang "] description = "Script wrapper to perform finer terraform destroy" repository = "https://github.com/guangie88/terraform-zap" @@ -16,10 +16,11 @@ codecov = { repository = "guangie88/terraform-zap" } derive_more = "=0.10.0" failure = "=0.1.1" is_executable = "=0.1.0" +itertools = "=0.7.8" structopt = "=0.2.7" structopt-derive = "=0.2.7" subprocess = "=0.1.12" -terraform-zap-ignore-lib = { path = "ignore-lib", version = "0.3.0" } +terraform-zap-ignore-lib = { path = "ignore-lib", version = "0.3.1" } toml = "=0.4.6" which = "=2.0.0" whiteread = "=0.4.3" diff --git a/ignore-lib/Cargo.toml b/ignore-lib/Cargo.toml index 93bf6a8..555da3b 100644 --- a/ignore-lib/Cargo.toml +++ b/ignore-lib/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "terraform-zap-ignore-lib" -version = "0.3.0" +version = "0.3.1" authors = ["Chen Weiguang "] description = "Provides ignore implementation to terraform-zap" documentation = "https://docs.rs/terraform-zap-ignore-lib" diff --git a/src/arg.rs b/src/arg.rs index e092e35..84d8c7b 100644 --- a/src/arg.rs +++ b/src/arg.rs @@ -13,4 +13,8 @@ pub struct Config { #[structopt(short = "v", long = "verbose", parse(from_occurrences))] /// Verbose flag (-v, -vv, -vvv) pub verbose: u8, + + #[structopt(short = "p", long = "pass")] + /// Additional arguments to pass to `terraform destroy` + pub pass_args: Option, } diff --git a/src/main.rs b/src/main.rs index 35bd9cf..0b6cb2d 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,5 +1,5 @@ #![cfg_attr(feature = "cargo-clippy", deny(clippy))] -#![deny(missing_debug_implementations, warnings)] +// #![deny(missing_debug_implementations, warnings)] //! # terraform-zap //! @@ -18,6 +18,7 @@ extern crate derive_more; #[macro_use] extern crate failure; extern crate is_executable; +extern crate itertools; extern crate structopt; #[macro_use] extern crate structopt_derive; @@ -37,11 +38,13 @@ use arg::Config; use error::{Error, Result}; use failure::Fail; use is_executable::IsExecutable; +use itertools::Itertools; use std::env; use std::fmt::Display; use std::fs::File; use std::io::Read; -use std::path::Path; +use std::iter; +use std::path::{Path, PathBuf}; use std::process; use structopt::StructOpt; use subprocess::{Exec, Redirection}; @@ -51,9 +54,8 @@ use yansi::Paint; const TF_CMD: &str = "terraform"; const TFZIGNORE_FILE: &str = ".tfzignore"; -fn find_ignore() -> Result> { +fn find_ignore(mut cwd: PathBuf) -> Result> { let ignore_path = { - let mut cwd = env::current_dir()?; cwd.push(TFZIGNORE_FILE); cwd }; @@ -75,6 +77,15 @@ fn find_ignore() -> Result> { } } +fn interleave_targets(targets: &[String]) -> Vec<&str> { + let filtered_target_refs = targets.iter().map(|t| t.as_ref()); + + iter::repeat("-target") + .take(targets.len()) + .interleave(filtered_target_refs) + .collect() +} + fn run(config: &Config) -> Result<()> { vlog::set_verbosity_level(config.verbose as usize); @@ -107,7 +118,7 @@ fn run(config: &Config) -> Result<()> { let targets: Vec = whiteread::parse_string(&targets_str)?; // ignore file is allowed to be missing - let ignore = find_ignore()?; + let ignore = find_ignore(env::current_dir()?)?; let filtered_targets: Vec = if let Some(ignore) = ignore { match ignore { @@ -123,12 +134,7 @@ fn run(config: &Config) -> Result<()> { targets }; - let mut target_args = vec![]; - - for filtered_target in filtered_targets { - target_args.push("-target".to_owned()); - target_args.push(filtered_target) - } + let target_args = interleave_targets(&filtered_targets); let destroy_capture = Exec::cmd(&tf_cmd) .arg("destroy") @@ -178,3 +184,223 @@ fn main() { } } } + +#[cfg(test)] +mod tests { + use super::*; + use std::env::temp_dir; + use std::fs::{create_dir, remove_dir, remove_file}; + use std::io::Write; + + struct IgnoreDirSetup { + dir: PathBuf, + ignore_path: Option, + } + + impl IgnoreDirSetup { + fn new(sub_dir: &str, content: Option<&str>) -> IgnoreDirSetup { + IgnoreDirSetup::new_with_custom_tmp_dir( + temp_dir(), + sub_dir, + content, + ) + } + + fn new_with_custom_tmp_dir( + tmp_dir: PathBuf, + sub_dir: &str, + content: Option<&str>, + ) -> IgnoreDirSetup { + let mut dir = tmp_dir; + dir.push(sub_dir); + create_dir(&dir).unwrap(); + + let ignore_path = if let Some(content) = content { + let mut ignore_path = dir.clone(); + ignore_path.push(TFZIGNORE_FILE); + + let mut ignore_file = File::create(&ignore_path).unwrap(); + + ignore_file + .write_fmt(format_args!("{}", content)) + .unwrap(); + + ignore_file.sync_all().unwrap(); + + Some(ignore_path) + } else { + None + }; + + IgnoreDirSetup { + dir, + ignore_path, + } + } + } + + impl Drop for IgnoreDirSetup { + fn drop(&mut self) { + if let Some(ref ignore_path) = self.ignore_path { + remove_file(ignore_path).unwrap(); + } + + if Path::exists(&self.dir) { + remove_dir(&self.dir).unwrap(); + } + } + } + + #[test] + fn test_find_ignore_valid_1() { + const IGNORE_CONTENT: &str = r#" + exact = [] + "#; + + let setup = IgnoreDirSetup::new( + "terraform-zap-test_find_ignore_valid_1", + Some(IGNORE_CONTENT), + ); + + let ignore = find_ignore(setup.dir.clone()).unwrap().unwrap(); + + match ignore { + Ignore::Exact { exact } => { + assert!(exact.len() == 0); + } + } + } + + #[test] + fn test_find_ignore_valid_2() { + const IGNORE_CONTENT: &str = r#" + exact = [ + "a.b", + "x.y", + ] + "#; + + let setup = IgnoreDirSetup::new( + "terraform-zap-test_find_ignore_valid_2", + Some(IGNORE_CONTENT), + ); + + let ignore = find_ignore(setup.dir.clone()).unwrap().unwrap(); + + match ignore { + Ignore::Exact { exact } => { + assert!(exact.len() == 2); + assert_eq!("a.b", exact[0]); + assert_eq!("x.y", exact[1]); + } + } + } + + #[test] + fn test_find_ignore_valid_3() { + let setup = + IgnoreDirSetup::new("terraform-zap-test_find_ignore_valid_3", None); + + let ignore = find_ignore(setup.dir.clone()).unwrap(); + + assert!(ignore.is_none()); + } + + #[test] + fn test_find_ignore_valid_4() { + const IGNORE_CONTENT: &str = r#" + exact = [ + "a.b", + "x.y", + ] + "#; + + let base_setup = IgnoreDirSetup::new( + "terraform-zap-test_find_ignore_valid_4", + Some(IGNORE_CONTENT), + ); + + let actual_setup = IgnoreDirSetup::new_with_custom_tmp_dir( + base_setup.dir.clone(), + "sub", + None, + ); + + let ignore = find_ignore(actual_setup.dir.clone()).unwrap(); + assert!(ignore.is_none()); + } + + #[test] + fn test_find_ignore_invalid_1() { + const IGNORE_CONTENT: &str = ""; + + let setup = IgnoreDirSetup::new( + "terraform-zap-test_find_ignore_invalid_1", + Some(IGNORE_CONTENT), + ); + + let ignore = find_ignore(setup.dir.clone()); + assert!(ignore.is_err()); + } + + #[test] + fn test_find_ignore_invalid_2() { + const IGNORE_CONTENT: &str = "[]"; + + let setup = IgnoreDirSetup::new( + "terraform-zap-test_find_ignore_invalid_2", + Some(IGNORE_CONTENT), + ); + + let ignore = find_ignore(setup.dir.clone()); + assert!(ignore.is_err()); + } + + #[test] + fn test_find_ignore_invalid_3() { + const IGNORE_CONTENT: &str = r#" + exact = abc [ + "a.b", + ] + "#; + + let setup = IgnoreDirSetup::new( + "terraform-zap-test_find_ignore_invalid_3", + Some(IGNORE_CONTENT), + ); + + let ignore = find_ignore(setup.dir.clone()); + assert!(ignore.is_err()); + } + + #[test] + fn test_target_interleave_1() { + let targets = vec![]; + let reference: [&str; 0] = []; + + assert_eq!( + &reference, + interleave_targets(&targets).as_slice() + ); + } + + #[test] + fn test_target_interleave_2() { + let targets = vec!["A".to_owned()]; + + assert_eq!( + &["-target", "A"], + interleave_targets(&targets).as_slice() + ); + } + + #[test] + fn test_target_interleave_3() { + let targets = vec!["A".to_owned(), "B".to_owned(), "C".to_owned()]; + + assert_eq!( + &["-target", "A", "-target", "B", "-target", "C"], + interleave_targets(&targets).as_slice() + ); + } +}