Skip to content

Commit 326f9c9

Browse files
committed
Properly handle context cancellation/deadline and kill
1 parent 07bb078 commit 326f9c9

17 files changed

+121
-35
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
@@ -126,7 +126,8 @@ func (tf *Terraform) buildEnv(mergeEnv map[string]string) []string {
126126
}
127127

128128
func (tf *Terraform) buildTerraformCmd(ctx context.Context, mergeEnv map[string]string, args ...string) *exec.Cmd {
129-
cmd := exec.CommandContext(ctx, tf.execPath, args...)
129+
cmd := exec.Command(tf.execPath, args...)
130+
130131
cmd.Env = tf.buildEnv(mergeEnv)
131132
cmd.Dir = tf.workingDir
132133

@@ -135,31 +136,18 @@ func (tf *Terraform) buildTerraformCmd(ctx context.Context, mergeEnv map[string]
135136
return cmd
136137
}
137138

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

142-
err := tf.runTerraformCmd(cmd)
143+
err := tf.runTerraformCmd(ctx, cmd)
143144
if err != nil {
144145
return err
145146
}
146147

147148
return json.Unmarshal(outbuf.Bytes(), v)
148149
}
149150

150-
func (tf *Terraform) runTerraformCmd(cmd *exec.Cmd) error {
151-
var errBuf strings.Builder
152-
153-
cmd.Stdout = mergeWriters(cmd.Stdout, tf.stdout)
154-
cmd.Stderr = mergeWriters(cmd.Stderr, tf.stderr, &errBuf)
155-
156-
err := cmd.Run()
157-
if err != nil {
158-
return tf.parseError(err, errBuf.String())
159-
}
160-
return nil
161-
}
162-
163151
// mergeUserAgent does some minor deduplication to ensure we aren't
164152
// just using the same append string over and over.
165153
func mergeUserAgent(uas ...string) string {

tfexec/cmd_default.go

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
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+
tf.logger.Printf("error from kill: %s", err)
23+
}
24+
}
25+
}()
26+
27+
// check for early cancellation
28+
select {
29+
case <-ctx.Done():
30+
return ctx.Err()
31+
default:
32+
}
33+
34+
err := cmd.Run()
35+
if err == nil && ctx.Err() != nil {
36+
err = ctx.Err()
37+
}
38+
if err != nil {
39+
return tf.parseError(err, errBuf.String())
40+
}
41+
42+
return nil
43+
}

tfexec/cmd_linux.go

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
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+
//err := cmd.Process.Signal(syscall.SIGINT)
30+
if err != nil {
31+
tf.logger.Printf("error from SIGINT: %s", err)
32+
}
33+
}
34+
35+
// TODO: send a kill if it doesn't respond for a bit?
36+
}
37+
}()
38+
39+
// check for early cancellation
40+
select {
41+
case <-ctx.Done():
42+
return ctx.Err()
43+
default:
44+
}
45+
46+
err := cmd.Run()
47+
if err == nil && ctx.Err() != nil {
48+
err = ctx.Err()
49+
}
50+
if err != nil {
51+
return tf.parseError(err, errBuf.String())
52+
}
53+
54+
return nil
55+
}

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
@@ -49,7 +49,7 @@ func (tf *Terraform) Show(ctx context.Context, opts ...ShowOption) (*tfjson.Stat
4949
showCmd := tf.showCmd(ctx, true, mergeEnv)
5050

5151
var ret tfjson.State
52-
err = tf.runTerraformCmdJSON(showCmd, &ret)
52+
err = tf.runTerraformCmdJSON(ctx, showCmd, &ret)
5353
if err != nil {
5454
return nil, err
5555
}
@@ -91,7 +91,7 @@ func (tf *Terraform) ShowStateFile(ctx context.Context, statePath string, opts .
9191
showCmd := tf.showCmd(ctx, true, mergeEnv, statePath)
9292

9393
var ret tfjson.State
94-
err = tf.runTerraformCmdJSON(showCmd, &ret)
94+
err = tf.runTerraformCmdJSON(ctx, showCmd, &ret)
9595
if err != nil {
9696
return nil, err
9797
}
@@ -133,7 +133,7 @@ func (tf *Terraform) ShowPlanFile(ctx context.Context, planPath string, opts ...
133133
showCmd := tf.showCmd(ctx, true, mergeEnv, planPath)
134134

135135
var ret tfjson.Plan
136-
err = tf.runTerraformCmdJSON(showCmd, &ret)
136+
err = tf.runTerraformCmdJSON(ctx, showCmd, &ret)
137137
if err != nil {
138138
return nil, err
139139
}
@@ -173,7 +173,7 @@ func (tf *Terraform) ShowPlanFileRaw(ctx context.Context, planPath string, opts
173173

174174
var ret bytes.Buffer
175175
showCmd.Stdout = &ret
176-
err := tf.runTerraformCmd(showCmd)
176+
err := tf.runTerraformCmd(ctx, showCmd)
177177
if err != nil {
178178
return "", err
179179
}

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)