From d94b44952abc7175985f253db867eba7769ac2d8 Mon Sep 17 00:00:00 2001 From: George Blue Date: Mon, 25 Oct 2021 14:17:14 +0100 Subject: [PATCH] fix: signal handling for Concourse 7.5 There seems to be a change (unidentified as yet) in Concourse 7.5 that results in no longer being able to stop or time-out once Terraform is running. This change introduces a wrapper around the Terraform command that catches signals and kills the Terraform process group if one is detected. --- src/terraform-resource/runner/runner.go | 82 ++++++++++++++++++++++ src/terraform-resource/terraform/client.go | 5 +- 2 files changed, 85 insertions(+), 2 deletions(-) create mode 100644 src/terraform-resource/runner/runner.go diff --git a/src/terraform-resource/runner/runner.go b/src/terraform-resource/runner/runner.go new file mode 100644 index 00000000..eb8f637a --- /dev/null +++ b/src/terraform-resource/runner/runner.go @@ -0,0 +1,82 @@ +package runner + +import ( + "fmt" + "io" + "os" + "os/exec" + "os/signal" + "syscall" +) + +type Runner struct { + Stdout io.Writer + Stderr io.Writer + cmd *exec.Cmd + sigs chan os.Signal + logger io.Writer +} + +func New(cmd *exec.Cmd, logger io.Writer) *Runner { + // Ensure that child is started in process group + cmd.SysProcAttr = &syscall.SysProcAttr{Setpgid: true} + + r := &Runner{ + cmd: cmd, + sigs: make(chan os.Signal, 1), + logger: logger, + } + r.startSignalHandler() + + return r +} + +func (r *Runner) Run() error { + r.cmd.Stdout = r.Stdout + r.cmd.Stderr = r.Stderr + err := r.cmd.Run() + + r.stopSignalHandler() + return err +} + +func (r *Runner) CombinedOutput() ([]byte, error) { + out, err := r.cmd.CombinedOutput() + r.stopSignalHandler() + return out, err +} + +func (r *Runner) Output() ([]byte, error) { + out, err := r.cmd.Output() + r.stopSignalHandler() + return out, err +} + +func (r *Runner) terminate() { + if r.cmd.Process != nil { + processGroup := -r.cmd.Process.Pid + if err := syscall.Kill(processGroup, syscall.SIGKILL); err != nil { + fmt.Fprintf(r.logger, "** Error signaling process group %d: %s\n", processGroup, err) + } + } else { + fmt.Fprintln(r.logger, "** Process already terminated.") + } + + fmt.Fprintln(r.logger, "** Exiting due to signal") + os.Exit(1) +} + +func (r *Runner) startSignalHandler() { + signal.Notify(r.sigs, syscall.SIGINT, syscall.SIGTERM) + go func() { + for s := range r.sigs { + fmt.Fprintf(r.logger, "** Received signal: %s\n", s) + r.terminate() + } + }() +} + +func (r *Runner) stopSignalHandler() { + signal.Reset(syscall.SIGINT, syscall.SIGTERM) + close(r.sigs) +} diff --git a/src/terraform-resource/terraform/client.go b/src/terraform-resource/terraform/client.go index eab59f4b..168e7604 100644 --- a/src/terraform-resource/terraform/client.go +++ b/src/terraform-resource/terraform/client.go @@ -17,6 +17,7 @@ import ( "strings" "github.com/ljfranklin/terraform-resource/models" + "github.com/ljfranklin/terraform-resource/runner" ) const defaultWorkspace = "default" @@ -868,7 +869,7 @@ func (c *client) resourceExistsLegacyStorage(tfID string) (bool, error) { return (len(strings.TrimSpace(string(rawOutput))) > 0), nil } -func (c *client) terraformCmd(args []string, env []string) (*exec.Cmd, error) { +func (c *client) terraformCmd(args []string, env []string) (*runner.Runner, error) { cmdPath, err := exec.LookPath("terraform") if err != nil { return nil, err @@ -892,5 +893,5 @@ func (c *client) terraformCmd(args []string, env []string) (*exec.Cmd, error) { cmd.Env = append(cmd.Env, fmt.Sprintf("%s=%s", key, value)) } - return cmd, nil + return runner.New(cmd, c.logWriter), nil }