diff --git a/tfexec/cmd_default.go b/tfexec/cmd_default.go index fca9def9..7323d555 100644 --- a/tfexec/cmd_default.go +++ b/tfexec/cmd_default.go @@ -40,8 +40,11 @@ func (tf *Terraform) runTerraformCmd(ctx context.Context, cmd *exec.Cmd) error { } err = cmd.Start() - if err == nil && ctx.Err() != nil { - err = ctx.Err() + if ctx.Err() != nil { + return cmdErr{ + err: err, + ctxErr: ctx.Err(), + } } if err != nil { return err @@ -66,8 +69,11 @@ func (tf *Terraform) runTerraformCmd(ctx context.Context, cmd *exec.Cmd) error { wg.Wait() err = cmd.Wait() - if err == nil && ctx.Err() != nil { - err = ctx.Err() + if ctx.Err() != nil { + return cmdErr{ + err: err, + ctxErr: ctx.Err(), + } } if err != nil { return err diff --git a/tfexec/cmd_linux.go b/tfexec/cmd_linux.go index 5572d142..9975791a 100644 --- a/tfexec/cmd_linux.go +++ b/tfexec/cmd_linux.go @@ -45,8 +45,11 @@ func (tf *Terraform) runTerraformCmd(ctx context.Context, cmd *exec.Cmd) error { } err = cmd.Start() - if err == nil && ctx.Err() != nil { - err = ctx.Err() + if ctx.Err() != nil { + return cmdErr{ + err: err, + ctxErr: ctx.Err(), + } } if err != nil { return err @@ -71,8 +74,11 @@ func (tf *Terraform) runTerraformCmd(ctx context.Context, cmd *exec.Cmd) error { wg.Wait() err = cmd.Wait() - if err == nil && ctx.Err() != nil { - err = ctx.Err() + if ctx.Err() != nil { + return cmdErr{ + err: err, + ctxErr: ctx.Err(), + } } if err != nil { return err diff --git a/tfexec/errors.go b/tfexec/errors.go index 7a32ef2f..3bbb431c 100644 --- a/tfexec/errors.go +++ b/tfexec/errors.go @@ -1,6 +1,9 @@ package tfexec -import "fmt" +import ( + "context" + "fmt" +) // this file contains non-parsed exported errors @@ -37,3 +40,25 @@ type ErrManualEnvVar struct { func (err *ErrManualEnvVar) Error() string { return fmt.Sprintf("manual setting of env var %q detected", err.Name) } + +// cmdErr is a custom error type to be returned when a cmd exits with a context +// error such as context.Canceled or context.DeadlineExceeded. +// The type is specifically designed to respond true to errors.Is for these two +// errors. +// See https://github.com/golang/go/issues/21880 for why this is necessary. +type cmdErr struct { + err error + ctxErr error +} + +func (e cmdErr) Is(target error) bool { + switch target { + case context.DeadlineExceeded, context.Canceled: + return e.ctxErr == context.DeadlineExceeded || e.ctxErr == context.Canceled + } + return false +} + +func (e cmdErr) Error() string { + return e.err.Error() +}