Skip to content

Commit

Permalink
Properly handle context cancellation/deadline and kill
Browse files Browse the repository at this point in the history
  • Loading branch information
paultyng committed Jan 8, 2021
1 parent 3609e67 commit 7befa2a
Show file tree
Hide file tree
Showing 20 changed files with 129 additions and 41 deletions.
2 changes: 1 addition & 1 deletion tfexec/apply.go
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ func (tf *Terraform) Apply(ctx context.Context, opts ...ApplyOption) error {
if err != nil {
return err
}
return tf.runTerraformCmd(cmd)
return tf.runTerraformCmd(ctx, cmd)
}

func (tf *Terraform) applyCmd(ctx context.Context, opts ...ApplyOption) (*exec.Cmd, error) {
Expand Down
20 changes: 4 additions & 16 deletions tfexec/cmd.go
Original file line number Diff line number Diff line change
Expand Up @@ -171,7 +171,8 @@ func (tf *Terraform) buildEnv(mergeEnv map[string]string) []string {
}

func (tf *Terraform) buildTerraformCmd(ctx context.Context, mergeEnv map[string]string, args ...string) *exec.Cmd {
cmd := exec.CommandContext(ctx, tf.execPath, args...)
cmd := exec.Command(tf.execPath, args...)

cmd.Env = tf.buildEnv(mergeEnv)
cmd.Dir = tf.workingDir

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

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

err := tf.runTerraformCmd(cmd)
err := tf.runTerraformCmd(ctx, cmd)
if err != nil {
return err
}
Expand All @@ -194,19 +195,6 @@ func (tf *Terraform) runTerraformCmdJSON(cmd *exec.Cmd, v interface{}) error {
return dec.Decode(v)
}

func (tf *Terraform) runTerraformCmd(cmd *exec.Cmd) error {
var errBuf strings.Builder

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

err := cmd.Run()
if err != nil {
return tf.parseError(err, errBuf.String())
}
return nil
}

// mergeUserAgent does some minor deduplication to ensure we aren't
// just using the same append string over and over.
func mergeUserAgent(uas ...string) string {
Expand Down
45 changes: 45 additions & 0 deletions tfexec/cmd_default.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
// +build !linux

package tfexec

import (
"context"
"os/exec"
"strings"
)

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

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

go func() {
<-ctx.Done()
if ctx.Err() == context.DeadlineExceeded || ctx.Err() == context.Canceled {
if cmd != nil && cmd.Process != nil && cmd.ProcessState != nil {
err := cmd.Process.Kill()
if err != nil {
tf.logger.Printf("error from kill: %s", err)
}
}
}
}()

// check for early cancellation
select {
case <-ctx.Done():
return ctx.Err()
default:
}

err := cmd.Run()
if err == nil && ctx.Err() != nil {
err = ctx.Err()
}
if err != nil {
return tf.parseError(err, errBuf.String())
}

return nil
}
54 changes: 54 additions & 0 deletions tfexec/cmd_linux.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
package tfexec

import (
"context"
"os/exec"
"strings"
"syscall"
)

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

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

cmd.SysProcAttr = &syscall.SysProcAttr{
// kill children if parent is dead
Pdeathsig: syscall.SIGKILL,
// set process group ID
Setpgid: true,
}

go func() {
<-ctx.Done()
if ctx.Err() == context.DeadlineExceeded || ctx.Err() == context.Canceled {
if cmd != nil && cmd.Process != nil && cmd.ProcessState != nil {
// send SIGINT to process group
err := syscall.Kill(-cmd.Process.Pid, syscall.SIGINT)
if err != nil {
tf.logger.Printf("error from SIGINT: %s", err)
}
}

// TODO: send a kill if it doesn't respond for a bit?
}
}()

// check for early cancellation
select {
case <-ctx.Done():
return ctx.Err()
default:
}

err := cmd.Run()
if err == nil && ctx.Err() != nil {
err = ctx.Err()
}
if err != nil {
return tf.parseError(err, errBuf.String())
}

return nil
}
2 changes: 1 addition & 1 deletion tfexec/destroy.go
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ func (tf *Terraform) Destroy(ctx context.Context, opts ...DestroyOption) error {
if err != nil {
return err
}
return tf.runTerraformCmd(cmd)
return tf.runTerraformCmd(ctx, cmd)
}

func (tf *Terraform) destroyCmd(ctx context.Context, opts ...DestroyOption) (*exec.Cmd, error) {
Expand Down
6 changes: 3 additions & 3 deletions tfexec/fmt.go
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ func (tf *Terraform) Format(ctx context.Context, unformatted io.Reader, formatte
cmd.Stdin = unformatted
cmd.Stdout = mergeWriters(cmd.Stdout, formatted)

return tf.runTerraformCmd(cmd)
return tf.runTerraformCmd(ctx, cmd)
}

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

return tf.runTerraformCmd(cmd)
return tf.runTerraformCmd(ctx, cmd)
}

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

err = tf.runTerraformCmd(cmd)
err = tf.runTerraformCmd(ctx, cmd)
if err == nil {
return true, nil, nil
}
Expand Down
2 changes: 1 addition & 1 deletion tfexec/import.go
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ func (tf *Terraform) Import(ctx context.Context, address, id string, opts ...Imp
if err != nil {
return err
}
return tf.runTerraformCmd(cmd)
return tf.runTerraformCmd(ctx, cmd)
}

func (tf *Terraform) importCmd(ctx context.Context, address, id string, opts ...ImportOption) (*exec.Cmd, error) {
Expand Down
2 changes: 1 addition & 1 deletion tfexec/init.go
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ func (tf *Terraform) Init(ctx context.Context, opts ...InitOption) error {
if err != nil {
return err
}
return tf.runTerraformCmd(cmd)
return tf.runTerraformCmd(ctx, cmd)
}

func (tf *Terraform) initCmd(ctx context.Context, opts ...InitOption) (*exec.Cmd, error) {
Expand Down
2 changes: 1 addition & 1 deletion tfexec/output.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ func (tf *Terraform) Output(ctx context.Context, opts ...OutputOption) (map[stri
outputCmd := tf.outputCmd(ctx, opts...)

outputs := map[string]OutputMeta{}
err := tf.runTerraformCmdJSON(outputCmd, &outputs)
err := tf.runTerraformCmdJSON(ctx, outputCmd, &outputs)
if err != nil {
return nil, err
}
Expand Down
2 changes: 1 addition & 1 deletion tfexec/plan.go
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ func (tf *Terraform) Plan(ctx context.Context, opts ...PlanOption) (bool, error)
if err != nil {
return false, err
}
err = tf.runTerraformCmd(cmd)
err = tf.runTerraformCmd(ctx, cmd)
if err != nil && cmd.ProcessState.ExitCode() == 2 {
return true, nil
}
Expand Down
2 changes: 1 addition & 1 deletion tfexec/providers_schema.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ func (tf *Terraform) ProvidersSchema(ctx context.Context) (*tfjson.ProviderSchem
schemaCmd := tf.providersSchemaCmd(ctx)

var ret tfjson.ProviderSchemas
err := tf.runTerraformCmdJSON(schemaCmd, &ret)
err := tf.runTerraformCmdJSON(ctx, schemaCmd, &ret)
if err != nil {
return nil, err
}
Expand Down
2 changes: 1 addition & 1 deletion tfexec/refresh.go
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ func (tf *Terraform) Refresh(ctx context.Context, opts ...RefreshCmdOption) erro
if err != nil {
return err
}
return tf.runTerraformCmd(cmd)
return tf.runTerraformCmd(ctx, cmd)
}

func (tf *Terraform) refreshCmd(ctx context.Context, opts ...RefreshCmdOption) (*exec.Cmd, error) {
Expand Down
8 changes: 4 additions & 4 deletions tfexec/show.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ func (tf *Terraform) Show(ctx context.Context, opts ...ShowOption) (*tfjson.Stat

var ret tfjson.State
ret.UseJSONNumber(true)
err = tf.runTerraformCmdJSON(showCmd, &ret)
err = tf.runTerraformCmdJSON(ctx, showCmd, &ret)
if err != nil {
return nil, err
}
Expand Down Expand Up @@ -93,7 +93,7 @@ func (tf *Terraform) ShowStateFile(ctx context.Context, statePath string, opts .

var ret tfjson.State
ret.UseJSONNumber(true)
err = tf.runTerraformCmdJSON(showCmd, &ret)
err = tf.runTerraformCmdJSON(ctx, showCmd, &ret)
if err != nil {
return nil, err
}
Expand Down Expand Up @@ -135,7 +135,7 @@ func (tf *Terraform) ShowPlanFile(ctx context.Context, planPath string, opts ...
showCmd := tf.showCmd(ctx, true, mergeEnv, planPath)

var ret tfjson.Plan
err = tf.runTerraformCmdJSON(showCmd, &ret)
err = tf.runTerraformCmdJSON(ctx, showCmd, &ret)
if err != nil {
return nil, err
}
Expand Down Expand Up @@ -175,7 +175,7 @@ func (tf *Terraform) ShowPlanFileRaw(ctx context.Context, planPath string, opts

var ret bytes.Buffer
showCmd.Stdout = &ret
err := tf.runTerraformCmd(showCmd)
err := tf.runTerraformCmd(ctx, showCmd)
if err != nil {
return "", err
}
Expand Down
2 changes: 1 addition & 1 deletion tfexec/state_mv.go
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ func (tf *Terraform) StateMv(ctx context.Context, source string, destination str
if err != nil {
return err
}
return tf.runTerraformCmd(cmd)
return tf.runTerraformCmd(ctx, cmd)
}

func (tf *Terraform) stateMvCmd(ctx context.Context, source string, destination string, opts ...StateMvCmdOption) (*exec.Cmd, error) {
Expand Down
2 changes: 1 addition & 1 deletion tfexec/upgrade012.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ func (tf *Terraform) Upgrade012(ctx context.Context, opts ...Upgrade012Option) e
if err != nil {
return err
}
return tf.runTerraformCmd(cmd)
return tf.runTerraformCmd(ctx, cmd)
}

func (tf *Terraform) upgrade012Cmd(ctx context.Context, opts ...Upgrade012Option) (*exec.Cmd, error) {
Expand Down
9 changes: 5 additions & 4 deletions tfexec/validate.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,14 +22,15 @@ func (tf *Terraform) Validate(ctx context.Context) (*tfjson.ValidateOutput, erro
var outbuf = bytes.Buffer{}
cmd.Stdout = &outbuf

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

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

return &out, nil
return &ret, nil
}
2 changes: 1 addition & 1 deletion tfexec/version.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ func (tf *Terraform) version(ctx context.Context) (*version.Version, map[string]
var outBuf bytes.Buffer
versionCmd.Stdout = &outBuf

err := tf.runTerraformCmd(versionCmd)
err := tf.runTerraformCmd(ctx, versionCmd)
if err != nil {
return nil, nil, err
}
Expand Down
2 changes: 1 addition & 1 deletion tfexec/workspace_list.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ func (tf *Terraform) WorkspaceList(ctx context.Context) ([]string, string, error
var outBuf bytes.Buffer
wlCmd.Stdout = &outBuf

err := tf.runTerraformCmd(wlCmd)
err := tf.runTerraformCmd(ctx, wlCmd)
if err != nil {
return nil, "", err
}
Expand Down
2 changes: 1 addition & 1 deletion tfexec/workspace_new.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ func (tf *Terraform) WorkspaceNew(ctx context.Context, workspace string, opts ..
if err != nil {
return err
}
return tf.runTerraformCmd(cmd)
return tf.runTerraformCmd(ctx, cmd)
}

func (tf *Terraform) workspaceNewCmd(ctx context.Context, workspace string, opts ...WorkspaceNewCmdOption) (*exec.Cmd, error) {
Expand Down
2 changes: 1 addition & 1 deletion tfexec/workspace_select.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,5 @@ import "context"
func (tf *Terraform) WorkspaceSelect(ctx context.Context, workspace string) error {
// TODO: [DIR] param option

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

0 comments on commit 7befa2a

Please sign in to comment.