diff --git a/tfexec/cmd.go b/tfexec/cmd.go index 5a3e38e2..f5457769 100644 --- a/tfexec/cmd.go +++ b/tfexec/cmd.go @@ -107,13 +107,13 @@ func (tf *Terraform) runTerraformCmd(cmd *exec.Cmd) error { stdout := tf.stdout if cmd.Stdout != nil { - stdout = io.MultiWriter(cmd.Stdout, stdout) + stdout = io.MultiWriter(stdout, cmd.Stdout) } cmd.Stdout = stdout stderr := io.MultiWriter(&errBuf, tf.stderr) if cmd.Stderr != nil { - stderr = io.MultiWriter(cmd.Stderr, stderr) + stderr = io.MultiWriter(stderr, cmd.Stderr) } cmd.Stderr = stderr diff --git a/tfexec/errors.go b/tfexec/errors.go index 22edbde9..933d5ba2 100644 --- a/tfexec/errors.go +++ b/tfexec/errors.go @@ -5,6 +5,20 @@ import ( "fmt" "os/exec" "regexp" + "strings" +) + +var ( + // The "Required variable not set:" case is for 0.11 + missingVarErrRegexp = regexp.MustCompile(`Error: No value for required variable|Error: Required variable not set:`) + missingVarNameRegexp = regexp.MustCompile(`The root module input variable "(.+)" is not set, and has no default|Error: Required variable not set: (.+)`) + + usageRegexp = regexp.MustCompile(`Too many command line arguments|^Usage: .*Options:.*|Error: Invalid -\d+ option`) + + // "Could not load plugin" is present in 0.13 + noInitErrRegexp = regexp.MustCompile(`Error: Could not satisfy plugin requirements|Error: Could not load plugin`) + + noConfigErrRegexp = regexp.MustCompile(`Error: No configuration files`) ) func parseError(err error, stderr string) error { @@ -13,16 +27,22 @@ func parseError(err error, stderr string) error { } switch { - // case ErrTerraformNotFound.regexp.MatchString(stderr): - // return ErrTerraformNotFound - case regexp.MustCompile(usageRegexp).MatchString(stderr): + case missingVarErrRegexp.MatchString(stderr): + name := "" + names := missingVarNameRegexp.FindStringSubmatch(stderr) + for i := 1; i < len(names); i++ { + name = strings.TrimSpace(names[i]) + if name != "" { + break + } + } + + return &ErrMissingVar{name} + case 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: Could not load plugin`).MatchString(stderr): - // this string is present in 0.13 + case noInitErrRegexp.MatchString(stderr): return &ErrNoInit{stderr: stderr} - case regexp.MustCompile(`Error: No configuration files`).MatchString(stderr): + case noConfigErrRegexp.MatchString(stderr): return &ErrNoConfig{stderr: stderr} default: return errors.New(stderr) @@ -77,8 +97,6 @@ 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 } @@ -92,3 +110,11 @@ type ErrManualEnvVar struct { func (err *ErrManualEnvVar) Error() string { return fmt.Sprintf("manual setting of env var %q detected", err.name) } + +type ErrMissingVar struct { + VariableName string +} + +func (err *ErrMissingVar) Error() string { + return fmt.Sprintf("variable %q was required but not supplied", err.VariableName) +} diff --git a/tfexec/internal/e2etest/errors_test.go b/tfexec/internal/e2etest/errors_test.go index c25a1402..f71262c1 100644 --- a/tfexec/internal/e2etest/errors_test.go +++ b/tfexec/internal/e2etest/errors_test.go @@ -35,3 +35,30 @@ func TestUnparsedError(t *testing.T) { } }) } + +func TestMissingVar(t *testing.T) { + runTest(t, "var", func(t *testing.T, tfv *version.Version, tf *tfexec.Terraform) { + err := tf.Init(context.Background()) + if err != nil { + t.Fatalf("err during init: %s", err) + } + + err = tf.Plan(context.Background()) + if err == nil { + t.Fatalf("expected error running Plan, none returned") + } + var e *tfexec.ErrMissingVar + if !errors.As(err, &e) { + t.Fatalf("expected ErrMissingVar, got %T, %s", err, err) + } + + if e.VariableName != "no_default" { + t.Fatalf("expected missing no_default, got %q", e.VariableName) + } + + err = tf.Plan(context.Background(), tfexec.Var("no_default=foo")) + if err != nil { + t.Fatalf("expected no error, got %s", err) + } + }) +} diff --git a/tfexec/internal/e2etest/testdata/var/main.tf b/tfexec/internal/e2etest/testdata/var/main.tf new file mode 100644 index 00000000..4abb28c3 --- /dev/null +++ b/tfexec/internal/e2etest/testdata/var/main.tf @@ -0,0 +1,6 @@ +variable "default" { + default = "foo" +} + +variable "no_default" { +} \ No newline at end of file diff --git a/tfexec/internal/e2etest/util_test.go b/tfexec/internal/e2etest/util_test.go index faf50454..0862b4a8 100644 --- a/tfexec/internal/e2etest/util_test.go +++ b/tfexec/internal/e2etest/util_test.go @@ -8,6 +8,7 @@ import ( "io/ioutil" "os" "path/filepath" + "strings" "testing" "github.com/hashicorp/go-version" @@ -62,7 +63,7 @@ func runTestVersions(t *testing.T, versions []string, fixtureName string, cb fun } } - var stdouterr bytes.Buffer + var stdouterr strings.Builder tf.SetStdout(&stdouterr) tf.SetStderr(&stdouterr)