Skip to content

Commit 2bab8c3

Browse files
committed
Properly handle context cancellation/deadline and kill
1 parent 7262de8 commit 2bab8c3

20 files changed

+129
-43
lines changed

tfexec/apply.go

Lines changed: 1 addition & 1 deletion
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

Lines changed: 4 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -171,7 +171,8 @@ func (tf *Terraform) buildEnv(mergeEnv map[string]string) []string {
171171
}
172172

173173
func (tf *Terraform) buildTerraformCmd(ctx context.Context, mergeEnv map[string]string, args ...string) *exec.Cmd {
174-
cmd := exec.CommandContext(ctx, tf.execPath, args...)
174+
cmd := exec.Command(tf.execPath, args...)
175+
175176
cmd.Env = tf.buildEnv(mergeEnv)
176177
cmd.Dir = tf.workingDir
177178

@@ -180,11 +181,11 @@ func (tf *Terraform) buildTerraformCmd(ctx context.Context, mergeEnv map[string]
180181
return cmd
181182
}
182183

183-
func (tf *Terraform) runTerraformCmdJSON(cmd *exec.Cmd, v interface{}) error {
184+
func (tf *Terraform) runTerraformCmdJSON(ctx context.Context, cmd *exec.Cmd, v interface{}) error {
184185
var outbuf = bytes.Buffer{}
185186
cmd.Stdout = mergeWriters(cmd.Stdout, &outbuf)
186187

187-
err := tf.runTerraformCmd(cmd)
188+
err := tf.runTerraformCmd(ctx, cmd)
188189
if err != nil {
189190
return err
190191
}
@@ -194,19 +195,6 @@ func (tf *Terraform) runTerraformCmdJSON(cmd *exec.Cmd, v interface{}) error {
194195
return dec.Decode(v)
195196
}
196197

197-
func (tf *Terraform) runTerraformCmd(cmd *exec.Cmd) error {
198-
var errBuf strings.Builder
199-
200-
cmd.Stdout = mergeWriters(cmd.Stdout, tf.stdout)
201-
cmd.Stderr = mergeWriters(cmd.Stderr, tf.stderr, &errBuf)
202-
203-
err := cmd.Run()
204-
if err != nil {
205-
return tf.parseError(err, errBuf.String())
206-
}
207-
return nil
208-
}
209-
210198
// mergeUserAgent does some minor deduplication to ensure we aren't
211199
// just using the same append string over and over.
212200
func mergeUserAgent(uas ...string) string {

tfexec/cmd_default.go

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
// +build !linux
2+
3+
package tfexec
4+
5+
import (
6+
"context"
7+
"os/exec"
8+
"strings"
9+
)
10+
11+
func (tf *Terraform) runTerraformCmd(ctx context.Context, cmd *exec.Cmd) error {
12+
var errBuf strings.Builder
13+
14+
cmd.Stdout = mergeWriters(cmd.Stdout, tf.stdout)
15+
cmd.Stderr = mergeWriters(cmd.Stderr, tf.stderr, &errBuf)
16+
17+
go func() {
18+
<-ctx.Done()
19+
if ctx.Err() == context.DeadlineExceeded || ctx.Err() == context.Canceled {
20+
if cmd != nil && cmd.Process != nil && cmd.ProcessState != nil {
21+
err := cmd.Process.Kill()
22+
if err != nil {
23+
tf.logger.Printf("error from kill: %s", err)
24+
}
25+
}
26+
}
27+
}()
28+
29+
// check for early cancellation
30+
select {
31+
case <-ctx.Done():
32+
return ctx.Err()
33+
default:
34+
}
35+
36+
err := cmd.Run()
37+
if err == nil && ctx.Err() != nil {
38+
err = ctx.Err()
39+
}
40+
if err != nil {
41+
return tf.parseError(err, errBuf.String())
42+
}
43+
44+
return nil
45+
}

tfexec/cmd_linux.go

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
package tfexec
2+
3+
import (
4+
"context"
5+
"os/exec"
6+
"strings"
7+
"syscall"
8+
)
9+
10+
func (tf *Terraform) runTerraformCmd(ctx context.Context, cmd *exec.Cmd) error {
11+
var errBuf strings.Builder
12+
13+
cmd.Stdout = mergeWriters(cmd.Stdout, tf.stdout)
14+
cmd.Stderr = mergeWriters(cmd.Stderr, tf.stderr, &errBuf)
15+
16+
cmd.SysProcAttr = &syscall.SysProcAttr{
17+
// kill children if parent is dead
18+
Pdeathsig: syscall.SIGKILL,
19+
// set process group ID
20+
Setpgid: true,
21+
}
22+
23+
go func() {
24+
<-ctx.Done()
25+
if ctx.Err() == context.DeadlineExceeded || ctx.Err() == context.Canceled {
26+
if cmd != nil && cmd.Process != nil && cmd.ProcessState != nil {
27+
// send SIGINT to process group
28+
err := syscall.Kill(-cmd.Process.Pid, syscall.SIGINT)
29+
if err != nil {
30+
tf.logger.Printf("error from SIGINT: %s", err)
31+
}
32+
}
33+
34+
// TODO: send a kill if it doesn't respond for a bit?
35+
}
36+
}()
37+
38+
// check for early cancellation
39+
select {
40+
case <-ctx.Done():
41+
return ctx.Err()
42+
default:
43+
}
44+
45+
err := cmd.Run()
46+
if err == nil && ctx.Err() != nil {
47+
err = ctx.Err()
48+
}
49+
if err != nil {
50+
return tf.parseError(err, errBuf.String())
51+
}
52+
53+
return nil
54+
}

tfexec/destroy.go

Lines changed: 1 addition & 1 deletion
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/fmt.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ func (tf *Terraform) Format(ctx context.Context, unformatted io.Reader, formatte
6363
cmd.Stdin = unformatted
6464
cmd.Stdout = mergeWriters(cmd.Stdout, formatted)
6565

66-
return tf.runTerraformCmd(cmd)
66+
return tf.runTerraformCmd(ctx, cmd)
6767
}
6868

6969
// FormatWrite attempts to format and modify all config files in the working or selected (via DirOption) directory.
@@ -82,7 +82,7 @@ func (tf *Terraform) FormatWrite(ctx context.Context, opts ...FormatOption) erro
8282
return err
8383
}
8484

85-
return tf.runTerraformCmd(cmd)
85+
return tf.runTerraformCmd(ctx, cmd)
8686
}
8787

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

107-
err = tf.runTerraformCmd(cmd)
107+
err = tf.runTerraformCmd(ctx, cmd)
108108
if err == nil {
109109
return true, nil, nil
110110
}

tfexec/import.go

Lines changed: 1 addition & 1 deletion
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

Lines changed: 1 addition & 1 deletion
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) {

tfexec/output.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ func (tf *Terraform) Output(ctx context.Context, opts ...OutputOption) (map[stri
3737
outputCmd := tf.outputCmd(ctx, opts...)
3838

3939
outputs := map[string]OutputMeta{}
40-
err := tf.runTerraformCmdJSON(outputCmd, &outputs)
40+
err := tf.runTerraformCmdJSON(ctx, outputCmd, &outputs)
4141
if err != nil {
4242
return nil, err
4343
}

tfexec/plan.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,7 @@ func (tf *Terraform) Plan(ctx context.Context, opts ...PlanOption) (bool, error)
9696
if err != nil {
9797
return false, err
9898
}
99-
err = tf.runTerraformCmd(cmd)
99+
err = tf.runTerraformCmd(ctx, cmd)
100100
if err != nil && cmd.ProcessState.ExitCode() == 2 {
101101
return true, nil
102102
}

tfexec/providers_schema.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ func (tf *Terraform) ProvidersSchema(ctx context.Context) (*tfjson.ProviderSchem
1212
schemaCmd := tf.providersSchemaCmd(ctx)
1313

1414
var ret tfjson.ProviderSchemas
15-
err := tf.runTerraformCmdJSON(schemaCmd, &ret)
15+
err := tf.runTerraformCmdJSON(ctx, schemaCmd, &ret)
1616
if err != nil {
1717
return nil, err
1818
}

tfexec/refresh.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@ func (tf *Terraform) Refresh(ctx context.Context, opts ...RefreshCmdOption) erro
7575
if err != nil {
7676
return err
7777
}
78-
return tf.runTerraformCmd(cmd)
78+
return tf.runTerraformCmd(ctx, cmd)
7979
}
8080

8181
func (tf *Terraform) refreshCmd(ctx context.Context, opts ...RefreshCmdOption) (*exec.Cmd, error) {

tfexec/show.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ func (tf *Terraform) Show(ctx context.Context, opts ...ShowOption) (*tfjson.Stat
5050

5151
var ret tfjson.State
5252
ret.UseJSONNumber(true)
53-
err = tf.runTerraformCmdJSON(showCmd, &ret)
53+
err = tf.runTerraformCmdJSON(ctx, showCmd, &ret)
5454
if err != nil {
5555
return nil, err
5656
}
@@ -93,7 +93,7 @@ func (tf *Terraform) ShowStateFile(ctx context.Context, statePath string, opts .
9393

9494
var ret tfjson.State
9595
ret.UseJSONNumber(true)
96-
err = tf.runTerraformCmdJSON(showCmd, &ret)
96+
err = tf.runTerraformCmdJSON(ctx, showCmd, &ret)
9797
if err != nil {
9898
return nil, err
9999
}
@@ -135,7 +135,7 @@ func (tf *Terraform) ShowPlanFile(ctx context.Context, planPath string, opts ...
135135
showCmd := tf.showCmd(ctx, true, mergeEnv, planPath)
136136

137137
var ret tfjson.Plan
138-
err = tf.runTerraformCmdJSON(showCmd, &ret)
138+
err = tf.runTerraformCmdJSON(ctx, showCmd, &ret)
139139
if err != nil {
140140
return nil, err
141141
}
@@ -175,7 +175,7 @@ func (tf *Terraform) ShowPlanFileRaw(ctx context.Context, planPath string, opts
175175

176176
var ret bytes.Buffer
177177
showCmd.Stdout = &ret
178-
err := tf.runTerraformCmd(showCmd)
178+
err := tf.runTerraformCmd(ctx, showCmd)
179179
if err != nil {
180180
return "", err
181181
}

tfexec/state_mv.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ func (tf *Terraform) StateMv(ctx context.Context, source string, destination str
6060
if err != nil {
6161
return err
6262
}
63-
return tf.runTerraformCmd(cmd)
63+
return tf.runTerraformCmd(ctx, cmd)
6464
}
6565

6666
func (tf *Terraform) stateMvCmd(ctx context.Context, source string, destination string, opts ...StateMvCmdOption) (*exec.Cmd, error) {

tfexec/upgrade012.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ func (tf *Terraform) Upgrade012(ctx context.Context, opts ...Upgrade012Option) e
4040
if err != nil {
4141
return err
4242
}
43-
return tf.runTerraformCmd(cmd)
43+
return tf.runTerraformCmd(ctx, cmd)
4444
}
4545

4646
func (tf *Terraform) upgrade012Cmd(ctx context.Context, opts ...Upgrade012Option) (*exec.Cmd, error) {

tfexec/validate.go

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,7 @@
11
package tfexec
22

33
import (
4-
"bytes"
54
"context"
6-
"encoding/json"
75
"fmt"
86

97
tfjson "github.com/hashicorp/terraform-json"
@@ -22,14 +20,15 @@ func (tf *Terraform) Validate(ctx context.Context) (*tfjson.ValidateOutput, erro
2220
var outbuf = bytes.Buffer{}
2321
cmd.Stdout = &outbuf
2422

25-
err = tf.runTerraformCmd(cmd)
23+
err = tf.runTerraformCmd(ctx, cmd)
2624
// TODO: this command should not exit 1 if you pass -json as its hard to differentiate other errors
2725
if err != nil && cmd.ProcessState.ExitCode() != 1 {
2826
return nil, err
2927
}
3028

31-
var out tfjson.ValidateOutput
32-
jsonErr := json.Unmarshal(outbuf.Bytes(), &out)
29+
var ret tfjson.ValidateOutput
30+
// TODO: ret.UseJSONNumber(true) validate output should support JSON numbers
31+
jsonErr := json.Unmarshal(outbuf.Bytes(), &ret)
3332
if jsonErr != nil {
3433
// the original call was possibly bad, if it has an error, actually just return that
3534
if err != nil {
@@ -39,5 +38,5 @@ func (tf *Terraform) Validate(ctx context.Context) (*tfjson.ValidateOutput, erro
3938
return nil, jsonErr
4039
}
4140

42-
return &out, nil
41+
return &ret, nil
4342
}

tfexec/version.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ func (tf *Terraform) version(ctx context.Context) (*version.Version, map[string]
4444
var outBuf bytes.Buffer
4545
versionCmd.Stdout = &outBuf
4646

47-
err := tf.runTerraformCmd(versionCmd)
47+
err := tf.runTerraformCmd(ctx, versionCmd)
4848
if err != nil {
4949
return nil, nil, err
5050
}

tfexec/workspace_list.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ func (tf *Terraform) WorkspaceList(ctx context.Context) ([]string, string, error
1414
var outBuf bytes.Buffer
1515
wlCmd.Stdout = &outBuf
1616

17-
err := tf.runTerraformCmd(wlCmd)
17+
err := tf.runTerraformCmd(ctx, wlCmd)
1818
if err != nil {
1919
return nil, "", err
2020
}

tfexec/workspace_new.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ func (tf *Terraform) WorkspaceNew(ctx context.Context, workspace string, opts ..
4141
if err != nil {
4242
return err
4343
}
44-
return tf.runTerraformCmd(cmd)
44+
return tf.runTerraformCmd(ctx, cmd)
4545
}
4646

4747
func (tf *Terraform) workspaceNewCmd(ctx context.Context, workspace string, opts ...WorkspaceNewCmdOption) (*exec.Cmd, error) {

tfexec/workspace_select.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,5 +6,5 @@ import "context"
66
func (tf *Terraform) WorkspaceSelect(ctx context.Context, workspace string) error {
77
// TODO: [DIR] param option
88

9-
return tf.runTerraformCmd(tf.buildTerraformCmd(ctx, nil, "workspace", "select", "-no-color", workspace))
9+
return tf.runTerraformCmd(ctx, tf.buildTerraformCmd(ctx, nil, "workspace", "select", "-no-color", workspace))
1010
}

0 commit comments

Comments
 (0)