Skip to content

Commit de53cb3

Browse files
committed
Unwrap exec.ExitError on all our special errors
And respond to errors.Is for Context errors
1 parent 464bd75 commit de53cb3

20 files changed

+195
-40
lines changed

tfexec/apply.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,7 @@ func (tf *Terraform) Apply(ctx context.Context, opts ...ApplyOption) error {
9191
if err != nil {
9292
return err
9393
}
94-
return tf.runTerraformCmd(cmd)
94+
return tf.runTerraformCmd(ctx, cmd)
9595
}
9696

9797
func (tf *Terraform) applyCmd(ctx context.Context, opts ...ApplyOption) (*exec.Cmd, error) {

tfexec/cmd.go

+4-4
Original file line numberDiff line numberDiff line change
@@ -135,27 +135,27 @@ func (tf *Terraform) buildTerraformCmd(ctx context.Context, mergeEnv map[string]
135135
return cmd
136136
}
137137

138-
func (tf *Terraform) runTerraformCmdJSON(cmd *exec.Cmd, v interface{}) error {
138+
func (tf *Terraform) runTerraformCmdJSON(ctx context.Context, cmd *exec.Cmd, v interface{}) error {
139139
var outbuf = bytes.Buffer{}
140140
cmd.Stdout = mergeWriters(cmd.Stdout, &outbuf)
141141

142-
err := tf.runTerraformCmd(cmd)
142+
err := tf.runTerraformCmd(ctx, cmd)
143143
if err != nil {
144144
return err
145145
}
146146

147147
return json.Unmarshal(outbuf.Bytes(), v)
148148
}
149149

150-
func (tf *Terraform) runTerraformCmd(cmd *exec.Cmd) error {
150+
func (tf *Terraform) runTerraformCmd(ctx context.Context, cmd *exec.Cmd) error {
151151
var errBuf strings.Builder
152152

153153
cmd.Stdout = mergeWriters(cmd.Stdout, tf.stdout)
154154
cmd.Stderr = mergeWriters(cmd.Stderr, tf.stderr, &errBuf)
155155

156156
err := cmd.Run()
157157
if err != nil {
158-
return tf.parseError(err, errBuf.String())
158+
return tf.wrapExitError(ctx, err, errBuf.String())
159159
}
160160
return nil
161161
}

tfexec/destroy.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,7 @@ func (tf *Terraform) Destroy(ctx context.Context, opts ...DestroyOption) error {
9292
if err != nil {
9393
return err
9494
}
95-
return tf.runTerraformCmd(cmd)
95+
return tf.runTerraformCmd(ctx, cmd)
9696
}
9797

9898
func (tf *Terraform) destroyCmd(ctx context.Context, opts ...DestroyOption) (*exec.Cmd, error) {

tfexec/errors.go

+6-2
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,10 @@ func (e *ErrNoSuitableBinary) Error() string {
1212
return fmt.Sprintf("no suitable terraform binary could be found: %s", e.err.Error())
1313
}
1414

15+
func (e *ErrNoSuitableBinary) Unwrap() error {
16+
return e.err
17+
}
18+
1519
// ErrVersionMismatch is returned when the detected Terraform version is not compatible with the
1620
// command or flags being used in this invocation.
1721
type ErrVersionMismatch struct {
@@ -27,9 +31,9 @@ func (e *ErrVersionMismatch) Error() string {
2731
// ErrManualEnvVar is returned when an env var that should be set programatically via an option or method
2832
// is set via the manual environment passing functions.
2933
type ErrManualEnvVar struct {
30-
name string
34+
Name string
3135
}
3236

3337
func (err *ErrManualEnvVar) Error() string {
34-
return fmt.Sprintf("manual setting of env var %q detected", err.name)
38+
return fmt.Sprintf("manual setting of env var %q detected", err.Name)
3539
}

tfexec/exit_errors.go

+80-13
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package tfexec
22

33
import (
4+
"context"
45
"errors"
56
"fmt"
67
"os/exec"
@@ -30,12 +31,21 @@ var (
3031
tfVersionMismatchConstraintRegexp = regexp.MustCompile(`required_version = "(.+)"|Required version: (.+)\b`)
3132
)
3233

33-
func (tf *Terraform) parseError(err error, stderr string) error {
34-
ee, ok := err.(*exec.ExitError)
34+
func (tf *Terraform) wrapExitError(ctx context.Context, err error, stderr string) error {
35+
exitErr, ok := err.(*exec.ExitError)
3536
if !ok {
37+
// not an exit error, short circuit, nothing to wrap
3638
return err
3739
}
3840

41+
ctxErr := ctx.Err()
42+
43+
// nothing to parse, return early
44+
errString := strings.TrimSpace(stderr)
45+
if errString == "" {
46+
return &unwrapper{exitErr, ctxErr}
47+
}
48+
3949
switch {
4050
case tfVersionMismatchErrRegexp.MatchString(stderr):
4151
constraint := ""
@@ -59,6 +69,8 @@ func (tf *Terraform) parseError(err error, stderr string) error {
5969
}
6070

6171
return &ErrTFVersionMismatch{
72+
unwrapper: unwrapper{exitErr, ctxErr},
73+
6274
Constraint: constraint,
6375
TFVersion: ver,
6476
}
@@ -72,33 +84,76 @@ func (tf *Terraform) parseError(err error, stderr string) error {
7284
}
7385
}
7486

75-
return &ErrMissingVar{name}
87+
return &ErrMissingVar{
88+
unwrapper: unwrapper{exitErr, ctxErr},
89+
90+
VariableName: name,
91+
}
7692
case usageRegexp.MatchString(stderr):
77-
return &ErrCLIUsage{stderr: stderr}
93+
return &ErrCLIUsage{
94+
unwrapper: unwrapper{exitErr, ctxErr},
95+
96+
stderr: stderr,
97+
}
7898
case noInitErrRegexp.MatchString(stderr):
79-
return &ErrNoInit{stderr: stderr}
99+
return &ErrNoInit{
100+
unwrapper: unwrapper{exitErr, ctxErr},
101+
102+
stderr: stderr,
103+
}
80104
case noConfigErrRegexp.MatchString(stderr):
81-
return &ErrNoConfig{stderr: stderr}
105+
return &ErrNoConfig{
106+
unwrapper: unwrapper{exitErr, ctxErr},
107+
108+
stderr: stderr,
109+
}
82110
case workspaceDoesNotExistRegexp.MatchString(stderr):
83111
submatches := workspaceDoesNotExistRegexp.FindStringSubmatch(stderr)
84112
if len(submatches) == 2 {
85-
return &ErrNoWorkspace{submatches[1]}
113+
return &ErrNoWorkspace{
114+
unwrapper: unwrapper{exitErr, ctxErr},
115+
116+
Name: submatches[1],
117+
}
86118
}
87119
case workspaceAlreadyExistsRegexp.MatchString(stderr):
88120
submatches := workspaceAlreadyExistsRegexp.FindStringSubmatch(stderr)
89121
if len(submatches) == 2 {
90-
return &ErrWorkspaceExists{submatches[1]}
122+
return &ErrWorkspaceExists{
123+
unwrapper: unwrapper{exitErr, ctxErr},
124+
125+
Name: submatches[1],
126+
}
91127
}
92128
}
93-
errString := strings.TrimSpace(stderr)
94-
if errString == "" {
95-
// if stderr is empty, return the ExitError directly, as it will have a better message
96-
return ee
129+
130+
return fmt.Errorf("%w\n%s", &unwrapper{exitErr, ctxErr}, stderr)
131+
}
132+
133+
type unwrapper struct {
134+
err error
135+
ctxErr error
136+
}
137+
138+
func (u *unwrapper) Unwrap() error {
139+
return u.err
140+
}
141+
142+
func (u *unwrapper) Is(target error) bool {
143+
switch target {
144+
case context.DeadlineExceeded, context.Canceled:
145+
return errors.Is(u.ctxErr, target)
97146
}
98-
return errors.New(stderr)
147+
return false
148+
}
149+
150+
func (u *unwrapper) Error() string {
151+
return u.err.Error()
99152
}
100153

101154
type ErrMissingVar struct {
155+
unwrapper
156+
102157
VariableName string
103158
}
104159

@@ -107,6 +162,8 @@ func (err *ErrMissingVar) Error() string {
107162
}
108163

109164
type ErrNoWorkspace struct {
165+
unwrapper
166+
110167
Name string
111168
}
112169

@@ -116,6 +173,8 @@ func (err *ErrNoWorkspace) Error() string {
116173

117174
// ErrWorkspaceExists is returned when creating a workspace that already exists
118175
type ErrWorkspaceExists struct {
176+
unwrapper
177+
119178
Name string
120179
}
121180

@@ -124,6 +183,8 @@ func (err *ErrWorkspaceExists) Error() string {
124183
}
125184

126185
type ErrNoInit struct {
186+
unwrapper
187+
127188
stderr string
128189
}
129190

@@ -132,6 +193,8 @@ func (e *ErrNoInit) Error() string {
132193
}
133194

134195
type ErrNoConfig struct {
196+
unwrapper
197+
135198
stderr string
136199
}
137200

@@ -148,6 +211,8 @@ func (e *ErrNoConfig) Error() string {
148211
// Currently cases 1 and 2 are handled.
149212
// TODO KEM: Handle exit 127 case. How does this work on non-Unix platforms?
150213
type ErrCLIUsage struct {
214+
unwrapper
215+
151216
stderr string
152217
}
153218

@@ -158,6 +223,8 @@ func (e *ErrCLIUsage) Error() string {
158223
// ErrTFVersionMismatch is returned when the running Terraform version is not compatible with the
159224
// value specified for required_version in the terraform block.
160225
type ErrTFVersionMismatch struct {
226+
unwrapper
227+
161228
TFVersion string
162229

163230
// Constraint is not returned in the error messaging on 0.12

tfexec/fmt.go

+3-3
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ func (tf *Terraform) FormatString(ctx context.Context, content string) (string,
5252
var outBuf bytes.Buffer
5353
cmd.Stdout = mergeWriters(cmd.Stdout, &outBuf)
5454

55-
err = tf.runTerraformCmd(cmd)
55+
err = tf.runTerraformCmd(ctx, cmd)
5656
if err != nil {
5757
return "", err
5858
}
@@ -76,7 +76,7 @@ func (tf *Terraform) FormatWrite(ctx context.Context, opts ...FormatOption) erro
7676
return err
7777
}
7878

79-
return tf.runTerraformCmd(cmd)
79+
return tf.runTerraformCmd(ctx, cmd)
8080
}
8181

8282
// FormatCheck returns true if the config files in the working or selected (via DirOption) directory are already formatted.
@@ -98,7 +98,7 @@ func (tf *Terraform) FormatCheck(ctx context.Context, opts ...FormatOption) (boo
9898
var outBuf bytes.Buffer
9999
cmd.Stdout = mergeWriters(cmd.Stdout, &outBuf)
100100

101-
err = tf.runTerraformCmd(cmd)
101+
err = tf.runTerraformCmd(ctx, cmd)
102102
if err == nil {
103103
return true, nil, nil
104104
}

tfexec/import.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,7 @@ func (tf *Terraform) Import(ctx context.Context, address, id string, opts ...Imp
7878
if err != nil {
7979
return err
8080
}
81-
return tf.runTerraformCmd(cmd)
81+
return tf.runTerraformCmd(ctx, cmd)
8282
}
8383

8484
func (tf *Terraform) importCmd(ctx context.Context, address, id string, opts ...ImportOption) (*exec.Cmd, error) {

tfexec/init.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,7 @@ func (tf *Terraform) Init(ctx context.Context, opts ...InitOption) error {
9898
if err != nil {
9999
return err
100100
}
101-
return tf.runTerraformCmd(cmd)
101+
return tf.runTerraformCmd(ctx, cmd)
102102
}
103103

104104
func (tf *Terraform) initCmd(ctx context.Context, opts ...InitOption) (*exec.Cmd, error) {

0 commit comments

Comments
 (0)