@@ -41,7 +41,7 @@ func (e executor) basicEnv() []string {
41
41
return env
42
42
}
43
43
44
- func (e executor ) execWriteOutput (ctx context.Context , args , env []string , stdOutWriter , stdErrWriter io.WriteCloser ) (err error ) {
44
+ func (e executor ) execWriteOutput (ctx , killCtx context.Context , args , env []string , stdOutWriter , stdErrWriter io.WriteCloser ) (err error ) {
45
45
defer func () {
46
46
closeErr := stdOutWriter .Close ()
47
47
if err == nil && closeErr != nil {
@@ -52,8 +52,12 @@ func (e executor) execWriteOutput(ctx context.Context, args, env []string, stdOu
52
52
err = closeErr
53
53
}
54
54
}()
55
+ if ctx .Err () != nil {
56
+ return ctx .Err ()
57
+ }
58
+
55
59
// #nosec
56
- cmd := exec .CommandContext (ctx , e .binaryPath , args ... )
60
+ cmd := exec .CommandContext (killCtx , e .binaryPath , args ... )
57
61
cmd .Dir = e .workdir
58
62
cmd .Env = env
59
63
@@ -63,19 +67,36 @@ func (e executor) execWriteOutput(ctx context.Context, args, env []string, stdOu
63
67
cmd .Stdout = syncWriter {mut , stdOutWriter }
64
68
cmd .Stderr = syncWriter {mut , stdErrWriter }
65
69
66
- return cmd .Run ()
70
+ err = cmd .Start ()
71
+ if err != nil {
72
+ return err
73
+ }
74
+ interruptCommandOnCancel (ctx , killCtx , cmd )
75
+
76
+ return cmd .Wait ()
67
77
}
68
78
69
- func (e executor ) execParseJSON (ctx context.Context , args , env []string , v interface {}) error {
79
+ func (e executor ) execParseJSON (ctx , killCtx context.Context , args , env []string , v interface {}) error {
80
+ if ctx .Err () != nil {
81
+ return ctx .Err ()
82
+ }
83
+
70
84
// #nosec
71
- cmd := exec .CommandContext (ctx , e .binaryPath , args ... )
85
+ cmd := exec .CommandContext (killCtx , e .binaryPath , args ... )
72
86
cmd .Dir = e .workdir
73
87
cmd .Env = env
74
88
out := & bytes.Buffer {}
75
89
stdErr := & bytes.Buffer {}
76
90
cmd .Stdout = out
77
91
cmd .Stderr = stdErr
78
- err := cmd .Run ()
92
+
93
+ err := cmd .Start ()
94
+ if err != nil {
95
+ return err
96
+ }
97
+ interruptCommandOnCancel (ctx , killCtx , cmd )
98
+
99
+ err = cmd .Wait ()
79
100
if err != nil {
80
101
errString , _ := io .ReadAll (stdErr )
81
102
return xerrors .Errorf ("%s: %w" , errString , err )
@@ -95,11 +116,11 @@ func (e executor) checkMinVersion(ctx context.Context) error {
95
116
if err != nil {
96
117
return err
97
118
}
98
- if ! v .GreaterThanOrEqual (minimumTerraformVersion ) {
119
+ if ! v .GreaterThanOrEqual (minTerraformVersion ) {
99
120
return xerrors .Errorf (
100
121
"terraform version %q is too old. required >= %q" ,
101
122
v .String (),
102
- minimumTerraformVersion .String ())
123
+ minTerraformVersion .String ())
103
124
}
104
125
return nil
105
126
}
@@ -109,6 +130,10 @@ func (e executor) version(ctx context.Context) (*version.Version, error) {
109
130
}
110
131
111
132
func versionFromBinaryPath (ctx context.Context , binaryPath string ) (* version.Version , error ) {
133
+ if ctx .Err () != nil {
134
+ return nil , ctx .Err ()
135
+ }
136
+
112
137
// #nosec
113
138
cmd := exec .CommandContext (ctx , binaryPath , "version" , "-json" )
114
139
out , err := cmd .Output ()
@@ -130,7 +155,7 @@ func versionFromBinaryPath(ctx context.Context, binaryPath string) (*version.Ver
130
155
return version .NewVersion (vj .Version )
131
156
}
132
157
133
- func (e executor ) init (ctx context.Context , logr logger ) error {
158
+ func (e executor ) init (ctx , killCtx context.Context , logr logger ) error {
134
159
outWriter , doneOut := logWriter (logr , proto .LogLevel_DEBUG )
135
160
errWriter , doneErr := logWriter (logr , proto .LogLevel_ERROR )
136
161
defer func () {
@@ -156,11 +181,11 @@ func (e executor) init(ctx context.Context, logr logger) error {
156
181
defer e .initMu .Unlock ()
157
182
}
158
183
159
- return e .execWriteOutput (ctx , args , e .basicEnv (), outWriter , errWriter )
184
+ return e .execWriteOutput (ctx , killCtx , args , e .basicEnv (), outWriter , errWriter )
160
185
}
161
186
162
187
// revive:disable-next-line:flag-parameter
163
- func (e executor ) plan (ctx context.Context , env , vars []string , logr logger , destroy bool ) (* proto.Provision_Response , error ) {
188
+ func (e executor ) plan (ctx , killCtx context.Context , env , vars []string , logr logger , destroy bool ) (* proto.Provision_Response , error ) {
164
189
planfilePath := filepath .Join (e .workdir , "terraform.tfplan" )
165
190
args := []string {
166
191
"plan" ,
@@ -184,11 +209,11 @@ func (e executor) plan(ctx context.Context, env, vars []string, logr logger, des
184
209
<- doneErr
185
210
}()
186
211
187
- err := e .execWriteOutput (ctx , args , env , outWriter , errWriter )
212
+ err := e .execWriteOutput (ctx , killCtx , args , env , outWriter , errWriter )
188
213
if err != nil {
189
214
return nil , xerrors .Errorf ("terraform plan: %w" , err )
190
215
}
191
- resources , err := e .planResources (ctx , planfilePath )
216
+ resources , err := e .planResources (ctx , killCtx , planfilePath )
192
217
if err != nil {
193
218
return nil , err
194
219
}
@@ -201,40 +226,52 @@ func (e executor) plan(ctx context.Context, env, vars []string, logr logger, des
201
226
}, nil
202
227
}
203
228
204
- func (e executor ) planResources (ctx context.Context , planfilePath string ) ([]* proto.Resource , error ) {
205
- plan , err := e .showPlan (ctx , planfilePath )
229
+ func (e executor ) planResources (ctx , killCtx context.Context , planfilePath string ) ([]* proto.Resource , error ) {
230
+ plan , err := e .showPlan (ctx , killCtx , planfilePath )
206
231
if err != nil {
207
232
return nil , xerrors .Errorf ("show terraform plan file: %w" , err )
208
233
}
209
234
210
- rawGraph , err := e .graph (ctx )
235
+ rawGraph , err := e .graph (ctx , killCtx )
211
236
if err != nil {
212
237
return nil , xerrors .Errorf ("graph: %w" , err )
213
238
}
214
239
return ConvertResources (plan .PlannedValues .RootModule , rawGraph )
215
240
}
216
241
217
- func (e executor ) showPlan (ctx context.Context , planfilePath string ) (* tfjson.Plan , error ) {
242
+ func (e executor ) showPlan (ctx , killCtx context.Context , planfilePath string ) (* tfjson.Plan , error ) {
218
243
args := []string {"show" , "-json" , "-no-color" , planfilePath }
219
244
p := new (tfjson.Plan )
220
- err := e .execParseJSON (ctx , args , e .basicEnv (), p )
245
+ err := e .execParseJSON (ctx , killCtx , args , e .basicEnv (), p )
221
246
return p , err
222
247
}
223
248
224
- func (e executor ) graph (ctx context.Context ) (string , error ) {
225
- // #nosec
226
- cmd := exec .CommandContext (ctx , e .binaryPath , "graph" )
249
+ func (e executor ) graph (ctx , killCtx context.Context ) (string , error ) {
250
+ if ctx .Err () != nil {
251
+ return "" , ctx .Err ()
252
+ }
253
+
254
+ var out bytes.Buffer
255
+ cmd := exec .CommandContext (killCtx , e .binaryPath , "graph" ) // #nosec
256
+ cmd .Stdout = & out
227
257
cmd .Dir = e .workdir
228
258
cmd .Env = e .basicEnv ()
229
- out , err := cmd .Output ()
259
+
260
+ err := cmd .Start ()
261
+ if err != nil {
262
+ return "" , err
263
+ }
264
+ interruptCommandOnCancel (ctx , killCtx , cmd )
265
+
266
+ err = cmd .Wait ()
230
267
if err != nil {
231
268
return "" , xerrors .Errorf ("graph: %w" , err )
232
269
}
233
- return string ( out ), nil
270
+ return out . String ( ), nil
234
271
}
235
272
236
273
// revive:disable-next-line:flag-parameter
237
- func (e executor ) apply (ctx context.Context , env , vars []string , logr logger , destroy bool ,
274
+ func (e executor ) apply (ctx , killCtx context.Context , env , vars []string , logr logger , destroy bool ,
238
275
) (* proto.Provision_Response , error ) {
239
276
args := []string {
240
277
"apply" ,
@@ -258,11 +295,11 @@ func (e executor) apply(ctx context.Context, env, vars []string, logr logger, de
258
295
<- doneErr
259
296
}()
260
297
261
- err := e .execWriteOutput (ctx , args , env , outWriter , errWriter )
298
+ err := e .execWriteOutput (ctx , killCtx , args , env , outWriter , errWriter )
262
299
if err != nil {
263
300
return nil , xerrors .Errorf ("terraform apply: %w" , err )
264
301
}
265
- resources , err := e .stateResources (ctx )
302
+ resources , err := e .stateResources (ctx , killCtx )
266
303
if err != nil {
267
304
return nil , err
268
305
}
@@ -281,12 +318,12 @@ func (e executor) apply(ctx context.Context, env, vars []string, logr logger, de
281
318
}, nil
282
319
}
283
320
284
- func (e executor ) stateResources (ctx context.Context ) ([]* proto.Resource , error ) {
285
- state , err := e .state (ctx )
321
+ func (e executor ) stateResources (ctx , killCtx context.Context ) ([]* proto.Resource , error ) {
322
+ state , err := e .state (ctx , killCtx )
286
323
if err != nil {
287
324
return nil , err
288
325
}
289
- rawGraph , err := e .graph (ctx )
326
+ rawGraph , err := e .graph (ctx , killCtx )
290
327
if err != nil {
291
328
return nil , xerrors .Errorf ("get terraform graph: %w" , err )
292
329
}
@@ -300,16 +337,33 @@ func (e executor) stateResources(ctx context.Context) ([]*proto.Resource, error)
300
337
return resources , nil
301
338
}
302
339
303
- func (e executor ) state (ctx context.Context ) (* tfjson.State , error ) {
340
+ func (e executor ) state (ctx , killCtx context.Context ) (* tfjson.State , error ) {
304
341
args := []string {"show" , "-json" }
305
342
state := & tfjson.State {}
306
- err := e .execParseJSON (ctx , args , e .basicEnv (), state )
343
+ err := e .execParseJSON (ctx , killCtx , args , e .basicEnv (), state )
307
344
if err != nil {
308
345
return nil , xerrors .Errorf ("terraform show state: %w" , err )
309
346
}
310
347
return state , nil
311
348
}
312
349
350
+ func interruptCommandOnCancel (ctx , killCtx context.Context , cmd * exec.Cmd ) {
351
+ go func () {
352
+ select {
353
+ case <- ctx .Done ():
354
+ switch runtime .GOOS {
355
+ case "windows" :
356
+ // Interrupts aren't supported by Windows.
357
+ _ = cmd .Process .Kill ()
358
+ default :
359
+ _ = cmd .Process .Signal (os .Interrupt )
360
+ }
361
+
362
+ case <- killCtx .Done ():
363
+ }
364
+ }()
365
+ }
366
+
313
367
type logger interface {
314
368
Log (* proto.Log ) error
315
369
}
@@ -381,9 +435,6 @@ func provisionReadAndLog(logr logger, reader io.Reader, done chan<- any) {
381
435
382
436
// If the diagnostic is provided, let's provide a bit more info!
383
437
logLevel = convertTerraformLogLevel (log .Diagnostic .Severity , logr )
384
- if err != nil {
385
- continue
386
- }
387
438
err = logr .Log (& proto.Log {Level : logLevel , Output : log .Diagnostic .Detail })
388
439
if err != nil {
389
440
// Not much we can do. We can't log because logging is itself breaking!
0 commit comments