custom errors
diff --git a/tfexec/errors.go b/tfexec/errors.go new file mode 100644 index 0000000..96e4913 --- /dev/null +++ b/tfexec/errors.go
@@ -0,0 +1,74 @@ +package tfexec + +import ( + "fmt" + "regexp" +) + +func parseError(stderr string) error { + switch { + // case ErrTerraformNotFound.regexp.MatchString(stderr): + // return ErrTerraformNotFound + case regexp.MustCompile(usageRegexp).MatchString(stderr): + return &ErrCLIUsage{stderr: stderr} + case regexp.MustCompile(`Error: Could not satisfy plugin requirements`).MatchString(stderr): + return &ErrNoInit{stderr: stderr} + case regexp.MustCompile(`Error: No configuration files`).MatchString(stderr): + return &ErrNoConfig{stderr: stderr} + default: + return &Err{stderr: stderr} + } +} + +type ErrNoSuitableBinary struct { + err error +} + +func (e *ErrNoSuitableBinary) Error() string { + return fmt.Sprintf("no suitable terraform binary could be found: %s", e.err.Error()) +} + +// Not yet implemented. +// Intended for use when the detected Terraform version is not compatible with the command or flags being used in this invocation. +type ErrVersionMismatch struct{} + +type ErrNoInit struct { + stderr string +} + +func (e *ErrNoInit) Error() string { + return e.stderr +} + +type ErrNoConfig struct { + stderr string +} + +func (e *ErrNoConfig) Error() string { + return e.stderr +} + +// Terraform CLI indicates usage errors in three different ways: either +// 1. Exit 1, with a custom error message on stderr. +// 2. Exit 1, with command usage logged to stderr. +// 3. Exit 127, with command usage logged to stdout. +// Currently cases 1 and 2 are handled. +// TODO KEM: Handle exit 127 case. How does this work on non-Unix platforms? +type ErrCLIUsage struct { + stderr string +} + +var usageRegexp = `Too many command line arguments|^Usage: .*Options:.*|Error: Invalid -\d+ option` + +func (e *ErrCLIUsage) Error() string { + return e.stderr +} + +// catchall error +type Err struct { + stderr string +} + +func (e *Err) Error() string { + return e.stderr +}
diff --git a/tfexec/main.go b/tfexec/main.go index d601e2e..627c8ba 100644 --- a/tfexec/main.go +++ b/tfexec/main.go
@@ -4,14 +4,13 @@ "fmt" "os" "os/exec" - "runtime" ) // FindTerraform attempts to find a Terraform CLI executable. // // As a first preference it will look for the environment variable // TFEXEC_TERRAFORM_PATH and return its value. If that variable is not set, it will -// look in PATH for a program named "terraform.exe" on Windows, or "terraform" otherwise, +// look in PATH for a program named "terraform", // and, if one is found, return its absolute path. func FindTerraform() (string, error) { if p := os.Getenv("TFEXEC_TERRAFORM_PATH"); p != "" { @@ -20,10 +19,6 @@ execName := "terraform" - if runtime.GOOS == "windows" { - execName = "terraform.exe" - } - p, err := exec.LookPath(execName) if err != nil { return "", fmt.Errorf("terraform executable could not be found: %s", err)
diff --git a/tfexec/terraform.go b/tfexec/terraform.go index 4eb1123..d501889 100644 --- a/tfexec/terraform.go +++ b/tfexec/terraform.go
@@ -4,10 +4,8 @@ "bytes" "context" "encoding/json" - "errors" "fmt" "os" - "os/exec" "strings" tfjson "github.com/hashicorp/terraform-json" @@ -36,7 +34,7 @@ if execPath == "" { execPath, err = FindTerraform() if err != nil { - return nil, err + return nil, &ErrNoSuitableBinary{err: err} } } @@ -48,7 +46,7 @@ execVersion, err := tf.version() if err != nil { - return nil, fmt.Errorf("error running 'terraform version': %s", err) + return nil, &ErrNoSuitableBinary{err: fmt.Errorf("error running 'terraform version': %s", err)} } tf.execVersion = execVersion @@ -92,10 +90,6 @@ return "", fmt.Errorf("%s, %s", err, errBuf.String()) } - // fmt.Println(outBuf.String()) - - // parse it - return outBuf.String(), nil } @@ -182,7 +176,7 @@ err := initCmd.Run() if err != nil { - return errors.New(errBuf.String()) + return parseError(errBuf.String()) } return nil @@ -268,7 +262,7 @@ err := applyCmd.Run() if err != nil { - return errors.New(errBuf.String()) + return parseError(errBuf.String()) } return nil @@ -350,7 +344,7 @@ err := destroyCmd.Run() if err != nil { - return errors.New(errBuf.String()) + return parseError(errBuf.String()) } return nil @@ -429,7 +423,7 @@ err := planCmd.Run() if err != nil { - return errors.New(errBuf.String()) + return parseError(errBuf.String()) } return nil @@ -541,7 +535,7 @@ err := outputCmd.Run() if err != nil { - return nil, err + return nil, parseError(err.Error()) } err = json.Unmarshal(outBuf.Bytes(), outputs) @@ -565,10 +559,7 @@ err := showCmd.Run() if err != nil { - if tErr, ok := err.(*exec.ExitError); ok { - err = fmt.Errorf("terraform failed: %s\n\nstderr:\n%s", tErr.ProcessState.String(), errBuf.String()) - } - return nil, err + return nil, parseError(errBuf.String()) } err = json.Unmarshal(outBuf.Bytes(), &ret) @@ -597,10 +588,7 @@ err := schemaCmd.Run() if err != nil { - if tErr, ok := err.(*exec.ExitError); ok { - err = fmt.Errorf("terraform failed: %s\n\nstderr:\n%s", tErr.ProcessState.String(), errBuf.String()) - } - return nil, err + return nil, parseError(errBuf.String()) } err = json.Unmarshal(outBuf.Bytes(), ret)
diff --git a/tfexec/terraform_test.go b/tfexec/terraform_test.go index 45d371e..c7a6a79 100644 --- a/tfexec/terraform_test.go +++ b/tfexec/terraform_test.go
@@ -316,18 +316,12 @@ err = copyFile(filepath.Join(testFixtureDir, testTerraformStateFileName), filepath.Join(td, testTerraformStateFileName)) - // This test will break if the error output of `terraform init` - // changes significantly. We tolerate this brittleness as a poor - // man's canary for significant changes to Terraform CLI. - // FIXME: Parse this in the actual command and return ErrNoInit - expected := "Error: Could not satisfy plugin requirements" - _, err = tf.StateShow(context.Background()) if err == nil { t.Fatal("expected Show to error, but it did not") } else { - if !strings.Contains(err.Error(), expected) { - t.Fatalf("expected error %s to contain %s", err, expected) + if _, ok := err.(*ErrNoInit); !ok { + t.Fatalf("expected error %s to be ErrNoInit", err) } }