Skip to content

Commit

Permalink
Allow passthrough args and pass back error code from commands (#21)
Browse files Browse the repository at this point in the history
* Able to pass through args to terraform destroy

* Capture exit status from command calls

* Pass the error code as the program error code

* Give better error formatting for command errors

* Add description to how to use passthrough

* Fix regular lint
  • Loading branch information
guangie88 authored Apr 19, 2018
1 parent e1fd439 commit f72b152
Show file tree
Hide file tree
Showing 7 changed files with 126 additions and 15 deletions.
6 changes: 3 additions & 3 deletions Cargo.lock

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

4 changes: 2 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "terraform-zap"
version = "0.3.1"
version = "0.3.2"
authors = ["Chen Weiguang <[email protected]>"]
description = "Script wrapper to perform finer terraform destroy"
repository = "https://github.com/guangie88/terraform-zap"
Expand All @@ -20,7 +20,7 @@ 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.1" }
terraform-zap-ignore-lib = { path = "ignore-lib", version = "0.3.2" }
toml = "=0.4.6"
which = "=2.0.0"
whiteread = "=0.4.3"
Expand Down
10 changes: 10 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,16 @@ If previously there were resources
with `prevent_destroy = true` set, if these resources are correctly ignored,
the confirmation prompt should appear properly.

If you need to pass arguments to `terraform destroy` instead, use positional
arguments, for e.g.

```bash
terraform-zap -vvv -- -no-color -var "foo=bar"
```

`-vvv` is passed into `terraform-zap`, while `-no-color`, `-var` and `"foo=bar"`
are passed into `terraform destroy`.

For more CLI argument details, type `terraform-zap -h`.

## `terraform zap` instead of `terraform-zap` (for `bash` set-up)
Expand Down
2 changes: 1 addition & 1 deletion ignore-lib/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "terraform-zap-ignore-lib"
version = "0.3.1"
version = "0.3.2"
authors = ["Chen Weiguang <[email protected]>"]
description = "Provides ignore implementation to terraform-zap"
documentation = "https://docs.rs/terraform-zap-ignore-lib"
Expand Down
3 changes: 1 addition & 2 deletions src/arg.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ pub struct Config {
/// Verbose flag (-v, -vv, -vvv)
pub verbose: u8,

#[structopt(short = "p", long = "pass")]
/// Additional arguments to pass to `terraform destroy`
pub pass_args: Option<String>,
pub pass_args: Vec<String>,
}
38 changes: 36 additions & 2 deletions src/error.rs
Original file line number Diff line number Diff line change
@@ -1,17 +1,51 @@
use std::fmt::{self, Display};
use std::{self, io};
use subprocess;
use subprocess::{self, ExitStatus};
use toml;
use which;
use whiteread;

/// Exit status and stderr message grouping
#[derive(Debug)]
pub struct CommandErrorCapture {
/// Exit status returned from the command
pub exit_status: ExitStatus,

/// Stderr message returned from the command
pub err_msg: String,
}

impl Display for CommandErrorCapture {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(
f,
"Command Error:\n- Exit Status: {:?}\n\n{}",
self.exit_status, self.err_msg
)
}
}

impl CommandErrorCapture {
/// Creates a new exit status and stderr message grouping
pub fn new<S>(exit_status: ExitStatus, err_msg: S) -> CommandErrorCapture
where
S: Into<String>,
{
CommandErrorCapture {
exit_status,
err_msg: err_msg.into(),
}
}
}

