diff --git a/pkg/skaffold/build/custom/custom.go b/pkg/skaffold/build/custom/custom.go index 5762683f5d6..d08900440c9 100644 --- a/pkg/skaffold/build/custom/custom.go +++ b/pkg/skaffold/build/custom/custom.go @@ -20,18 +20,15 @@ import ( "context" "fmt" "io" - "os" "os/exec" "path/filepath" - "runtime" "strings" - "time" + "github.com/GoogleContainerTools/skaffold/pkg/skaffold/build/misc" "github.com/GoogleContainerTools/skaffold/pkg/skaffold/constants" "github.com/GoogleContainerTools/skaffold/pkg/skaffold/schema/latest" "github.com/GoogleContainerTools/skaffold/pkg/skaffold/util" "github.com/pkg/errors" - "github.com/sirupsen/logrus" ) var ( @@ -65,43 +62,7 @@ func (b *ArtifactBuilder) Build(ctx context.Context, out io.Writer, a *latest.Ar return errors.Wrap(err, "starting cmd") } - return b.handleGracefulTermination(ctx, cmd) -} - -func (b *ArtifactBuilder) handleGracefulTermination(ctx context.Context, cmd *exec.Cmd) error { - done := make(chan struct{}) - defer close(done) - - go func() { - select { - case <-ctx.Done(): - // On windows we can't send specific signals to processes, so we kill the process immediately - if runtime.GOOS == "windows" { - cmd.Process.Kill() - return - } - - logrus.Debugf("Sending SIGINT to process %v\n", cmd.Process.Pid) - if err := cmd.Process.Signal(os.Interrupt); err != nil { - // kill process on error - cmd.Process.Kill() - } - - // wait 2 seconds or wait for the process to complete - select { - case <-time.After(2 * time.Second): - logrus.Debugf("Killing process %v\n", cmd.Process.Pid) - // forcefully kill process after 2 seconds grace period - cmd.Process.Kill() - case <-done: - return - } - case <-done: - return - } - }() - - return cmd.Wait() + return misc.HandleGracefulTermination(ctx, cmd) } func (b *ArtifactBuilder) retrieveCmd(out io.Writer, a *latest.Artifact, tag string) (*exec.Cmd, error) { diff --git a/pkg/skaffold/build/custom/custom_test.go b/pkg/skaffold/build/custom/custom_test.go index 6d2e3cc074a..c79d3684f5d 100644 --- a/pkg/skaffold/build/custom/custom_test.go +++ b/pkg/skaffold/build/custom/custom_test.go @@ -17,12 +17,9 @@ limitations under the License. package custom import ( - "context" "io/ioutil" "os/exec" - "runtime" "testing" - "time" "github.com/GoogleContainerTools/skaffold/pkg/skaffold/schema/latest" "github.com/GoogleContainerTools/skaffold/pkg/skaffold/util" @@ -127,42 +124,6 @@ func TestRetrieveCmd(t *testing.T) { } } -func TestGracefulBuildCancel(t *testing.T) { - if runtime.GOOS == "windows" { - t.Skip("graceful cancel doesn't work on windows") - } - - tests := []struct { - description string - command string - shouldErr bool - }{ - { - description: "terminate gracefully and exit 0", - command: "trap 'echo trap' INT; sleep 2", - }, { - description: "terminate gracefully and kill process", - command: "trap 'echo trap' INT; sleep 5", - shouldErr: true, - }, - } - - for _, test := range tests { - testutil.Run(t, test.description, func(t *testutil.T) { - builder := NewArtifactBuilder(false, nil) - ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second) - - cmd := exec.Command("bash", "-c", test.command) - t.CheckNoError(cmd.Start()) - - err := builder.handleGracefulTermination(ctx, cmd) - t.CheckError(test.shouldErr, err) - - cancel() - }) - } -} - func expectedCmd(buildCommand, dir string, args, env []string) *exec.Cmd { cmd := exec.Command(buildCommand, args...) cmd.Dir = dir diff --git a/pkg/skaffold/build/misc/graceful.go b/pkg/skaffold/build/misc/graceful.go new file mode 100644 index 00000000000..d18753bfc42 --- /dev/null +++ b/pkg/skaffold/build/misc/graceful.go @@ -0,0 +1,63 @@ +/* +Copyright 2019 The Skaffold Authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package misc + +import ( + "context" + "os" + "os/exec" + "runtime" + "time" + + "github.com/sirupsen/logrus" +) + +func HandleGracefulTermination(ctx context.Context, cmd *exec.Cmd) error { + done := make(chan struct{}) + defer close(done) + + go func() { + select { + case <-ctx.Done(): + // On windows we can't send specific signals to processes, so we kill the process immediately + if runtime.GOOS == "windows" { + cmd.Process.Kill() + return + } + + logrus.Debugf("Sending SIGINT to process %v\n", cmd.Process.Pid) + if err := cmd.Process.Signal(os.Interrupt); err != nil { + // kill process on error + cmd.Process.Kill() + } + + // wait 2 seconds or wait for the process to complete + select { + case <-time.After(2 * time.Second): + logrus.Debugf("Killing process %v\n", cmd.Process.Pid) + // forcefully kill process after 2 seconds grace period + cmd.Process.Kill() + case <-done: + return + } + case <-done: + return + } + }() + + return cmd.Wait() +} diff --git a/pkg/skaffold/build/misc/graceful_test.go b/pkg/skaffold/build/misc/graceful_test.go new file mode 100644 index 00000000000..0690e8b610b --- /dev/null +++ b/pkg/skaffold/build/misc/graceful_test.go @@ -0,0 +1,62 @@ +/* +Copyright 2019 The Skaffold Authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package misc + +import ( + "context" + "os/exec" + "runtime" + "testing" + "time" + + "github.com/GoogleContainerTools/skaffold/testutil" +) + +func TestGracefulBuildCancel(t *testing.T) { + if runtime.GOOS == "windows" { + t.Skip("graceful cancel doesn't work on windows") + } + + tests := []struct { + description string + command string + shouldErr bool + }{ + { + description: "terminate gracefully and exit 0", + command: "trap 'echo trap' INT; sleep 2", + }, { + description: "terminate gracefully and kill process", + command: "trap 'echo trap' INT; sleep 5", + shouldErr: true, + }, + } + + for _, test := range tests { + testutil.Run(t, test.description, func(t *testutil.T) { + ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second) + + cmd := exec.Command("bash", "-c", test.command) + t.CheckNoError(cmd.Start()) + + err := HandleGracefulTermination(ctx, cmd) + t.CheckError(test.shouldErr, err) + + cancel() + }) + } +}