1
1
package tfexec
2
2
3
3
import (
4
- "errors "
4
+ "context "
5
5
"fmt"
6
6
"os/exec"
7
7
"regexp"
@@ -31,12 +31,21 @@ var (
31
31
configInvalidErrRegexp = regexp .MustCompile (`There are some problems with the configuration, described below.` )
32
32
)
33
33
34
- func (tf * Terraform ) parseError ( err error , stderr string ) error {
35
- ee , ok := err .(* exec.ExitError )
34
+ func (tf * Terraform ) wrapExitError ( ctx context. Context , err error , stderr string ) error {
35
+ exitErr , ok := err .(* exec.ExitError )
36
36
if ! ok {
37
+ // not an exit error, short circuit, nothing to wrap
37
38
return err
38
39
}
39
40
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
+
40
49
switch {
41
50
case tfVersionMismatchErrRegexp .MatchString (stderr ):
42
51
constraint := ""
@@ -60,6 +69,8 @@ func (tf *Terraform) parseError(err error, stderr string) error {
60
69
}
61
70
62
71
return & ErrTFVersionMismatch {
72
+ unwrapper : unwrapper {exitErr , ctxErr },
73
+
63
74
Constraint : constraint ,
64
75
TFVersion : ver ,
65
76
}
@@ -73,32 +84,74 @@ func (tf *Terraform) parseError(err error, stderr string) error {
73
84
}
74
85
}
75
86
76
- return & ErrMissingVar {name }
87
+ return & ErrMissingVar {
88
+ unwrapper : unwrapper {exitErr , ctxErr },
89
+
90
+ VariableName : name ,
91
+ }
77
92
case usageRegexp .MatchString (stderr ):
78
- return & ErrCLIUsage {stderr : stderr }
93
+ return & ErrCLIUsage {
94
+ unwrapper : unwrapper {exitErr , ctxErr },
95
+
96
+ stderr : stderr ,
97
+ }
79
98
case noInitErrRegexp .MatchString (stderr ):
80
- return & ErrNoInit {stderr : stderr }
99
+ return & ErrNoInit {
100
+ unwrapper : unwrapper {exitErr , ctxErr },
101
+
102
+ stderr : stderr ,
103
+ }
81
104
case noConfigErrRegexp .MatchString (stderr ):
82
- return & ErrNoConfig {stderr : stderr }
105
+ return & ErrNoConfig {
106
+ unwrapper : unwrapper {exitErr , ctxErr },
107
+
108
+ stderr : stderr ,
109
+ }
83
110
case workspaceDoesNotExistRegexp .MatchString (stderr ):
84
111
submatches := workspaceDoesNotExistRegexp .FindStringSubmatch (stderr )
85
112
if len (submatches ) == 2 {
86
- return & ErrNoWorkspace {submatches [1 ]}
113
+ return & ErrNoWorkspace {
114
+ unwrapper : unwrapper {exitErr , ctxErr },
115
+
116
+ Name : submatches [1 ],
117
+ }
87
118
}
88
119
case workspaceAlreadyExistsRegexp .MatchString (stderr ):
89
120
submatches := workspaceAlreadyExistsRegexp .FindStringSubmatch (stderr )
90
121
if len (submatches ) == 2 {
91
- return & ErrWorkspaceExists {submatches [1 ]}
122
+ return & ErrWorkspaceExists {
123
+ unwrapper : unwrapper {exitErr , ctxErr },
124
+
125
+ Name : submatches [1 ],
126
+ }
92
127
}
93
128
case configInvalidErrRegexp .MatchString (stderr ):
94
129
return & ErrConfigInvalid {stderr : stderr }
95
130
}
96
- errString := strings .TrimSpace (stderr )
97
- if errString == "" {
98
- // if stderr is empty, return the ExitError directly, as it will have a better message
99
- return ee
131
+
132
+ return fmt .Errorf ("%w\n %s" , & unwrapper {exitErr , ctxErr }, stderr )
133
+ }
134
+
135
+ type unwrapper struct {
136
+ err error
137
+ ctxErr error
138
+ }
139
+
140
+ func (u * unwrapper ) Unwrap () error {
141
+ return u .err
142
+ }
143
+
144
+ func (u * unwrapper ) Is (target error ) bool {
145
+ switch target {
146
+ case context .DeadlineExceeded , context .Canceled :
147
+ return u .ctxErr == context .DeadlineExceeded ||
148
+ u .ctxErr == context .Canceled
100
149
}
101
- return errors .New (stderr )
150
+ return false
151
+ }
152
+
153
+ func (u * unwrapper ) Error () string {
154
+ return u .err .Error ()
102
155
}
103
156
104
157
type ErrConfigInvalid struct {
@@ -110,6 +163,8 @@ func (e *ErrConfigInvalid) Error() string {
110
163
}
111
164
112
165
type ErrMissingVar struct {
166
+ unwrapper
167
+
113
168
VariableName string
114
169
}
115
170
@@ -118,6 +173,8 @@ func (err *ErrMissingVar) Error() string {
118
173
}
119
174
120
175
type ErrNoWorkspace struct {
176
+ unwrapper
177
+
121
178
Name string
122
179
}
123
180
@@ -127,6 +184,8 @@ func (err *ErrNoWorkspace) Error() string {
127
184
128
185
// ErrWorkspaceExists is returned when creating a workspace that already exists
129
186
type ErrWorkspaceExists struct {
187
+ unwrapper
188
+
130
189
Name string
131
190
}
132
191
@@ -135,6 +194,8 @@ func (err *ErrWorkspaceExists) Error() string {
135
194
}
136
195
137
196
type ErrNoInit struct {
197
+ unwrapper
198
+
138
199
stderr string
139
200
}
140
201
@@ -143,6 +204,8 @@ func (e *ErrNoInit) Error() string {
143
204
}
144
205
145
206
type ErrNoConfig struct {
207
+ unwrapper
208
+
146
209
stderr string
147
210
}
148
211
@@ -159,6 +222,8 @@ func (e *ErrNoConfig) Error() string {
159
222
// Currently cases 1 and 2 are handled.
160
223
// TODO KEM: Handle exit 127 case. How does this work on non-Unix platforms?
161
224
type ErrCLIUsage struct {
225
+ unwrapper
226
+
162
227
stderr string
163
228
}
164
229
@@ -169,6 +234,8 @@ func (e *ErrCLIUsage) Error() string {
169
234
// ErrTFVersionMismatch is returned when the running Terraform version is not compatible with the
170
235
// value specified for required_version in the terraform block.
171
236
type ErrTFVersionMismatch struct {
237
+ unwrapper
238
+
172
239
TFVersion string
173
240
174
241
// Constraint is not returned in the error messaging on 0.12
0 commit comments