/// Application error type
#[derive(Fail, From, Debug)]
pub enum Error {
#[fail(display = "{}", _0)]
Command(#[cause] subprocess::PopenError),

#[fail(display = "{}", _0)]
CommandError(String),
CommandError(CommandErrorCapture),

#[fail(display = "{}", _0)]
Io(#[cause] io::Error),
Expand Down
78 changes: 73 additions & 5 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ mod arg;
mod error;

use arg::Config;
use error::{Error, Result};
use error::{CommandErrorCapture, Error, Result};
use failure::Fail;
use is_executable::IsExecutable;
use itertools::Itertools;
Expand All @@ -47,12 +47,14 @@ use std::iter;
use std::path::{Path, PathBuf};
use std::process;
use structopt::StructOpt;
use subprocess::{Exec, Redirection};
use subprocess::{Exec, ExitStatus, Redirection};
use terraform_zap_ignore_lib::Ignore;
use yansi::Paint;

const TF_CMD: &str = "terraform";
const TFZIGNORE_FILE: &str = ".tfzignore";
const OTHER_ERROR_EXIT_CODE: i32 = 1;
const UNDETERMINED_EXIT_CODE: i32 = 2;

fn find_ignore(mut cwd: PathBuf) -> Result<Option<Ignore>> {
let ignore_path = {
Expand Down Expand Up @@ -111,7 +113,10 @@ fn run(config: &Config) -> Result<()> {
.capture()?;

if !state_capture.exit_status.success() {
Err(Error::CommandError(state_capture.stderr_str()))?
Err(CommandErrorCapture::new(
state_capture.exit_status,
state_capture.stderr_str(),
))?
}

let targets_str = state_capture.stdout_str();
Expand Down Expand Up @@ -139,12 +144,14 @@ fn run(config: &Config) -> Result<()> {
let destroy_capture = Exec::cmd(&tf_cmd)
.arg("destroy")
.args(&target_args)
.args(&config.pass_args)
.stdin(Redirection::None)
.stderr(Redirection::Pipe)
.capture()?;

if !destroy_capture.exit_status.success() {
Err(Error::CommandError(
Err(CommandErrorCapture::new(
destroy_capture.exit_status,
destroy_capture.stderr_str(),
))?
}
Expand All @@ -159,6 +166,16 @@ where
Paint::new(arg).bold()
}

fn exit_status_to_code(exit_status: &ExitStatus) -> i32 {
match *exit_status {
// this is not exactly lossy, but will wrap (u32 -> i32)
ExitStatus::Exited(code) => code as i32,
ExitStatus::Signaled(code) => i32::from(code),
ExitStatus::Other(code) => code,
ExitStatus::Undetermined => UNDETERMINED_EXIT_CODE,
}
}

fn main() {
let config = Config::from_args();

Expand All @@ -180,7 +197,13 @@ fn main() {
ve0!("Backtrace: {}", to_err_color(backtrace));
}

process::exit(1);
match e {
Error::CommandError(ref capture) => {
process::exit(exit_status_to_code(&capture.exit_status))
}

_ => process::exit(OTHER_ERROR_EXIT_CODE),
}
}
}
}
Expand All @@ -191,6 +214,7 @@ mod tests {
use std::env::temp_dir;
use std::fs::{create_dir, remove_dir, remove_file};
use std::io::Write;
use std::{i32, u32, u8};

struct IgnoreDirSetup {
dir: PathBuf,
Expand Down Expand Up @@ -403,4 +427,48 @@ mod tests {
interleave_targets(&targets).as_slice()
);
}

#[test]
fn test_exit_status_to_code_1() {
let code = exit_status_to_code(&ExitStatus::Exited(u32::MIN));
assert_eq!(0, code);
}

#[test]
fn test_exit_status_to_code_2() {
// overflow wrap
let code = exit_status_to_code(&ExitStatus::Exited(u32::MAX));
assert_eq!(-1, code);
}

#[test]
fn test_exit_status_to_code_3() {
// overflow wrap
let code = exit_status_to_code(&ExitStatus::Signaled(u8::MIN));
assert_eq!(u8::MIN as i32, code);
}

#[test]
fn test_exit_status_to_code_4() {
let code = exit_status_to_code(&ExitStatus::Signaled(u8::MAX));
assert_eq!(u8::MAX as i32, code);
}

#[test]
fn test_exit_status_to_code_5() {
let code = exit_status_to_code(&ExitStatus::Other(i32::MIN));
assert_eq!(i32::MIN, code);
}

#[test]
fn test_exit_status_to_code_6() {
let code = exit_status_to_code(&ExitStatus::Other(i32::MAX));
assert_eq!(i32::MAX, code);
}

#[test]
fn test_exit_status_to_code_7() {
let code = exit_status_to_code(&ExitStatus::Undetermined);
assert_eq!(UNDETERMINED_EXIT_CODE, code);
}
}

0 comments on commit f72b152

Please sign in to comment.